ユーザーにグラフィカルユーザインターフェースを提供したり、広範な計算を実行したりするために、PCを含めることが有用な場合もあります。Arduinoではシリアルインターフェースが標準化されているため、コンピュータのシリアルポートにアクセスできるソフトウェアであれば、原則として通信が可能です。
ここでは、Arduinoプラットフォームとほぼ同時期に開発されたProcessingプラットフォームに注目したいと思います。
このプラットフォームは、主にアーティスト、デザイナー、プログラミング初心者を対象に開発し、比較的少ない労力でグラフィカルなユーザインターフェースの作成を可能にしています。ユーザーにとっての利点は、Arduino IDEがかつてProcessing IDEから派生したものだったので、すぐに慣れることができると思います。
Processingのセットアップ
始める前に、ソフトウェアは公式プロジェクトサイト https://processing.org/download/ からダウンロードする必要があります。
すべての一般的なオペレーティングシステムで利用可能なバージョンがあり、以下では、例としてWindowsのバージョンを使用します。Processingはフリーソフトウェアプロジェクトでもあるので、ダウンロードに関連して寄付をする機会があります。
Arduino IDEとは異なり、Processingのインストールは不要です。ダウンロードしたファイルはzip-archiveで、解凍するだけです。右クリックでこの解凍オプションを選び、保存先フォルダを選択します。
解凍が完了すると、プラットフォームに関連付けられたすべてのファイルを含むフォルダが保存先になります。processing.exeをダブルクリックするとプログラムが起動します。
アンインストールする場合は、先ほど解凍したフォルダを削除するだけです。
Processingの起動
Processingプログラムを起動すると、Arduino IDEと明らかに似ていることがわかります。プログラムコードは、スケッチとして管理されており、中央に位置しています。左上隅の再生ボタン(三角マーク)では、プログラムを開始することができ、Arduinoで知られているアップロードに相当します。下部には、ステータスやエラーメッセージが表示される黒い影のコンソールがあります。
Processingプラットフォームでどんなことができるか感覚をつかむためには、提供された例を見てみるといいでしょう。ファイル -> サンプルからスケッチをロードして、編集メニューの直下の再生ボタンを有効にすることで、自分でテストすることができます。
このProcessingプラットフォームは、ユーザーとのグラフィカルなインタラクションを重要視していることがすぐにわかると思います。スケッチを開始すると、当該プログラムが動作する新しいウィンドウが開きます。これを利用して、Arduinoプロジェクトのボタン、コントロール、ディスプレイをデザインすることができます。
Processingの例
Processingの基本的なコマンドについて紹介します。この基礎ができてしまえば、必要に応じてすぐに更なる知識を身につけることができます。
最初に知ったArduinoのスケッチはBlinkと呼ばれるもので、LEDを点滅させる機能しかありませんでした。これに続いて、Processingのスケッチも瞬き(Blink)することから始めたいと思います。画面上に長方形が表示され、赤と緑に交互に点滅します。
図3 最初のProcessingスケッチはこの矩形を赤緑に点滅させます。
Processing IDEで以下のスケッチを作成します。
void setup() { size(300,300); //(1) background(255); //(2) stroke(0); //(3) } void draw() { if(millis() % 1000 > 500) //(4) fill(255,0,0); //(5) else fill(0,255,0); //(6) rect(20,20,260,260); //(7) }
ここでも、Arduinoプラットフォームとの類似性がすぐに見て取れます。プログラム開始時に一度だけ実行されるsetup()関数と、常に繰り返される別の関数があります。しかし、この関数はloop()ではなくdraw()と呼ばれています。この関数名は、常にユーザインターフェースを「描画」することを示しています。そのため、まだ馴染みのない機能はグラフィカルなものが中心となります。
(1) 関数 size() は、ユーザインターフェイスのウィンドウのサイズを決定します。引数は幅と高さをピクセル単位で指定します。
(2) background()関数は、背景色を設定します。色指定を必要とする他の関数と同様に、赤、緑、青の輝度値として3つの引数を渡します。これらは常に8ビットの値、つまり0から255までの範囲の値です。 3つの値がすべて同じであれば、この明るさは単に唯一の引数として渡すこともできます。ここでは、赤、緑、青がそれぞれ全輝度で選択され、背景色は白が出力されます。
(3) stroke() 関数は、描画されたオブジェクトの輪郭の色を選択します。デフォルトでは、すべてのオブジェクトは1ピクセルの境界線を持ちます。この関数によって黒い境界線が描画されます。
(4) この条件は、0.5秒の間は真で、次の0.5秒の間は偽であり、このようにして点滅が発生します。
(5) fill() 関数は、オブジェクトを描画するための塗りつぶし色を事前に1色だけ選択しますが、stroke() 関数と同様に、まだ何も描画されていません。一般的にこの関数の後に描画コマンドが使用されるでしょう。引数は順番に(赤、緑、青)と渡します。したがって、ここではif条件が満たされていれば赤が選択されます。
(6) そうでなければ、色の選択は緑になります。
(7) 実際の描画コマンドはこんな感じです。関数 rect() は、選択された塗りつぶしと枠線の色で矩形を描きます。引数は左上隅のX-位置とY-位置、幅と高さをピクセル単位で指定します。この場合、高さ260ピクセル、幅260ピクセルの長方形が描かれており、左端から20ピクセル、上端から20ピクセル離れた長方形が描かれている。
スケッチを使って、位置や色の値を自由に変更し、結果を見てください。その変化を確認するには、もう一度再生ボタンのクリックが必要です
ProcessingとArduinoの通信
ProcessingとArduinoを使って各々のスケッチで作った環境を通信させて実験したいと思います。Arduinoではセンサーの読み取り・スイッチの状態を得て、コンピュータ上で表示させます。
その逆の場合は、コンピュータ上のインターフェイスを介してArduino のLEDのオン/オフを切り替えさせます。
A3ピンはフォトレジスタの明るさのアナログ値を持つようになり、8ピンと9ピンは押しボタンスイッチによるプルアップ入力として接続され、6ピンはLEDを出力制御するために使用されます。以下の例では、Processingのスケッチで作られたプログラムでこのArduinoのピン情報を表示させようとしています。
両プラットフォーム用のプログラムを書く前に、両者のコミュニケーションがどのように行うかを考えなければなりません。シリアル接続は、単一の文字や文字列を比較的簡単な方法で転送することができます。この情報をArduinoは文字列にしてPCに送信し、スイッチの状態や明るさを報告することができます。このようなデータパケットは、一定の形式に加えて一定の長さを有するものであれば、データ処理に常に有利である。この例では、次のような形式が適しています。
実際のデータ : S100456E
通信形式 : S□■△△△△E
データパケットは常に”S”の文字で始まることを指定しています。これに続いて、実際のデータが数字で表したものが、□■△に入ります。
最初の□は、第1ボタンスイッチの状態(1または0)が表示されます。
次の■には、第2ボタンスイッチの状態(1または0)が表示されます。
最後の△は、光抵抗器の明るさのアナログ値を読み込んだ場合の4桁の数字で0から1023までが入ります。この数字は常に4桁で、必要に応じて「0」を入れます。
エンドにはエンドマークとして「E」の文字が入っています。これは、データパケットの長さが常に正確に8文字であることを意味します。
逆方向(PC→Arduino)では、1つの信号のみでLEDの点灯・消灯の制御可能です。このためには、1文字を送信すれば十分です。今回は”e”はLEDをオンにして、”s”はLEDをオフにするように定義します。 これでもうArduinoのプログラムを書くことができます。
#define BUTTON1 9 #define KEY2 8 #define LEDPIN 6 boolean Button1pressed = false; boolean Button2pressed = false; int SensorValue = 0; char ReceptionData; char DataPacket[] = "A000000E"; void setup() { Serial.begin(9600); pinMode(BUTTON1, INPUT_PULLUP); pinMode(KEY2, INPUT_PULLUP); pinMode(LEDPIN, OUTPUT); } void loop() { Button1pressed = !digitalRead(BUTTON1); Button2pressed = !digitalRead(KEY2); SensorValue = analogRead(3); sprintf(DataPacket,"S%1d%1d%04dE",Button1pressed,Button2pressed,SensorValue); // (1) Serial.println(DataPacket); // (2) delay(50); if(Serial.available()) // (3) { char ReceptionData = Serial.read(); if(ReceptionData == 'e') digitalWrite(LEDPIN, HIGH); if(ReceptionData == 's') digitalWrite(LEDPIN, LOW); } }
(1) sprintf()という関数は特定の形式の文字列を作成するために使用します。引数として、第1引数は結果が格納されるchar型の変数配列を渡します。第2引数はパターンで、文字列のフォーム指定のようなものです。このパターンには、特定の書式設定文字が含まれています。例えば、%1d は、この位置に桁がある 10 進数を表しています。04dシンボリック・ボーナスは、先頭に「0」を持つ4桁の 10進数です。第3引数以降は、対応する数値をパターン内で出現する順に指定する必要があります。この関数を実行した後、文字列に対応する値が返されます。例えば、両方のボタンが押されていて明るさの値が298である場合にはA110298Eのように、指定する形が返されます。
(2) 転送は従来のデータ出力と全く同じ方法でPC上に行われます。違いは、PCではシリアルモニタではなく、Processing IDEを実行している点です。Processingを使用しているにも関わらず、トラブルシューティングなどのために、データパケットをシリアルモニタに出力することはもちろん可能です。ただし、一方のプログラムのみが一度にシリアルポートにアクセスできるため、他方のプログラムが同時にシリアルポートにアクセスしようとすると、エラーメッセージが表示されます。
(3) ここでデータを受信します。
これでArduinoのプログラムコードは完成です。マイクロコントローラから見れば、シリアルモニタ上でのデータ出力に使用されるように、シリアルインターフェースを介した通常の通信です。
Processingのスケッチについては、まずユーザインターフェースのデザインを考え、それに基づいてプログラムコードを開発していくべきです。今回のデザインを下記のように決めました。
- ボタンの状態は、2つの長方形で象徴されている必要があり、ボタンが押されると緑、そうでない場合は赤で表示させる。
- 明るさの値を数値で表示し、バーとして可視化させる。
- LEDのON/OFFを切り替えるボタンを各1つずつ表示する。
実際のソフトウェア開発では、少なくともこの時点でスケッチが作成されているのが普通です。
この例では、センサーの値は移動バーで可視化されています。測定値が大きくなるほど、真ん中のバーの水色の部分が大きくなります。右下の2つの角をマウスでクリックすると、それに応じてLEDを切り替えることができます。これらのボタンの上にマウスを移動させると、LEDがクリックできることを示す色がわずかに変化します。
Processingのスケッチを見てみましょう。
import processing.serial.*; //(1) Serial serialPort; String DataPacket; int Button1 = 0; int Button2 = 0; int SensorValue = 0; void setup() { size(380,500); // ウィンドウサイズ(幅380ピクセル、高500ピクセル) background(255); // 白い背景色を出力 stroke(160); // 灰色の境界線を出力 textSize(20); // テキストサイズ20を選択 printArray(Serial.list()); String PortName = Serial.list()[0]; //(2) serialPort = new Serial(this, PortName, 9600); //(3) serialPort.bufferUntil('\n'); //(4) } void draw() { if(DataPacket != null) { // データパケットを受信しましたか? if (DataPacket.length() == 10 && DataPacket.charAt(0) == 'S' && DataPacket.charAt(7) == 'E') { //(5) Button1 = int(DataPacket.substring(1,2)); //(6) Button2 = int(DataPacket.substring(2,3)); SensorValue = int(DataPacket.substring(3,7)); // ボタンの状態を描画します if (Button1 == 1) { //ボタン1が押された場合 fill(0,255,0); rect(20,20,160,160); fill(0,0,0); text("Button 1\npressed!", 60,90); //(7) } else { //ボタン1が押されていない場合 fill(255,0,0); //赤を選択しています rect(20,20,160,160); //長方形を描いています fill(255,255,255); text("Button 1", 60,108); } if (Button2 == 1) { fill(0,255,0); //緑を選択しています rect(200,20,160,160); fill(0,0,0); text("Button 2\npressed!", 240,90); } else { fill(255,0,0); //赤を選択しています rect(200,20,160,160); //長方形を描いています fill(255,255,255); //白を選択しています text("Button 2", 240,108); } //センサー fill(0,0,128); rect(20,200,340,160); //ここではバーサインを設定する。 //変数SensorValueの値が0から340Pixelの //範囲に再変換され、その値に応じて描かれる fill(0,200,255); rect(20,200,map(SensorValue,0,1023,0,340),160); fill(255); //白を選択しています text("SensorValue: " + SensorValue, 100,280); //センサー値をテキストで表示します } } //LED制御 if(mouse_over_power_button()) fill(150,150,200); else fill(20,20,100); rect(20,380,160,100); // スイッチボタンの描画 if(mouse_over_sleep_button()) fill(150,150,200); //薄い紫を選択しています else fill(20,20,100); //濃い紫を選択しています rect(200,380,160,100); // ボタンをオフにする fill(255); text("LED on", 68,435); // LEDボタンのラベル付け text("LED off", 245,435); } //ここでdraw()の記述は終了です。 // 実際は関数の書き始めの // 33行目にもどり、 //ループします。 void serialEvent(Serial serialPort) { //(8) DataPacket = serialPort.readString(); } boolean mouse_over_power_button() //(9) { if(mouseX >= 20 && mouseX <= 180 && mouseY >= 380 && mouseY <= 480) return true; else return false; } boolean mouse_over_sleep_button() { if(mouseX >= 200 && mouseX <= 360 && mouseY >= 380 && mouseY <= 480) return true; else return false; } void mousePressed() //(10) { if(mouse_over_power_button()) serialPort.write('e'); if(mouse_over_sleep_button()) serialPort.write('s'); }
(1) processing用のライブラリもありますが、ここではArduinoの世界とは少しスペルが異なります。シリアルインターフェースには、プリインストールされているライブラリprocessing.serial.*が必要です。
(2) Arduino IDEでは、メニューとシリアルモニタで希望のシリアルポートを選択することができます。通常、現在のコンピュータのほとんどは1つしか持っていないので、これは必要ありません。Processingの場合、このオプションはソフトウェアでは利用できず、代わりにスケッチで希望のポートを選択する必要があります。なので、PortNameの代わりに直接「COM3」と書けばいいのですが、残念ながら特定の状況下でポート番号が変わってしまい、スケッチが動かなくなってしまいます。この現象を避けるために、最初のエントリで利用可能なすべてのCOMポートのリストから選択します。シリアルポートが複数あり、最初のエントリがうまくいかない場合は、インデックスを0から1またはそれ以上に変更してみてください。トラブルシューティングを簡単にするために、上の行のコマンドを実行すると、コンソールで利用可能なCOMポートのリストが画面下部の黒い部分に表示されます。
(3) シリアル接続は、シリアルクラスから新しいオブジェクトserialPortを作成することで確立されます。引数にはシリアルポート名と速度を渡します。予約語thisは、ここで使われているオブジェクト指向プログラミングに由来するもので、ポインタを表しています。
(4) この関数は、データパケットの終了をどのように認識すべきかを定義します。Arduino上でprintln()で最後に改行して送っているので、パケットの終わりを認識することができます。改行自体は制御文字としては見えませんが、「\n」という文字の組み合わせで表すことができます。
(5) データパケットが受信されると、まず、それが正式な書式に準拠しているかどうかがテストされます。そうでない場合は通信が乱れていた可能性が高いです。長さが正確に10文字(8文字のデータパケット+2文字の改行)であるかどうか、始点と終点がそれぞれ”S”と”E”で構成されているかどうかをテストしています。
(6) 正式な要件を満たしていれば、文字列からデータを抽出します。substring()関数は、文字列から特定部分を切り取るのに使用されます。第1引数で開始を指定し、第2引数で終了を指定します。ただし、開始点はこの特定部分に含まれていますが、終了点は含まれていません。具体的にケース(1,2)では、「位置1から位置2の前まで」、つまり位置1だけが指定されています。カウントは”位置0″から始まることを考慮すると、文字列の2番目の1文字だけが抽出されます。関数int()は、char型の引数を任意のint変数に戻り値として変換します。
(7) 関数text()は、引数で渡された位置に、選択された色とフォントサイズの文字列を書き込みます。「\n」は改行することを意味します。
(8) これはprocessingの特殊性であり、(4)で合意した文字を受信した時に自動的に呼び出されます。この場合、受信バッファからのデータを可変データパケットに転送して、さらに処理を行うことができる。
(9) この自己定義関数は、マウスカーソル(その位置はシステム変数 mouseX と mouseY を介して常に呼び出されます)が現在 LED 電源ボタンの上にあるかどうかをチェックし、それに応じて true または false を返します。
(10) 関数 mousePressed() は固定項であり、マウスのボタンが押されるたびに呼び出されます。Arduinoの割り込み機能に似ています。この関数の中で、マウスが現在ボタンの上に配置されているかどうかをチェックします。例えば、現在LEDの電源ボタンの上にある場合は、指定した文字「e」をArduinoに送信します。スリープボタンには、代わりに”s”を送ります。マウスがどのボタンの上にも乗っていない場合は、何も起こりません。
コメントを残す