Maker Pro
Maker Pro

ADC on PIC 12F1840 with inconsistent readings

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.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
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.
 

KrisBlueNZ

Sadly passed away in 2015
Start by posting a circuit and the relevant sections of code (ADC initialisation, ADC reading, and calculations).
 
@kris:
The whole project, source files(MPLABX and XC8) and schematics (to be opened with ExpressPCB) is on this thread messages 22 and 25.
https://www.electronicspoint.com/threads/lipo-over-discharge-protection.268596/page-2
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).
@Steve:
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.

Results:
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.

Code:
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
#endif

}


/*
* 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

    switch(Channel)
    {
        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
}
#endif



__CONFIG(FOSC_INTOSC & WDTE_OFF & PWRTE_ON & MCLRE_OFF & BOREN_ON & CP_OFF & CPD_OFF);


// 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;
#endif

#ifdef _12F1840
  //clock configuration
  OSCCONbits.IRCF = 14; //8MHz
  OSCCONbits.SPLLEN = 1; // 4 x PLL = 8 x 4 =32MHz

  PORTA = 0x00;  //all pins 0
#endif

  UART_Init();      // Intialize Software UART pin remapped to alternative
  InitADC();

   while(1)
   {
  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_Transmit('\x0d');
  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);

  __delay_ms(2000);

#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;
#endif

   }
}
 

Attachments

  • Schematic.jpg
    Schematic.jpg
    22.3 KB · Views: 309
Last edited:

KrisBlueNZ

Sadly passed away in 2015
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
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!
 
Top