/* * NiCd, NiMh, LiIon charger * (c) Henry Palonen & Kristian Rosendahl * * Released under GPL (Gnu Public Licence) * * Spring 2002 * English Translation - Spring 2006 */ /* * NiCd and NiMh CHARGE: * * 1. attach battery * 2. choose memory (Sn, eg. S1) * 3. check parameters * 4. command "G" starts charging * 5. measure battery voltage, and save to BATT_MEAS_VOLT * 6. put memoryplaces value BATT_CURRENT to PWM1 * 7. measure temperature and check BATT_MAX_TEMP variable * . If higher, stop charging and put PWM1 0. * 8. measure battery voltage and check BATT_MEAS_VOLT variable * - if voltage equals or is greater than BATT_MEAS_VOLT, * save voltage to variable BATT_MEAS_VOLT * - if voltage is lower than BATT_MEAS_VOLT and difference is greater or equal * than BATT_MAX_DROP, stop charging and put PWM1 = 0. * 9. print measured values from serial port and wait for delay MEAS_DELAY * 10. rotate to place 5. and start over */ /* * Settings : * * 10 memories for different batteries (0-9). Settings are saved when they are * changed - saving to EEPROM, so no need for stand-by voltage. * * S1 = select memory 1 * C1200 = set Current at selected memory to value 1200 * * First character tells what command does: * - S (Select) memory 0-9 * - Y (tYpe) 0=NiCd, 1=NiMh, 0=LiIon * - C (Current) set charge current * - T (Temperature) maximum temperature * - D (Drop) * For NiCd & NiMh batteries: * voltage drop, "deltaV" that tells that battery is full * For LiIon batteries: * Maximum allowed LiIon battery voltage - when * reached, software must start to drop charge current by amount of * BATT_C_DROP (defaults to 1 A/D tick, see definition below) * - L (Limit current) LiIon charge current that stops the charge when * reached (eg. when charge current eventually drops to this, decide that * battery is full and stop charge.) * - I (tIme) max charge time in 5 sec steps * - O (stOp) stop charge immediately * - G (Go) start charge. * - P (Print) print everything to the console * * after first character there comes a value: * - S: 0-9 (memory) * - Y: 1=NiCd, 2=NiMh, 3=LiIon * - C: 0-1024 (default 100) * - T: 0-9999 (default 100) * - D: 0-9999 (default 100) * - L: 0-9999 (default 25) * - I: 0-9999 (default 720) = 1h (3600s / 5s) * - O: - * - G: - * - P: - * */ // Makefile has this "pic16f87x.h" that includes processor type specific things #include incfile // 20Mhz crystal #use DELAY (clock=20000000) //#use DELAY (clock=12000000) // #use RS232 (baud=19200, xmit=PIN_C4,rcv=PIN_C5,invert) // vanha #use RS232 (baud=19200, xmit=PIN_C6,rcv=PIN_C7) #fuses HS,NOWDT,NOPROTECT #define VOLT_MEAS_PIN PIN_A0 // pin that has battery voltage #define TEMP_MEAS_PIN PIN_A1 // pin that has battery temperature #define SET_BUTTON_PIN PIN_C3 #define START_BUTTON_PIN PIN_C4 #define STOP_BUTTON_PIN PIN_C0 // lcd-functions #include "lcdakku.c" #include #define MEAS_DELAY 50000 // delay between measurements, also delay that RS232 waits for commands to arrive #define MEMORIES 9 // how many memories // 6 * 4 * MEMORIES + 6 = how much eeprom-memory is needed // long is 4 bytes, so // 6 * 4 * 9 + 6 = 222 bytes // charger calibration (max calibration parameters 9 kpl) #define MEM_ADDR_VOLT_CAL 1 // mV / DA #define MEM_ADDR_LAST_SEL_MEM 5 // last selected memoryplace #define MEM_ADDR_AMPER_CAL 9 // mA / AD // per memoryplace #define MEM_ADDR_BATT_CURRENT 1 * 4 * MEMORIES + 1 // current #define MEM_ADDR_BATT_TYPE 2 * 4 * MEMORIES + 1 // battery type (NiCd,NiMh,LiIon) #define MEM_ADDR_MAX_TEMP 3 * 4 * MEMORIES + 1 // max temp #define MEM_ADDR_MAX_TIME 4 * 4 * MEMORIES + 1 // max time #define MEM_ADDR_BATT_VOLT_DROP 5 * 4 * MEMORIES + 1 // NiCd ja NiMh voltage drop and // LiIon battery limit // voltage #define MEM_ADDR_BATT_C_LIMIT 6 * 4 * MEMORIES + 1 // stop current #define READ 0 #define WRITE 1 #define TYPE_NICD 0 #define TYPE_NIMH 1 #define TYPE_LIION 2 #define INTS_PER_SECOND 76 // how many interrupts per second (20 000 000 / (4*256*256)) #define BATT_C_DROP 1 // how much current is dropped when full voltage is achieved long BATT_CURRENT=0; // battery current int BATT_TYPE=TYPE_NICD; // battery type int BATT_MAX_TEMP=100; // max temperature long BATT_MAX_TIME=720; // max time int BATT_VOLT_DROP=100; // voltage that stops sthe charge (NiCd/NiMh) // voltage that drops the current (LiIon) int BATT_C_LIMIT=25; // current that when reached the charge is ended int1 CHARGE_IN_PROGRESS=FALSE; // is there currently charging in place int8 SELECTED_MEM; // selected memoryplace float MEAS_VOLT=0; // measured voltage long retval; // short timeout_error; // serial port timeout byte int_count; // how many interrupts long seconds=0; // charging seconds int16 cumulated_mAh=0; // cumulated mAh amount int16 mA_Per_10Sec=0; // milliampers in 10 seconds long MV_PER_STEP; // how many millivolts is on A/D tick int MA_PER_STEP; // how many milliamps is on D/A tick int8 lcd_info=0; // what to show in lcd int8 ten_seconds=0; // tens of seconds float VOLT_TMP; // volts long TEMP_TMP; // temperature // commands that control the charging #define COMMAND_NOTHING 0 #define COMMAND_START 1 // keep always COMMAND_STOP_USER first and // COMMAND_STOP_TIME last, because only values between them // are seen as valid stop reason #define COMMAND_STOP_USER 2 #define COMMAND_STOP_VOLT 3 #define COMMAND_STOP_TEMP 4 #define COMMAND_STOP_CURRENT 5 #define COMMAND_STOP_ZEROVOLT 6 #define COMMAND_STOP_TIME 7 int8 COMMAND=COMMAND_NOTHING; // command that user enters via the buttons or via the serial port // for future bootloader #ORG 0x1F00,0x1FFF //for the 8k 16F876/7 void loader() { } void start_charge(void); #separate void stop_charge(char); #separate char timed_getc(void); #separate void charge(void); // string conversion to long value #separate signed long atol(char *s) { signed long result; int sign, base, index; char c; index = 0; sign = 0; base = 10; result = 0; if(s) c = s[index++]; if (c >= '0' && c <= '9') { // The number is a decimal number if (base == 10) { while (c >= '0' && c <= '9') { result = 10*result + (c - '0'); c = s[index++]; } } } return(result); } #separate void get_string(char * s,int max) { int len; char c; max--; len=0; do { c=getc(); if(c==8) { // Backspace if(len>0) { len--; putc(c); putc(' '); putc(c); } } else if ((c>=' ')&&(c<='~')) if(len59) { while(s>59) { s = s - 59; if (s>1) min++; if (min>59) { hour++; min=0; } } } sprintf(aika, "%u:%02u:%02lu", hour,min,s); return (aika); } // forwards users commands and counts how many seconds the charge-process // has been on #int_rtcc // RTCC (timer0) on clock_isr() { // every time RTCC rotates (255->0), this gets called // it's about 76 times per second in this program char aika[7]; // time to lcd in h:mm:ss-format // if user has pressed buttons // set-button changes from memories 0 ... 9 -> 0 ... 9 if ( !input(SET_BUTTON_PIN) ) { SELECTED_MEM=SELECTED_MEM+4; // float is 4 bytes if (SELECTED_MEM > 9*4) // last memory SELECTED_MEM=0; printf(lcd_putc,"\fMEM: %u", SELECTED_MEM/4); delay_ms(50); // quick and dirty debounce } // start-button if (!input(START_BUTTON_PIN)) { COMMAND = COMMAND_START; } // stop-button if (!input(STOP_BUTTON_PIN)) { COMMAND = COMMAND_STOP_USER; } // if charge start/stop is issued by rs232 or by buttons, start/stop charge if (COMMAND == COMMAND_START) { start_charge(); COMMAND=COMMAND_NOTHING; } // stopping charge if (COMMAND >= COMMAND_STOP_USER && COMMAND <= COMMAND_STOP_TIME) { stop_charge(COMMAND); COMMAND=COMMAND_NOTHING; } if (CHARGE_IN_PROGRESS == TRUE) { // count time that charge has been on int_count--; if(int_count <= 0) { charge(); // call function that handles charging seconds++; int_count=INTS_PER_SECOND; // if it's ten-second mark ten_seconds++; if (ten_seconds>9) { // FIXME: DOESN'T WORK YET. SHOULD DIVIDE EVERY SECOND BY 3600 // BUT FOR SOME REASON IT DOESN'T WORK - CUMULATED VALUE IS // ZERO !? cumulated_mAh = cumulated_mAh + (BATT_CURRENT*100/36); // 1000 error ten_seconds=0; } sprintf(aika, laske_aika(seconds)); // mAh if (lcd_info>=0 && lcd_info<2) printf(lcd_putc,"\fC%S\n %4lumAh",aika, cumulated_mAh/1000); // Volts if (lcd_info>=2 && lcd_info<4) printf(lcd_putc,"\fC%S\n %2.2f V", aika, VOLT_TMP); // temperature if (lcd_info>=4) printf(lcd_putc,"\fC%S\n %5lu C", aika, TEMP_TMP); lcd_info++; delay_ms(10); if (lcd_info>5) lcd_info=0; } } } // waits for character to arrive from rs232 for delay-time #separate char timed_getc() { long timeout; timeout_error=FALSE; timeout=0; while(!kbhit() && (++timeout MEAS_VOLT) MEAS_VOLT = VOLT_TMP; if (VOLT_TMP*1000 <= ((MEAS_VOLT*1000) - BATT_VOLT_DROP)) { COMMAND=COMMAND_STOP_VOLT; return; } break; case TYPE_LIION: delay_us(10); set_pwm1_duty(PWM_TMP); if (VOLT_TMP*1000 >= BATT_VOLT_DROP) { BATT_CURRENT = BATT_CURRENT - BATT_C_DROP; PWM_TMP = BATT_CURRENT / MA_PER_STEP; set_pwm1_duty(PWM_TMP); } if (BATT_CURRENT <= BATT_C_LIMIT) { COMMAND=COMMAND_STOP_CURRENT; return; } break; } // see if temperature limit has been reached set_adc_channel(TEMP_MEAS_PIN); delay_us(10); TEMP_TMP = read_adc(); TEMP_TMP = ((0.004883 * TEMP_TMP) - 0.33)*33; //(5V/1024*AD-0,33V)*100/3 printf(",T: %lu,S: %lu,mA: %04lu\r\n", TEMP_TMP, seconds, cumulated_mAh/1000); if (TEMP_TMP > BATT_MAX_TEMP) { COMMAND=COMMAND_STOP_TEMP; return; } // see if time limit has been reached if (seconds > BATT_MAX_TIME && BATT_MAX_TIME>0) { COMMAND=COMMAND_STOP_TIME; return; } } // stops the charge and puts total cumulated time and stop reason to console. #separate void stop_charge(int reason) { char aika[7]; printf("\r\nSTOP: %i\r\n",reason); sprintf(aika, laske_aika(seconds)); printf(lcd_putc,"\fS%S\n%4lum(%i)",aika, cumulated_mAh/1000,reason); CHARGE_IN_PROGRESS = FALSE; seconds=0; set_pwm1_duty(0); MEAS_VOLT=0; cumulated_mAh=0; } // reads values from memory and starts charging process void start_charge(void) { printf("\r\nSTART\r\n"); MV_PER_STEP = l_read_eeprom(MEM_ADDR_VOLT_CAL); MA_PER_STEP = l_read_eeprom(MEM_ADDR_AMPER_CAL); BATT_TYPE = l_read_eeprom(MEM_ADDR_BATT_TYPE + SELECTED_MEM); BATT_CURRENT = l_read_eeprom(MEM_ADDR_BATT_CURRENT + SELECTED_MEM); BATT_VOLT_DROP = l_read_eeprom(MEM_ADDR_BATT_VOLT_DROP + SELECTED_MEM); BATT_MAX_TEMP = l_read_eeprom(MEM_ADDR_MAX_TEMP + SELECTED_MEM); BATT_MAX_TIME = l_read_eeprom(MEM_ADDR_MAX_TIME + SELECTED_MEM); BATT_C_LIMIT = l_read_eeprom(MEM_ADDR_BATT_C_LIMIT + SELECTED_MEM); dump_memory(); CHARGE_IN_PROGRESS=TRUE; int_count=INTS_PER_SECOND; } // asks for user input for specific command #separate void ask_rs232(char kirjain) { long int addr; int1 RW; // read or write long value; kirjain=toupper(kirjain); switch (kirjain) { case 'O': COMMAND = COMMAND_STOP_USER; RW = READ; break; case 'G': COMMAND = COMMAND_START; RW = READ; break; case 'P': RW = READ; dump_memory(); break; case 'S': RW = WRITE; break; case 'Y': RW = WRITE; addr = MEM_ADDR_BATT_TYPE + SELECTED_MEM; break; case 'C': RW = WRITE; addr = MEM_ADDR_BATT_CURRENT + SELECTED_MEM; break; case 'T': RW = WRITE; addr = MEM_ADDR_MAX_TEMP + SELECTED_MEM; break; case 'D': RW = WRITE; addr = MEM_ADDR_BATT_VOLT_DROP + SELECTED_MEM; break; case 'L': RW = WRITE; addr = MEM_ADDR_BATT_C_LIMIT+ SELECTED_MEM; break; case 'I': RW = WRITE; addr = MEM_ADDR_MAX_TIME + SELECTED_MEM; break; case 'V': RW = WRITE; addr = MEM_ADDR_VOLT_CAL; break; case 'A': RW = WRITE; addr = MEM_ADDR_AMPER_CAL; break; } if (RW == WRITE) { value = get_long(); if (strcmp(kirjain,'S') == 0) { // this is handles separately because long takes 4x SELECTED_MEM = value * 4; printf("M: %u\r\n",SELECTED_MEM/4); printf(lcd_putc,"\fMEM: %u", SELECTED_MEM/4); addr = MEM_ADDR_LAST_SEL_MEM; value = SELECTED_MEM; } l_write_eeprom(addr, value); retval = l_read_eeprom(addr); //printf("W: %lu -> %lu / R: %lu <- %lu)\r\n", value, addr, retval,addr); dump_memory(); } } // initializes the processor #separate void alusta(void) { int last_selected_mem; // pwm-initialization setup_ccp1(CCP_PWM); setup_timer_2(T2_DIV_BY_1, 64, 1); // A/D init setup_port_a(ALL_ANALOG); setup_adc(adc_clock_internal); set_rtcc(0); setup_counters(RTCC_INTERNAL, RTCC_DIV_256); enable_interrupts(INT_RTCC); enable_interrupts(GLOBAL); set_pwm1_duty(0); // no charge at start lcd_init(); lcd_putc("\fPIC ACCU\n v0.10"); printf("PIC ACCU v0.10\r\n(c) 2002 / Henry Palonen & Kristian Rosendahl\r\n"); delay_ms(1000); last_selected_mem = l_read_eeprom(MEM_ADDR_LAST_SEL_MEM); SELECTED_MEM = last_selected_mem; printf(lcd_putc,"\fMEM: %u", SELECTED_MEM/4); } // Main function just sits and waits for command to arrive from serial port // Actual control is handled by interrupt routine "clock_isk" once every second // void main(void) { char kirjain; alusta(); while (1) { kirjain=timed_getc(); if (timeout_error == FALSE) { ask_rs232(kirjain); } } }