Electronic – Measuring Battery Voltage from Microcontroller using ADC

adcbatteriesmicrocontrollervoltage dividervoltage measurement

I am currently using a PIC16f1826 to try and measure the voltage of the 3.7v battery that is powering said microcontroller.

From my understanding, the ADC can measure from 0v up to the reference voltage divided in 1024 parts so I can't use the battery as a reference voltage. Instead I'm using a Fixed Voltage Reference at 2.048v that is generated in the microcontroller.

However 2.048 is below the 3.7v battery that I'm trying to measure so I'm using a voltage divider to halve the input so I can get meaningful readings, however when I do this and try and get my reading I get a value of 6-5/1023 which seems plain wrong. If I connect the battery without the voltage divider I get the full 1023/1023.

My circuit looks like the following:

schematic

simulate this circuit – Schematic created using CircuitLab

I have left out the UART connection because it is not relevant to the question. My code for the ADC is the following:

#include <xc.h>
#include <stdlib.h>

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#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 CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#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), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

// Define Oscillator Frecuency to use in delay_ms calculations.
#define _XTAL_FREQ   4000000  // 4MHz

// Function that initializes ADC parameters.
void initADC(void) {

    // Enable Fixed Voltage Reference.
    FVRCONbits.FVREN = 1;

    // Set Fixed Voltage Reference to 2.048V.
    FVRCONbits.ADFVR = 0b10;

    // Enable Analog input on RA1.
    TRISA1 = 0;
    ANSELAbits.ANSA1 = 0;

    // Define Conversion Clock to use FOSC/4.
    ADCON1bits.ADCS = 0b100;

    // Configure ADC Positive Reference Voltage to use FVR buffer.
    ADCON1bits.ADPREF = 0b11;

    // Configure ADC Negative Reference Voltage to use VSS.
    ADCON1bits.ADNREF = 0;

    // Output format as right justified.
    ADCON1bits.ADFM = 1;

    // Select Pin AN1 as Channel.
    ADCON0bits.CHS = 0b00001;

    // Start ADC Module.
    ADCON0bits.ADON = 1;

    // Start conversion.
    ADCON0bits.GO_nDONE = 1;
}
// Main function.
void main(void) {

    // Setup Internal Oscillator to run at 4MHz.
    OSCCONbits.IRCF = 0b1101;

    // Initialize ADC.
    initADC();

    while(1) {
        if(!ADCON0bits.GO_nDONE) {
            sendString("Ready: ");
            // Read ADC Result Register High and 
            // Register Low for full 10 bit scope.
            int adcResult = ((ADRESH)<<8)|(ADRESL);

            char percentToChar[5];
            itoa(percentToChar, adcResult, 10);
            sendString(percentToChar);
            sendString("\n");
            ADCON0bits.GO_nDONE = 1;

        } else {
            sendString("Not ready \n");
        }
        __delay_ms(300);
    }
}

The manual for the microcontroller can be found at: http://ww1.microchip.com/downloads/en/DeviceDoc/41391D.pdf
Page 135 covers the Fixed Voltage Reference and Page 140 covers ADC.

Why am I getting the reading I'm getting in the ADC? It doesn't make sense to me, am I doing something wrong in the code maybe? Should I use the comparator module instead of the ADC to do this? Any help you can provide to help me solve this will be greatly appreciated!

Best Answer

This is not my answer, but this question has been sitting here with the answer in the comments for months.

As Wouter van Ooijen states in the comments, using the internal reference as the A/D input and using Vdd as the reference, the

reading of N coresponds to a Vdd of 1023 * Vref/N