Connect with us

ADC on PIC 12F1840 with inconsistent readings

Discussion in 'Microcontrollers, Programming and IoT' started by shumifan50, Jun 2, 2014.

Scroll to continue with content
  1. shumifan50


    Jan 16, 2014
    I am using a PIC 12F1840 (or PIC12F675) to read the voltage on a 3 cell LiPo and getting 'surprising' values on the 3 ADC channels. Each cell is connected to a voltage divider with a 10K/4K7 keeping the voltage on the PIC pins to max around 4V. On the 12F1840 I use the internal reference voltage (4.096V) while the 675 uses Vdd. When I measure the voltage across the 4K7 resistors(Va) I expect the value read, for the 12F1840, to be (Va / 4.096) * 1023. For the first cell I get very close to this reading, maybe 1 off. However, the second and third cell read quite a way under the expected values - translates to about 0.3V too low. I found a recommendation to put a 0.1uF cap across the 4K7 resistor to correct the impedance to the analogue pin, however this makes no difference on the 1840, which should be more accurate than the 675 because of the internal reference voltage. I have tried various delays to allow the ADC cap to charge, but this made no difference. I have also checked and double checked connections. The readings are all stable within 1 between samples on all cells. Note that each cell is read between its positive and ground for the circuit.

    I can't believe the ADC of the PIC is off, it must be something in my circuit or program.

    Is it possible that the voltage divider of the first cell is interfering with second cell and the first and second cell voltage dividers interfering with the third cell. I don't think this is the case as the readings are correct using a multimeter, but maybe.

    Any suggestions on where to look for the fault.
  2. (*steve*)

    (*steve*) ¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd Moderator

    Jan 21, 2010
    Try a larger capacitor across the resistor (you're not looking for rapid changes anyway).

    Also see if there is any setup time required. I recall a problem a colleague had with a PIC where a delay needed to be inserted somewhere to give the ADC time to settle. I can't remember exactly, but I think the problem was that the first reading was OK, but subsequent ones were affected, and he was also reading from multiple inputs. The more I write the more I think I remember. I *think* that the chip had a single ADC that could be switched to multiple input pins, and his problem was that insufficient delay between switching to the pin and reading the ADC would cause some issue that was solved by selecting the pin, waiting, then reading. The wait was something small, like some NOPs or something equally trivial.

    Take this "advice" with a grain of salt.
  3. KrisBlueNZ

    KrisBlueNZ Sadly passed away in 2015

    Nov 28, 2011
    Start by posting a circuit and the relevant sections of code (ADC initialisation, ADC reading, and calculations).
  4. shumifan50


    Jan 16, 2014
    The whole project, source files(MPLABX and XC8) and schematics (to be opened with ExpressPCB) is on this thread messages 22 and 25.
    The only circuit change is that the voltage divider now uses a 4K7 rather 5K6 to limit the voltage on the PIC pin around 4V (internal reference of 12F1840 is 4.096V).
    I have varied the settling time for the cap in the PIC between 50uS up to 50ms, but it makes no difference, except at the very low ranges. As these measurements are not time critical, I have settled for 10ms between selecting the channel and initiating the conversion. It is true the 1840 has only one ADC converter and you have to select the channel that you want to connect to it to make a reading.

    In the source posted on the other thread, I fudged some numbers as the calculations were way off with integer arithmetic and the 12F675 does not have enough flash to use the floating point libraries. I use the floating point libraries on the 1840, but it still seems like the floating point module does some strange rounding and the sequence of operations, irrespective of brackets, have to be done carefully. My conclusions about the incorrect readings does, however, not rely on my calculations in the program; I display the ADC readings and then calculate manually.

    The clock is running at 32MHz.

    I scoped the analogue pins on the PIC and there is no ripple.
    The following results are dead stable.

    Multimeter----ADC value---------ADC Volts-------ADC Cell voltage
    4.14 /4.14------------332-------------4.15------------4.15 //0.01 volt too high
    8.30 /4.16------------662-------------8.29------------4.14 //0.02V too low
    12.46 /4.16-----------991------------12.41-----------4.12 //0.05 volt too low

    Below the code for the 12F1840
    The 12F1840 is more attractive as it has a built-in USART and enough memory to allow use of the floating point libraries. In the final version I will run with the lowest clock speed possible.

    void InitADC()
    #ifdef _12F1840
            ADCON1bits.ADFM = 1; //right justified result L=8bits, H=2bits
            ADCON1bits.ADCS = 2; //conversion clock selection (Fosc / 32)
            ADCON1bits.ADPREF = 3;  //select internal fixed voltage reference
            FVRCONbits.FVREN = 1; //enable internal voltage reference
            FVRCONbits.ADFVR = 3; //select 4.096V as reference(4 x 1.024)
            ANSELAbits.ANSA0 = 1; //select AN0
            ANSELAbits.ANSA1 = 1;
            ANSELAbits.ANSA2 = 1;
            TRISAbits.TRISA0 = 1; //input
            TRISAbits.TRISA1 = 1;
            TRISAbits.TRISA2 = 1;
            ADCON0bits.ADON = 1;
            ADCON0bits.CHS = 0; //turn off all ADC channels
            CM1CON0bits.C1ON = 0; //disbale comparator
    * Function Name: GetADCValue
    * Input(s) :     Channel name, it can be AN0, AN1, AN2 or AN3 only.
    *                Channel is selected according to the pin you want to use in
    *                the ADC conversion. For example, use AN0 for GP0 pin.
    *                  Similarly for GP1 pin use AN1 etc.
    * Output(s):     10 bit ADC value is read from the pin and returned.
    * Author:        M.Saeed Yasin   20-06-12
    #ifdef _12F1840
    unsigned int GetADCValue(unsigned char Channel)
        ADCON0bits.CHS = 0;  // Clear Channel selection bits
            case AN0:    ADCON0bits.CHS = 0; break;      // Select AN0 pin as ADC input
            case AN1:    ADCON0bits.CHS = 1; break;      // Select AN1 pin as ADC input
            case AN2:    ADCON0bits.CHS = 2; break;      // Select AN2 pin as ADC input
            case AN3:    ADCON0bits.CHS = 3; break;      // Select AN4 pin as ADC input
            default:    return 0;                     //Return error, wrong channel selected
        __delay_ms(10);      // Time for Acqusition capacitor to charge up and show correct value
        ADCON0bits.ADGO  = 1;         // Enable Go/Done
        while(ADCON0bits.GO_nDONE);     //wait for conversion completion
        return ((ADRESH<<8)+ADRESL);   // Return 10 bit ADC value
    // Define CPU Frequency
    // This must be defined, if __delay_ms() or
    // __delay_us() functions are used in the code
    #define _XTAL_FREQ  32000000
    // Main function
    void main()
       double adcVal = 0;
    #ifdef _12F1840
      double Volt1;
      double Volt2;
      double Volt3;
      double adc1;
      double adc2;
      double adc3;
    #ifdef _12F1840
      //clock configuration
      OSCCONbits.IRCF = 14; //8MHz
      OSCCONbits.SPLLEN = 1; // 4 x PLL = 8 x 4 =32MHz
      PORTA = 0x00;  //all pins 0
      UART_Init();      // Intialize Software UART pin remapped to alternative
      UART_Transmit('\x0c');  //first cell
      adc1 = adcVal = (double)GetADCValue(AN0) ;
      Volt1 = adcVal * 1.252286766;  // const for * 14700 / 4700 * 4.096 / 1023.0 * 100;
      UART_displayVolts((int)((double)(Volt1)), 2); //((adcVal*100)/1023)*5);
      UART_Transmit(' '); //2nd cell
      adc2 = adcVal = (double)GetADCValue(AN1);
      Volt2 = adcVal * 1.252286766;  // const for * 14700 / 4700 * 4.096 / 1023.0 * 100;
      UART_displayVolts((int)((double)((Volt2 - Volt1))), 2);
      UART_Transmit(' '); //third cell
      adc3 = adcVal = (double)GetADCValue(AN2);
      Volt3 = adcVal * 1.252286766 ; // const for * 14700 / 4700 * 4.096 / 1023.0 * 100;
      UART_displayVolts((int)((double)(Volt3 - Volt2)), 2);
      //display adc values
      UART_displayVolts((int)adc1, 0);
      UART_Transmit(' '); //2nd cell
      UART_displayVolts((int)adc2, 0);
      UART_Transmit(' '); //2nd cell
      UART_displayVolts((int)adc3, 0);
      UART_Transmit(' '); //Battery
      UART_displayVolts((int)(Volt3+5), 1);
    #ifdef _12F1840
      PORTAbits.RA5 = 1;
      if ((Volt1+5) < MINVOLTS )  //413=4.13V
           PORTAbits.RA5 = 0;    //this kills the power to everything
      if (((Volt2-Volt1)+5) < MINVOLTS)
           PORTAbits.RA5 = 0;
      if ((Volt3-(Volt2)+5) < MINVOLTS)
           PORTAbits.RA5 = 0;

    Attached Files:

    Last edited: Jun 2, 2014
    vishnu likes this.
  5. KrisBlueNZ

    KrisBlueNZ Sadly passed away in 2015

    Nov 28, 2011
    An error of 0.05V when measuring with a full scale of 12.9V is only 0.4% which is well within the expected tolerance when you're calculating the difference between voltages coming from two independent voltage dividers made with 1% resistors. And that's without even considering the error in VREF or VCC!
  6. shumifan50


    Jan 16, 2014
    Thanks that's great - so I was trying to solve a problem that did not exist.
Ask a Question
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.
Electronics Point Logo
Continue to site
Quote of the day