Microchip makes reading ADC easy enough. Select a channel, start measurement, read a registed - doesn't get much easier than that. However, in one project of mine I got a bit stumped. While most of my readings were spot on, one was stubornly too high.
As I went over PIC16F15386 documentation a bit I saw a following note: "It is recommended that ... the user selects Vss channel before connecting to the channel with the lower voltage." Whether due to a high channel count of some other pecularity of this PIC, capacitance was strong with this one. One of the rare times when reading instructions actually solved an issue.
Well, solved might have been too optimistic of a statement. While my low voltage ADC channel now read a correct value, my higher voltage inputs read slightly too low. Yes, I am aware I sound like a daddy bear looking at his bed but I too wanted my readings to be just right and for the darn Goldilocks to leave my home.
What I found working for me is doing the following: switch channel to GND, (dummy) read ADC, switch to the correct channel, do the first (proper) reading, do the second reading, average the two readings. In code it would be something like this:
ADCON0bits.CHS = 0b011011; // select Vss channel
ADCON0bits.GO_nDONE = 1; // start an A/D conversion cycle
while (ADCON0bits.GO_nDONE); // wait for conversion to complete
ADCON0bits.CHS = channel; // select channel
ADCON0bits.GO_nDONE = 1; // start an A/D conversion cycle
while (ADCON0bits.GO_nDONE); // wait for conversion to complete
uint16_t value1 = ADRES; // read value
ADCON0bits.GO_nDONE = 1; // start an A/D conversion cycle
while (ADCON0bits.GO_nDONE); // wait for conversion to complete
uint16_t value2 = ADRES; // read value
ADCON0bits.CHS = 0b011011; // select Vss channel
return (value1 + value2 + 1) / 2;
Now, the obvious issue here is that three readings are done when only one is needed. Since we want to do averaging, there is nothing that can be done about reading it twice. However, if you are not pooling it all the time, you can pretty much skip the first (dummy) reading as switching to Vss channel at the end of routine does the trick.
While 16-bit operations are not the most pleasant thing 8-bit PIC can do, it's actually not too bad as we're talking about only 3 additions and one right shift (division by 2 gets optimized). Not good, but not terrible.
Even better, this works flawlessly with ADCs that have no issues with reading being too high or too low. This means I don't need to worry if I use routine with a different PIC microcontroller.
All in all, it's a cheap way to get a correct reading.