Improve ADC accuracy in PICs using the integrated Fixed Voltage Reference (when available).
Here I have shared my approach on increasing the accuracy of reading analog values with the integrated ADC of a PIC microcontroller which has integrated Fixed Voltage Reference FVR source. When using VDD as the reference voltage inaccuracies might appear due to fluctuations in VDD value.
The C code bellow comes from a project I have based on a PIC16f1503. It has a FVR that can be configured for 1.024V, 2.048V or 4.096V. It can be used as the reference voltage for the ADC, but then the voltage that can be measured can not be higher than the FVR voltage. The more important is that only the reference voltage will be fixed, but if the masured value is dependant on the fluctuations in the supply voltage of the cirquit, those fluctuations can’t be accounted for by simply using the FVR source as a reference for the ADC.
My approach is to use VDD as the reference voltage and periodically switch the ADC to measure the FVR value. FVR voltage is configured to 2.048V. Given that, the real ADC resolution with the current value of VDD can be calculated.
This is done by the analog_calculate_resolution(void) function listed below. The idea is described with an example in the comments before the fuction body.
There should be a call to analog_calculate_resolution(); in the main cycle or before every read of the ADC.
Then switch the ADC to the input on which voltage must be measured and do the math.
uint16_t voltage = 0u; analog_calculate_resolution(); analog_select_input(analog_input_1); voltage = ((uint16_t)(((int32_t)g_analog_resolution * (int32_t)analog_convert()) / 10000u));
Here is the listing of the analog.h and analog.c files.
analog.h
#ifndef ANALOG_H #define ANALOG_H typedef enum analog_inputs_t { analog_input_1 = 0u, analog_frv_in } analog_inputs_t; extern uint16_t g_analog_resolution; void analog_config(void); uint16_t analog_convert(void); void analog_select_input(analog_inputs_t pin); void analog_calculate_resolution(void); #endif //ANALOG_H
analog.c
#include <xc.h> #include <pic.h> #include <pic16f1503.h> #include <stdint.h> #include "types.h" #include "analog.h" #define ANALOG_PIN_DIRECTION_INPUT_1 TRISA4 #define ANALOG_PIN_INPUT_1 ANSELAbits.ANSA4 #define ANALOG_CONVERSION_TIME 400u //uS to wait for reconfigure #define ANALOG_SELECT_RA4 0x03u //analog input 1 #define ANALOG_SELECT_FVR 0x1Fu //Fixed Reference Voltage #define ANALOG_VFR 2048000u //2.048V in micro volts uint16_t g_analog_resolution = 4887u; //ADC resolution in micro volts void analog_config(void) { //configure by default ADC on PIN RC3, ANALOG_PIN_DIRECTION_INPUT_1 = INPUT; ANALOG_PIN_INPUT_1 = ON; //analog input for analog input 1 ADCON0 = ANALOG_SELECT_RA4; //default analog input 1 ADCON1 = 0x80u; //right justified; Fosc/16 clock; Vrpos = Vdd ADCON2 = 0x00u; //no auto conversion FVRCON = 0x82u; // Enable Fixed Voltage Reference at 2.048 V ADCON0bits.ADON = ON; //AD on __delay_us(ANALOG_CONVERSION_TIME); return; } uint16_t analog_convert(void) { ADCON0bits.GO_nDONE = ON; //start conversion while ( ON == ADCON0bits.GO_nDONE ) { //Wait for conversion to finish. } return (uint16_t)(((ADRESH & 0x03u) << 8u) + ADRESL); } void analog_select_input(analog_inputs_t pin) { ADCON0bits.GO_nDONE = OFF; //stop conversion if going on ADCON0bits.ADON = OFF; //AD off //switch the source switch (pin) { case analog_input_1: ADCON0bits.CHS = ANALOG_SELECT_RA4; break; case analog_frv_in: //Fixed Voltage Reference for correction ADCON0bits.CHS = ANALOG_SELECT_FVR; break; default: break; } ADCON0bits.ADON = ON; //AD on __delay_us(ANALOG_CONVERSION_TIME); return; } // Vrfv = 2.048V - Fixed reference voltage // Afrv - ADC content when Vrfv is measured // Rfrv - Resolution when Vfrv is measured // The actual resolution is calculated based on the ADC value for the Vfrv. // // Rfvr = Vrfv / Afrv, to do the math with integers only, micro volts are used // Example: // Vrfv = 2_048_000 uV // Afrv = 419 // Rfvr = 2_048_000 / 419 = 4887 uV/bit // In this case when Vdd is exactly 5V, the ADC should have the value of: // 5_000_000 / 4887 = 1023 // // If the measured Afrv is lets say 422, then: // Rfvr = 2_048_000 / 422 = 4853 uV/bit // Based on this we can determine that the Vdd is: // Vdd = Rfvr * 1023 = 4853 * 1023 = 4_964_619 uV // // // Vdd variations due to ambient temperature changes are compensated for, by // using Rfvr to calculate the actual voltage read from the temperature sensor. // void analog_calculate_resolution(void) { analog_select_input(analog_frv_in); g_analog_resolution = (uint16_t)(ANALOG_VFR / analog_convert()); return; }
types.h
#ifndef TYPES_H #define TYPES_H #define OUTPUT 0u #define INPUT 1u #define LOW 0u #define HIGH 1u #define OFF 0u #define ON 1u typedef uint8_t BOOL; #define FALSE 0u #define TRUE !FALSE #endif //TYPES_H