12V Battery Capacity Tester
Measuring the capacity of a battery
Components and supplies
1
RGB LCD Shield Kit, 16x2 Character Display
1
Arduino Nano R3
Tools and machines
1
Soldering iron (generic)
Project description
Code
Code for Arduino Nano
c_cpp
Open in Arduino IDE
1#include <EEPROM.h> 2#include <LiquidCrystal.h> 3 4/*----- PIN Definitions -----*/ 5 6// LCD: 7const uint8_t rs=12; // PB4 8const uint8_t en=11; // PB3 9const uint8_t ld4=7; // PD7 10const uint8_t ld5=6; // PD6 11const uint8_t ld6=5; // PD5 12const uint8_t ld7=4; // PD4 13 14const int Encod1=2; // PD2 encoder pinA 15const int Encod2=3; // PD3 encoder pinB 16 17const int KEYPRESS=8; // PB0 Button 18const int AMP_IN=A0; // PC0 Current in 19const int VOLT_IN=A1; //15; // PC1 Bat Voltage in 20const int HEATSINK_IN=A2; // PC2 Heatsink temp 21const int AMBIENT_IN=A3; // PC3 Ambient temp 22const int AMP_OUT=9; // PB1 Current control 23const int CHRG_OUT=13; // PB5 Charge relay on 24const int FAN_OUT=10; // PB2 Fan on 25 26LiquidCrystal lcd(rs, en, ld4, ld5, ld6, ld7); 27 28struct EEPROMDATA { 29 uint16_t voltFactor; // 0..1023=0..20.00V. 600 = 12.000V -> factor=~2000 30 long Ain1; // 31 long Ain5_1; // Ain5 - Ain1 32 int stopmV; 33 int battery_Ah; // Battery size 34 int duration; // test hours 35 int measured_Ah; // Battery size 36 uint8_t chksum; 37} eepromdata; 38 39const EEPROMDATA eeprom_default={ 40 1500, // voltFactor 41 32, // Ain1 42 260, // Ain5_1 43 10500, // stopMV 44 40, // batt Ah 45 20, // test hours 46 0, // measured 47 0}; // chksum 48 49const uint8_t chktoken = 0x5A; 50 51enum STATE { 52STATE_INIT, 53STATE_HOME, 54STATE_SET_AH, 55STATE_SET_DURATION, 56STATE_SET_STOPV, 57STATE_RUNNING, 58STATE_FINISHED, 59STATE_CALIB_START, 60STATE_CALIB_AMP_1, 61STATE_CALIB_AMP_5, 62STATE_CALIB_VOLTAGE}; 63 64const char NEXT_STATE[]={ 65/* STATE_INIT*/ STATE_HOME, 66/* STATE_HOME */ STATE_SET_AH, 67/* STATE_SET_AH */ STATE_SET_DURATION, 68/* STATE_SET_DURATION */ STATE_SET_STOPV, 69/* STATE_SET_STOPV */ STATE_RUNNING, 70/* STATE_RUNNING */ STATE_FINISHED, 71/* STATE_FINISHED */ STATE_HOME, 72/* STATE_CALIB_START */ STATE_CALIB_AMP_1, 73/* STATE_CALIB_AMP_1 */ STATE_CALIB_AMP_5, 74/* STATE_CALIB_AMP_5 */ STATE_CALIB_VOLTAGE, 75/* STATE_CALIB_VOLTAGE */ STATE_HOME }; 76 77volatile char state = -1; 78 79const char *menus[] = { 80/* STATE_INIT */ " Battery Tester ", 81/* STATE_HOME */ " Press to begin ", 82/* STATE_SET_AH */ "Set Battery size", 83/* STATE_SET_DURATION */ "Set Test time ", 84/* STATE_SET_STOPV */ "Set End Voltage ", 85/* STATE_RUNNING */ " Running ", 86/* STATE_FINISHED */ " Recharging ", 87/* STATE_CALIB_START */ " Calibration ", 88/* STATE_CALIB_AMP_1 */ "Calibrate amps 1", 89/* STATE_CALIB_AMP_5 */ "Calibrate amps 5", 90/* STATE_CALIB_VOLTAGE */ "Calibrate volts"}; 91 92 93char line[20]; // a few extra chars just in case 94char lastKey = 1; 95 96/* Encoder interrupt. 97 NOTE: No debounce elimination. 98 There can be several INTs per step, but encodeval 99 always ends up with the right value */ 100volatile char oldPA; 101volatile int encodeval = 0; // this is the decode output 102volatile bool encodeNegAllowed=false; // true if negative values allowed 103uint32_t startseconds = 0; 104int32_t set_mA=0; 105uint16_t low_V_Count=0; 106 107const uint8_t max_Amp=10; 108 109void decodeInt() { // Interrupt from iPinA 110 int newPA = digitalRead(Encod1); // //(PIND>>2) & B11; // volatile 111 int newPB = digitalRead(Encod2); 112 if (oldPA ^ newPA) { 113 if (newPA==newPB) encodeval--; 114 else encodeval++; 115 } 116 if (!encodeNegAllowed && encodeval<0){ 117 encodeval=0; 118 } 119 oldPA = newPA; 120} 121 122 123/***************** ANALOG/DIGITAL I/O ***********************/ 124 125void charge_on(bool on){ 126 digitalWrite(CHRG_OUT,on); 127} 128 129void fan_on(bool on){ 130 digitalWrite(FAN_OUT,on); 131} 132 133const int analogDamping=4; 134long Ain=0; 135int Vin=0; 136uint8_t Aout=0; 137 138 139long get_current_mA(){ 140 return (Ain-eepromdata.Ain1) * 4000 / eepromdata.Ain5_1 + 1000; 141// return (Ain-eepromdata.Ain1) * 10 / eepromdata.Ain5_1 * 400 + 1000; 142} 143 144int32_t get_voltage_mV(){ 145 return ((int32_t)Vin * eepromdata.voltFactor) / 100; // factor =~2000 146} 147 148void set_Aout(int val){ 149 if (val>255) val=255; 150 if (val<0) val=0; 151 Aout=val; 152 analogWrite(AMP_OUT,val); 153 fan_on((val>0)); 154} 155 156void read_analog_inputs(){ // called at 20 Hz 157 Vin=(Vin*(analogDamping-1)+analogRead(VOLT_IN))/analogDamping; // 0..1023 -> 0..17V; 12V ~= 600 158 Ain=(Ain*(analogDamping-1)+analogRead(AMP_IN))/analogDamping; // 0..1023 -> 0..17000 mA; 10000 mA ~= 600 159} 160 161uint32_t seconds(){ 162 return millis()/1000; 163} 164 165/***************** LCD ***********************/ 166 167void clearLine() { 168 for (char i = 0; i < 16; i++) { 169 line[i] = ' '; 170 } 171 line[16] = 0; 172} 173 174void lcdPrint(int line, const char *text) { 175 // note: display is not cleared - make sure line is 16 chars 176 lcd.setCursor(0, line); 177 lcd.print(text); 178} 179 180uint16_t dPrint_batt_mV(){ 181 uint16_t v=get_voltage_mV(); 182 sprintf(line,"Battery = %2i.%01i V", v/1000, v%1000/10); 183 lcdPrint(1, line); 184 return v; 185} 186 187uint16_t dPrint_batt_mV_m(){ 188 uint16_t v=get_voltage_mV(); 189 sprintf(line, "%2i.%02iV RES=%3iAh", v/1000, v%1000/10, eepromdata.measured_Ah ); 190 lcdPrint(1, line); 191 return v; 192} 193 194void dPrint_stopmV(){ 195 int v=eepromdata.stopmV; 196 sprintf(line,"Dischrg to %2i.%1iV" ,v/1000, v%1000 / 100); 197 lcdPrint(1, line); 198} 199 200void dPrint_Ah(){ 201 int v=eepromdata.battery_Ah; 202 sprintf(line,"Capacity = %3iAh", v); 203 lcdPrint(1, line); 204} 205 206void dPrint_Duration(){ 207 int v=eepromdata.duration; 208 sprintf(line,"Duration = %3ih", v); 209 lcdPrint(1, line); 210} 211 212void dPrint_volt_curr_time(){ 213 int v=get_voltage_mV(); 214 int a=get_current_mA(); 215 if (a<0) a=0; 216 if (a>9999) a=9999; 217 int t=(seconds() - startseconds)/60; // minutes 218 sprintf(line,"%2i.%1iV %1i.%1iA %02i:%02i", v/1000, v%1000/100, a/1000, a%1000/100, t/60, t%60); 219 lcdPrint(1, line); 220} 221 222void dPrint_result(){ 223 sprintf(line, "Result %3iAh %2i%%", eepromdata.measured_Ah, eepromdata.measured_Ah*100/eepromdata.battery_Ah); 224 lcdPrint(1, line); 225} 226 227 228int calc_sum(uint8_t *p, char len){ 229 int sum=0; 230 for (char i=0; i<len; i++){ 231 sum += p[i]; 232 } 233 return sum; 234} 235 236void eepromPut(){ 237 eepromdata.chksum=chktoken-calc_sum((uint8_t*)&eepromdata, sizeof(eepromdata)-1); 238 EEPROM.put(0, eepromdata); 239} 240 241void eepromGet(){ 242 EEPROM.get(0, eepromdata); 243 uint8_t s = calc_sum((uint8_t*)&eepromdata, sizeof(eepromdata)); 244 if (s!=chktoken){ 245 eepromdata = eeprom_default; 246 eepromPut(); 247 } 248} 249 250 251void setState(int newState){ 252 if (state!=newState){ 253 state = newState; 254 lcd.clear(); 255 lcdPrint(0, menus[state]); 256 charge_on(state==STATE_FINISHED); 257 258 } 259} 260 261void setup() { 262 263 pinMode(AMP_OUT, OUTPUT); // sets the pin as output 264 analogWrite(AMP_OUT,0); // turn off load 265 pinMode(FAN_OUT, OUTPUT); 266 analogWrite(FAN_OUT,0); // turn off fan 267 pinMode(CHRG_OUT, OUTPUT); 268 digitalWrite(CHRG_OUT,0); // turn off charger 269 pinMode(VOLT_IN,INPUT); 270 Vin=analogRead(VOLT_IN); 271 pinMode(HEATSINK_IN, INPUT); 272 pinMode(AMBIENT_IN, INPUT); 273 274// Serial.begin(9600); 275 276 lcd.begin(16, 2); 277 278 pinMode(KEYPRESS, INPUT_PULLUP); 279 pinMode(Encod1, INPUT_PULLUP); 280 pinMode(Encod2, INPUT_PULLUP); 281 oldPA = digitalRead(Encod1); 282 283 eepromGet(); 284 285 attachInterrupt(digitalPinToInterrupt(Encod1), decodeInt, CHANGE); 286 287 lastKey = digitalRead(KEYPRESS); 288 if (!lastKey) { 289 setState(STATE_CALIB_START); 290 } else { 291 setState(STATE_INIT); 292 lcdPrint(1, "www.state.dk/bct"); 293 } 294 295} 296 297void loop() { 298 299 int32_t v, a, d; 300 char k; 301 int press_duration=0; 302 303 delay(50); // max 20 runs per sec to avoid key bounce 304 305 read_analog_inputs(); 306 307 k = digitalRead(KEYPRESS); //readKey(); 308 309 if (!k) press_duration++; 310 else press_duration=0; 311 if (press_duration>100) setState(STATE_CALIB_START); // 5 sec press -> calibrate 312 313 bool clicked = !k && lastKey; 314 lastKey=k; 315 316 switch (state) { 317 case STATE_INIT: 318 break; 319 case STATE_HOME: 320 dPrint_batt_mV_m(); 321 break; 322 case STATE_SET_AH: 323 if (encodeval>200) encodeval=200; 324 eepromdata.battery_Ah=encodeval; 325 dPrint_Ah(); 326 break; 327 case STATE_SET_DURATION: 328 if (encodeval<1) encodeval=1; 329 if (encodeval < eepromdata.battery_Ah/max_Amp) encodeval = eepromdata.battery_Ah/max_Amp; 330 eepromdata.duration=encodeval; 331 dPrint_Duration(); 332 break; 333 case STATE_SET_STOPV: 334 eepromdata.stopmV=encodeval*10; 335 dPrint_stopmV(); 336 break; 337 case STATE_RUNNING: 338 v=get_voltage_mV(); 339 a=get_current_mA(); //if (a>9999) a=9999; 340 d = set_mA - a; 341 342 dPrint_volt_curr_time(); 343 if (abs(d) > 50){ 344 if (d>0) set_Aout(Aout+1); 345 else set_Aout(Aout-1); 346 } 347 if (v<eepromdata.stopmV){ 348 low_V_Count++; 349 if (low_V_Count>100){ // 5 sec 350 setState(STATE_FINISHED); 351 set_Aout(0); 352 eepromdata.measured_Ah= (seconds() - startseconds) / eepromdata.duration * eepromdata.battery_Ah / 3600; 353 eepromPut(); 354 dPrint_result(); 355 } 356 } else{ 357 low_V_Count=0; 358 } 359 break; 360 case STATE_FINISHED: 361 break; 362 case STATE_CALIB_START: 363 break; 364 case STATE_CALIB_AMP_1: 365 set_Aout(encodeval); 366 eepromdata.Ain1 = Ain; 367 sprintf(line,"Set 1A %03i %03i", encodeval, Ain); 368 lcdPrint(1, line); 369 break; 370 case STATE_CALIB_AMP_5: 371 set_Aout(encodeval); 372 eepromdata.Ain5_1 = Ain - eepromdata.Ain1; 373 sprintf(line,"Set 5A %03i %03i", encodeval, Ain); 374 lcdPrint(1, line); 375 break; 376 case STATE_CALIB_VOLTAGE: 377 eepromdata.voltFactor = encodeval; 378 dPrint_batt_mV(); 379 break; 380 } 381 382 if (clicked) { // Last thing to do just before acting on a click 383 set_Aout(0); 384 switch (state) { 385 case STATE_SET_AH: 386 case STATE_SET_DURATION: 387 case STATE_SET_STOPV: 388 case STATE_CALIB_AMP_1: 389 case STATE_CALIB_AMP_5: 390 case STATE_CALIB_VOLTAGE: 391 eepromPut(); 392 break; 393 } 394 395 setState(NEXT_STATE[state]); // on to next state 396 397 switch (state) { // First thing to do after setting new state 398 case STATE_HOME: 399 break; 400 case STATE_SET_AH: 401 encodeval=min(200,eepromdata.battery_Ah); 402 break; 403 case STATE_SET_DURATION: 404 encodeval=eepromdata.duration; 405 break; 406 case STATE_SET_STOPV: 407 encodeval=max(9000, eepromdata.stopmV)/10; // resolution 10 mV 408 break; 409 case STATE_RUNNING: 410 Aout=0; 411 set_mA = ((eepromdata.battery_Ah * 100) / eepromdata.duration) * 10; // split *1000 to avoid overrun 412 low_V_Count = 0; 413 startseconds=seconds(); 414 break; 415 case STATE_FINISHED: // user interrupted 416 eepromdata.measured_Ah=0; 417 eepromPut(); 418 setState(STATE_HOME); 419 break; 420 case STATE_CALIB_AMP_1: 421 encodeval=40; 422 break; 423 case STATE_CALIB_AMP_5: 424 encodeval=120; 425 break; 426 case STATE_CALIB_VOLTAGE: 427 encodeval=eepromdata.voltFactor; 428 break; 429 } 430 } 431 432} 433
Code for Arduino Nano
c_cpp
Open in Arduino IDE
1#include <EEPROM.h> 2#include <LiquidCrystal.h> 3 4/*----- PIN Definitions -----*/ 5 6// LCD: 7const uint8_t rs=12; // PB4 8const uint8_t en=11; // PB3 9const uint8_t ld4=7; // PD7 10const uint8_t ld5=6; // PD6 11const uint8_t ld6=5; // PD5 12const uint8_t ld7=4; // PD4 13 14const int Encod1=2; // PD2 encoder pinA 15const int Encod2=3; // PD3 encoder pinB 16 17const int KEYPRESS=8; // PB0 Button 18const int AMP_IN=A0; // PC0 Current in 19const int VOLT_IN=A1; //15; // PC1 Bat Voltage in 20const int HEATSINK_IN=A2; // PC2 Heatsink temp 21const int AMBIENT_IN=A3; // PC3 Ambient temp 22const int AMP_OUT=9; // PB1 Current control 23const int CHRG_OUT=13; // PB5 Charge relay on 24const int FAN_OUT=10; // PB2 Fan on 25 26LiquidCrystal lcd(rs, en, ld4, ld5, ld6, ld7); 27 28struct EEPROMDATA { 29 uint16_t voltFactor; // 0..1023=0..20.00V. 600 = 12.000V -> factor=~2000 30 long Ain1; // 31 long Ain5_1; // Ain5 - Ain1 32 int stopmV; 33 int battery_Ah; // Battery size 34 int duration; // test hours 35 int measured_Ah; // Battery size 36 uint8_t chksum; 37} eepromdata; 38 39const EEPROMDATA eeprom_default={ 40 1500, // voltFactor 41 32, // Ain1 42 260, // Ain5_1 43 10500, // stopMV 44 40, // batt Ah 45 20, // test hours 46 0, // measured 47 0}; // chksum 48 49const uint8_t chktoken = 0x5A; 50 51enum STATE { 52STATE_INIT, 53STATE_HOME, 54STATE_SET_AH, 55STATE_SET_DURATION, 56STATE_SET_STOPV, 57STATE_RUNNING, 58STATE_FINISHED, 59STATE_CALIB_START, 60STATE_CALIB_AMP_1, 61STATE_CALIB_AMP_5, 62STATE_CALIB_VOLTAGE}; 63 64const char NEXT_STATE[]={ 65/* STATE_INIT*/ STATE_HOME, 66/* STATE_HOME */ STATE_SET_AH, 67/* STATE_SET_AH */ STATE_SET_DURATION, 68/* STATE_SET_DURATION */ STATE_SET_STOPV, 69/* STATE_SET_STOPV */ STATE_RUNNING, 70/* STATE_RUNNING */ STATE_FINISHED, 71/* STATE_FINISHED */ STATE_HOME, 72/* STATE_CALIB_START */ STATE_CALIB_AMP_1, 73/* STATE_CALIB_AMP_1 */ STATE_CALIB_AMP_5, 74/* STATE_CALIB_AMP_5 */ STATE_CALIB_VOLTAGE, 75/* STATE_CALIB_VOLTAGE */ STATE_HOME }; 76 77volatile char state = -1; 78 79const char *menus[] = { 80/* STATE_INIT */ " Battery Tester ", 81/* STATE_HOME */ " Press to begin ", 82/* STATE_SET_AH */ "Set Battery size", 83/* STATE_SET_DURATION */ "Set Test time ", 84/* STATE_SET_STOPV */ "Set End Voltage ", 85/* STATE_RUNNING */ " Running ", 86/* STATE_FINISHED */ " Recharging ", 87/* STATE_CALIB_START */ " Calibration ", 88/* STATE_CALIB_AMP_1 */ "Calibrate amps 1", 89/* STATE_CALIB_AMP_5 */ "Calibrate amps 5", 90/* STATE_CALIB_VOLTAGE */ "Calibrate volts"}; 91 92 93char line[20]; // a few extra chars just in case 94char lastKey = 1; 95 96/* Encoder interrupt. 97 NOTE: No debounce elimination. 98 There can be several INTs per step, but encodeval 99 always ends up with the right value */ 100volatile char oldPA; 101volatile int encodeval = 0; // this is the decode output 102volatile bool encodeNegAllowed=false; // true if negative values allowed 103uint32_t startseconds = 0; 104int32_t set_mA=0; 105uint16_t low_V_Count=0; 106 107const uint8_t max_Amp=10; 108 109void decodeInt() { // Interrupt from iPinA 110 int newPA = digitalRead(Encod1); // //(PIND>>2) & B11; // volatile 111 int newPB = digitalRead(Encod2); 112 if (oldPA ^ newPA) { 113 if (newPA==newPB) encodeval--; 114 else encodeval++; 115 } 116 if (!encodeNegAllowed && encodeval<0){ 117 encodeval=0; 118 } 119 oldPA = newPA; 120} 121 122 123/***************** ANALOG/DIGITAL I/O ***********************/ 124 125void charge_on(bool on){ 126 digitalWrite(CHRG_OUT,on); 127} 128 129void fan_on(bool on){ 130 digitalWrite(FAN_OUT,on); 131} 132 133const int analogDamping=4; 134long Ain=0; 135int Vin=0; 136uint8_t Aout=0; 137 138 139long get_current_mA(){ 140 return (Ain-eepromdata.Ain1) * 4000 / eepromdata.Ain5_1 + 1000; 141// return (Ain-eepromdata.Ain1) * 10 / eepromdata.Ain5_1 * 400 + 1000; 142} 143 144int32_t get_voltage_mV(){ 145 return ((int32_t)Vin * eepromdata.voltFactor) / 100; // factor =~2000 146} 147 148void set_Aout(int val){ 149 if (val>255) val=255; 150 if (val<0) val=0; 151 Aout=val; 152 analogWrite(AMP_OUT,val); 153 fan_on((val>0)); 154} 155 156void read_analog_inputs(){ // called at 20 Hz 157 Vin=(Vin*(analogDamping-1)+analogRead(VOLT_IN))/analogDamping; // 0..1023 -> 0..17V; 12V ~= 600 158 Ain=(Ain*(analogDamping-1)+analogRead(AMP_IN))/analogDamping; // 0..1023 -> 0..17000 mA; 10000 mA ~= 600 159} 160 161uint32_t seconds(){ 162 return millis()/1000; 163} 164 165/***************** LCD ***********************/ 166 167void clearLine() { 168 for (char i = 0; i < 16; i++) { 169 line[i] = ' '; 170 } 171 line[16] = 0; 172} 173 174void lcdPrint(int line, const char *text) { 175 // note: display is not cleared - make sure line is 16 chars 176 lcd.setCursor(0, line); 177 lcd.print(text); 178} 179 180uint16_t dPrint_batt_mV(){ 181 uint16_t v=get_voltage_mV(); 182 sprintf(line,"Battery = %2i.%01i V", v/1000, v%1000/10); 183 lcdPrint(1, line); 184 return v; 185} 186 187uint16_t dPrint_batt_mV_m(){ 188 uint16_t v=get_voltage_mV(); 189 sprintf(line, "%2i.%02iV RES=%3iAh", v/1000, v%1000/10, eepromdata.measured_Ah ); 190 lcdPrint(1, line); 191 return v; 192} 193 194void dPrint_stopmV(){ 195 int v=eepromdata.stopmV; 196 sprintf(line,"Dischrg to %2i.%1iV" ,v/1000, v%1000 / 100); 197 lcdPrint(1, line); 198} 199 200void dPrint_Ah(){ 201 int v=eepromdata.battery_Ah; 202 sprintf(line,"Capacity = %3iAh", v); 203 lcdPrint(1, line); 204} 205 206void dPrint_Duration(){ 207 int v=eepromdata.duration; 208 sprintf(line,"Duration = %3ih", v); 209 lcdPrint(1, line); 210} 211 212void dPrint_volt_curr_time(){ 213 int v=get_voltage_mV(); 214 int a=get_current_mA(); 215 if (a<0) a=0; 216 if (a>9999) a=9999; 217 int t=(seconds() - startseconds)/60; // minutes 218 sprintf(line,"%2i.%1iV %1i.%1iA %02i:%02i", v/1000, v%1000/100, a/1000, a%1000/100, t/60, t%60); 219 lcdPrint(1, line); 220} 221 222void dPrint_result(){ 223 sprintf(line, "Result %3iAh %2i%%", eepromdata.measured_Ah, eepromdata.measured_Ah*100/eepromdata.battery_Ah); 224 lcdPrint(1, line); 225} 226 227 228int calc_sum(uint8_t *p, char len){ 229 int sum=0; 230 for (char i=0; i<len; i++){ 231 sum += p[i]; 232 } 233 return sum; 234} 235 236void eepromPut(){ 237 eepromdata.chksum=chktoken-calc_sum((uint8_t*)&eepromdata, sizeof(eepromdata)-1); 238 EEPROM.put(0, eepromdata); 239} 240 241void eepromGet(){ 242 EEPROM.get(0, eepromdata); 243 uint8_t s = calc_sum((uint8_t*)&eepromdata, sizeof(eepromdata)); 244 if (s!=chktoken){ 245 eepromdata = eeprom_default; 246 eepromPut(); 247 } 248} 249 250 251void setState(int newState){ 252 if (state!=newState){ 253 state = newState; 254 lcd.clear(); 255 lcdPrint(0, menus[state]); 256 charge_on(state==STATE_FINISHED); 257 258 } 259} 260 261void setup() { 262 263 pinMode(AMP_OUT, OUTPUT); // sets the pin as output 264 analogWrite(AMP_OUT,0); // turn off load 265 pinMode(FAN_OUT, OUTPUT); 266 analogWrite(FAN_OUT,0); // turn off fan 267 pinMode(CHRG_OUT, OUTPUT); 268 digitalWrite(CHRG_OUT,0); // turn off charger 269 pinMode(VOLT_IN,INPUT); 270 Vin=analogRead(VOLT_IN); 271 pinMode(HEATSINK_IN, INPUT); 272 pinMode(AMBIENT_IN, INPUT); 273 274// Serial.begin(9600); 275 276 lcd.begin(16, 2); 277 278 pinMode(KEYPRESS, INPUT_PULLUP); 279 pinMode(Encod1, INPUT_PULLUP); 280 pinMode(Encod2, INPUT_PULLUP); 281 oldPA = digitalRead(Encod1); 282 283 eepromGet(); 284 285 attachInterrupt(digitalPinToInterrupt(Encod1), decodeInt, CHANGE); 286 287 lastKey = digitalRead(KEYPRESS); 288 if (!lastKey) { 289 setState(STATE_CALIB_START); 290 } else { 291 setState(STATE_INIT); 292 lcdPrint(1, "www.state.dk/bct"); 293 } 294 295} 296 297void loop() { 298 299 int32_t v, a, d; 300 char k; 301 int press_duration=0; 302 303 delay(50); // max 20 runs per sec to avoid key bounce 304 305 read_analog_inputs(); 306 307 k = digitalRead(KEYPRESS); //readKey(); 308 309 if (!k) press_duration++; 310 else press_duration=0; 311 if (press_duration>100) setState(STATE_CALIB_START); // 5 sec press -> calibrate 312 313 bool clicked = !k && lastKey; 314 lastKey=k; 315 316 switch (state) { 317 case STATE_INIT: 318 break; 319 case STATE_HOME: 320 dPrint_batt_mV_m(); 321 break; 322 case STATE_SET_AH: 323 if (encodeval>200) encodeval=200; 324 eepromdata.battery_Ah=encodeval; 325 dPrint_Ah(); 326 break; 327 case STATE_SET_DURATION: 328 if (encodeval<1) encodeval=1; 329 if (encodeval < eepromdata.battery_Ah/max_Amp) encodeval = eepromdata.battery_Ah/max_Amp; 330 eepromdata.duration=encodeval; 331 dPrint_Duration(); 332 break; 333 case STATE_SET_STOPV: 334 eepromdata.stopmV=encodeval*10; 335 dPrint_stopmV(); 336 break; 337 case STATE_RUNNING: 338 v=get_voltage_mV(); 339 a=get_current_mA(); //if (a>9999) a=9999; 340 d = set_mA - a; 341 342 dPrint_volt_curr_time(); 343 if (abs(d) > 50){ 344 if (d>0) set_Aout(Aout+1); 345 else set_Aout(Aout-1); 346 } 347 if (v<eepromdata.stopmV){ 348 low_V_Count++; 349 if (low_V_Count>100){ // 5 sec 350 setState(STATE_FINISHED); 351 set_Aout(0); 352 eepromdata.measured_Ah= (seconds() - startseconds) / eepromdata.duration * eepromdata.battery_Ah / 3600; 353 eepromPut(); 354 dPrint_result(); 355 } 356 } else{ 357 low_V_Count=0; 358 } 359 break; 360 case STATE_FINISHED: 361 break; 362 case STATE_CALIB_START: 363 break; 364 case STATE_CALIB_AMP_1: 365 set_Aout(encodeval); 366 eepromdata.Ain1 = Ain; 367 sprintf(line,"Set 1A %03i %03i", encodeval, Ain); 368 lcdPrint(1, line); 369 break; 370 case STATE_CALIB_AMP_5: 371 set_Aout(encodeval); 372 eepromdata.Ain5_1 = Ain - eepromdata.Ain1; 373 sprintf(line,"Set 5A %03i %03i", encodeval, Ain); 374 lcdPrint(1, line); 375 break; 376 case STATE_CALIB_VOLTAGE: 377 eepromdata.voltFactor = encodeval; 378 dPrint_batt_mV(); 379 break; 380 } 381 382 if (clicked) { // Last thing to do just before acting on a click 383 set_Aout(0); 384 switch (state) { 385 case STATE_SET_AH: 386 case STATE_SET_DURATION: 387 case STATE_SET_STOPV: 388 case STATE_CALIB_AMP_1: 389 case STATE_CALIB_AMP_5: 390 case STATE_CALIB_VOLTAGE: 391 eepromPut(); 392 break; 393 } 394 395 setState(NEXT_STATE[state]); // on to next state 396 397 switch (state) { // First thing to do after setting new state 398 case STATE_HOME: 399 break; 400 case STATE_SET_AH: 401 encodeval=min(200,eepromdata.battery_Ah); 402 break; 403 case STATE_SET_DURATION: 404 encodeval=eepromdata.duration; 405 break; 406 case STATE_SET_STOPV: 407 encodeval=max(9000, eepromdata.stopmV)/10; // resolution 10 mV 408 break; 409 case STATE_RUNNING: 410 Aout=0; 411 set_mA = ((eepromdata.battery_Ah * 100) / eepromdata.duration) * 10; // split *1000 to avoid overrun 412 low_V_Count = 0; 413 startseconds=seconds(); 414 break; 415 case STATE_FINISHED: // user interrupted 416 eepromdata.measured_Ah=0; 417 eepromPut(); 418 setState(STATE_HOME); 419 break; 420 case STATE_CALIB_AMP_1: 421 encodeval=40; 422 break; 423 case STATE_CALIB_AMP_5: 424 encodeval=120; 425 break; 426 case STATE_CALIB_VOLTAGE: 427 encodeval=eepromdata.voltFactor; 428 break; 429 } 430 } 431 432} 433
Code for Battery Tester
c_cpp
Open in Arduino IDE
1#include <EEPROM.h> 2#include <LiquidCrystal.h> 3 4/* 512V battery capacity tester v. 2.3 6By Christen Monberg chr@monberg.com 7May, 2022 8See description at 9https://create.arduino.cc/projecthub/monse53/12v-battery-capacity-tester-fb95b9 10*/ 11 12/*----- PIN Definitions -----*/ 13 14// LCD: 15const uint8_t rs=12; // PB4 16const uint8_t en=11; // PB3 17const uint8_t ld4=7; // PD7 18const uint8_t ld5=6; // PD6 19const uint8_t ld6=5; // PD5 20const uint8_t ld7=4; // PD4 21 22const int Encod1=2; // PD2 encoder pinA 23const int Encod2=3; // PD3 encoder pinB 24 25const int KEYPRESS=8; // PB0 Button 26const int AMP_IN=A0; // PC0 Current in 27const int VOLT_IN=A1; //15; // PC1 Bat Voltage in 28const int HEATSINK_IN=A2; // PC2 Heatsink temp 29const int AMBIENT_IN=A3; // PC3 Ambient temp 30const int AMP_OUT=9; // PB1 Current control 31const int CHRG_OUT=13; // PB5 Charge relay on 32const int FAN_OUT=10; // PB2 Fan on 33 34LiquidCrystal lcd(rs, en, ld4, ld5, ld6, ld7); 35 36struct EEPROMDATA { 37 uint16_t voltFactor; // 0..1023=0..20.00V. 600 = 12.000V -> factor=~2000 38 long Ain1; // 39 long Ain5_1; // Ain5 - Ain1 40 int stopmV; 41 int battery_Ah; // Battery size 42 int duration; // test hours 43 int measured_Ah; // Battery size 44 uint8_t chksum; 45} eepromdata; 46 47const EEPROMDATA eeprom_default={ 48 1500, // voltFactor 49 32, // Ain1 50 260, // Ain5_1 51 10500, // stopMV 52 40, // batt Ah 53 20, // test hours 54 0, // measured 55 0}; // chksum 56 57const uint8_t chktoken = 0x5A; 58 59enum STATE { 60STATE_INIT, 61STATE_HOME, 62STATE_SET_AH, 63STATE_SET_DURATION, 64STATE_SET_STOPV, 65STATE_RUNNING, 66STATE_FINISHED, 67STATE_CALIB_START, 68STATE_CALIB_AMP_1, 69STATE_CALIB_AMP_5, 70STATE_CALIB_VOLTAGE}; 71 72const char NEXT_STATE[]={ 73/* STATE_INIT*/ STATE_HOME, 74/* STATE_HOME */ STATE_SET_AH, 75/* STATE_SET_AH */ STATE_SET_DURATION, 76/* STATE_SET_DURATION */ STATE_SET_STOPV, 77/* STATE_SET_STOPV */ STATE_RUNNING, 78/* STATE_RUNNING */ STATE_FINISHED, 79/* STATE_FINISHED */ STATE_HOME, 80/* STATE_CALIB_START */ STATE_CALIB_AMP_1, 81/* STATE_CALIB_AMP_1 */ STATE_CALIB_AMP_5, 82/* STATE_CALIB_AMP_5 */ STATE_CALIB_VOLTAGE, 83/* STATE_CALIB_VOLTAGE */ STATE_HOME }; 84 85volatile char state = -1; 86 87const char *menus[] = { 88/* STATE_INIT */ " Battery Tester ", 89/* STATE_HOME */ " Press to begin ", 90/* STATE_SET_AH */ "Set Battery size", 91/* STATE_SET_DURATION */ "Set Test time ", 92/* STATE_SET_STOPV */ "Set End Voltage ", 93/* STATE_RUNNING */ " Running ", 94/* STATE_FINISHED */ " Recharging ", 95/* STATE_CALIB_START */ " Calibration ", 96/* STATE_CALIB_AMP_1 */ "Calibrate amps 1", 97/* STATE_CALIB_AMP_5 */ "Calibrate amps 5", 98/* STATE_CALIB_VOLTAGE */ "Calibrate volts"}; 99 100 101char line[20]; // a few extra chars just in case 102char lastKey = 1; 103 104/* Encoder interrupt. 105 NOTE: No debounce elimination. 106 There can be several INTs per step, but encodeval 107 always ends up with the right value */ 108volatile char oldPA; 109volatile int encodeval = 0; // this is the decode output 110volatile bool encodeNegAllowed=false; // true if negative values allowed 111uint32_t startseconds = 0; 112int32_t set_mA=0; 113uint16_t low_V_Count=0; 114 115const uint8_t max_Amp=10; 116 117void decodeInt() { // Interrupt from iPinA 118 int newPA = digitalRead(Encod1); // //(PIND>>2) & B11; // volatile 119 int newPB = digitalRead(Encod2); 120 if (oldPA ^ newPA) { 121 if (newPA==newPB) encodeval--; 122 else encodeval++; 123 } 124 if (!encodeNegAllowed && encodeval<0){ 125 encodeval=0; 126 } 127 oldPA = newPA; 128} 129 130 131/***************** ANALOG/DIGITAL I/O ***********************/ 132 133void charge_on(bool on){ 134 digitalWrite(CHRG_OUT,on); 135} 136 137void fan_on(bool on){ 138 digitalWrite(FAN_OUT,on); 139} 140 141const int analogDamping=4; 142long Ain=0; 143int Vin=0; 144uint8_t Aout=0; 145 146 147long get_current_mA(){ 148 return (Ain-eepromdata.Ain1) * 4000 / eepromdata.Ain5_1 + 1000; 149// return (Ain-eepromdata.Ain1) * 10 / eepromdata.Ain5_1 * 400 + 1000; 150} 151 152int32_t get_voltage_mV(){ 153 return ((int32_t)Vin * eepromdata.voltFactor) / 100; // factor =~2000 154} 155 156void set_Aout(int val){ 157 if (val>255) val=255; 158 if (val<0) val=0; 159 Aout=val; 160 analogWrite(AMP_OUT,val); 161 fan_on((val>0)); 162} 163 164void read_analog_inputs(){ // called at 20 Hz 165 Vin=(Vin*(analogDamping-1)+analogRead(VOLT_IN))/analogDamping; // 0..1023 -> 0..17V; 12V ~= 600 166 Ain=(Ain*(analogDamping-1)+analogRead(AMP_IN))/analogDamping; // 0..1023 -> 0..17000 mA; 10000 mA ~= 600 167} 168 169uint32_t seconds(){ 170 return millis()/1000; 171} 172 173/***************** LCD ***********************/ 174 175void clearLine() { 176 memset(line,0,20); 177} 178 179void lcdPrint(int line, const char *text) { 180 // note: display is not cleared - make sure line is 16 chars 181 lcd.setCursor(0, line); 182 lcd.print(text); 183} 184 185uint16_t dPrint_batt_mV(){ 186 uint16_t v=get_voltage_mV(); 187 sprintf(line,"Battery = %2i.%01i V", v/1000, v%1000/10); 188 lcdPrint(1, line); 189 return v; 190} 191 192uint16_t dPrint_batt_mV_m(){ 193 uint16_t v=get_voltage_mV(); 194 sprintf(line, "%2i.%02iV RES=%3iAh", v/1000, v%1000/10, eepromdata.measured_Ah ); 195 lcdPrint(1, line); 196 return v; 197} 198 199void dPrint_stopmV(){ 200 int v=eepromdata.stopmV; 201 sprintf(line,"Dischrg to %2i.%1iV" ,v/1000, v%1000 / 100); 202 lcdPrint(1, line); 203} 204 205void dPrint_Ah(){ 206 int v=eepromdata.battery_Ah; 207 sprintf(line,"Capacity = %3iAh", v); 208 lcdPrint(1, line); 209} 210 211void dPrint_Duration(){ 212 int v=eepromdata.duration; 213 sprintf(line,"Duration = %3ih", v); 214 lcdPrint(1, line); 215} 216 217void dPrint_volt_curr_time(){ 218 int v=get_voltage_mV(); 219 int a=get_current_mA(); 220 if (a<0) a=0; 221 if (a>9999) a=9999; 222 int t=(seconds() - startseconds)/60; // minutes 223 sprintf(line,"%2i.%1iV %1i.%1iA %02i:%02i", v/1000, v%1000/100, a/1000, a%1000/100, t/60, t%60); 224 lcdPrint(1, line); 225} 226 227void dPrint_result(){ 228 sprintf(line, "Result %3iAh %2i%%", eepromdata.measured_Ah, eepromdata.measured_Ah*100/eepromdata.battery_Ah); 229 lcdPrint(1, line); 230} 231 232 233int calc_sum(uint8_t *p, char len){ 234 int sum=0; 235 for (char i=0; i<len; i++){ 236 sum += p[i]; 237 } 238 return sum; 239} 240 241void eepromPut(){ 242 eepromdata.chksum=chktoken-calc_sum((uint8_t*)&eepromdata, sizeof(eepromdata)-1); 243 EEPROM.put(0, eepromdata); 244} 245 246void eepromGet(){ 247 EEPROM.get(0, eepromdata); 248 uint8_t s = calc_sum((uint8_t*)&eepromdata, sizeof(eepromdata)); 249 if (s!=chktoken){ 250 eepromdata = eeprom_default; 251 eepromPut(); 252 } 253} 254 255 256void setState(int newState){ 257 if (state!=newState){ 258 state = newState; 259 lcd.clear(); 260 lcdPrint(0, menus[state]); 261 charge_on(state==STATE_FINISHED); 262 263 } 264} 265 266void setup() { 267 268 pinMode(AMP_OUT, OUTPUT); // sets the pin as output 269 analogWrite(AMP_OUT,0); // turn off load 270 pinMode(FAN_OUT, OUTPUT); 271 analogWrite(FAN_OUT,0); // turn off fan 272 pinMode(CHRG_OUT, OUTPUT); 273 digitalWrite(CHRG_OUT,0); // turn off charger 274 pinMode(VOLT_IN,INPUT); 275 Vin=analogRead(VOLT_IN); 276 pinMode(HEATSINK_IN, INPUT); 277 pinMode(AMBIENT_IN, INPUT); 278 279// Serial.begin(9600); 280 281 lcd.begin(16, 2); 282 283 pinMode(KEYPRESS, INPUT_PULLUP); 284 pinMode(Encod1, INPUT_PULLUP); 285 pinMode(Encod2, INPUT_PULLUP); 286 oldPA = digitalRead(Encod1); 287 288 eepromGet(); 289 290 attachInterrupt(digitalPinToInterrupt(Encod1), decodeInt, CHANGE); 291 292 lastKey = digitalRead(KEYPRESS); 293 if (!lastKey) { 294 setState(STATE_CALIB_START); 295 } else { 296 setState(STATE_INIT); 297 lcdPrint(1, "www.state.dk/bct"); 298 } 299 300} 301 302void loop() { 303 304 int32_t v, a, d; 305 char k; 306 int press_duration=0; 307 308 delay(50); // max 20 runs per sec to avoid key bounce 309 310 read_analog_inputs(); 311 312 k = digitalRead(KEYPRESS); //readKey(); 313 314 if (!k) press_duration++; 315 else press_duration=0; 316 if (press_duration>100) setState(STATE_CALIB_START); // 5 sec press -> calibrate 317 318 bool clicked = !k && lastKey; 319 lastKey=k; 320 321 switch (state) { 322 case STATE_INIT: 323 break; 324 case STATE_HOME: 325 dPrint_batt_mV_m(); 326 break; 327 case STATE_SET_AH: 328 if (encodeval>200) encodeval=200; 329 eepromdata.battery_Ah=encodeval; 330 dPrint_Ah(); 331 break; 332 case STATE_SET_DURATION: 333 if (encodeval<1) encodeval=1; 334 if (encodeval < eepromdata.battery_Ah/max_Amp) encodeval = eepromdata.battery_Ah/max_Amp; 335 eepromdata.duration=encodeval; 336 dPrint_Duration(); 337 break; 338 case STATE_SET_STOPV: 339 eepromdata.stopmV=encodeval*10; 340 dPrint_stopmV(); 341 break; 342 case STATE_RUNNING: 343 v=get_voltage_mV(); 344 a=get_current_mA(); //if (a>9999) a=9999; 345 d = set_mA - a; 346 347 dPrint_volt_curr_time(); 348 if (abs(d) > 50){ 349 if (d>0) set_Aout(Aout+1); 350 else set_Aout(Aout-1); 351 } 352 if (v<eepromdata.stopmV){ 353 low_V_Count++; 354 if (low_V_Count>100){ // 5 sec 355 setState(STATE_FINISHED); 356 set_Aout(0); 357 eepromdata.measured_Ah= (seconds() - startseconds) / eepromdata.duration * eepromdata.battery_Ah / 3600; 358 eepromPut(); 359 dPrint_result(); 360 } 361 } else{ 362 low_V_Count=0; 363 } 364 break; 365 case STATE_FINISHED: 366 break; 367 case STATE_CALIB_START: 368 break; 369 case STATE_CALIB_AMP_1: 370 set_Aout(encodeval); 371 eepromdata.Ain1 = Ain; 372 sprintf(line,"Set 1A %03i %03i", encodeval, Ain); 373 lcdPrint(1, line); 374 break; 375 case STATE_CALIB_AMP_5: 376 set_Aout(encodeval); 377 eepromdata.Ain5_1 = Ain - eepromdata.Ain1; 378 sprintf(line,"Set 5A %03i %03i", encodeval, Ain); 379 lcdPrint(1, line); 380 break; 381 case STATE_CALIB_VOLTAGE: 382 eepromdata.voltFactor = encodeval; 383 dPrint_batt_mV(); 384 break; 385 } 386 387 if (clicked) { // Last thing to do just before acting on a click 388 set_Aout(0); 389 switch (state) { 390 case STATE_SET_AH: 391 case STATE_SET_DURATION: 392 case STATE_SET_STOPV: 393 case STATE_CALIB_AMP_1: 394 case STATE_CALIB_AMP_5: 395 case STATE_CALIB_VOLTAGE: 396 eepromPut(); 397 break; 398 } 399 400 setState(NEXT_STATE[state]); // on to next state 401 402 switch (state) { // First thing to do after setting new state 403 case STATE_HOME: 404 break; 405 case STATE_SET_AH: 406 encodeval=min(200,eepromdata.battery_Ah); 407 break; 408 case STATE_SET_DURATION: 409 encodeval=eepromdata.duration; 410 break; 411 case STATE_SET_STOPV: 412 encodeval=max(9000, eepromdata.stopmV)/10; // resolution 10 mV 413 break; 414 case STATE_RUNNING: 415 Aout=0; 416 set_mA = ((eepromdata.battery_Ah * 100) / eepromdata.duration) * 10; // split *1000 to avoid overrun 417 low_V_Count = 0; 418 startseconds=seconds(); 419 break; 420 case STATE_FINISHED: // user interrupted 421 eepromdata.measured_Ah=0; 422 eepromPut(); 423 setState(STATE_HOME); 424 break; 425 case STATE_CALIB_AMP_1: 426 encodeval=40; 427 break; 428 case STATE_CALIB_AMP_5: 429 encodeval=120; 430 break; 431 case STATE_CALIB_VOLTAGE: 432 encodeval=eepromdata.voltFactor; 433 break; 434 } 435 } 436 437} 438
Downloadable files
Schematic
Created in TinyCad
Schematic

TinyCad source file
Download TinyCad and open this file
TinyCad source file
TinyCad source file
Download TinyCad and open this file
TinyCad source file
Schematic
Created in TinyCad
Schematic

Comments
Only logged in users can leave comments