/* * NiCd, NiMh, LiIon akkulaturi * (c) Henry Palonen & Kristian Rosendahl * * Released under GPL (Gnu Public Licence) * * Spring 2002 * */ /* * LATAUS: * * 1. akku kiinni * 2. valitaan muistipaikka (Sn, esim. S1) * 3. tarkistetaan parametrit * 4. komennolla "G" aloitetaan lataus * 5. mitataan akun jännite ja laitetaan se muistipaikkaan BATT_MEAS_VOLT * 6. laitetaan muistipaikan BATT_CURRENT arvo PWM1:een * 7. mitataan lämpötila ja verrataan sitä BATT_MAX_TEMP * muistipaikkaan. Jos lämpötila on suurempi, lopetetaan lataus ja laitetaan * PWM1:n arvoksi 0. * 8. mitataan akun jännite ja verrataan sitä BATT_MEAS_VOLT muuttujaan * - jos jännite nyt yhtäsuuri tai suurempi kuin BATT_MEAS_VOLT, * talletetaan nykyinen jännite muistipaikkaan BATT_MEAS_VOLT * - jos jännite on pienempi kuin BATT_MEAS_VOLT ja erotus on yhtäsuuri tai suurempi * kuin BATT_MAX_DROP, lopetetaan lataus ja laitetaan PWM1:n * arvoksi 0. * 9. tulostetaan mitatut arvot sarjaportista ja odotetaan viive MEAS_DELAY * */ /* * Asetusten alustus: * * Laitteessa 10 muistipaikkaa eri akuille (muistipaikat 0-9). Asetukset * talletetaan EEPROM-muistiin aina kun niitä muutetaan (ts. asetetut arvot * säilyvät myös ilman käyttöjännitettä.) * * S1 = valitaan muistipaikka 1 käsittelyä varten * C1200 = asetetaan valitun muistipaikan virta (C) arvoon 1200 * * Ensimmäinen merkki ilmoittaa toiminnon: * - S (Select) valitse käsiteltävä muistipaikka 0-9 * - Y (tYpe) 0=NiCd, 1=NiMh, 0=LiIon * - C (Current) asetetaan latausvirta * - T (Temperature) asetetaan katkaisu lämpötila * - D (Drop) asetetaan latauksen päättävä jännitteenpudotus (=NiCd & NiMh jännitteenpudotus, * LiIon = jännite jolla virtaa pudotetaan) * - L (Limit current) Latausvirta jolla LiIon lataus lopetetaan * - I (tIme) asetetaan max lataus aika viiden sekunnin pykälin, jonka jälkeen katkaistaan lataus * - O (stOp) katkaistaan lataus. * - G (Go) aloitetaan lataus. * - P (Print) tulostetaan tietyn muistipaikan kaikki asetukset * * Tämän jälkeen syötetään arvo: * - S: 0-9 (muistipaikka) * - 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:stä tuleva "pic16f87x.h" joka sisältää prosessorikohtaiset // muuttujat jne. #include incfile // 20Mhz kide #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 // pinni josta mitataan akun jännitettä #define TEMP_MEAS_PIN PIN_A1 // pinni josta mitataan akun lämpötilaa #define SET_BUTTON_PIN PIN_C3 #define START_BUTTON_PIN PIN_C4 #define STOP_BUTTON_PIN PIN_C0 // "räätälöidyt" lcd-funktiot #include "lcdakku.c" #include #define MEAS_DELAY 50000 // mittausten välinen viive mikrosekunneissa (tämän viiven verran rs232 odottaa komentoja) #define MEMORIES 9 // muistipaikkojen määrä // 6 * 4 * MEMORIES + 6 = EEPROM:n tarve // long vie tilaa 4 tavua, joten // 6 * 4 * 9 + 6 = 222 tavua // laitekohtaiset parametrit (max määrä 9 kpl) #define MEM_ADDR_VOLT_CAL 1 // mV / DA kalibrointitieto #define MEM_ADDR_LAST_SEL_MEM 5 // viimeksi valittuna ollut muistipaikka #define MEM_ADDR_AMPER_CAL 9 // mA / AD kalibrointi // muistipaikkakohtaiset tiedot #define MEM_ADDR_BATT_CURRENT 1 * 4 * MEMORIES + 1 // muistipaikat latausvirralle #define MEM_ADDR_BATT_TYPE 2 * 4 * MEMORIES + 1 // muistipaikat akun tyypille #define MEM_ADDR_MAX_TEMP 3 * 4 * MEMORIES + 1 // muistipaikat maksimilämmöille #define MEM_ADDR_MAX_TIME 4 * 4 * MEMORIES + 1 // muistipaikat maksimiajalle #define MEM_ADDR_BATT_VOLT_DROP 5 * 4 * MEMORIES + 1 // muistipaikat NiCd ja NiMh akkujen jännitepudotukselle ja // LiIon akun rajajännitteelle #define MEM_ADDR_BATT_C_LIMIT 6 * 4 * MEMORIES + 1 // muistipaikat virralle jossa lataus lopetetaan #define READ 0 #define WRITE 1 #define TYPE_NICD 0 #define TYPE_NIMH 1 #define TYPE_LIION 2 #define INTS_PER_SECOND 76 // montako keskeytystä tulee sekunnissa (20 000 000 / (4*256*256)) #define BATT_C_DROP 1 // paljonko latausvirtaa pudotetaan kun täysi jännite on saavutettu long BATT_CURRENT=0; // akun latausvirta int BATT_TYPE=TYPE_NICD; // akun tyyppi int BATT_MAX_TEMP=100; // maksimilämpö long BATT_MAX_TIME=720; // maksimiaika int BATT_VOLT_DROP=100; // jännitepudotus jolla lataus katkaistaa (NiCd/NiMh) / jännite jolla virtaa // pienennetään (LiIon) int BATT_C_LIMIT=25; // latausvirta jonka alittuessa LiIon lataus päätetään int1 CHARGE_IN_PROGRESS=FALSE; // onko lataus käynnissä int8 SELECTED_MEM; // valittu muistipaikka float MEAS_VOLT=0; // mitattu jännite long retval; // palautusarvo (FIXME: onko pakko olla globaalina, ei kai ?) short timeout_error; // onko tullut aikakatkaisu sarjaliikenteeseen byte int_count; // montako keskeytystä on tullut long seconds=0; // latauksen kuluneet sekunnit int16 cumulated_mAh=0; // latausmäärä yhteensä int16 mA_Per_10Sec=0; // milliamppeerimäärä kymmenessä sekunnissa long MV_PER_STEP; // montako millivolttia on yksi A/D pykälä int MA_PER_STEP; // montako milliamppeeria on yksi D/A pykälä int8 lcd_info=0; // mitä lcd:llä näytetään int8 ten_seconds=0; // kymmenet sekunnit float VOLT_TMP; // voltit long TEMP_TMP; // lämpötila // komennot joilla latausta ohjataan #define COMMAND_NOTHING 0 #define COMMAND_START 1 // pidä aina COMMAND_STOP_USER ensimmäisenä latauksen lopetussyynä ja // COMMAND_STOP_TIME viimeisenä syynä koska vain niiden välissä olevat arvot // katsotaan latauksen pysäyttämiseksi #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; // komento jonka käyttäjä antaa joka napeilla tai sarjaliikenteellä // varataan bootloaderille tilaa #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); // funktio muuntaa stringin long-arvoksi #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); } // funktio hoitaa käyttäjän komentojen välityksen eteenpäin sekä // laskee montako sekuntia lataus on ollut käynnissä #int_rtcc // RTCC (timer0) päälle clock_isr() { // joka kerta kun RTCC pyörähtää ympäri (255 -> 0), kutsutaan tätä funktiota, // tässä ohjelmassa n. 76 kertaa sekunnissa char aika[7]; // lcd:lle vietävä aika muodossa h:mm:ss // tutkitaan onko käyttäjä painanut näppäimiä // set-nappi vaihtaa muistipaikkoja 0 ... 9 -> 0 ... 9 if ( !input(SET_BUTTON_PIN) ) { SELECTED_MEM=SELECTED_MEM+4; // float on 4 tavua if (SELECTED_MEM > 9*4) // viimeinen muistipaikka SELECTED_MEM=0; printf(lcd_putc,"\fMEM: %u", SELECTED_MEM/4); delay_ms(50); // nopea ja huono tapa... } // start-nappi if (!input(START_BUTTON_PIN)) { COMMAND = COMMAND_START; } // stop-nappi if (!input(STOP_BUTTON_PIN)) { COMMAND = COMMAND_STOP_USER; } // jos latauksen aloittava komento on annettu joko sarjaliikenteellä // tai näppäimillä, aloitetaan tai lopetetaan lataus if (COMMAND == COMMAND_START) { start_charge(); COMMAND=COMMAND_NOTHING; } // lopetetaan lataus if (COMMAND >= COMMAND_STOP_USER && COMMAND <= COMMAND_STOP_TIME) { stop_charge(COMMAND); COMMAND=COMMAND_NOTHING; } if (CHARGE_IN_PROGRESS == TRUE) { // lasketaan vain aika jonka lataus on päällä int_count--; if(int_count <= 0) { charge(); // kutsutaan varsinaisen latauksen hoitavaa funktiota seconds++; int_count=INTS_PER_SECOND; // onko kymmenes sekunti ten_seconds++; if (ten_seconds>9) { // FIXME: EI TOIMI VIELÄ, PITÄISI JAKAA JOKA // SEKUNTI 3600:LLA MUTTA JOSTAKIN SYYSTÄ SE EI // PELAA, KUMULOIDUKSI ARVOKSI TULEE NOLLA cumulated_mAh = cumulated_mAh + (BATT_CURRENT*100/36); // 1000 virhe 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); // Voltit if (lcd_info>=2 && lcd_info<4) printf(lcd_putc,"\fC%S\n %2.2f V", aika, VOLT_TMP); // lämpötila 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; } } } // funktio odottaa rs232-portista kirjainta viiveen ajan #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; } // lämpötilakatkaisu 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; } // aikakatkaisu if (seconds > BATT_MAX_TIME && BATT_MAX_TIME>0) { COMMAND=COMMAND_STOP_TIME; return; } } // funktio lopettaa akun latauksen #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; } // funktio lukee arvot muistista käyttöön ja aloittaa akun latauksen 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; } // funktio kysyy käyttäjältä arvoa annetulle komennolle #separate void ask_rs232(char kirjain) { long int addr; int1 RW; // luku vai kirjoitusoperaatio ? 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) { // muistipaikka käsitellään erikseen koska // long-tyyliset muuttujat // vievät muistitilaa 4x int... 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(); } } // funktio alustaa prosessorin käyttöä varten #separate void alusta(void) { int last_selected_mem; // pwm-alustus setup_ccp1(CCP_PWM); setup_timer_2(T2_DIV_BY_1, 64, 1); // ad-alustus 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); // latausvirta nollaksi alussa 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); } // Pääohjelma ei tee muuta kuin kysyy komentoa sarjaportista. // Varsinaisen ohjauksen hoitaa keskeytysrutiini "clock_isr" kerran sekunnissa. // void main(void) { char kirjain; alusta(); while (1) { kirjain=timed_getc(); if (timeout_error == FALSE) { ask_rs232(kirjain); } } }