こんにちは、DENです。
前回に引き続き、セリアのNi-MH電池です。今回は、電池4本で約5Vとなることに着目し、USB扇風機やUSBライトなどの機器を使えるようにする回路の紹介です。
電池4本をそのままUSB扇風機に接続すればいいじゃないか、と思われるかもしれませんが、それだと電池の電圧が0Vになるまで放電されてしまいます。アルカリ乾電池のような使い捨て電池ならそれでよいですが、過放電は充電池にダメージを与えるので、適切な電圧で放電を止める保護回路が必要です。Ni-MH電池は1本あたり0.9~1.0Vで空なので、そのへんで放電が止まるようにします。
今回の回路の要求仕様は、以下の通りです。
- 電池(4本)の電圧を監視し、3.9Vになったら出力を止める
- タクトスイッチで出力をオンオフできる
つまり、タクトスイッチを押すとUSB出力がオンになり、もう一度タクトスイッチを押すか、電圧が下がったらオフになるというものです。前回紹介した放電回路のようにハードウェアのみで解決もできると思いますが、今回は閾値や検出のディレイを柔軟にしておきたかったのでマイコンを使いました。
回路図
OUT+とOUT-から約5Vを出力するので、ここをUSB扇風機につなぎます。マイコンとしてPIC10F322を使いました。安くて小さいものが好きなのでお気に入りのマイコンです。
SW1は出力のオンオフ操作スイッチです。実際にはQ3がオンオフすることで出力をコントロールします。LED1は出力が出ているかどうかを示すLEDで、回路の働きには関係ありません。
機能の説明
マイコンソフトは以下のようなプログラムです。
- プログラムがスタートすると、RA2にHighを出力する。
- 2.5msごとに、VDD電圧をAD変換する。(FVR機能を使います)
- 256回分のAD変換値を合計したものが、VDD=3.9V相当を下回っているか監視。3.2秒間連続で下回れば、RA2にLowを出力しQ3をオフにする。
- RA1でSW1を押したかを監視し、押した場合はRA2にLowを出力しQ3をオフにする。
これにより、SW1を押すとQ3がオンしてマイコンがスタートし、再度SW1を押すか、電池電圧が3.9Vを下回るとQ3がオフして回路全体の電源が落ちるという動作を実現しています。
VDD電圧の監視は、PICのFVR(固定参照電圧)機能を使います。これはPICに搭載されている、電源電圧に関わらず一定の電圧を生成する電源です。VDDをリファレンスとしてFVR電圧をAD変換した値から、次のようにVDDが逆算できます。
VDD=256÷AD値×FVR電圧(2048mV)
単純なプログラムなのでアセンブラで書きました。アセンブラはメモリ1ビットまで全て自分の手の内にあるような感覚がいいですね。
需要あるかわかりませんが以下にソースコード添付します。
#include "p10f322.inc" ; ※ __CONFIGの前にタブまたはスペースを入れること(入れないと警告が出る) ; CONFIG ; __config 0xFCDE __CONFIG _FOSC_INTOSC & _BOREN_ON & _WDTE_ON & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _LVP_OFF & _LPBOR_OFF & _BORV_LO & _WRT_OFF ; 内蔵オシレータ使用、ブラウンアウトリセットあり(2.4V), ウォッチドッグタイマあり ; MCLRピン=リセットピン、コードプロテクトなし ; ※ equ文は先頭にタブまたはスペースを入れないこと(入れると警告が出る) ; ------------------------------------------------------------------------- ; 変数定義(SRAMアドレス) ; ------------------------------------------------------------------------- pa_in equ 0x40 ; ポートA入力コピー pa_out equ 0x41 ; ポートA出力コピー sw_cnt equ 0x42 ; スイッチオンカウンタ LSB:2.5ms lv_cnt equ 0x43 ; 低電圧検出カウンタ LSB:2.5*256=640ms vbat_ad_l equ 0x44 ; Pack電圧AD値L側 LSB:- vbat_ad_h equ 0x45 ; Pack電圧AD値H側 LSB:- seq_cnt equ 0x46 ; 2.5msシーケンスカウンタ add16_a_l equ 0x47 ; 16bit足し算関数用 引数兼戻り値 add16_a_h equ 0x48 ; 16bit足し算関数用 引数兼戻り値 add16_b_l equ 0x49 ; 16bit足し算関数用 引数 add16_b_h equ 0x4A ; 16bit足し算関数用 引数 ; --- ビット位置定義 -------------------------------------------------------- ; 本作はなし ; ------------------------------------------------------------------------- ; 定数マクロ定義(リテラル) ; ------------------------------------------------------------------------- ; --- クロック・Timer0・WDT INIT_OSCCON equ 0x5B ; 内蔵クロック4MHz INIT_OPTION equ 0x03 ; Timer0プリスケーラ 1:16 (ワンカウント16us) INIT_TMR0 equ 0x64 ; Timer0初期値 INIT_WDTCON equ 0x18 ; 0b00 01100 0 WDTタイムアップ時間=4s(typ.) ; --- GPIO ; RA0:NoUse, RA1:SW_in, RA2:self_hold INIT_ANSELA equ 0x00 ; b00000000 INIT_TRISA equ 0x0A ; b00001010 INIT_WPUA equ 0x00 ; b00000000 INIT_LATA equ 0x04 ; b00000100 ;INIT_PORTA equ 0x04 ; b00000100 SW_PORT equ 1 HLD_PORT equ 2 ; --- ADC INIT_ADCON equ 0x3D ; b00111101 ; ADCのクロックはCPUクロックの1/8, ADポートはFVR, ADコンバータの電源はオン ; 消費電流が重要なアプリケーションでは変換時以外は電源オフにすること ; --- FVR INIT_FVRCON equ 0x82 ; b10000010 ; FVR電源オン、温度インジケータ無効、参照電圧2.048V ; --- 各種閾値 VBAT_LV_END equ 0x86 ; 過放電判断の電圧 LV_CNT_END equ 0x05 ; 過放電判定時間 640ms * 5 = 3.2s SW_CNT_END equ 0x0C ; スイッチオン確定時間 2.5ms * 12 = 30ms ;******************************************************************************* ; Reset Vector ;******************************************************************************* RES_VECT CODE 0x0000 ; processor reset vector GOTO START ; go to beginning of program ;******************************************************************************* ; MAIN PROGRAM ;******************************************************************************* MAIN_PROG CODE ; let linker place main program org 0x0008 ; STARTを0x0008番地に配置 START ; TODO Step #5 - Insert Your Program Here ; ==================================================================== ; ユーザRAMクリア(日本語データシートp.19の例2.1のコピペ) MOVLW 0x40 ;initialize pointer MOVWF FSR ;to RAM SRAM_CLR CLRF INDF ;clear INDF register INCF FSR,1 ;inc pointer BTFSS FSR,7 ;all done? GOTO SRAM_CLR ;no - clear next SRAM_CLR_END ;yes - continue ; ==================================================================== ; 各種SFR初期設定 ; 1. メインクロック movlw INIT_OSCCON movwf OSCCON ; メインクロック4MHz ; 2. WDT movlw INIT_WDTCON movwf WDTCON ; 3. GPIO movlw INIT_ANSELA movwf ANSELA movlw INIT_TRISA movwf TRISA movlw INIT_WPUA movwf WPUA movlw INIT_LATA movwf pa_out movlw INIT_LATA movwf LATA movf PORTA,0 ; w = PORTA movwf pa_in ; 4. ADC movlw INIT_ADCON movwf ADCON ; 5. FVR movlw INIT_FVRCON movwf FVRCON fvrrdy_wait btfss FVRCON, FVRRDY ; FVRRDY = 1なら次の命令をスキップ goto fvrrdy_wait ; FVRがレディになるまでウェイト ; 6. Timer0 movlw INIT_OPTION movwf OPTION_REG movlw INIT_TMR0 movwf TMR0 bcf INTCON, TMR0IF ; TMR0割り込みフラグクリア ; ==================================================================== ; SW戻すまでここで待つ(SWオンのままメインに進むと即シャットダウンしてしまう) sw_off_wait clrwdt ; WDTリセットにならないようにクリア movf PORTA,0 ; w = PORTA movwf pa_in ; pa_in = w btfsc pa_in, SW_PORT ; SW_PORT = 0(押してない)なら次の命令をスキップ goto sw_off_wait ; スイッチを戻すまで待つ ; ==================================================================== ; メインループ loop btfss INTCON, TMR0IF ; TMR0IF = 1なら次の命令をスキップ goto loop ; === 2.5ms毎処理 movlw INIT_TMR0 ; Timer0をリスタート movwf TMR0 bcf INTCON, TMR0IF ; TMR0割り込みフラグクリア ; --- 2.5ms毎 各種制御 ; 毎ライン call port_check ; ポート入力, refresh call get_ad ; AD取得 incf seq_cnt, 1 ; seq_cnt++ ; if seq_cnt == 0 then movf seq_cnt, 1 ; seq_cnt = seq_cnt (0検証処理) btfss STATUS, Z ; 結果が0である(seq_cnt==0)なら次の命令をスキップ goto loop_seq_continue ; --- 640ms毎処理 ; 過放電判定処理 call lv_judge ; 過放電判定処理関数 ; AD値クリア clrf vbat_ad_l ; AD積算値クリア clrf vbat_ad_h ; AD積算値クリア ; WDTクリア clrwdt ; Clear WDT loop_seq_continue goto loop ; ==================================================================== ; ポート入力、リフレッシュ関数 port_check ; --- 1. ポート入力取得 movf PORTA, 0 ; w = PORTA movwf pa_in ; pa_in = w ; --- 2. 入力判定処理 ; if SW_PORT == High then sw_cnt++ else sw_cnt = 0 btfss pa_in, SW_PORT ; SW_PORT = 1なら次の命令をスキップ goto port_check_sw_off ; SW押していないのでカウンタクリア、動作継続 ; SW押している処理 incf sw_cnt, 1 ; sw_cnt++ ; if sw_cnt >= SW_CNT_END then call shutdown ; if mtimer_l >= 0x0A then movlw SW_CNT_END ; w = SW_CNT_END subwf sw_cnt, 0 ; w = sw_cnt - SW_CNT_END btfss STATUS, C ; ボロー無し(sw_cnt >= SW_CNT_END)なら次の命令をスキップ goto port_check_continue ; sw_cnt閾値未満なので動作継続 ; sw_cnt閾値以上、シャットダウンへ call shutdown ; 戻ってこないのでこれでOK port_check_sw_off clrf sw_cnt ; sw_cnt = 0 port_check_continue ; --- 3. ポートリフレッシュ movlw INIT_ANSELA movwf ANSELA ; ANSELAリフレッシュ movlw INIT_TRISA movwf TRISA ; TRISAリフレッシュ movlw INIT_WPUA movwf WPUA ; WPUAリフレッシュ movf pa_out, 0 ; w = pa_out movwf LATA ; LATA(PORTA)リフレッシュ return ; ==================================================================== ; AD変換関数 get_ad ; 1. チャネル選択 ; 今回はなし ; 2. 変換開始 bcf PIR1, ADIF ; AD割り込みフラグクリア bsf ADCON, GO_NOT_DONE ; AD実行ビットセット get_ad_wait btfss PIR1, ADIF ; ADIF = 1なら次の命令をスキップ goto get_ad_wait bcf PIR1, ADIF ; AD割り込みフラグクリア ; 3. 結果の取得 movf ADRES, 0 ; w = ADRES(AD結果8bit) ; 4. 積算 ; vbat_ad += ADRES movwf add16_b_l ; add16_b_l = w clrf add16_b_h ; add16_b_h = 0 movf vbat_ad_l, 0 ; w = vbat_ad_l movwf add16_a_l ; add16_a_l = w movf vbat_ad_h, 0 ; w = vbat_ad_h movwf add16_a_h ; add16_a_h = w call add16 movf add16_a_l, 0 ; w = add16_a_l movwf vbat_ad_l ; vbat_ad_l = w movf add16_a_h, 0 ; w = add16_a_h movwf vbat_ad_h ; vbat_ad_h = w ; 終了 return ; ==================================================================== ; 16bit + 16bit = 16bit 足し算 (0xFFFFガード付き) ; a <- a + b add16 ; 1. 下位バイトの計算 movf add16_b_l, 0 ; w = BL addwf add16_a_l, 1 ; AL = AL + w この命令のキャリーフラグ有無を利用 ; 2. 繰り上がり判定 btfss STATUS, C ; キャリーありなら次の命令をスキップ goto add16_calc_h ; キャリー無しなら繰り上がり処理を飛ばす ; 繰り上がり処理 incf add16_a_h, 1 ; AH++ movf add16_a_h, 1 ; 0検証処理 0だったらオーバーフローしている btfsc STATUS, Z ; 結果が0でなければ次の命令をスキップ goto add16_overflow ; オーバーフロー処理へ add16_calc_h ; 3. 上位バイトの計算 movf add16_b_h, 0 ; w = BH addwf add16_a_h, 1 ; AH = AH + BH (+1) ; オーバーフロー判定 btfss STATUS, C ; キャリーありなら次の命令をスキップ goto add16_end ; キャリー無しならオーバーフロー処理を飛ばして正常終了 add16_overflow ; オーバーフローしたときのみ通るところ movlw 0xFF ; w = 0xFF movwf add16_a_l ; AL = 0xFF movlw 0xFF ; w = 0xFF movwf add16_a_h ; AH = 0xFF add16_end return ; ==================================================================== ; 過放電判定処理 lv_judge ; if vbat_ad_h >= VBAT_LV_END then lv_cnt++ else lv_cnt=0 ; ※不等号の向き注意:Vbatが小さいほどADは大きくなる movlw VBAT_LV_END ; w = VBAT_LV_END subwf vbat_ad_h, 0 ; w = vbat_ad_h - VBAT_LV_END btfsc STATUS, C ; ボローあり(条件不成立)なら次の命令をスキップ goto lv_judge_lvdtct ; 低電圧でないので動作継続 clrf lv_cnt ; lv_cnt = 0 goto lv_judge_end lv_judge_lvdtct ; 低電圧検出中 incf lv_cnt, 1 ; lv_cnt++ ; if lv_cnt >= LV_CNT_END then movlw LV_CNT_END ; w = LV_CNT_END subwf lv_cnt, 0 ; w = lv_cnt - LV_CNT_END btfss STATUS, C ; ボロー無しなら次の命令をスキップ goto lv_judge_end ; lv_cnt閾値未満なので動作継続 ; lv_cnt閾値以上 シャットダウン call shutdown ; 戻ってこないのでこれでOK lv_judge_end return ; ==================================================================== ; シャットダウン処理 shutdown ; HLD_PORT = 0 bcf pa_out, HLD_PORT movf pa_out, 0 ; w = pa_out movwf LATA ; LATA = w ; while(1) shutdown_wait clrwdt ; WDTリセットにならないようにクリア goto shutdown_wait ; 電源落ちるまで無限ループ ;------------------------------------------------------------------------- END