PICでUSB電圧・電流モニター
USBの電圧・電流モニターは1000円以下で買えますが、PICで自作してみました。
通常はLDOレギュレーターで5Vから3.3Vを作りPICを動作させるのですが、余っていたPIC16F88を使用したため4V以下では動作保証されません。このため可変電圧LDOレギュレーターで4Vを作っています。今回はLEDを点灯させているので、3.3Vよりは4Vの方が使いやすいです。
ZXCT1021はハイサイド電流検出専用ICで、電流センス抵抗両端電圧差の10倍の電圧が出力され(0〜1.5V)また電源電圧の絶対最大定格は20Vです。今回は20mΩの電流検出抵抗器を使ったため、これらの制約により測定範囲は4.5〜20V、0〜7.5Aとなり、USB Quick Charge2.0/3.0のClass A(最大12V)が測定可能です。USB以外のコネクターを使えば他のDC電圧・電流簡易測定にも使えると思います。表示更新周期が1秒なので、A/D入力にはノイズ取り用に大きな抵抗とコンデンサーを入れています。
やっている事が電圧を2つADコンバーターで取り込んでLEDに出力しているだけで、あまり面白くないためフォトカプラーを追加して絶縁型シリアルデーター出力を追加しました。フォーマットは9600/8/1/Nで、 000:00:10,08.93,0.413 この形式で時分秒、電圧、電流がコンマ区切りで出力されます。この出力はシリアル⇔USB変換LSI Prolific PL-2303HXに接続してUSB経由でPCへ接続しているため正論理の出力にしていますが、フォトカプラーの出力で反転させれば直接PCのRS-232Cへ接続しても短いケーブル長なら読み取れると思います。この回路の消費電力は実測15mA、LEDを消灯した場合は4mAでした。アナログ入力ポートがまだ余っているので、モバイルバッテリーを接続して絶縁型簡易電圧測定にも使えるかもしれません。
フォアグラウンドプログラム main() はI/Oや変数の初期化を行った後Timer1割り込みを許可し、グローバル変数に値がセットされるまで待ち続けます。
2.36msごとのTimer1割り込みでバックグラウンドの割り込み処理プログラムが走り、まず6個の7セグメントLEDのうち1つに順番にデーターを表示させます。次にシリアルポート出力が未完了の場合は出力します。その後電圧・電流をローカル変数に加算します。電圧・電流サンプリングが#define SAMPLINGで規定された回数になったら電圧・電流の加算値をグローバル変数に格納してローカル変数をクリアします。
フォアグラウンドプログラムは、グローバル変数に値がセットされたら表示桁を合わせてからBCD変換してLED表示バッファーに格納し、シリアルポート出力文字列をセットし、処理終了後にグローバル変数をクリアして再度セットされるまで待ちます。これを1秒周期で繰り返す事になります。
調整点は2つで、まず抵抗値のばらつきにより測定値に若干の誤差が出るため、テスターで測定したUSB電源電圧とLED表示が「ほぼ」合うように#define SAMPLINGの値を増減させます。この回路で抵抗の誤差が全く無い場合はVDD=4.064Vで電圧の分圧比が10/101=0.099なので4.064÷0.099✕10(表示する小数点の位置を調整)でSAMPLING=410が理論値になるのですが、今回の製作例では424になりました。次にこのサンプリング数の取り込みが終了し、出力処理が終了した時にちょうど1秒経過するようにタイマーT1の周期を#define TMR1H_VAL/TMR1L_VALの値で設定します。今回はSAMPLINGが424回+結果の出力1回なので割り込み周期は1÷(424+1)=2.353msになります。これで1秒おきに電圧・電流値がシリアルポートへ出力されます。
#include#include #include //__CONFIG( UNPROTECT & BOREN & OSC_8MHZ & MCLRDIS & PWRTEN & WDTDIS & INTIO ); #pragma config FCMEN=OFF #pragma config IESO=OFF #pragma config CPD=OFF #pragma config CP=OFF #pragma config BOREN=OFF #pragma config MCLRE=OFF #pragma config PWRTE=OFF #pragma config LVP=OFF // #pragma config WDTE=OFF #pragma config FOSC=INTOSCIO #define _XTAL_FREQ 8000000 #define TMR1H_VAL 0xf6 // 65536-2353=0xf6cf #define TMR1L_VAL 0xcf // 2353 * 1us = 2.353ms period #define ADC_V 2 // Vsense ADC ch. #define ADC_C 3 // Csense ADC ch. #define SAMPLING 424; // no. of sampling unsigned char h, m, s; unsigned char disp[6]; // display line buffer unsigned char ser_out[] = "\n\r000.0,00.00,00:00:000\0"; // serial out buffer, reverse order unsigned char ser_ctr; unsigned int r_voltage; unsigned int r_current; void init(void) { OSCCON = 0x70; // INTOSC 8MHz ANSEL = 0x0c; // RA3:Current(20A@full scale), RA2:Voltage(40V@full scale) TRISA = 0x6d; // RA7:LED_data bit2 RA4:dot pos @Voltage 1:left dig. 0:mid dig. RA1:debug TRISB = 0x00; // RB7,6,4:LED_select bit2,1,0 RB3,1,0:LED_data RB5:TX PORTA = 0x00; PORTB = 0x00; OPTION_REG = 0xff; // PORT B weak pull up disable // adc setup ADCON0 = 0b00100001; // clock=fosc/16, a/d enable ADCON1 = 0b01000000; // left justified, Vdd/Vss ref // timer1 setup T1CON = 0b00010100; // T1CKPS:1/2 T1OSCEN:F *T1SYNC:F TMR1CS:INT TMR1ON:F // timer1 clock freq. is 8MHz/4/2=1MHz PEIE = 1; // all peripheral interrupts are enabled GIE = 1; // global interrupt enabled TMR1H = TMR1H_VAL; // set timer1 interrupt period TMR1L = TMR1L_VAL; TMR1IE = 1; // timer1 interrupt enable //serial port setup TXSTA = 0b00100000; // tx9:0, txen:1, sync:0, brgh:0 RCSTA = 0b10000000; // spen:1, rx9:0, cren:0 SPBRG = 12; // 9600bps h = m = s = 0; ser_ctr = 0; r_voltage = 0; r_current = 0; TMR1ON = 1; // timer1 start } // read adc unsigned int adc_read( unsigned char ch ) { unsigned int value; // adc value unsigned char i; RA1 = 1; ADCON0 = (ADCON0&0xc7)|(ch<<3); __delay_us(16); // Tacq for(i=0, value=0; i<32; i++){ GO_nDONE = 1; // ADC start while( GO_nDONE ) continue; value += ADRESH; } RA1 = 0; return value>>5; } interrupt void timer1_overflow(void) { static unsigned int voltage = 0; static unsigned int current = 0; static unsigned char disp_ctr = 0; static unsigned int meas_ctr = SAMPLING; // mesurement counter TMR1ON = 0; // stop timer1 TMR1IF = 0; // clear timer1 interrupt flag TMR1H = TMR1H_VAL; // reset timer1 TMR1L = TMR1L_VAL; TMR1ON = 1; // start timer1 PORTB = disp[disp_ctr]; // LED display RA7 = (disp[disp_ctr++]&0x04)?1:0; if(disp_ctr == 6) disp_ctr = 0; if(ser_ctr){ // serial port output ser_ctr--; TXREG = ser_out[ser_ctr]; } if(meas_ctr){ if(meas_ctr&0x01){ voltage += adc_read(ADC_V); }else{ current += adc_read(ADC_C); } meas_ctr--; }else{ // measurement complete, copy data if(s!=59){ // timestamp s++; }else{ s = 0; if(m!=59){ m++; }else{ m=0; h++; } } r_voltage = voltage; r_current = current; voltage = 0; current = 0; meas_ctr = SAMPLING; } return; } void bin2bcd(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.bcd0; b[1] = work.bcd.bcd1; b[2] = work.bcd.bcd2; b[3] = work.bcd.bcd3; return; } int main(int argc, char** argv) { unsigned char bcd[4]; init(); while(1){ while(!r_voltage){ // wait until measurement complete } bin2bcd(s, bcd); // second ser_out[14] = bcd[0] + '0'; ser_out[15] = bcd[1] + '0'; bin2bcd(m, bcd); // minutes ser_out[17] = bcd[0] + '0'; ser_out[18] = bcd[1] + '0'; bin2bcd(h, bcd); // hour ser_out[20] = bcd[0] + '0'; ser_out[21] = bcd[1] + '0'; ser_out[22] = bcd[2] + '0'; // calculate Current, r_current / 128 * 100 bin2bcd((r_current>>2)+(r_current>>3)+(r_current>>6), bcd); ser_out[2] = bcd[0] + '0'; ser_out[3] = bcd[1] + '0'; ser_out[4] = bcd[2] + '0'; ser_out[6] = bcd[3] + '0'; disp[0] = bcd[1]; disp[1] = 0x10 | bcd[2]; disp[2] = 0x40 | bcd[3]; // caluculate Voltage, r_voltage / 128 * 10 bin2bcd((r_voltage>>4)+(r_voltage>>6), bcd); ser_out[8] = bcd[0] + '0'; ser_out[9] = bcd[1] + '0'; ser_out[11] = bcd[2] + '0'; ser_out[12] = bcd[3] + '0'; if(bcd[3]){ RA4 = 0; // Mid LED dot on disp[3] = 0x80 | bcd[1]; disp[4] = 0x90 | bcd[2]; disp[5] = 0xc0 | bcd[3]; }else{ RA4 = 1; // left LED dot on disp[3] = 0x80 | bcd[0]; disp[4] = 0x90 | bcd[1]; disp[5] = 0xc0 | bcd[2]; } ser_ctr = 23; r_current = r_voltage = 0; } return (EXIT_SUCCESS); }
BCD変換は以下のサイトのプログラムを参考にさせて頂きました。
プログラミングな日々: PICマイコンでBCD変換
また私はProlific PL-2303HXというチップを使ったシリアル→USB変換アダプターを使っているのですが、Windows10ではドライバーが提供されていないため以下のサイト経由でパッチ当てドライバーを入手してインストールしています。
Windows10でPL2303を無理やり動かす | なんでも独り言
※7セグメントLEDを6個並べると配線工数のほとんどがLEDですね。次はI2C接続の液晶使おうと思います。