Categories
Embedded projects.

Improve ADC accuracy in PICs.

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s