今週も引き続きArduino Megaを使った改造の話である。ハードウェアの完成後、まずはList 1の様なSketchを流してみた。何をやっているかというと、とりあえずStatic Bufferをチャネルあたり50個用意し、これに連続的にanalogRead()でサンプリング結果を格納する、というものである。で50個サンプリングが終わったら結果をまとめてシリアルポートに出力し、1秒待機してから次のサンプリングに掛かるというものだ。ワットメーターの側はAC100Vにのみ繋ぐ(ワットメーターの先には何もつけない)状態だと、電圧は±141.4Vの範囲の正弦波となり、一方電流は0なので一定値となる。とりあえずこれが正しく取れれば、回路は正常だろう、という確認のためのSketchである。

さて、List 1では"// delayMicroseconds(1);"となっているが、当初はここを"delay(1)"として実行した(Photo01)結果がグラフ1である。確かに正常に波形は取れているのだが、電圧の側を見ると判る通り50回のサンプルで4.5周期といったところで、1周期あたりのサンプル数は11回ちょい。筆者の地域は50Hzだから、1周期が20msecになるわけで、サンプリング間隔は2ms弱である。これはさすがにちょっと間隔が長すぎる。

Photo01: Autoscrollをとめた状態で表示し、1回分をコピーしてエディタにペーストし、":"をTabコードに変換してExcelに貼り付けるという、比較的プリミティブな方法で確認を行っている

そこでウェイトを100μs、つまり"delayMicrosecond(100)"にしてみたのがグラフ2である。サンプル頻度がもっと増えたことで、2周期半ほどになっている。つまり1周期のサンプル数は20回ほど。サンプリングル間隔は1msまで短縮された。性能的には大分改善である。

この調子でどこまでいけるか? ということでウェイトを10μsにしたのがグラフ3で、こちらだと2.2周期程度、1周期あたりのサンプル数は23回弱、サンプリング間隔は900μs弱になった。ウェイトを1μsにしたのがグラフ4で、50サンプルが2.1周期程度。1周期あたりのサンプル数は23回強、サンプリング間隔は800μs強と、非常にわずかな改善である。で、もうサンプリング間隔と比較してウェイトは猛烈に短いわけで、こうなるとウェイトを呼ぶ意味があるのか? ということで(List 1の様に)ウェイトを外したのがグラフ5であるが、もう殆どグラフ4と一緒である。

これはどういうことか? というと、analogRead()の処理のオーバーヘッドが大きすぎて、サンプル頻度を上げられなくなっているという話だ。勿論理論上はSRAMに格納するオーバーヘッドが大きい、という可能性はあるが通常SRAMへの書き込みは1サイクルで終了するし、仮にSRAMの書き込みがオーバーヘッドだったとしても他に回避方法はないから、まぁanalogRead()のオーバーヘッドが大きすぎ、と判断しても差し支えないだろう。

そこでちょっと条件を変えてみたのがグラフ6~8だ。今度はウェイトを"delayMicrosecond(100)"にした上で、読み込むワットメーターの台数を1~3台に減らしたものだ。1台の場合、グラフ6の様に0.5周期を30サンプルほどだから、1周期あたり60サンプル、サンプリング間隔は330μsecほどになる。これが2台になると、グラフ7の様に1周期が36サンプルほどに減る。サンプリング間隔は560μsecほどだ。3台だと26サンプル、周期は770μsecほどに伸びる感じだ。ちなみにこの3台の時点でウェイトを省くと、サンプル数が1周期あたり30サンプルに改善するが、せいぜいそんなあたりである。

要するにArduinoを使う限り、4台は多すぎるという話である。もっともこれは、Arduinoというよりも搭載するAVR MCUの制限という部分が大きい。AVRはサンプリング頻度が最大10KHz(毎秒1万回)に制限されており、グラフ5(4台でディレイなし)のケースだと23×50×8=9200回で、かなり限界に近い状態にあることがわかる。

ま、これは後知恵という話であって作っている最中はここまで遅くなるとは思わなかった。最終的にはList 2の様なSketchを作ってこれをArduino MEGAにロードして持ち込んでいる。とりあえずウェイトは省き、ワットメーター4台8ch分のデータをまとめて読み込んで処理する形だ。基本的には402回で紹介した、1台分のワットメーター向けSketchの中で、データ取り込み部分を2chから8chに増やしたというだけのものだ。ただ402回のSketchは、電圧側の正負の反転をチェックして取り込みタイミングを決めている。これを4ch分に拡張するのはさすがに骨であるため、今回は1台目のワットメーターの電圧反転で4台分を全部取り込むようにした。手抜きといえば手抜きなのだが、今回のケースでは4台のワットメーターに同じコンセントから供給する(Photo02)ので、ここで位相差が出ることは殆ど考慮しなくて良いだろう、という判断だ。

Photo02: ELECOMの「ACアダプタを4個つなげるケーブル」T-AD4WH。Amazonでは1000円しなかった。ちなみに当初は同じELECOMの「雷ガード付ACアダプタ対応スリムタップ」ならば、4つのワットメーターがすっきり装着できそうに思えたのだが、実際にかってみたら「あと1mmワットメーターの幅が狭ければなんとかなったのに」状態で、がんばっても2台までしか並べて装着できなかった。サンダーでワットメーターのケースを削ることも考えたのだが、それほどケースの厚みがないので断念

(続く)

List 1:

#define  VOL1PIN  0
#define  AMP1PIN  1
#define  VOL2PIN  2
#define  AMP2PIN  3
#define  VOL3PIN  4
#define  AMP3PIN  5
#define  VOL4PIN  6
#define  AMP4PIN  7
#define  BUFSIZE  50
int  vol0[BUFSIZE], amp0[BUFSIZE];
int  vol1[BUFSIZE], amp1[BUFSIZE];
int  vol2[BUFSIZE], amp2[BUFSIZE];
int  vol3[BUFSIZE], amp3[BUFSIZE];

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int lpCnt;
  for(lpCnt=0;lpCnt < BUFSIZE;lpCnt++)
  {
    vol0[lpCnt] = analogRead(VOL1PIN);
    amp0[lpCnt] = analogRead(AMP1PIN);
    vol1[lpCnt] = analogRead(VOL2PIN);
    amp1[lpCnt] = analogRead(AMP2PIN);
    vol2[lpCnt] = analogRead(VOL3PIN);
    amp2[lpCnt] = analogRead(AMP3PIN);
    vol3[lpCnt] = analogRead(VOL4PIN);
    amp3[lpCnt] = analogRead(AMP4PIN);
    // delayMicroseconds(1);
  }

  for(lpCnt=0;lpCnt < BUFSIZE;lpCnt++)
  {
    Serial.print(lpCnt);
    Serial.print(":");
    Serial.print(vol0[lpCnt]);
    Serial.print(":");
    Serial.print(amp0[lpCnt]);
    Serial.print(":");
    Serial.print(vol1[lpCnt]);
    Serial.print(":");
    Serial.print(amp1[lpCnt]);
    Serial.print(":");
    Serial.print(vol2[lpCnt]);
    Serial.print(":");
    Serial.print(amp2[lpCnt]);
    Serial.print(":");
    Serial.print(vol3[lpCnt]);
    Serial.print(":");
    Serial.println(amp3[lpCnt]);
  }
  delay(1000);
}

List 2:

#define  VOL1PIN 0
#define  AMP1PIN 1
#define  VOL2PIN 2
#define  AMP2PIN 3
#define  VOL3PIN 4
#define  AMP3PIN 5
#define  VOL4PIN 6
#define  AMP4PIN 7

#define  VOL1BIAS  483
#define  AMP1BIAS  495
#define  VOL2BIAS  484
#define  AMP2BIAS  497
#define  VOL3BIAS  480
#define  AMP3BIAS  487
#define  VOL4BIAS  483
#define  AMP4BIAS  476

int flag;      /* 電圧の反転状態を定義 */

void setup()
{
  Serial.begin(9600);
  flag = 0;  /* Flag初期値 */
}

void loop()
{
  long numSample;               /* サンプリング数 */
  long vol1, vol2, vol3, vol4;  /* 取得した電圧/電流の値 */
  long amp1, amp2, amp3, amp4;  /* 取得した電圧/電流の値 */
  long vol1Sum, vol2Sum, vol3Sum, vol4Sum; /* 合算した電圧の値 */
  long amp1Sum, amp2Sum, amp3Sum, amp4Sum; /* 合算した電流の値 */

  numSample = 0;  /* サンプリング数取得 */
  vol1Sum = vol2Sum = vol3Sum = vol4Sum = 0;
  amp1Sum = amp2Sum = amp3Sum = amp4Sum = 0;

  while(1)
  {
    /* 電圧と電流をサンプリング */
    vol1 = analogRead(VOL1PIN);
    amp1 = analogRead(AMP1PIN);
    vol2 = analogRead(VOL2PIN);
    amp2 = analogRead(AMP2PIN);
    vol3 = analogRead(VOL3PIN);
    amp3 = analogRead(AMP3PIN);
    vol4 = analogRead(VOL4PIN);
    amp4 = analogRead(AMP4PIN);
    numSample++;

    /* 両方0なら0としてBreak; */
    if (!(vol1||amp1))
    {
      numSample = 1;
      vol1Sum = vol2Sum = vol3Sum = vol4Sum = 0;
      amp1Sum = amp2Sum = amp3Sum = amp4Sum = 0;
      break;
    }

    /* flagの値を判断 */
    if ((flag==2)&&(vol1-VOL1BIAS>0))
      flag=0;              /* データサンプリング開始 */
    else if ((flag==0)&&(vol1-VOL1BIAS<0))
      flag++;              /* 一度目の値の符号反転 */
    else if((flag==1)&&(vol1-VOL1BIAS>0))
      break;               /* 二度目の値の符号反転 */

    /* 取得した値を加算 */
    if(flag!=2)
    {
      vol1Sum += abs(vol1-VOL1BIAS);
      amp1Sum += abs(amp1-AMP1BIAS);
      vol2Sum += abs(vol2-VOL2BIAS);
      amp2Sum += abs(amp2-AMP2BIAS);
      vol3Sum += abs(vol3-VOL3BIAS);
      amp3Sum += abs(amp3-AMP3BIAS);
      vol4Sum += abs(vol4-VOL4BIAS);
      amp4Sum += abs(amp4-AMP4BIAS);
    }
  }

  /* 結果を出力 */
  Serial.print("z");
  Serial.print(vol1Sum);
  Serial.print("\t");
  Serial.print(amp1Sum);
  Serial.print("\t");
  Serial.print(vol2Sum);
  Serial.print("\t");
  Serial.print(amp2Sum);
  Serial.print("\t");
  Serial.print(vol3Sum);
  Serial.print("\t");
  Serial.print(amp3Sum);
  Serial.print("\t");
  Serial.print(vol4Sum);
  Serial.print("\t");
  Serial.print(amp4Sum);
  Serial.print("\t");
  Serial.println(numSample);

  while(1)
  {
    vol1 = analogRead(VOL1PIN);
    if (vol1-VOL1BIAS<0)  break;  /* 次のシーケンスの取得待ち */
    delayMicroseconds(500);
  }
  flag++;  /* flag = 2:次のシーケンス待ち */
}