このたびの震災にあわれた皆様にはお見舞い申し上げます。余りたいしたことも出来ませんが、募金や節電の形での貢献はさせていただきたいと存じます。

*****

ということで、今回からはソフトウェア編である。まずはArduinoのSketchの紹介である。List 1が初期化ルーチン、List 2がメインルーチンとなる。いきなり複雑になるので、順を追って説明する。

まずこのSketchが何を行っているか、である。図1は、ソフトウェアから見た今回のシステム全体である。PC側は立ち上がると、Windowsのサービスとして用意されているPerformance CounterからCPU #0~#3の利用率を取得し、これを文字列(たとえば56%なら、"56"という文字列)に変換する。変換した文字列を画面に表示も行ったりしつつ、一定間隔毎にこの数字を仮想RS232Cポート経由でArduinoに送り出す。こう書くとえらく単純であるが、実際はちょっと面倒な話があったりするが、それはArudinoの後でゆっくり解説することにする。

図1

一方Arduinoであるが、まずRS232Cポートからテキストを受け取ることになる。ただArduinoの場合、一度に受け取れる文字は1回につき1Byte(=1文字)となる。だから、たとえば4つのCPUの負荷を表示する場合、最低でも4Bytes(このあたりは後述)の文字を受け取ることになる。なので、1回分(全CPU分)のデータを全部受け取り終わるまで何回かループして受信を繰り返すことになる。これを取得し終わったら、初めてバッファの内容をLEDの表示On/Offのパラメータに変換し、その後はループさせながら80個のLEDを順に表示する、ということになる。

さて、以下もう少し細かく。まずRS232Cポート経由での通信である。今回の場合、1CPUあたり20個のLEDを割り当てる形になるので、一応四捨五入ということで、

CPU負荷 点灯個数
0%~2.5% 0
2.5%~7.5% 1
7.5%~12.5% 2
12.5%~17.5% 3
17.5%~22.5% 4
22.5%~27.5% 5
27.5%~32.5% 6
: :
92.5%~97.5% 19
97.5%~100% 20

となるようにWindows側のプログラムで設定し、この点灯個数である0~20という数字をRS232C経由で送ることにしている。問題はこれをどう送るか、だ。プログラムというかデバッグで見やすい方法は、全部2桁の数字にしてしまうことだ。つまり0ならば"00"、1ならば"01"という調子で"00"~"20"までを送ればよい。が、これでは無駄に通信データ量が増えてしまう。もともと通信はASCIIコードで行っており、1Byteで0~255までを表現できるわけで、これを使わないのは無駄である。そんなわけで実際の転送では、

点灯個数 送信コード
0 '0'(0x30)
1 '1'(0x31)
2 '2'(0x32)
3 '3'(0x33)
4 '4'(0x34)
5 '5'(0x35)
6 '6'(0x36)
7 '7'(0x37)
8 '8'(0x38)
9 '9'(0x39)
10 ':'(0x3A)
11 ';'(0x3B)
12 '
13 '='(0x3D)
14 '>'(0x3E)
15 '?'(0x3F)
16 '@'(0x40)
17 'A'(0x41)
18 'B'(0x42)
19 'C'(0x43)
20 'D'(0x44)

を送るようにした。0~9はASCIIテキストそのままである。で、10以降は(「ASCIIコード表」を検索してみて頂ければわかるが)'9'の後に続く文字をそのままずーっと並べただけである。こうすることにより、受信した値から0x30(10進数の48:要するに'0'のASCIIコードの値)を引くと、その結果が点灯個数として計算できるわけだ。

これ以外に、実はもう一つスペシャルコードが仕込んである。それは小文字の'z'(ASCIIコードでは0x7A=十進数で122)である。これは何か? という話は受信速度と処理性能に関係してくる。今回の場合、9600bpsでの転送をデフォルトとしており、これは毎秒1200文字に相当する。つまり文字はおおむね1ms弱の間隔で1文字づつ送られる計算になる。この1ms、という時間はArduinoにとってはかなり長い時間間隔であり、1文字受け取ってから次の文字を受け取るまでの間に、LEDの表示を数十回行うのも容易である。この結果として、たとえばPC側が"20% 15% 20% 30%"というCPU負荷だったとして、これを表示するために"4346"という文字列を送った場合を考える。直前が全部消灯状態だとすると、Arduinoは、

(1) 初期状態は0/0/0/0で、全部消灯状態
(2) まず先頭の"4"を取得し、4/0/0/0と点灯する
(3) 2つ目の"3"を取得し、3/4/0/0と点灯する
(4) 3つ目の"4"を取得し、4/3/4/0と点灯する
(5) 4つ目の"6"を取得し、6/4/3/4と点灯する

ことになり、無駄にちらつくことになる。これを避けるため、4つのCPUの負荷を示す文字の後に小文字の'z'を負荷し、実際には"4346z"という5バイトの文字列を送るようにした。Arduinoはこの'z'を受信するまで、LEDの表示を更新しないようにする。これにより

(1) 初期状態は0/0/0/0で、全部消灯状態
(2) まず先頭の"4"を取得するが、LEDは0/0/0/0の消灯状態
(3) 2つ目の"3"を取得するが、LEDは0/0/0/0の消灯状態
(4) 3つ目の"4"を取得するが、LEDは0/0/0/0の消灯状態
(5) 4つ目の"6"を取得するが、LEDは0/0/0/0の消灯状態
(6) 最後の"z"を取得し、LEDは6/4/3/4と点灯する

という形になり、無駄にちらつかないようになった。

ということで今回はリストの説明まで行かなかったが、とりあえず通信プロトコル周りの説明は無事に済んだ。次回はもう少しリストを追いながら説明する。

List 1:

#define WAIT_LED  200
#define BAUDRATE  9600 /* 通信ボーレート */

int  ReceiveBuf[4]; /* USB経由で受信したデータのバッファ */
int  DisplayBuf[8]; /* データ表示用バッファ */
/* 出力ポートとLEDの位置関係を記述 */
char PosX[10] = { 2, 3, 4, 5, 6, 7, 9, 8,10,11 };
char PosY[8]  = { 12,13,14,15,16,17,18,19 };

void setup()
{
  int lpCnt;

  for( lpCnt = 2; lpCnt < 12; lpCnt++ )
  {
    pinMode(lpCnt, OUTPUT);  /* #2~#11までをDigital Outに設定 */
    digitalWrite(lpCnt, LOW); /* 全部Lowに設定=消灯 */
  }

  for( lpCnt = 12; lpCnt < 20; lpCnt++ )
  {
    pinMode(lpCnt, OUTPUT);  /* #12~#19までをDigital Outに設定 */
    digitalWrite(lpCnt, HIGH); /* 全部Highに設定=消灯 */
  }

  Serial.begin( BAUDRATE );  /* USB経由の通信初期化 */

  /* 表示用受信バッファの初期化 */
  for( lpCnt = 0; lpCnt < 4; lpCnt++ )
  {
    ReceiveBuf[lpCnt] = 0;  /* データ無し */
    DisplayBuf[lpCnt] = 0;  /* データ無し */
  }
}

List2:

void loop()
{
  int lpCnt, lpCnt2;
  int readBuf;         /* 受信データを一時的に受けるバッファ */

  while( Serial.available() > 0)  /* データ到着なら受信を行う */
  {
    readBuf = Serial.read();  /* まず1Byte読み込み */
    if (readBuf == 122)  /* 小文字の"z"が来た */
    {
      for ( lpCnt = 0; lpCnt < 4; lpCnt++ )
      {
        if(ReceiveBuf[lpCnt] < 11)
        {
          DisplayBuf[lpCnt*2] = ReceiveBuf[lpCnt];
          DisplayBuf[lpCnt*2+1] = 0;
        }
        else
        {
          DisplayBuf[lpCnt*2] = 10;
          DisplayBuf[lpCnt*2+1] = ReceiveBuf[lpCnt] -10;
        }
      }
    }
    else
    {
      /* 桁送り */
      ReceiveBuf[3] = ReceiveBuf[2];
      ReceiveBuf[2] = ReceiveBuf[1];
      ReceiveBuf[1] = ReceiveBuf[0];

      if ((readBuf > 47 ) && ( readBuf < 69 ))  /* 読み込んだのは0~'D'の範囲か? */
      {
        ReceiveBuf[0] = readBuf-48;  /* 数字ならその値を格納 : atoi()の代わり */
      }
      else
      {
        ReceiveBuf[0] = 0;  /* それ以外なら無条件で0 */
      }
    }
  }

  /* LED点灯 */
  for (lpCnt = 0; lpCnt < 8; lpCnt++)
  {
    digitalWrite( PosY[lpCnt], LOW );
    for (lpCnt2 = 0; lpCnt2 < DisplayBuf[lpCnt]; lpCnt2++)
    {
      digitalWrite( PosX[lpCnt2], HIGH );
    }
    delayMicroseconds( WAIT_LED ); 
    for (lpCnt2 = 0; lpCnt2 < DisplayBuf[lpCnt]; lpCnt2++)
    {
      digitalWrite( PosX[lpCnt2], LOW );
    }
    digitalWrite( PosY[lpCnt], HIGH );
  }
}

(続く)