おもちゃ、家電、もろもろの修理の足跡と備忘録

色々と忘れるので、趣味のメモ

電圧・電流・電力計

2021-01-17 08:59:24 | その他工作
 毎月楽しみにしているおもちゃの病院も、緊急事態宣言で1月2月と休診になってしまいました。また巣ごもりしないといけない状況ですね。
 そんなこともあり、前回の電子負荷装置に続いて、直流の電圧・電流・電力計をPICで作ってみました。忘れないうちにポイントを記載して備忘録にします。

[概要]
 最大16V/5Aを設計値として作成しました。1602のLCDの1行目は、電圧値(mVにしてあります)、後ろの3桁x2はデバッグ用のADC値(電圧、電流)です。
2行目は、電流値(mA)と電力値(mW)で、電力値は単に電圧値*電流値です。
いつものようにありあわせの部品で作りました。PICは16F88、OpeAMPはLM358、電圧校正にシャントレギュレータのTL431、LCDも手元にたくさんある1602を4bit modeで接続しています。すべて古い時代のものです。
基板はLCDの後ろ側に2段になるように配置しています(写真では見えません)。
 16F88のADCは10bit、上記の設計値をなんとか最大限の精度が出せるように、手持ちの部品からあちこち色々とパラメータを選択して作りましたので、へんてこな数値の部分が多々あります。わからなくなるので、メモ記載してあります。

[回路]
 回路図は、手書きで作ったものをKiCADで投入しました。まだ基板設計機能までは手つかずです。マニュアルもたいして読まずに入れてきましたが、なんとなく、投入方法がわかってきました。左手でキーボード、右手でマウス、というスタイルで入れるのが良いようです。
 左上がモニタ用の入力と出力端子、ここに設計値の16V/5Aが加わります。OpeAMPで上が電圧計測用、下が電流計測用で約11倍(10.7倍)にしてあります。電流計測は主回路のGND側に50mOhmの抵抗を入れて、その両端の電圧を計測しています。
 その下がシャントレギュレータTL431で、基準電圧2.495Vをちょっと上げて2.906VにしてPICのVref+に供給しています。16F88は、設定によってVref+に加えた電圧を上限にして10bitでADCをしてくれる機能がありますので、OpeAMP出力(16F88のADC入力)の最大値がこの値に近いほど精度が良くなると考えて、OpeAMPの増幅率(抵抗値の選択)などを設計してあります。
→LCDの1行目の右側の数値は、16F88がADCをした結果を表示しており、設計最大値で1024を越さない、近くなるようにしてあります。(最後の微調整用に表示したままなのですけれど、、)
Vref+=2.906V
Vmon_max=16V*10/(10+47)=2.807V (OpeAMP入力で電圧分圧)
Imon_max=5A*50m*(10+47+47+4.7)/10=2.717V (OpeAMPで増幅)

 LCDのVO(pin3)は、LCDのコントラストを制御するpinで、電源とGNDの間に可変抵抗を接続して調整してね、とマニュアル?には書いてあるのですが、前回作ったときに色々とやってみて、5V供給でこのくらいの数値だろう、と決め打ちで設定してあります。手元に複数枚あるLCDは同じメーカのものなので、いくつかやってみまて許容範囲でした。使用するデバイスによって大きなばらつきがあると思われますので、デバイスマニュアルの指示通りに調整が必要です。

[基板作成]
 基板は、これまた古いUniversal基板を切り出してLCDの裏側に重ねるように作成しています。LCDとは左側のPINで接続しています。14pin中使用している10pinのみハンダ付けしています。基板左から、LCD接続コネクタ、16F88、LM358、右の緑の上下の端子が主回路の入力、出力となります。
配線は、いつ買ったかも覚えていませんが、基板裏でポリウレタン銅線(UEW線)を使ってハンダ付けしました。上がわにPickit3のICEを接続しています。 
 LEDはRA4に接続して、動作中に点灯するようにソフトで制御しています。当初LEDはつけていなかったのですが、LCDに何も表示されない場合、PICが動いているのかどうなのかもわからなかったで、後付しました。動いてしまえば必要ないですね。


[ソフト]
 ソフトは、XC8@MPLAB X 作成しました。
 16F88はADCは1chしかないので、ソフト側で電圧と電流で切り替えて別々に計測します。最小電圧分解能は、上記のVref+=2.906Vを1024で割ったもの→2.837mVです。入力計測電圧が16V時にはほぼ、16/1024=15.6mVにもなってしまうので、複数回計測の平均値を取るようにしています。100回も取れば十分かな、と適当なことを考えて100回に設定してありますが、#define で設定してあるので、適当に。電圧、電流計測ともに100回にしてありますが、そんなに遅くもなく、プラプラmVのひとけた目が動く位なので、そのままにしてあります。(そもそもそんなに精度は必要ないのですけれど、、、)
 最後の調整は、抵抗や配線長、デバイスのばらつきなどで設計通りにはゆきませんので、既知の電圧と電流を加えて、演算係数Vcoef、Icoefを設定します。これらは#defineで設定します。Vcoefは、例えば12.00Vを入力に加えて、その時のADCの値を見ます。Vcoef=12.00/ADC値、として与えます。電流も同様に、既知の電圧、既知の抵抗を接続してADC値から求めます。ソフトではmV,mAで計算しているので、桁を間違えないように。
 あとは、電力ですが、mV,mAで計算しているので、計算値が64kを越してしまうので、面倒なのですべて long、にしてあります。
 sprintfで入れているのですが、表示できていたのですが、なにかの拍子に突然、2つ目の変数が表示されなくなってしまいました。よくわからないのですが、とりあえず動くように、分割して表示していますので、処理量は多くなってしまっています。(→v2.2で対応済(BUGでした)です。)
[残課題]
 これを作ると、じゃ、LOGが取りたい、と思うようになったので、途中で回路図もSerialOutができるように配線を変えました。ソフトの実装はしていないので、動きません。今後のお楽しみ、ということで。
(→これもこのあと対応しました。後日記事参照)

[最後に]
 前回作った「電子負荷装置」をつなげてみました。スムーズに電流値を増減させることができ、電子負荷装置の検証にもなりました。(写真は25.9Wにもなっていますが、短時間であれば大丈夫でした。)

2021/1/24追記:消費電力が32kを超えると負の値、になってしまっていました。sprintfで"%6ld"にしないとlong/64bitにならないようです。以下、修正済です。
あとは、sprintfなどを使うとプログラムエリアを食うようで、v1.5aで、使用率が90%になっています。RS-232CのようなSerial通信を入れようとすると多分溢れます、、、。

[Source Code]
/* 
 * File:   main.c
 * Author: tomoharic
 * 2021/1/23 V1.5a
 * 
 * 設計目標:16V,5A電源を計測
 * 16x02のLCDに測定電圧(mV)、電流(mA)、消費電力(mW)を表示
 * PIC16F88、TL431、LM358、LCD:SC1602コンパチ?を使用
 * 
 * ・TL431シャントレギュレータを用いて、2.906V(2.495Vから作成)を
 *  標準電圧とし、この2.906をPICでVref+として1024分割したもの用いる
 *  (電源電圧、電流、OpeAmpの増幅率などを勘案して決定した)
 * ・電圧計測: 電圧@16V で、47kohm+10kohmで分圧したものをLM358
 *  VoltageFollowerでPICのAN0に入力
 * ・電流計測: 50mOhmの両端電圧を測定電圧として電流を測定、LM358
 *  で11倍(10kOhm/100kOhm)に増幅してPICのAN1に入力する
 *
 * 環境:
 * MPLAB X IDE/5.35, XC8/2.20, C99, LCDlib2 
 * 
 * 調整、設定方法:→ソフト側で係数を微調整する
 * 1)電源を入れた際に、電圧、電流ともにゼロになるように、offsetを設定する
 * Line1の右側の4桁x2が、Volt,CurrentのAD変換後の数値:0-1023
 * 2)電圧:例えば、外部から外部計測した12.000Vを加えて、その時のAD変換後の数値XXXXを読む
 * →係数Vcoef=(12.000/XXXX)*1000, if XXXX=734 then 12/734*1000=16.3487=Vcoef
 * 3)電流:同様に、抵抗値のわかっているLoadを接続し、電流を計算、
 * →係数Icoef=(Current/YYYY)*1000, で係数を得、#define に両者を設定する
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "LCDlib2.h"

//--------------------------------------
// このアプリに必要な設定
//--------------------------------------
#define INITIAL_CREDIT1 "Power Mon v1.5a"
#define INITIAL_CREDIT2 "20210123a"

#define offset  4               //印加電圧=0V、電流=0の時に表示がゼロになるように設定
#define repeat_num  100         //繰り返し計測数。電圧、電流ともに効く
#define Vcoef   16.3487         //例えば、12Vを印加した時には、12.0/adconv()*1000で求めた乗算係数
                                //Over Allでの調整
#define Icoef   5.2215          //電圧と同様、Over Allでの調整係数

//--------------------------------------
// コンフィグレーションビットの指定
//--------------------------------------

// CONFIG1
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
//#pragma config FOSC = HS        // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (MCLR enabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB3      // CCP1 Pin Selection bit (CCP1 function on RB3)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// CONFIG2
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode disabled)

///////////delay関数を使うために発振周波数を定義 1Mhz////////////
#define _XTAL_FREQ 1000000
#define LED     RA4             //動作確認用のLED。初期ディスプレイ表示、変換動作中に点灯

//********************************************************************** 
/*
    <電圧・電流計>

 ■コンフィグ設定
  LVP_OFF
  MCLR_OFF
  WDT_OFF
  EXTCLK
 ■ピンアサイン (16F88)
  Pin-01 
  Pin-02 Vref 
  Pin-03 RA4/LED
  Pin-04 /MCLR to VDD(+5V) via 10kohm 
  Pin-05 Vss to GND
  Pin-06 RB0/LCD D4
  Pin-07 RB1/LCD D5
  Pin-08 RB2/LCD D6
  Pin-09 RB3/LCD D7
  Pin-10 RB4/LCD:E
  Pin-11 RB5/SerialOUT →未搭載、拡張予定
  Pin-12 PGC/LCD:RS
  Pin-13 PGD
  Pin-14 Vdd to +5V 
  Pin-15 
  Pin-16  
  Pin-17 AN0/Vvmon 
  Pin-18 AN1/Vimon
*/
//********************************************************************** 

// ADconv function 
unsigned int adconv()
{
    ADON = 1;           //AD変換
    __delay_us(20);
    GO_nDONE = 1;       //AD変換開始
    while(GO_nDONE);    //完了待ち
//    return ((ADRESH<<8)+ADRESL);        //右詰め
    return (ADRESH*256+ADRESL-offset);
}
//********************************************************************** 

void main()
{
//    static    unsigned    long    // 32bit 0...4294967295
//    static    unsigned    int     // 16bit 0...64k
  static    unsigned    long    Vmon, Imon, Power, ad, count, Vad, Iad; //mV,mAなのでlongが必要
    static  char    fmtbuf[16];
    
    OSCCON = 0b01000000;//内部周波数1MHz
    // アナログの設定
    ANSEL  = 0b00000011;    //AN0:Vvmon, AN1:Vimon
    // ポートの設定 1=input
    TRISA  = 0b00001011;    //RA0:AN0,RA1:AN1, RA2:opn, RA3:Vref RA4:LED
    TRISB  = 0b00000000;    //RB0-RB4:LCDout RB6+PGC RB7:PGD

//ADCON0
    //bit7:ADCS1
    //bit6:ADCS0
    //bit5:CHS2
    //bit4:CHS1
    //bit3:CHS0
    //bit2:GO_nDONE
    //bit1:not used
    //bit0:ADON
    
    ADCS2 =0;               //ADCON1のbitです
    ADCS1 =0;
    ADCS0 =0;               //000->ACDのclock:1.6usec

    CHS2 = 0;
    CHS1 = 0;
    CHS0 = 0;               //000->AN0,001->AN1

 //ADCON1
    //bit7:ADFM
    //bit6:ADCS2
    //bit5:Vref+
    //bit4:Vref-
    //bit3-0:not used
    ADFM = 1;               //ADC結果の格納方法、右詰め
    //ADCS2はADCON0で設定
    VCFG0 =0;               //ADCを行うポートの基準電圧をVref+に設定
    VCFG1 =1;               //Vref- ->Vss

    LED = 0;
    
// LCD初期化
    lcd_init();
    
// LCD画面クリア
    lcd_cls();
    LED = 1;
// LCDにstart-up messageを出力
    lcd_locate( 0, 0 );
//    lcd_puts( fmtbuf );
    lcd_puts( INITIAL_CREDIT1 );
    lcd_locate( 1, 0 );
    lcd_puts( INITIAL_CREDIT2 );

    __delay_ms(2000);
    LED = 0;
    lcd_cls();
    __delay_ms(2000);
//
    while (1) {
      LED = 1;                        //動作中表示の点灯

// Voltage display...
    // Vmon計測@AN0
      CHS0 = 0;                       //select AN0
    //  TL431シャントレギュレータを使ってVref+に2.906Vを供給
    //  最小分解能:2.906/1024=2.837mV、これ以上にする場合は複数回で平均を取る
    //  入力電圧を分圧してVref+(2.906Vを設定済)以下にしてAN0に入力する
    //  max入力電圧を16Vとして
    //  10k+47kohm(→16*10/(10+47)=2.807V)を設定 max:16.5V
    //    Vmon = (2.906/1024)*adconv()*(47+10)/10;
    //    Vmon = 0.016176*adconv();      //単位は"V"
    // 計算上は上記だが、Over Allでの誤差があるので、表示とadconv()の数値でVcoef,Icoefを算出する

        ad = 0;
        for (count=repeat_num;count>0;count--)
            ad = ad + adconv();
        Vad = ad / repeat_num ;
        Vmon = Vcoef/repeat_num * (float) ad  ;
        //表示係数をかけたものの整数部分 単位は"mV"
        if (Vmon <= 1) Vmon = 0;
        sprintf(fmtbuf, "%6umV", Vmon);
        lcd_locate( 0, 0 );
        lcd_puts( fmtbuf );

// Current display...
        CHS0 = 1;                       //select AN1 for Current...
    //  非反転増幅回路による電流計測
    //  TL431シャントレギュレータを使ってVref+に2.906Vを供給
    //  最小分解能:2.906/1024=2.837mV、これ以上にする場合は複数回で平均を取る
    //    Imon = {R1/(R1+R2)}*adconv()*{(2.906/1024)}*{1000/50ohm};
    //    Imon = {R1/(R1+R2)}*adconv()*0.056757812
    //    @ R1=10kohm, R2=47+47+4.7kohm=108.7kohm(100kohmがなかったので、、)
    //    Imon = 5.2215098896*adconv();  //mA maximum:5.2215*1024=5346mA
    // 最終的には実際の数値を求めて、Icoefの割り戻しする。1Aで234とか。
        ad = 0;
        for (count=repeat_num;count>0;count--)
            ad += adconv();
        Iad = ad / repeat_num;
        Imon = Icoef/repeat_num * (float) ad ;    //単位は"mA"
        if (Imon <= 1) Imon = 0;
        
        sprintf(fmtbuf, "%6dmA%6d", Imon, ad );
        sprintf(fmtbuf, "%6dmA", Imon );
        lcd_locate( 1, 0 );
        lcd_puts( fmtbuf );
        
// Power display...
        Power = (float) Vmon * (float) Imon / 1000 ;  //単位は"mW"

        sprintf(fmtbuf, "%6ldmW", Power );
        lcd_locate( 1, 8 );
        lcd_puts( fmtbuf );

// for debugging...  show the AD converted value in 0..1023        
        sprintf(fmtbuf, "%4u", Vad);
        lcd_locate( 0, 8 );
        lcd_puts( fmtbuf );

        sprintf(fmtbuf, "%4u", Iad);
        lcd_locate( 0, 12 );
        lcd_puts( fmtbuf );
        
//  LCD:16x02 display map
//        0123456789012345
// Line1: 123456mVXXXXYYYY XXXX:Voltage ADconved, YYYY:Current ADconved.
// Line2: 123456mA1234xxmW 
        LED = 0;

        }
}
//**********************************************************************