@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
}
}