このたびの震災にあわれた皆様にはお見舞い申し上げます。余りたいしたことも出来ませんが、募金や節電の形での貢献はさせていただきたいと存じます。
*****
ということで、今回からはソフトウェア編である。まずはArduinoのSketchの紹介である。List 1が初期化ルーチン、List 2がメインルーチンとなる。いきなり複雑になるので、順を追って説明する。
まずこのSketchが何を行っているか、である。図1は、ソフトウェアから見た今回のシステム全体である。PC側は立ち上がると、Windowsのサービスとして用意されているPerformance CounterからCPU #0~#3の利用率を取得し、これを文字列(たとえば56%なら、"56"という文字列)に変換する。変換した文字列を画面に表示も行ったりしつつ、一定間隔毎にこの数字を仮想RS232Cポート経由でArduinoに送り出す。こう書くとえらく単純であるが、実際はちょっと面倒な話があったりするが、それはArudinoの後でゆっくり解説することにする。
一方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 );
}
}
(続く)