はじめに
トラ技2021年5月号に、8ビットPICマイコンが特集されていた。大変面白かったので、その日のうちにPICkit2と16F84Aをゴミ箱に投げ込んで、PICkit4と16F18857を買い求めた読者の方も多かっただろう(んなわけ)。今回は、個人的に推しているPIC16F1705でNi-MH充電器を作って、まだ購入を迷っている方の背中を押してみたいと思う。
今回使うPIC16F1705は、現在展開されているPIC16F1シリーズの中でも、フルスイングオペアンプが2回路搭載されている珍しい石である。その他の内蔵モジュールと組み合わせることで、色々なおもちゃを作る時これひとつで完結させることができる。しかも安い!そして充電の対象となるのはこちらの電池。
https://akizukidenshi.com/catalog/g/gB-07015/
丁度いい大きさで安いが専用充電器はない。皆思い思いの充電をしているようである。そういう意味でも参考になったら嬉しい。
コンセプト
- USB(5V)から電源を取ること。
- Ni-MH 3セルを最高200mA程度でCCCV充電できること。
- PIC16F1705の機能をフルに使って外付け部品が最小になるようにすること。
回路構成
メインは教科書に出てくるような定電流回路(V-I変換回路といった方が適切かもしれない)の入力に、内蔵DACをつなげたものである。C1とR2は発振防止で、D1は電源未接続時に電池からの逆流防止でついている。
ただこれでは定電圧にならないので、充電池の電圧を取って、充電終始電圧になったら充電電流を加減するようにして擬似的な定電圧動作としている。この時GNDから離れている電池電圧を取るためにオペアンプ1回路を用いて作動増幅回路を構成している。あとはデバッグのためのシリアル通信と、実用のためのLCDとスイッチである。
USB電源をそれなりに信頼して3端子レギュレーターも省略する。AD変換DA変換に関しては内蔵基準電圧(1.024Vのx1/x2/x4)があるのでそれで良いということにしている。
キーパーツ一覧
適当な抵抗だとか、コンデンサーとかだとかは省かせてもらう。
パーツ | 備考 |
---|---|
PIC16F1705 | 本日の主役 |
金属板抵抗器 2W1Ω | 電流検出用。放熱性が高そうだったもののうち小型のものということで採用。 |
2SK4017 | ちょうどいい感じのMOSFET。 |
1S4 | 普通のショットキーバリアダイオード。 |
8x2行のI2CなLCDモジュール | いつも使っている。 |
LED付押しボタンスイッチ(青) | ケースに付けるタイプの小さいプッシュスイッチでやすかったやつ。正直LEDいらんかった~。 |
バッテリーパック専用電池ボックス | 今回充電するやつ用の電池ボックス。 |
基板
PasSで部品配置を検討させてもらった。USBコネクタとXHコネクタのデータがなかったのでそれぞれ適当なコネクタを配置している。また、格納するケースの都合上、基板の右端を切り落としている。
PICの下に部品を仕込むことにより、ぱっと見の部品点数が少なく見せるインチキをしている。完成品がこちら。
USBコネクタだが、そのままではユニバーサル基板に刺さらなかったので、電源ライン以外の足をもいでつけた。そのままだと強度的に心配だったので、ハックルーで補強している。
プログラム
基本、モジュールをポチポチ追加していくだけでMCCがお膳立てしてくれる。ちなみにMSSPがI2Cを構成するモジュール本体で、I2CSIMPLEというのが面倒な割り込み処理やらステートマシンをすべて構成してくれる神機能である。各々の設定はそれほど難しくないので省略する。また、delayの実装とLCDの制御も省略する。
int mA2DAC(int mA) {
return mA / 4; //1ohm DAC:0-1.024V,8bit
}
int ADC2mV(int mV) {
//回路: in->(4.7/10.0)->ADC(0-4.096V,10bit)
//実測: 0-5.110[V]:0-620
return mV * 33 / 4;
}
const unsigned int charge_end_voltage = 1450 * 3; //mV
const unsigned int charge_const_current = 200; //mA
const unsigned int charge_end_current = 40; //mV
const unsigned int good_battery_voltage_min = 2300; //mV
const unsigned int good_battery_voltage_max = 4500; //mV
const unsigned int cycle_ms = 20;
unsigned int mAh = 0;
unsigned long mA_cycle = 0;
unsigned long cycle10_per_h = 1000 / cycle_ms * 360;
int get_battery_voltage(void) {
return ADC2mV(ADC_GetConversion(channel_AN2));
}
void add_mAh_count(unsigned int mA) {
mA_cycle += mA / 10;
if (cycle10_per_h <= mA_cycle) {
mA_cycle -= cycle10_per_h;
mAh++;
}
}
bool check_battery(void) {
int v = get_battery_voltage();
if (v < good_battery_voltage_min) {
LCDSendMsg("TOO LOW VOLTAGE ", 16);
return false;
} else if (good_battery_voltage_max < v) {
LCDSendMsg("TOO HIGHVOLTAGE ", 16);
return false;
} else if (charge_end_voltage < v) {
LCDSendMsg("BATTERY IS FULL!", 16);
return false;
}
return true;
}
void charge_CC(void) {
int i = 0;
while (1) {
DAC_SetOutput(mA2DAC(charge_const_current));
add_mAh_count(charge_const_current);
delay(cycle_ms);
int v = get_battery_voltage();
if (i > 500) {
printf("CCmode: %dmV,%dmA,%dmAh\n", v, charge_const_current, mAh);
snprintf(msg, sizeof (msg), "CC %3dmA %4dmV", charge_const_current, v);
LCDSendMsg(msg, 16);
i = 0;
}
if (charge_end_voltage < v) return;
i++;
}
}
void charge_CV(void) {
int current = charge_const_current;
int i = 0;
int v = 0;
while (1) {
while (1) {
v = get_battery_voltage();
if (charge_end_voltage < v) {
current--;
DAC_SetOutput(mA2DAC(current));
if (current <= charge_end_current) {
DAC_SetOutput(0);
RC3_SetLow();
RC3_SetDigitalOutput();
RC3_SetDigitalMode();
return;
}
} else {
break;
}
}
if (i > 500) {
printf("CVmode: %dmV,%dmA,%dmAh\n", get_battery_voltage(), current, mAh);
snprintf(msg, sizeof (msg), "CV %3dmA %4dmV", current, v);
LCDSendMsg(msg, 16);
i = 0;
}
i++;
add_mAh_count(current);
delay(cycle_ms);
}
}
bool charge(void) {
LCDSendMsg("battery checking", 16);
if (!check_battery()) return false;
LCDSendMsg("start charge ", 16);
charge_CC();
charge_CV();
puts("Complete!");
snprintf(msg, sizeof (msg), "COMPLETE %4dmAh", mAh);
LCDSendMsg(msg, 16);
return true;
}
void main(void) {
SYSTEM_Initialize();
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
TMR2_SetInterruptHandler(timer2_Interrupt);
TMR2_StartTimer();
PWM4_LoadDutyValue(PR4);
LCDInit();
LCDSendMsg("hello", 5);
delay(500);
LCDSendMsg("stand by PUSH->", 16);
while (pushSW_GetValue());
charge();
while (1);
}
完成品
さて、最近は「基板の上で完結させずに、ちゃんとケースとかに収めてきれいな形にして完成品を作る」という目標を自分で勝手に課している。その結果こうなった。
毎度この液晶の固定方法に困っている。両面テープで止めたら、ケースを開いたときに液晶が割れてしまった。ハックルーの高熱に晒すのも怖い。普通に接着剤でいいかもしれないが。あと、秋月で売っているこのバッテリーパック専用の電池ボックス、皿ネジが入るように一応座ぐりが入っているが、同じく秋月で売っている皿ネジで止めると、頭が0.5mmほど出っ張る。一応その状態でバッテリーパックは入るが、入れるときのスムーズさは失われる。ちょっと悲しい。
充電の様子
4215mVあたりになにかすごく引っかかりがある。原因はわかっていないが、概ね良好ということにしている。定電圧動作のほうはだいたい問題なさそうだ。ほぼ4mAずつ下がっているのはDA変換の精度的に合っている。ちなみに今回のテストでは適当に放電して持ってきた電池だが、ちょうど公称より多めの880mAhになった。
解決していない課題
重大な課題は無いが、まあまあな課題があるので真似しようとしている人に注意書きとして残しておく。
オペアンプのオフセット
実は何も対策していない。このため、計算より4~5mAくらい常に多く電流が流れている。これはDACの出力を0にしても電流が流れることを意味しており、ちょっと困る。仕方ないので、充電終了時にはオペアンプを切り離して、デジタルのLOWレベルを入れることによりカットしている。
一応回路的な対策もできる。最初からR1にちょっと電流が流れてればいいのである。データシートによると、オフセットの最悪値が9mVなので、プラス方向にまあだいたい調整次第で1mV~9mVくらい流れていれば良い。
マイナス方向にオフセットがあるなら逆につなげれば良い(多分)。ちなみにこれを可変抵抗1つで解決する回路は知らないので教えて下さい。
ひょっとしたら電圧が足りていない問題
電源5V - 終端電圧 4.35V - ダイオード降下電圧 0.4V = 0.25V。電流検出抵抗1Ω+FETのON抵抗0.15Ω = 1.15Ωなので、電池の内部抵抗次第では200mA流せない可能性がある。ちょっとカツカツだが、上は最悪値での計算だし、USB電源たいてい5.1V出てるので実際には多少マシなはず。(ひょっとしたら4215mVあたりでフラフラするのはこれ周りかもしれない)
内部抵抗分の電圧
充電しながら電池の電圧を計っているので、実際には充電電流×内部抵抗[V]分だけ高く出ている。正確にやりたいなら計るときに一旦充電を止めれば良いが、CVモードでギリギリまで充電しようとしているのでこれで不足分は補えていると思っている。……これでいいのかは知らない。
おわりに
目が肥えたelchikaユーザーには、降圧DCDCでもないこの低効率な充電器では物足りなかったかもしれないがご容赦願いたい。
わかりやすく単純なおもちゃだが、結構応用がききそうだ。電流検出抵抗を小さいものに変えれば、精度を犠牲にして電流を増やせるので、-ΔV検出の急速充電器に早変わりなんてこともできる。マイクロインダクタとPWMかなんかで雑に昇圧すれば、006P型のような高電圧低容量なものも充電できるだろう。一応プログラム次第でリチウム系も充電できるだろうが、ちょっと怖いからそういうのは専用ICに譲ったほうがいい。デバッグ用のシリアル出力もデバッグが要らなくなったらブザーなどにつなげても良いし、低電圧書き込みを諦めるならMCLRも普通の端子として使えるので、更に色々部品をつけれたりする。融通がきくって素晴らしい!
投稿者の人気記事
-
Asi_a
さんが
2021/06/17
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する