自作サブバッテリーモニター

前記事で触れたN-VANで使っている自作サブバッテリーモニターです。SBC-004とSMF31MS-850の間に入れています。機能は以下のとおり。

・サブバッテリー電圧、充電電流表示
・長時間タイマー付サブバッテリー電源供給回路
インバーターON/OFF用リレーコントロール(30分で自動OFF)

サブバッテリーモニター外観

・起動するとサブバッテリーの電圧と充電電流を表示します。
・長時間タイマー付サブバッテリー電源供給回路はサブバッテリーからPower MOSFET経由で出力しています。ACC ONで自動ON、サブバッテリーの過放電を防ぐためACC OFF後24時間で自動OFFします。残り時間は液晶右下に表示。ENGELのポータブル冷蔵庫、ルームランプ、車載USB電源などに使っています。電源供給中は液晶のバックライトがONになり、LEDが点滅します。
・中央のプッシュスイッチで12V→100Vインバーター制御用のリレーをON/OFFします。サブバッテリーの過放電を防ぐためON後30分で自動OFFします。残り分数は液晶右上に表示。インバーターにコントロール端子が無い場合はリレー出力をインバーターの電源スイッチと並列にはんだ付けします。リレーON時は液晶のバックライトがONになり、OFFタイマー付電源供給回路もその後24時間ONになり、液晶下のLEDが点灯します。

回路図は以下のとおり。使っている液晶パネルは秋月のAQM0802A-FLW-GBWです。

サブバッテリーモニター回路図

サブバッテリーモニター回路図

制御プログラムは以下のとおり(少し大きい)。消費電力を抑えるためにClockは1MHzで動作。初期化後は125msおきのタイマー割り込みで電源OFFタイマーを減算。フォアグラウンド側で電圧・電流測定、タイマー値を見て電源・リレーON/OFF、ACC電圧とプッシュスイッチON/OFFの確認などを行っています。

#include 

#pragma config FCMEN=OFF
#pragma	config IESO=OFF
#pragma	config CPD=OFF
#pragma	config CP=OFF
#pragma	config BOREN=ON
#pragma	config MCLRE=OFF
#pragma	config PWRTE=OFF
#pragma config LVP=OFF  //
#pragma config WDTE=OFF
#pragma config FOSC=INTOSCIO

#define _XTAL_FREQ 1000000
#define TMR1H_VAL 0x85      // 65536-31250=34286=0x85ee
#define TMR1L_VAL 0xee      // 31250 * 4us = 125ms period
#define RELAY_TIME 30        // relay on time 30min.
#define EXT_POW_TIME 24       // ext. power on time 24H.
#define RELAY RA7
#define ACC RA6
#define PUSH_SW RA5
#define EXT_POW RA1
#define LED_R RB5
#define SCL RB4
#define LED_BL RB2
#define SDA RB1
#define PANEL_POW RB0
#define TRIS_SDA TRISB1
#define LCD_ADDR 0x7c
#define ON 1
#define OFF 0

unsigned char t_rm=0, t_rs=0;   // relay-on counter t_rm:minute, t_rs:second
unsigned char t_ph=0;           // external power-on counter
unsigned int t_ps=0;
unsigned char timer_flag=0, disp_refresh=0;

void pic_init(void)
{
    OSCCON = 0x40;		// INTOSC 1MHz
    ANSEL = 0x09;		// RA3:VSB, RA0:CSNS
    PORTA = 0x00;
    PORTB = 0x12;		// SCL=1, SDA=1
    TRISA = 0x79;		// RA7:RELAY, RA6:ACC, RA5:PUSH-SW, RA4:
                        // RA3:VSB, RA2:, RA1:Ext_Pow, RA0:CSNS
    TRISB = 0x00;		// RB5:LED_R, RB4:SCL, RB1:SDA, RB0:PANAL_POW
    OPTION_REG = 0xff;  // PORT B weak pull up disable

                                // adc setup
    ADCON0 = 0b00000001;	// clock=fosc/2, a/d enable
    ADCON1 = 0b10000000;	// right justified, Vdd/Vss ref

                                // timer1 setup
    T1CON = 0b00000100;		// T1CKPS:1/1 T1OSCEN:F *T1SYNC:F TMR1CS:INT TMR1ON:F
				// timer1 clock freq. is 1MHz/4/1=250KHz
    PEIE = ON;			// all peripheral interrupts enable
    GIE = ON;			// global interrupt enable
    TMR1H = TMR1H_VAL;		// set timer1 interrupt period
    TMR1L = TMR1L_VAL;
    TMR1IE = ON;			// timer1 interrupt enable

    TMR1ON = ON;				// timer1 start
    return;
}

// read adc 'sample' times, return value: 0x0000-0xffff
unsigned int adc_read( unsigned char ch, unsigned char sample )
{
	unsigned int value;	// adc value
	unsigned char i;

	ADCON0 = (unsigned char)( (ch<<3) | 0b00000001);	// right justified, VDD ref, ADON

	// measure 'sample' times for noise reduction
	for( i = 0, value = 0; i < sample; i++ ){
		GO_nDONE = 1;		// ADC start
		while( GO_nDONE )
			continue;
		value += ADRESH*256+ADRESL;
	}
	return value;
}

void i2c_start(){
	SDA = 1;
	SCL = 1;
	SDA = 0;
	SCL = 0;
    return;
}

unsigned char i2c_write(unsigned char d){
	unsigned char i, ack;

 	for (i=0; i<8; i++){
        SDA = (d & 0x80)? 1: 0;
 		SCL = 1;    // send 1bit
 		SCL = 0;
        d <<= 1;    // next bit
	}
	TRIS_SDA = 1;   // SDA input mode
	SCL = 1;
	ack = SDA;
	SCL = 0;
	TRIS_SDA = 0;	// SDA output mode
	return(ack);
}

void i2c_stop(){
	SDA = 0;
	SCL = 1;
    SDA = 1;
    return;
}

void lcd_cmd(unsigned char cmd){
    i2c_start();
    i2c_write(LCD_ADDR);
    i2c_write(0x00);
    i2c_write(cmd);
    i2c_stop();
    __delay_us(30);
    return;
}

void lcd_putc(unsigned char data){
    i2c_start();
    i2c_write(LCD_ADDR);
    i2c_write(0x40);
    i2c_write(data);
    i2c_stop();
    __delay_us(30);
    return;
}

void lcd_puts(unsigned char pos, unsigned char* s){
    lcd_cmd( pos );
    while(*s){
        lcd_putc(*s++);
    }
    return;
}

void lcd_init(){
    TMR1IE = OFF;			// timer1 interrupt disable
    TMR1ON = OFF;			// timer1 stop
    PANEL_POW = 1;      // LCD power on
    __delay_ms(40);
    lcd_cmd(0x38);
    lcd_cmd(0x39);
    lcd_cmd(0x14);
    lcd_cmd(0x70);
    lcd_cmd(0x52);
    lcd_cmd(0x6c);
    __delay_ms(200);
    lcd_cmd(0x38);
    lcd_cmd(0x0c);
    lcd_puts(0x80, (unsigned char*)"SUB BATT");
    lcd_puts(0xc0, (unsigned char*)" MONITOR");
    __delay_ms(1000);
    lcd_cmd(0x01);
    __delay_ms(2);
    lcd_puts(0x84, (unsigned char*)"V");
    lcd_puts(0xc4, (unsigned char*)"A");
    TMR1ON = ON;	// timer1 start
    TMR1IE = ON;			// timer1 interrupt enable
    return;
}

void bin2str4(unsigned int val, unsigned char *b){
    union{
        unsigned long int bd32;
        struct{
            unsigned bd8l : 8;
            unsigned bd8h : 8;
            unsigned bcd0 : 4;
            unsigned bcd1 : 4;
            unsigned bcd2 : 4;
            unsigned bcd3 : 4;
        }bcd;
    }work;
    char i;

    work.bd32 = (unsigned long int)val;
    for(i = 0; i < 16; i++){
        if(work.bcd.bcd0 >= 5)
            work.bcd.bcd0 += 3;
        if(work.bcd.bcd1 >= 5)
            work.bcd.bcd1 += 3;
        if(work.bcd.bcd2 >= 5)
            work.bcd.bcd2 += 3;
        if(work.bcd.bcd3 >= 5)
            work.bcd.bcd3 += 3;
        work.bd32 <<= 1;
    }
    b[0] = (work.bcd.bcd2)? work.bcd.bcd2 + '0': ' ';
    b[1] = work.bcd.bcd1 + '0';
    b[2] = '.';
    b[3] = work.bcd.bcd0 + '0';
    b[4] = 0x00;
    return;
}

void bin2str2(unsigned char val, unsigned char *b){
    union{
        unsigned short int bd16;
        struct{
            unsigned bd8 : 8;
            unsigned bcd0 : 4;
            unsigned bcd1 : 4;
        }bcd;
    }work;
    char i;

    work.bd16 = (unsigned short int)val;
    for(i = 0; i < 8; i++){
        if(work.bcd.bcd0 >= 5)
            work.bcd.bcd0 += 3;
        if(work.bcd.bcd1 >= 5)
            work.bcd.bcd1 += 3;
        work.bd16 <<= 1;
    }
    b[0] = (work.bcd.bcd1)? work.bcd.bcd1 + '0': ' ';
    b[1] = work.bcd.bcd0 + '0';
    b[2] = 0x00;
    return;
}

void __interrupt() timer1_overflow(void)
{
    static unsigned char freerun=0;
    
    TMR1ON = OFF;			// stop timer1
    TMR1IF = OFF;           // clear timer1 interrupt flag
    TMR1H = TMR1H_VAL;      // reset timer1
    TMR1L = TMR1L_VAL;
    TMR1ON = ON;			// restart timer1
    timer_flag = 1;
    freerun++;
    if(!(freerun & 0x07)){        // once per 8 interrupts
        disp_refresh = 1;
        if(t_rm){
            t_rs++;         // count up relay timer
            if(t_rs >= 60){
                t_rs = 0;
                t_rm--;
            }
        }
        if(t_ph){
            t_ps++;         // count up ext.pow timer
            if(t_ps >= 3600){
                t_ps = 0;
                t_ph--;
            }
        }
    }
    return;
}

void main(void) {
    unsigned char state;
    unsigned char s_flag=OFF, s_ctr=16;
    unsigned int batt, csns; // sub battery voltage and charge current
    unsigned char disp[5];  // LCD display buffer

    pic_init();
    lcd_init();

    while(1){
        while(!timer_flag)
            ;
        timer_flag = 0;
        if(disp_refresh){
            disp_refresh = 0;
            batt = (adc_read(3, 72)+0x80)>>8;   //voltage:175mV/V
            bin2str4(batt, disp);
            lcd_puts(0x80, disp);
            csns = adc_read(0, 63)>>6;  //csns:50mV/A
            bin2str4(csns, disp);
            lcd_puts(0xc0, disp);
            bin2str2(t_rm, disp);   // relay on timer
            lcd_puts(0x86, disp);
            bin2str2(t_ph, disp);   // ext.power on timer
            lcd_puts(0xc6, disp);
            if(t_rm){
                RELAY = ON;          // relay power on
                LED_R = ON;
            }else{
                RELAY = OFF;          // relay power off
                LED_R = OFF;
            }
            if(t_ph){
                EXT_POW = ON;   // ext.power on
                LED_BL = ON;    // backlight on
                if(!t_rm){
                    LED_R = ON;           // LED on
                    __delay_ms(10);
                    LED_R = OFF;          // LED off
                }
            }else{
                EXT_POW = OFF;  // ext.power off
                LED_BL = OFF;   // backlight off
            }
        }
        if(!PUSH_SW){                   // switch pushed
            if(s_flag&&s_ctr){          // pushed continuously
                s_ctr--;
            }
            s_flag=ON;
        }else{
            if(s_flag){                 // switch pushed->released
                if(s_ctr){
                    // pushed less than 2 sec.
                    if(!t_rm){  // relay is off?
                        t_rm = RELAY_TIME;       // turn on relay 30min.
                        t_ph = EXT_POW_TIME;     // turn on ext.pow 24h
                    }else{
                        t_rm = 0;               // turn off relay
                    }
                    t_rs = t_ps = 0;
                }else{
                    // pushed more than 2 sec.
                    t_rm = t_rs = t_ph = t_ps = 0; // clear relay & ext_pow. timer
                    lcd_init();         // initialize LCD
                    __delay_ms(100);    // prevent sw. chattering
                }
            }
            s_flag=OFF;
            s_ctr=16;                   // 16*125ms=2sec
        }
        if(ACC){
            t_ph = EXT_POW_TIME;       // hold ext.power 24h after ACC-OFF
            t_ps = 0;
        }
    }

    return;
}