ニッケル水素電池放電器の製作

[公開:any]

[電子工作/AVR]
[AVR,ATmega168,MOS FET,PWM,放電器,電子負荷,測定器,LM358]

origin 2012-02-02


 携帯ゲーム機器や携帯電話、スマートフォン等にUSB充電するアダプタとしてニッケル水素電池のエネループ電池を使用したモバイル用アダプタを使用しています。 

エネループモバイル用USBアダプタ

 交換用のエネループ電池を複数持ち歩けば色々と充電できて便利ですが、エネループ電池を完全に放電させることなく中途半端な状態で充放電を繰り返した成果、メモリー効果が出たのか放電時間が短くなってきたような気がします。また、古い電池と新しい電池が混ざってしまい、特性の違いが気になります。
 エネループ電池自体はメモリー効果が少ない電池と言われています。しかし、純正の充電器にも放電機能を搭載していることからわかるように、ある程度使いこむと、メモリー効果を放電によりリフレッシュする必要があります。
 純正の充電器では、中途半端に使用したエネループ電池の放電に時間がかかるので、今回は、エネループ電池を定電流で高速放電させて容量や平均電圧などの特性を把握できる放電器を製作することにしました。
 なお、2008年3月にニッカド電池やニッケル水素電池の放電器として「AVRを使ったバッテリー放電器の製作」(下の画像)で、一度製作していますが、今回は勤務先で使用できるようにコンパクトタイプを製作してみました。

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波形と負荷抵抗両端からローパスフィルタを経過した波形です。

放電器の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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#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 = 36800;            // 11.776MHz / 64 = 184000. 200ms / (1/184000) = 36800.  

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

▲ページ Top へ...