PIC16LF1554 can't seem to read internal FVR (fixed voltage reference) - Page 1
Forum section: electronics / general

author: uer166
author_rank: Super Contributor
timestamp: August 05, 2019, 05:02:26 am
content: Can't really tell if I'm being dense or what, but when reading the internal 1.024V reference on a PIC I get codes around 0xE1 in debugger which is 225. So 225/1024*3.3 = 0.725V, not even close to 1.024Code snippet is:Code:ADC1_StartConversion(0b11111); // FVR buffer 1while(ADC1_IsConversionDone() == false);ref_1V024 = ADC1_GetConversionResult();ADC1 is not used for anything else at all. ADC2 is used for a capacitive touch input so should be no interaction at all..

author: Dabbot
author_rank: Regular Contributor
timestamp: August 05, 2019, 09:00:22 am
content: This is odd. Everything appears to be in order, from what you've shown so far.What happens if you use ADC1 to sample an external pin connected to a voltage divider?What about configuring ADC1, leaving ADC2 and the cap touch module unused?Try a different chip?

author: Howardlong
author_rank: Super Contributor
timestamp: August 05, 2019, 08:35:29 pm
content: Code:// PIC16LF1554 Configuration Bit Settings// 'C' source line config statements// CONFIG1#pragma config FOSC = INTOSC // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled. SWDTEN bit is ignored.)#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)#pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled. SBOREN bit is ignored.)//#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)#pragma config CLKOUTEN = ON // Clock Out Enable (CLKOUT function is enabled on the CLKOUT pin)// CONFIG2#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), 1.9V trip point selected.)#pragma config LPBOR = OFF // Low-Power Brown Out Reset (Low-Power BOR is disabled)#pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)// #pragma config statements should precede project file includes.// Use project enums instead of for ON and OFF.<xc.h><stdint.h>_XTAL_FREQ 16000000static volatile uint16_t _u16ADCResult=0;void main(void){// Set to 16MHz INTOSCOSCCONbits.IRCF=0b1111; // 0b1111 => 16MHz// RC3 is output for debugTRISCbits.TRISC3=0;// Setup FVR to 1.024VFVRCONbits.ADFVR=0b01; // 0b01 => 1.024V (1x)FVRCONbits.FVREN=1;while (!FVRCONbits.FVRRDY){NOP();}// Set up ADCADCOMCONbits.ADCS=0b110; // 0b110 => Fosc/64ADCOMCONbits.ADFM=0; // 0 => Left justifyADCOMCONbits.ADPREF=0b00; // 0b00 => Vrefh = VddAD1CON0bits.CHS=0b11111; // 0b11111 => FVRAD1CON0bits.ADON=1;while (1){LATCbits.LATC3=1;__delay_us(200); // Wait enough time to sample channelAD1CON0bits.GO_nDONE=1; // Conversion startLATCbits.LATC3=0;while (AD1CON0bits.GO_nDONE) // Wait for conversion to finish.{NOP();}_u16ADCResult=AD1RES0;NOP();NOP();NOP();}return;}This gives 0x4F40, or 317 when nomalised.317 / 1024 * 3.3v = 1.022v, good enough.I don't bother with MCC, I found I spent far more time debugging what MCC generates than I did my own code.Edit: I notice that thehash includetext within the code tag went missing during the cut and paste, so I attached the main.c file.

author: KL27x
author_rank: Super Contributor
timestamp: August 06, 2019, 03:02:20 am
content: QuoteDoes the debugger gives you "codes" for the result of an ADC? I'm not being a wiseass. Serious question.I would assume the debugger gives you access to the contents of all the registers, including the ADC hi and lo results, which you have to locate and interpret, yourself. The result is 10 bits, stored across these two registers. You selected left justified. E1h still makes no sense for either one of those registers. I don't get how the debugger gave you a 10 bit value you happened to be interested in as a "code." Is the debugger really that fancy?.225 across two bytes, left justified, would be b'00111000' in the high byte, and b'10xxxxxx' in the low byte. Or 0x38, 0x80.

author: uer166
author_rank: Super Contributor
timestamp: August 06, 2019, 03:15:31 am
content: Nope, sorry, by "codes" I just meant the variable value when I inspect it after it hits a breakpoint on the assignment.

author: uer166
author_rank: Super Contributor
timestamp: August 06, 2019, 03:20:37 am
content: Thanks, your code works! Now to figure out what is going wrong in mine..

author: uer166
author_rank: Super Contributor
timestamp: August 06, 2019, 03:28:21 am
content: Oh god it's this MCC garbage.Generated code that doesn't work because it doesn't leave any time for sampling:Code:void ADC1_StartConversion(adc1_channel_t channel){// select the A/D channelAD1CON0bits.CHS = channel;// Turn on the ADC1 moduleAD1CON0bits.ADON= 1;// Start the conversionAD1CON0bits.GO_nDONE1 = 1;}Code that works based on above post:Code:void ADC1_StartConversion(adc1_channel_t channel){// select the A/D channelAD1CON0bits.CHS = channel;// Turn on the ADC1 moduleAD1CON0bits.ADON= 1;__delay_us(200);// Start the conversionAD1CON0bits.GO_nDONE1 = 1;}@Microchip, can you please figure your shit out, I used old parts with success, but the more I delve into newer stuff the more garbage it becomes..

author: NorthGuy
author_rank: Super Contributor
timestamp: August 06, 2019, 04:10:43 am
content: Quote from: uer166 on August 06, 2019, 03:28:21 amIf you want good code, write it yourself.

author: uer166
author_rank: Super Contributor
timestamp: August 06, 2019, 04:18:56 am
content: Quote from: NorthGuy on August 06, 2019, 04:10:43 amI guess one leans the hard way. I used to always only ever write own code, but got tempted by MCC's simple peripheral setup stuff..

author: KL27x
author_rank: Super Contributor
timestamp: August 06, 2019, 05:52:49 am
content: Is it possible that it doesn't automatically generate a delay for a reason?For starters, that minimum delay will depend on the output impedance of the voltage source being sampled.For second, even if you are sampling multiple pins, you might not need those delays, at all.You only need that delay after changing the ADC input, and there's nothing to say you need to change/select it before you take a sample. I mean, you're fairly likely to be using a timer(s) to trigger periodic sampling of these pins. So if you set the next input pin after your current read, you won't necessarily need that delay.*Addendum: If you left that delay in and just deducted that amount of time from your timer, that would be 200 microseconds of your main level where the processor can't do anything, every cycle of your ADC read period. The processor can be interrupted, but otherwise the program counter is just going to be pissing off time when it could be doing w/e else it is tasked with when the program is not taking ADC readings. Of course, if you used that code in a top level ISR, that couldn't be interrupted, at all.**Additional Addendum: This is just one potential example. And the cons might not be a big deal for a given project. I just wanted to throw that out there.***Another Additional Addendum: In places where you do need an inline delay, but you have too many other high priority tasks to handle thru ISR (because it's full, perhaps), with a dab of assembly you can code software interrupts right into a custom delay. I bet you can do that in C, just fine, too.It might be better to think of the MCC more as a device specific code template generator that spits out the bare minimum to operate that peripheral, vs an IDE "smart" feature that starts defining the sandbox without knowing what you intend to do with it.

author: Dabbot
author_rank: Regular Contributor
timestamp: August 06, 2019, 07:42:32 am
content: If you want a straight-forward approach, just discard the first ADC result after a channel switch. You're using the ADC itself as the delay.

author: malagas_on_fire
author_rank: Frequent Contributor
timestamp: August 08, 2019, 10:02:47 pm
content: another usage is using interrupt driven function for adc capture. Furthermore on page 132 of datasheet it has an example where it writes to a register the aquisiton time to AAD1ACQ and it is only required to wait for the GO/DONE in this maner or interrupt driven. as said, so no delay function is required?http://ww1.microchip.com/downloads/en/DeviceDoc/40001761E.pdf

author: KL27x
author_rank: Super Contributor
timestamp: August 10, 2019, 12:31:40 am
content: ^Delay will be needed AFTER switching ADC input pin in the muxulator. If you are not switching or reconfiguring ADC input (from digital output or something), then no, you don't need that extra delay.If driving from an interrupt, you can do a read, switch to the next input pin in the progression, then set a timer to re-arm the interrupt after the suitable delay.In the meantime, the processor can continue processing rather than timing itself out in the corner.If an external interrupt determines which pin must be read, then you could still get around an inline delay by setting the ADC pin and then starting a timer to trigger the read after a suitable delay.MCC creating an arbitrary inline delay would make it a very unprofessional tool, IMO. Even where you want to use an inline delay, MCC can't know how large or small to make it. Maybe you are connecting the pin to a 100K pot. Maybe it's connected to a 1K pot with a opamp buffer so you can get on with the reading faster.Heck, how does MCC even know you want an accurate ADC reading? Maybe you are using it in an unconventional way to achieve some purpose, and you are intentionally taking a reading before the voltage is stabilized. Maybe you already know the voltage and you are indirectly measuring output impedance.

author: NorthGuy
author_rank: Super Contributor
timestamp: August 10, 2019, 01:18:30 am
content: Quote from: KL27x on August 10, 2019, 12:31:40 amYou can use a timer to automatically trigger the conversion at regular intervals. This is as simple as specifying a timer in TRIGSEL. Then you just select the appropriate next channel from the ADC ISR (say from a circular list).Either way, you probably don't enable ADC every time you read it (as MCC does in OP's example) but do it once at the beginning. Honestly, you'll save lots of time and effort if you forget MCC and handle the ADC as you please. This way, you know exactly what you do, can select the best method, and can fix things quickly.

author: KL27x
author_rank: Super Contributor
timestamp: August 10, 2019, 03:46:15 am
content: ^malagas asking about interrupt. I already described what you have posted in a previous post/example. I agree that is a very common, if not most common, way that one might be doing ADC reads.Also, Malagas, you might be confusing two different delays. There's a delay that is necessitated after switching ADC input pin in the muxer. And there's a TAD. I forget what the T stands for. But the "AD" is acquisition delay (I think). This is how long it takes to measure/process the ADC, once it is initiated. This has to be messed with to account for clock speed, but the minimum TAD before you get skewed readings is otherwise predictable/consistent. When you initiate the ADC read, the simplified way to view it is the micro disconnects the input pin from the ADC cap/module, connects the cap to a sink connected to the ground reference, and then starts timing how long it takes for the ADC cap to be discharged to near zero by a comparator reading. That mechanism is run by the clock. So you have to adjust the number of ticks to the clock, so's it is not stopping too early. (Or taking too many redundant comparator readings after it no longer matters).The delay after switching the muxer has to cover the time required for the ADC module to reach the same voltage as on the input pin. MCC has no way to know how long that should take, and you don't necessarily want that time interval to be guaranteed by an inline delay. So it should not be generated by MCC, unless you want to turn MCC into a mickey mouse dev tool.

author: malagas_on_fire
author_rank: Frequent Contributor
timestamp: August 10, 2019, 11:34:46 am
content: Ok KL27x sorry to repeat that description. About the delays very clarified and thanks for you're explanation. I was refering to the example on the datasheet where the TAD is set by a register AAD1ACQ which is confusing because if you follow the datasheet on page 138 there is the formula to calculate the Time Aquisition time but then this register is described on the Cap Sense pins or CVD operation. So it's a bit tricky... Do we need two delays ? One for mux switching and the other for the TAD? Do we rely on configuration on this register ou doing delays only by clock cycles?More edit... Don't expect the MCC to generate since it is ment to be user configurable and although MCC helps generate code, it is recommended to do try by hand and follow examples, erratas, etc...

author: NorthGuy
author_rank: Super Contributor
timestamp: August 10, 2019, 01:14:44 pm
content: Quote from: malagas_on_fire on August 10, 2019, 11:34:46 amTAD is simply a period of ADC clock. Everything which is done by ADC is measured in TADs.The first step is acquisition. The duration depends on the input impedance. Weak signals require longer aquisition. Other PICs have ADACQ reguster. I guess this one uses AAD1ACQ instead. If you put something other than 0 into there, the ADC will wait the corresponding number of TADs after the GO bit is set, and only then goes to conversion.The second step is conversion. It takes a fixed number of TADs (usually one TAD per digit plus 2 extra TADs).You need to - select a channel, wait for acquisition, start conversion, wait for the result (GO bit goes low).If you only want one conversion. ADACQ may spare you extra manual wait. If it is not zero, there will be an automatic delay before the conversion starts (measured in TADs). So, you only select the channel, set the GO bit and wait for the result. Thus, you skip the manual wait that the OP had to insert. You don't have to use ADACQ - you can set it to 0 and wait manually (as OP did).If the process is interrupt driven and timed, you set ADACQ to 0. This is because the channel has been selected long time before trigger, and ADC is constantly acquiring the signal. Your perfectly timed trigger ends the acquisition at exactly perfect time and starts the conversion. Once the conversion is over, you get the ADC interrupt, where you read the result and change the channel thereby starting new aquisition. If ADACQ wasn't 0, it would mess up your timing by delaying the measurement relative to the trigger.

author: malagas_on_fire
author_rank: Frequent Contributor
timestamp: August 10, 2019, 07:33:31 pm
content: My problem related to TAD and this particular PIC16F is how the information on the datasheet is scatered when showing the procedure and example. I've done such thing in the 18F MCU's and they have a register ADCON2 for the TAD configuration ( page 227 ADCON2) on the ADC section but that was the old micro 18F2520 with lots of erratas : :https://malagas.wordpress.com/2011/11/25/nokia-3510-lcd-pic18f2520/#more-248Still it works fine with the lm35 sensor.