最終更新時間:2012年02月02日 20時10分44秒
[公開:any]
[電子工作/AVR]
[AVR,ATmega168,MOS FET,PWM,放電器,電子負荷,測定器,LM358]
携帯ゲーム機器や携帯電話、スマートフォン等にUSB充電するアダプタとしてニッケル水素電池のエネループ電池を使用したモバイル用アダプタを使用しています。
交換用のエネループ電池を複数持ち歩けば色々と充電できて便利ですが、エネループ電池を完全に放電させることなく中途半端な状態で充放電を繰り返した成果、メモリー効果が出たのか放電時間が短くなってきたような気がします。また、古い電池と新しい電池が混ざってしまい、特性の違いが気になります。
エネループ電池自体はメモリー効果が少ない電池と言われています。しかし、純正の充電器にも放電機能を搭載していることからわかるように、ある程度使いこむと、メモリー効果を放電によりリフレッシュする必要があります。
純正の充電器では、中途半端に使用したエネループ電池の放電に時間がかかるので、今回は、エネループ電池を定電流で高速放電させて容量や平均電圧などの特性を把握できる放電器を製作することにしました。
なお、2008年3月にニッカド電池やニッケル水素電池の放電器として「AVRを使ったバッテリー放電器の製作」(下の画像)で、一度製作していますが、今回は勤務先で使用できるようにコンパクトタイプを製作してみました。
放電の仕組みは前回製作したものを参考に、AVRからのPWMでMOS-FETをスイッチングして放電電流を制御する方式とします。負荷の抵抗にかかる電圧をAD変換で取り込み、放電電流が定電流となるようにPWMのデューティ比をフィードバック制御します。定電流で放電させれば、電流値と経過時間から容量を計算することができます。また、放電中は常に電池電圧をモニターし、あらかじめ設定した電圧になると放電を停止します。
回路図です。MOS-FETは、2SK2231を使用します。前回は、TO-220パッケージの2SK2232を使用したのですが、今回は最大放電電流1000mAとするので一回り小さなものとしました。負荷となる抵抗は酸化金属皮膜抵抗の0.2Ω3Wを使用します。1000mA時でも0.2W程度なので1W程度の抵抗でも十分かもしれませんが、結構、発熱するので表面積の大きな余裕あるものが安心です。負荷抵抗の両端の電圧は最大(1000mA放電時)でも200mVと小さいのでオペアンプで10倍に増幅します。AVRのクロックは時間計測の精度が必要なので水晶発振器を使用しました。
PWM周波数は46KHzとしています。周波数が高いとMOS-FETの発熱が心配になりますが、特に問題はなさそうです。ただ、2SK2231は、ON抵抗がVGS4Vで約0.2Ωと大きいせいか、ある程度は発熱します。指で触れる程度なので、放熱板は不要と判断しました。下の画像は、放電時のPWM波形と負荷抵抗両端からローパスフィルタを経過した波形です。
ユニバーサル基板への実装図です。負荷抵抗の周辺は、熱の影響を受けないようにスペースを確保する必要があります。また、抵抗下の基板に穴をあけるなどして空気の流れを作ります。
オペアンプの出力は、フィードバック制御に重要です。オペアンプの増幅率の変動は、放電電流の精度に影響が出ます。このためオペアンプ周りは金属皮膜抵抗を使用します。半田面の配線は、部品のリードや0.5mmのすずメッキ線を使用しました。最大でも1000mAなので問題はありません。
オペアンプの増幅率が正確に10倍となるようにマルチメータ等で電圧を測定しながら可変抵抗を調整します。
ケースは秋月電子のポリカーボネートケース大を使用しました。(平成24年2月現在、ポリカーボネートケースはABS樹脂製112-TM-ABSに代わっています。)
単3用電池ケースは秋月電子で1個30円で入手したプラスティックのを使用しました。前回の製作で利用した金属製のものが入手できればよいのですが、現在は単セル用の扱いがありません。スイッチは、安物のケース用のプッシュスイッチを採用しましたが、チャタリングがひどいです。ソフトウェアで対応しましたが、10msのチャタリング防止ディレイでは、誤動作が多いので20msのディレイとしました。
基板上のLEDは、放電制御のPWM出力を分岐して点灯させています。したがって放電電流により明るさが異なります。
この電池ケースは、電極やバネをハトメで固定してあるタイプで、接触不良が発生することがあります。見た目は良くありませんが、+極は、電極のハトメに直接半田付けします。−極はバネに半田付けして接触抵抗が少なくするようにします。
充電から1カ月程度経過したエネループ電池を放電電流500mA、放電停止電圧0.9Vで放電してみました。両電池とも放電容量1700mA程度、平均電圧1.20V、放電時間は3時間20分程度となったので、ほぼ同じ特性の電池と言えるでしょう。(同時に購入したものです)
また、エネループ電池の特徴である充電後も長期間にわたって容量を保持していることがわかります。
AVRのプログラムです。AVRStudio4.0とWinAVR(gcc)で作成しました。前回の放電器のものをベースに作っています。モードスイッチで放電電流(100mA~1000mAを100mA単位で可変)、放電停止電圧(0.5V~1.2Vを0.1V単位で可変)、最大放電時間(1~18時間を1時間単位で可変)を選択。
スタート・ストップスイッチでは、どの状態からも、その時の設定内容で放電のスタート・ストップができます。
放電中は、電圧表示、電流表示、放電容量表示、放電時間表示、デューティー比表示が選択できます。また、各セルごとの前記項目の一覧表示も可能です。
放電完了後は、放電時間、放電容量、平均放電電圧を表示できるので、満充電したエネループを放電させて特性を見ることでマッチングをとることが可能となります。。
1 |
#include <avr/io.h> #include <util/delay.h> #include <avr/pgmspace.h> #include <avr/interrupt.h> #include <stdio.h> #include <math.h> #include "lcd.h" #define cbi(addr, bit) addr &= ~(1<<bit) #define sbi(addr, bit) addr |= (1<<bit) #define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|0b111) #define ADC_START (ADC_ENABLE|_BV(ADSC)) void delay_ms(unsigned int t) { while(t--) _delay_ms(1); } FILE *fp; // for fdevopen() double coff; unsigned int hm, sec; unsigned int duty[2]; unsigned int load[2]; unsigned int volt[2]; unsigned int lv_cnt[2]; unsigned int cur; unsigned int amh[2]; unsigned int tm[2]; double avg[2], total[2]; unsigned char t1flg; unsigned int mode; unsigned int batt[2]; unsigned int view_sel; unsigned int view_mode; unsigned int last_batt; unsigned int toff; unsigned char toff_flag; // Timer1 Compare match interrupt ISR(TIMER1_COMPA_vect) { if(mode == 5) { hm++; if(hm == 5) { // 200ms*5=1s hm = 0; sec++; // 平均電圧計算のための電圧和 total[0] = total[0] + volt[0] * 0.00244; total[1] = total[1] + volt[1] * 0.00244; } } t1flg = 1; } unsigned int get_adc(unsigned char ch) { ADMUX = ch; ADCSRA = ADC_START; loop_until_bit_is_set(ADCSRA, ADIF); return ADCW; } void adc_check(void) { unsigned char i; for(i = 0; i < 2; i++ ) { volt[i] = get_adc(i + 4); } } void display(void) { if(mode == 0) { // 電池電圧表示 adc_check(); lcd_goto(0, 0); fprintf(fp, "Batt Voltage "); lcd_goto(1, 0); fprintf(fp, "1:%.2fV 2:%.2fV", volt[0] * 0.00244, volt[1] * 0.00244); } if(mode == 1) { // 放電電流設定 lcd_goto(0, 0); fprintf(fp, "Dischrg Current "); lcd_goto(1, 0); fprintf(fp, "%4dmA ", cur); } if(mode == 2) { // 放電停止電圧設定 lcd_goto(0, 0); fprintf(fp, "Cutoff Voltege "); lcd_goto(1, 0); fprintf(fp, "%.1fV ", coff); } if(mode == 3) { // 放電停止時間設定 lcd_goto(0, 0); fprintf(fp, "Max Cutoff Time "); lcd_goto(1, 0); fprintf(fp, "%dhour ", toff); } if(mode == 4) { // 設定確認画面 lcd_goto(0, 0); fprintf(fp, "Set: %4dmA %.1fV", cur, coff); lcd_goto(1, 0); fprintf(fp, "%dHour Push Start", toff); } if(mode == 5) { // 放電中表示 if(view_mode) { lcd_goto(0, 0); fprintf(fp, "%d: %.2fV %04dmAH", view_sel + 1, volt[view_sel] * 0.00244, amh[view_sel]); lcd_goto(1, 0); fprintf(fp, " %5dsec %4dmA", sec, load[view_sel]); } else { lcd_goto(0, 0); fprintf(fp, "elaps %3dm%02ds ", sec / 60, sec % 60); lcd_goto(1, 0); switch(view_sel) { case 0: // 電圧表示 fprintf(fp, "volt %.2fV %.2fV", volt[0] * 0.00244, volt[1] * 0.00244); break; case 1: // 放電電流表示 fprintf(fp, "%4dmA %4dmA", load[0], load[1]); break; case 2: // 放電容量表示 fprintf(fp, "%4dmAH %4dmAH", amh[0], amh[1]); break; case 3: // デューティ表示(PWM ratio) fprintf(fp, "duty %4d %4d ", duty[0], duty[1]); break; } } } if(mode == 6) { // 放電結果表示 switch(view_sel) { case 0: lcd_goto(0, 0); if(toff_flag) { fprintf(fp, "==TimeOut End!=="); } else { fprintf(fp, "=Discharge End!="); } lcd_goto(1, 0); fprintf(fp, " %4dmA %.1fV", cur, coff); break; case 1: lcd_goto(0, 0); // 平均放電電圧 fprintf(fp, "volt average "); lcd_goto(1, 0); fprintf(fp, "1:%.2fV 2:%.2fV", avg[0], avg[1]); break; case 2: lcd_goto(0, 0); // 放電容量 fprintf(fp, "discarge capt "); lcd_goto(1, 0); fprintf(fp, "%4dmAH %4dmAH", amh[0], amh[1]); break; case 3: lcd_goto(0, 0); // 放電時間 fprintf(fp, "dischrge time "); lcd_goto(1, 0); fprintf(fp, "%5ds %5ds", tm[0], tm[1]); break; } } } void discharge(unsigned char i) { volt[i] = get_adc(i + 4); load[i] = (int)(get_adc(i + 4 - 2) * 1.22); // 2.5/1024/2 * level = Load current if(volt[i] <= (int)(coff / 0.00244 + 0.5)) { // 0.9 / (2.5/1024) = 368.... lv_cnt[i]++; } else { lv_cnt[i] = 0; } if(lv_cnt[i] > 10) { // 10回連続で基準以下の場合、放電停止 tm[i] = sec; if(sec == 0) { avg[i] = 0; } else { avg[i] = total[i] / sec; } duty[i] = 255; batt[i] = 0; last_batt++; } else { if(load[i] < cur) { // 設定電流 > 放電電流 duty[i]--; if(duty[i] < 100) { duty[i] = 100; } } if(load[i] > cur) { // 設定電流 < 放電電流 duty[i]++; if(duty[i] > 255) { duty[i] = 255; } } } switch(i) { case 0: OCR0B = duty[i]; break; case 1: OCR0A = duty[i]; break; } amh[i] = (int)(cur / 3600.0 * sec + 0.5); } int main() { unsigned char sw0_state, sw1_state, sw2_state, sw3_state; unsigned int i; DDRB = 0b11111111; // LCD DDRC = 0b11000011; // A/D input DDRD = 0b11110000; // PD0-3 SW input DIDR0 = 0b11111111; // ADC PORT Digital Input Disable PORTD = 0b00001111; // PD0-3 pull up PORTC = 0b11000011; ADCSRA = ADC_ENABLE; lcd_init(); lcd_cls(); fp = fdevopen(lcd_putch, NULL); // LCD出力ファイルディスクプリタ coff = 0.9; cur = 100; toff = 12; sec = 0; hm = 0; t1flg = 0; view_sel = 0; view_mode = 0; toff_flag = 0; mode = 0; last_batt = 0; sw0_state = sw1_state = sw2_state = sw3_state = 0; TCCR1B = 0b01001011; // Timer1 プリスケーラ 1/64 TIMSK1 = _BV(OCIE1A); // Timer1 compare match A interrupt OCR1A = 36799; // 11.776MHz / 64 = 184000. 200ms / (1/184000) = 36800. 36800 - 1 = 36799. TCCR0A = _BV(WGM01)|_BV(WGM00)|_BV(COM0A1)|_BV(COM0B1)|_BV(COM0A0)|_BV(COM0B0); // TCCR0B = 1; sei(); lcd_goto(0, 0); fprintf(fp, "NiMH Discharger "); lcd_goto(1, 0); fprintf(fp, " Ver 1.0 "); delay_ms(2000); while(1) { // UP----------------------------------------- if(bit_is_clear(PIND, PD2)) { sw0_state = 1; delay_ms(20); } if(sw0_state && bit_is_set(PIND, PD2)) { sw0_state = 0; if(cur < 1000 && mode == 1) { // Current Up 100mA cur += 100; } if(coff < 1.2 && mode == 2) { // Cut off volt up 0.1v coff += 0.1; } if(toff < 18 && mode == 3) { toff += 1; } if(mode == 5 || mode == 6) { view_sel++; if(view_mode) { if(view_sel > 1) { view_sel = 0; } } else { if(view_sel > 3) { view_sel = 0; } } } } // DOWN--------------------------------------- if(bit_is_clear(PIND, PD3)) { sw1_state = 1; delay_ms(20); } if(sw1_state && bit_is_set(PIND, PD3)) { sw1_state = 0; if(cur > 100 && mode == 1) { // Current Down 100mA cur -= 100; } if(coff > 0.5 && mode == 2) { coff -= 0.1; } if(toff > 0 && mode == 3) { toff -= 1; } if(mode == 5 || mode == 6) { if(view_mode) { if(view_sel == 0) { view_sel = 2; } } else { if(view_sel == 0) { view_sel = 4; } } view_sel--; } } // MODE--------------------------------------- if(bit_is_clear(PIND, PD0)) { sw2_state = 1; delay_ms(20); } if(sw2_state && bit_is_set(PIND, PD0)) { sw2_state = 0; if(mode != 5) { mode++; lcd_cls(); if(mode > 3) { mode = 0; } } else { if(view_mode) { view_mode = 0; view_sel = 0; } else { view_mode = 1; view_sel = 0; } } } // START/STOP--------------------------------- if(bit_is_clear(PIND, PD1)) { sw3_state = 1; delay_ms(20); } if(sw3_state && bit_is_set(PIND, PD1)) { sw3_state = 0; if(mode == 4 && cur > 0) { mode = 5; TCCR0B = 1; sec = 0; last_batt = 0; toff_flag = 0; for(i = 0; i < 2; i++) { total[i] = 0.0; lv_cnt[i] = 0; batt[i] = 1; avg[i] = 0; amh[i] = 0; } } else { mode = 4; } } //-------------------------------------------------- if(mode == 5) { // 放電モード for(i = 0; i < 2; i++) { if(batt[i]) { discharge(i); } } if(last_batt == 2) { // 電池2本終了でモード6へ mode = 6; view_sel = 0; view_mode = 0; } if(sec > (toff * 3600)) { // Time out mode = 6; toff_flag = 1; for(i = 0; i < 2; i++) { if(batt[i]) { tm[i] = sec; avg[i] = total[i] / sec; } } } } else { OCR0A = 255; OCR0B = 255; } delay_ms(10); if(t1flg) { // 200ms間隔で表示 display(); t1flg = 0; } } } |