ホーム < 電子工作やってみた


Arduino編

その6 お部屋の明るさを数値化してみよう(PCへ送信)


 前章で光センサーであるCdsセルの反応をLEDに反映させる回路を何とか作れました。明るい所ではLEDも明るく、暗くするとLEDも暗くなってくれました。そこでこの章では、光センサーの反応を数値として見る試みをしてみます。センサーの値を数値化できるというのは実は大きな事で、様々な実験や調査、調整などを具体的な数字で語れるという事になります。



@ 「Serial」が鍵

 Arduinoのプログラムは関数ベースと思いきや「Serial」というオブジェクトもあります。Arduinoの本家HPが公開しているリファレンスにあるSerialの最初の件を見てみると、

Used for communication between the Arduino board and a computer or other devices. All Arduino boards have at least one serial port (also known as a UART or USART): Serial. It communicates on digital pins 0 (RX) and 1 (TX) as well as with the computer via USB. Thus, if you use these functions, you cannot also use pins 0 and 1 for digital input or output.
You can use the Arduino environment's built-in serial monitor to communicate with an Arduino board. Click the serial monitor button in the toolbar and select the same baud rate used in the call to begin().
(訳)Arduino基板とコンピュータやその他のデバイスとの間でコミュニケーションを取るのに使います。すべてのArduino基板は少なくとも一つのシリアルポート(Sirial)を持っています(UARTもしくはUSARTとしても知られています)。それはUSBを経由してコンピュータだけでなくデジタルピン0番(RX)と1番(TX)上でコミュニケーションを取ります。そのため、もしあなたがこれらの関数を使う場合、あなたはデジタル入力や出力用としてピン0番と1番を使えません。
あなたはArduino基板とコミュニケーションを取るためにArduino環境に組み込まれたシリアルモニターを使えます。ツールバーにあるシリアルモニターボタンをクリックし、begin関数を呼び出すのに使われるのと同じボーレートを選択して下さい。

とあります。

 Serialオブジェクトを通すとUSBもしくは0、1番ピンで通信ができる。その値はIDEのシリアルモニターで確認できる、ほぉ!では、早速それで確認してみましょう。



A Serialで出力実験

 まずはSerialについて色々実験してみましょう:

int outPin = 3;
int serialVal = 0;

void setup() {
    pinMode( outPin, OUTPUT );
    Serial.begin( 9600 );
}

void loop() {

    if ( Serial.available() > 0 ) {
        serialVal = Serial.read();
        Serial.print( serialVal, DEC );
    }
}

 Serial.begin関数はシリアルポートでの通信を開始する関数です。引数の数値は1秒間に送信するデータの量(baud/sec)です。baud(ボー)というのはbit数の事で、例えば9600baud/secと言うと、1秒間に9600bitつまり1200バイト送信するという意味になります。

 Serial.available関数は、読み込むべき値が存在する時に1以上の値(読む値のバイト数)が返ります。上のプログラムでは何か読み込みする値が入ってきた時にSerial.read関数でそれを取り出しserialValという整数変数に格納しています。

 Serial.print関数はIDEのシリアルモニターに何か値を表示する関数です。上の場合、外から与えられた値をそのまま表示しています。第2引数に「DEC」というのがありますが、これは10進数の数値として表示するという意味になっています。

 上のプログラムは「Arduino基板を通して外からシリアルポートを通して与えられた値をそのまま返す物になっている」というわけです。肝心の「値を外から与える」というのは、例えばArduinoのIDEからも行えます。

 IDEのメニューにある[ツール]→[シリアルモニター]をクリックすると、

こんなシリアルの読み書きができるウィンドウが開きます。上のバーに何か文字を入れて送信ボタンを押すとUSBを通してArduino基板のメモリにそれが送信されます。Serial.avairable関数はそれに反応して1以上の値を返し、Serial.read関数でその値が取り出せます。では、先程のプログラムをArduino基板に転送し、試しに「0」を送信してみましょう:

送信ボタンを押すとバー内の文字列はクリアされてしまいます。でも、Arduino基板には送信が成功していて、その値が返されています…が、0ではなくて48という値になっています。そうなんです、このバーに入力した文字は「ASCII文字」として送信されます。一方Serial.read関数はそれをASCIIコードとして返します。0のアスキーコードは48という10進数の数値なので、そういう値が表示されているわけです。



B Cdsセルによる入力ピンの値を表示する

 さてでは本番。前章の光センサーの反応をLEDに反映させるプログラムにSerial.print関数を追加してみます:

int inPin = 0;
int outPin = 3;

void setup() {
    pinMode( outPin, OUTPUT );
    Serial.begin( 9600 );
}

void loop() {
    int analogInVal = analogRead( inPin );
    analogWrite( outPin, analogInVal / 4 );

    Serial.print( analogInVal / 4, DEC );
    Serial.print( "\n" );
}

analogValには光センサーからの値がアナログ値として0〜1023の範囲でやってきます。LEDにはその4分の1の値(0〜255)を渡しています。Serial.print関数にもその値を渡し、改行文字を次に出力しています。こうしないとシリアルモニターに文字が改行無しでずら〜〜っと並んでしまいます。

 プログラムを流すとこんな感じの数字が返ってきました:

MAXが255ですからおよそ151/255=59%くらいの明るさでLEDは光っている事になります。夜、私の部屋でカーテンを閉めて電気を点けるとこの位の明るさなのだ、という事がわかりました(^-^)。

 実際に動かしてみると、Cdsセルは相当に反応が良いのがわかりました。ほんの少しだけ手で影を作るだけで瞬く間に数値が下がります。大変面白いです。



C コンソールでシリアルポートからの情報を受け取る

 さて、上のIDEが提供してくれるシリアルモニターは便利なのですが、このままですとシリアルの値を使って何かするなど活用が出来ません。そこで、Windowsのコンソールアプリケーション上でシリアルポートからの情報を受け取る簡易なアプリケーションの作り方を紹介します。

コンソールにシリアルポートからの値をダンプ
#include "stdafx.h"
#include <Windows.h>


int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hCom = CreateFileA( "COM3", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 );
    if ( hCom == INVALID_HANDLE_VALUE )
        return -1;

    // 通信方法を設定
    // 9600 baud/sec, バイナリモード転送(必須)、DTRフロー許可、1byteは8bit
    DCB dcb;
    GetCommState( hCom, &dcb );
    dcb.BaudRate = 9600;
    dcb.fBinary = 1;
    dcb.fDtrControl = DTR_CONTROL_ENABLE;
    dcb.ByteSize = 8;
    SetCommState( hCom, &dcb );

    // シリアルポートからの情報を出力
    const unsigned int bufSize = 256;
    char str[ bufSize ];
    DWORD readByte = 0;
    unsigned int pos = 0;

    while ( ~::GetAsyncKeyState( 'C' ) & 0x8000 ) {
        if ( pos + 1 >= bufSize ) {
            str[ pos ] = 0;
            printf( "%s\n", str );
            pos = 0;
        }

        readByte = 0;
        ReadFile( hCom, str + pos, 1, &readByte, 0 );

        if ( readByte > 0 ) {
            if ( str[ pos ] == 10 ) {
                str[ pos ] = 0;
                printf( "%s\n", str );
                pos = 0;
            } else
                pos++;
        }
    }

    CloseHandle( hCom );

    return 0;
}

 上のプログラムはコンソールアプリケーションにCOM3ポートに入力されてくる値を1バイトずつ取得し、改行がくるかバッファが埋まったらガンガン出力します。CreateFile関数はファイルを作ったりオープンしたりするWindows APIですが、上のように第1引数に"COM3"のようなシリアルポートの名前、OPEN_EXISTING、そして最後の引数を0にすると、通信リソースを開く事ができます。オープンに成功したらハンドルが返ります。ReadFile関数でシリアルポートに投げられている値をバッファに格納する事ができます。上の例では1バイトずつ文字列strに格納していっています。画面に出力する条件は、読み込んだ文字がASCIIで10(CR)の時、もしくはバッファがいっぱいになる直前のどちらかです。終了させるには「C」キーを押し下げます(whileの判定部分)。後片付けはCloseHandle関数でハンドルを閉じます。

 上のコードを改良するなり、クラス化するなりすれば、Arduinoが返してくれる値をパソコン上で活用する道が開けます。例えば、今回の光センサーの値をコンソールに出力する変わりに大きなバッファに格納し、それをExcelに持って行ってグラフ化したのがこちら:

 電気を消した部屋で、Cdsセルに対して定期的に懐中電灯を当ててみました。ちゃんと反応しているのがわかりますよね。これ、もう「センサー」をArduinoの回路に組み込んでプログラム側からSerial.print関数にセンサーが返す値を渡せば何でも数値化できる事を示しています。わくわく過ぎですよね〜。他にもいくつかセンサーを買って、似たような事を試してみようかなと思いました(^-^)