やりたい事2:消費電力量測定(続き)

その1では、消費電力の測定に挑んだのですが、結局売買電力しか測定できなかったので、対策方法を考えたいと思います

対策:

電力量(売) → 電力量(+)で測定

電力量(買) → 電力量(-)で測定

現状、Arduinoのスケッチは下記のようになっており、電力は絶対値(+値)でしか測定できないようになっています。

 //####################################################
 //電圧と電流のサンプリング値から消費電力(W)を計算
 //####################################################
 float calc_watt(int *a, int *v){
≪省略≫
     //----------------------------------------------
     //電流と電圧の波形が反転しているとマイナスWになるため、+に反転する
     //----------------------------------------------
     if(sum < 0){
       sum = sum * (-1);
     }
≪省略≫
     return((sum)/SAMPLE_CNT);
 }

ひとまずcalc_watt関数に手を入れようと考えましたが、この関数を修正すると、他にも都合が悪いところが色々と出てきたため、全体的にソース見直しをすることにしました。

修正のポイントは以下

1.電力量計算をloop()ではなく、Timer割り込み中に行う

2.loop()内では、HTTP POSTのみ実施する

HTTP POST処理にかかる時間は不定であり、この間に、電圧・電流のサンプリングが実行されると

・電力計算が行われない

・電力計算途中にサンプリングが開始し、電力計算結果が壊れる

といった問題がある

3.売買電力を±値で計測できるようにする

消費電力計測のため

4.サンプリング周期を250usから200usに変更する

ブレーカーで実際に計測した電流波形形状は、正弦波とほど遠い形状となっており、今のサンプリング周期で拾いきれない部分がありそうなので、サンプリング周期を短くすることにした(精度2割増し)

順に対応内容を説明します

1.電力量計算をloop()ではなく、Timer割り込み中に行う

ステート制御について、以下のように変更することにしました

各チャンネルのサンプリング後のWAITの間に、電力計算を行います。

ただ、割り込み1回で全サンプリング分を計算するには時間が足りないため、1回の割り込みで1サンプルの電力計算を行います。(STATE_CALCステート)

1サンプルの計算結果は、「g_sum_watt」に加算していきます

チャンネル1のソースは以下のようになります

     //----------------------------------------------
     //  CH1
     //----------------------------------------------
     case STATE_ANP1: //電流のサンプリング
       myArrayA[arrCnt] = analogRead(1);
       arrCnt++;
       if(arrCnt == SAMPLE_CNT){
         arrCnt = 0;
         state_smp = STATE_VLT1;
       }
       break;
     case STATE_VLT1: //電圧のサンプリング
       myArrayV[arrCnt] = analogRead(0);
       arrCnt++;
       if(arrCnt == SAMPLE_CNT){
         arrCnt = 0;
         state_smp = STATE_CALC1;
       }
       break;
     case STATE_CALC1: //サンプリング結果からワット数を計算する
       calc_watt_real(arrCnt); //ここでは1サンプル(arrCnt番目)のサンプリング結果の計算しg_sum_wattに加算する
       arrCnt++;
       if(arrCnt == SAMPLE_CNT){
         arrCnt = 0;
         state_smp = STATE_WAIT1;
         digitalWrite(8,LOW);//デバッグ用
       }
       break;
     case STATE_WAIT1: //しばらく待ち
       arrCnt++;
       if(arrCnt == WAIT_CNT){
         arrCnt = 0;
         g_sum_watt1+=((g_sum_watt)/SAMPLE_CNT);
         state_smp = STATE_ANP2;
       }
       break;
//####################################################
//電圧と電流のサンプリング値から消費電力(W)を計算
//####################################################
void calc_watt_real(register int cnt){
     register int a;
     register int v;
     
     //----------------------------------------------
     //サンプリングした電圧波形の「-」側を「+」波形で補完
     //----------------------------------------------
     if(cnt == 0) g_sum_watt = 0; //最初の1サンプルの時は、加算値を0にする
     if(cnt < SAMPLE_CNT){
       a = myArrayA[cnt];
       v = myArrayV[cnt];
       if(a == 0){
            if(cnt < HARF_CNT){
              a = -myArrayA[cnt + HARF_CNT];
            } else {
              a = -myArrayA[cnt - HARF_CNT];
            }
       }
       
       if(v == 0){
            if(cnt < HARF_CNT){
              v = -myArrayV[cnt + HARF_CNT];
            } else {
              v = -myArrayV[cnt - HARF_CNT];
            }
       }
       
       g_sum_watt += ((a) * 0.977) * (v * 141.4 / 950);
       
     }
}

2.loop()内では、HTTP POSTのみ実施する

割り込み関数内で加算した各チャンネルの電力はSTATE_WAITの最後にg_sum_watt1,2,3,4の変数に格納されます。

     case STATE_WAIT1: //しばらく待ち
       arrCnt++;
       if(arrCnt == WAIT_CNT){
         arrCnt = 0;
         g_sum_watt1+=((g_sum_watt)/SAMPLE_CNT);
         state_smp = STATE_ANP2;
       }
       break;

この1~4チャンネルの電力を、loop()に渡す必要がありますので、今回は「リングバッファ」と言うFIFOバッファを作成し、受け渡しをすることにしました。

使い方としては、割り込み関数内で、4チャンネル分の電力計算後、「watt_buf」配列の「watt_w_buf」番目に4チャンネル分の電力を書き込みます。

書き込み終わったら、書き込み位置「watt_w_buf」を+1します。

一方、loop()では、watt_r_buf(読み込み位置)と、watt_w_buf(書き込み位置)が一致しているかを常に確認し、一致していなければ書き込みがされていると判断して、読み込み位置「watt_r_buf」から1件読み込み、watt_r_bufを+1します。

//=====================================================
//リングバッファの定義
//=====================================================
struct WATT_REC {
  float sum_watt1;
  float sum_watt2;
  float sum_watt3;
  float sum_watt4;
};
#define WATT_BUF_CNT (5)          //リングバッファの件数
WATT_REC watt_buf[WATT_BUF_CNT];  //リングバッファ
WATT_REC *watt_pbuf;              //リングバッファPointer
int watt_r_pos;                   //リングバッファ読み込み位置
int watt_w_pos;                   //リングバッファ書き込み位置
//割り込み関数内のリングバッファ書き込み箇所
     case STATE_WAIT4: //しばらく待ち
       arrCnt++;
       if(arrCnt == (WAIT_CNT + WAIT_CNT_S)){
         arrCnt = 0;
         g_sum_watt4+=((g_sum_watt)/SAMPLE_CNT);
         state_smp = STATE_ANP1;
         
         //----------------------------------------------
         //WAIT_SEND_SEC_CNT満了したらリングバッファに書き込み
         //----------------------------------------------
         sec_cnt=sec_cnt+1;
         if(sec_cnt == WAIT_SEND_SEC_CNT){
           sec_cnt = 0;
           watt_buf[watt_w_pos].sum_watt1 = g_sum_watt1;
           watt_buf[watt_w_pos].sum_watt2 = g_sum_watt2;
           watt_buf[watt_w_pos].sum_watt3 = g_sum_watt3;
           watt_buf[watt_w_pos].sum_watt4 = g_sum_watt4;
           //リングバッファのWriteカウントを進める
           if(watt_w_pos == (WATT_BUF_CNT - 1)){
             watt_w_pos = 0;
           } else {
             watt_w_pos++;
           }
           g_sum_watt1 = 0;
           g_sum_watt2 = 0;
           g_sum_watt3 = 0;
           g_sum_watt4 = 0;
         }
       }
       break;
     }
//loop関数のリングバッファ読み込み箇所
//####################################################
//空き時間処理
//####################################################
void loop() {
  float w1;
  float w2;
  float w3;
  float w4;
  
  delay(1);
  
  //リングバッファに値が書き込まれている場合だけ実行
  if(watt_w_pos != watt_r_pos){
       w1 = watt_buf[watt_r_pos].sum_watt1;
       w2 = watt_buf[watt_r_pos].sum_watt2;
       w3 = watt_buf[watt_r_pos].sum_watt3;
       w4 = watt_buf[watt_r_pos].sum_watt4;
       if(watt_r_pos == (WATT_BUF_CNT - 1)){
         watt_r_pos = 0;
       } else {
         watt_r_pos++;
       }
       
       //電力は正の値しかありえないので絶対値を使用する
       if(w1 < 0) w1 = w1 * (-1);
       if(w2 < 0) w2 = w2 * (-1);
       if(w3 < 0) w3 = w3 * (-1);
       if(w4 < 0) w4 = w4 * (-1);
       
       //Postメッセージの作成
       sprintf(params,"watt_value1=%i&watt_value2=%i&reserve1=0&reserve2=0",
           (int)((w1 + w2) / WAIT_SEND_SEC_CNT),    //発電電力(watt_value1)    
           (int)((w3 + w4) / WAIT_SEND_SEC_CNT));   //消費電力(watt_value2) 
       //HTTPサーバーへ送信
       if(!postPage(serverName,serverPort,pageName,params)){
         Serial.print(F("POST Fail "));
       } else {
         Serial.print(F("POST Pass "));
       }
       Serial.println(totalCount,DEC);
     }
 }

3.売買電力を±値で計測できるようにする

loop()内にある電力の絶対値を求めている以下ソースを修正

//電力は正の値しかありえないので絶対値を使用する

       if(w1 < 0) w1 = w1 * (-1);
       if(w2 < 0) w2 = w2 * (-1);
       if(w3 < 0) w3 = w3 * (-1);
       if(w4 < 0) w4 = w4 * (-1);

↓↓↓↓↓↓↓修正↓↓↓↓↓↓↓

//発電電力は正の値しかありえないので絶対値を使用する

       if(w1 < 0) w1 = w1 * (-1);
       if(w2 < 0) w2 = w2 * (-1);

//売買電力は逆流(売電)負数も考慮に入れたいためどちらかの正負反転する必要がある

//CH3のみ電圧と逆位相であると仮定し、電流を正の値にする

       w3 = w3 * (-1);

チャンネル3は、電圧と電流を逆位相とし、チャンネル4は電圧と電流が同位相になっている前提で修正をします。

交流波形で表すと、以下のような関係になっていることを前提にしている事になります。

4.サンプリング周期を250usから200usに変更する

これは単純にdefine値を以下のように変更して対応しました

//#define SAMPLE_MICROSEC (250)    //サンプリング周期250us
//#define SAMPLE_CNT (67)          //サンプリング回数(60Hz AC1周期)
//#define WAIT_CNT   (799)         //1ch分の電圧/電流/計算をサンプリングした後の待ちカウント
//#define WAIT_CNT_S (0)           //1分あたりのカウント不足分の補正値
#define SAMPLE_MICROSEC (200)     //サンプリング周期200us
#define SAMPLE_CNT (83)           //サンプリング回数(60Hz AC1周期)
#define WAIT_CNT   (1001)         //1ch分の電圧電流をサンプリングした後の待ちカウント
#define WAIT_CNT_S (0)           //1分あたりのカウント不足分の補正値

以上でやりたい事は一通り実装できました

全ソースコードは、また別途公開したいと思います

やりたい事3:電力計測精度向上