37
エルゴノミクスコンピューティング実習 入力インタフェースと信号処理 井村 誠孝 ([email protected]) 2017 11 7 バーチャルリアリティシステムの構築にあたって,ユーザの状態を計測するセンシングは,バーチャル世界に ユーザの状態を反映させる入力インタフェースの構築のために必要不可欠な要素である.本演習では,主にユーザ の手の挙動を対象として,センサとマイクロコントローラを用いた計測と信号処理の方法について学習する.マ イクロコントローラとしては Arduino を用いる. ■■■ 演習: この項目が演習 各自で順次実施する.発展と書かれている項目は任意. 4 回で実施する. 1. Arduino の使い方 基礎 (1-6) 2. キャリブレーションと信号処理 (7,8) 3. Processing との連携 (9) 4. 発展課題 (10) レポートには主に後半の演習で作成したものを提出するが,前半の演習についても,ソースコードは保存してお くようにする. (11/7) 7.48.2(8.3 あるいは 8.4 で代用可) については,実施した内容を説明するレポートを提出していただき ます.加えて,9 以降で説明する Processing と連携したものについても,自由に制作して提出していただきます. 1

エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

Embed Size (px)

Citation preview

Page 1: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

エルゴノミクスコンピューティング実習入力インタフェースと信号処理

井村誠孝 ([email protected])

2017年 11月 7日

バーチャルリアリティシステムの構築にあたって,ユーザの状態を計測するセンシングは,バーチャル世界にユーザの状態を反映させる入力インタフェースの構築のために必要不可欠な要素である.本演習では,主にユーザの手の挙動を対象として,センサとマイクロコントローラを用いた計測と信号処理の方法について学習する.マイクロコントローラとしては Arduinoを用いる.

演習: この項目が演習 各自で順次実施する.発展と書かれている項目は任意.4回で実施する.

1. Arduinoの使い方基礎 (1-6)2. キャリブレーションと信号処理 (7,8)3. Processingとの連携 (9)4. 発展課題 (10)

レポートには主に後半の演習で作成したものを提出するが,前半の演習についても,ソースコードは保存しておくようにする.

(11/7) 7.4,8.2(8.3あるいは 8.4で代用可)については,実施した内容を説明するレポートを提出していただきます.加えて,9以降で説明する Processingと連携したものについても,自由に制作して提出していただきます.

1

Page 2: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

1 Arduinoで動作するプログラムの開発本節では,Arduinoの紹介と,プログラム開発の流れについて解説する.

1.1 Arduinoとは

Arduino はオープンソースのプロトタイピングプラットフォームであり,安価なハードウェアと簡潔なソフトウェアから構成される.

Arduinoは多様なセンサからの信号を入力し,それを処理し,光 (LED)や動き (モーター)などを通じて外部に出力することが非常に簡単にできる.また,シリアル通信を利用して,PCをはじめとする他の機器と連携することができる.図 1に,Arduinoの果たす役割を示す.プログラムの開発は C/C++ を簡略化した言語を用いて行う.開発は PC 上の統合環境で行えるため,敷居が低い.本実習では,Arduinoの入力および出力機能を用いて,ユーザの手の位置に基づく制御などの簡単な演習を行う.

1.2 Arduinoの外観を確認する

Arduino のボードを確認する.これまでに多数の種類のボードが発売されているが,本実習では ArduinoDuemilanove 328を用いる.マイクロコントローラとして Atmel ATmega328を搭載している.Flash Memoryを32KB,SRAMを 2KB備えており,クロック周波数は 16MHzである.図 2に Arduino Duemilanove 328の外観を示す.

• USBコネクタ (Bタイプ): 電源 (5V)の供給,シリアル通信 (PCや他のデバイスとの通信に用いる)• デジタルピン: 0番から 13番まである.On(5V)/Off(0V=GND)の入力と出力に用いる.0番と 1番はシリアル通信と共用である (シリアル通信を使用しているときは,別の用途に使用することはできない).PWMと書かれているものは,PWM(Pulse Width Modulation)を用いた擬似的なアナログ出力が可能である.出力の際には電源として用いることができるが,取り出せる電流は最大 40mAであるため,モータなどのより多くの電流を必要とする機器を駆動する際には回路を工夫する必要がある.

• アナログ入力ピン: A0から A5まである.A/D(analog/digital)変換に用いる.• 電源ピン: 外部のデバイス (センサなど)に電源を供給する.5Vと 3.3Vがある.こちらもあまり大容量の電流を取り出すことはできないので注意.

演習: ボード確認 Arduino Duemilanove 328のボード上のピン配置などを確認する.

センサ

アクチュエータ

Arduino(マイクロコントローラ) 他の機器

図 1 Ardinoの役割

2

Page 3: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

USBコネクタ

デジタルピン

アナログ入力ピン電源ピン

リセットボタン

図 2 Ardino Duemilanove 328の外観

図 3 WBuzzerのアイコン

1.3 Arduinoの接続とプログラムの実行

Arduino Duemilanove 328を USBケーブルで PCの USBポートに接続する.USBは電源の供給および PCとのシリアル通信に使用される.ボード上の LED(PWR)が緑に常時点灯する.

Arduinoのボード上に電源スイッチは無く,ボードに書き込まれているプログラムは,電源が供給されると自動的に実行が開始される.またリセットボタンを押すと,プログラムの最初から再実行する.

演習: ボード接続 Arduino Duemilanove 328を USBケーブルで PCに接続する.

1.4 ポートの確認

Arduino と PC は USB ケーブルで接続される.USB は Universal Serial Bus の略称である.PC で作成したプログラムを Arduinoに転送するためには,Arduinoが接続されている COMポートの番号を確認しておく必要がある.

演習室での方法 一般的には後述するようにデバイスマネージャーを開いてポートを確認するが,演習室の環境では,学生はデバイスマネージャーを開くことができない.タスクバー右下の上向き三角をクリックすると現れるWBuzzer(図 3のオレンジ丸内)を使うと調べることができる.図 4にある FTDI: USB Serial Port (COM?)がArduinoである.複数の機器が接続されている場合に区別するために,ポートには番号が付いている.COMの後ろの数字が,接続されているポート番号である.ポート番号はそれぞれの PC環境によって異なる可能性が高いので,注意すること.

演習: ポート確認 WBuzzerを用いて,Arduinoが接続されているポートを確認する.

一般的な方法 図 5に示すように, スタートボタン コントロールパネル デバイスマネージャー ポート(COMと LPT)を見る.

3

Page 4: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 4 WBuzzerでポートを確認

これがArduino

図 5 デバイスマネージャーでポートを確認

USB Serial Port (COM5)等と書かれているのが Arduinoである.

シリアルポートのプロパティ デバイスマネージャーで見つけた Arduinoの項目の上で,右クリックし,プロパティを確認してみる.ポートの設定のタブに,

• ビット/秒• データビット• パリティ• ストップビット• フロー制御

とある.これらの項目は 5 節で言及するシリアル通信の一般的な設定項目である.PC 側の設定と Arduino側の設定が一致していないと,正常な通信が行えない.通信がうまくいかない場合にまずチェックするべき箇所である.

4

Page 5: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

1.5 プログラム開発の流れ

Arduinoのプログラム開発は,PCを用いて行う.PC上で動くプログラムの開発との大きな違いは,プログラムを開発する環境と,それが実行される環境が異なることである.開発には Arduino IDEという開発ツールを用いる.

統合開発環境 IDEは Integrated Development Environmentの略であり,テキストエディタ,コンパイラ,デバッガなどが一つにまとめられた統合開発環境を指す.Visual Studioや Eclipseなども IDEの一種である. 開発の一般的な手順について,以下に示す.まず開発ツールを立ち上げる.Arduino IDEの起動は, スタートボタン 4.Tools arduinoにより行う.Arduino IDEを起動すると,既にプログラムの雛形が準備されており,setup()と loop()という二つの関数を定義するように促される.setup()は起動時に一度だけ実行される関数で,初期化の処理を記述する.loop()は繰り返し実行される関数で,メインの処理を記述する.適宜,プログラムを作成する.Arduino ではプログラムのことをスケッチと呼ぶ.Arduino IDE の開発言語は

C/C++をベースにしているため,C言語の知識があれば特に問題なくプログラムが記述できる.プログラムが作成できたら,コンパイルし,エラーが無ければ,Arduinoへの書き込みを行う.Arduinoは書き込まれたプログラムを即座に実行開始する.

1.6 情報源

• 日本語の情報は,以下のウェブサイトが見通しよく簡潔にまとまっているが,情報が古いかもしれない.http://www.musashinodenpa.com/arduino/ref/

• 以下のウェブサイトが情報の充実度が高い.https://garretlab.web.fc2.com/

• 最新のリファレンスは英語ではあるが,以下を参照するとよい.https://www.arduino.cc/en/Reference/HomePage

• Arduino IDEの ファイル スケッチの例には様々なサンプルがある.

演習: リファレンス確認 http://www.musashinodenpa.com/arduino/ref/を開き,特に左側半分を眺めて,どのような機能があるか,ざっと確認する.

1.7 Arduinoでの制約

PC上での C言語によるプログラム作成と,異なる点を示す.

• 本演習で使用する Arduino Duemilanove 328 では,int 型は 2 バイトであり,格納できる値の範囲は−32768 ∼ 32767である.演算の途中で int型の範囲を超えてしまう場合が起こりやすいので,挙動がおかしい場合は計算途中でのオーバーフローの確認も必要.

• float型と double型はどちらも 4バイトである.double型を使用しても精度は上がらない.float型を使用するのがよい.

(本項は随時追記します)

5

Page 6: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

2 デジタル出力: LEDを光らせるArduino でできることはデジタル/アナログの入力/出力である.最初にデジタル出力の例として,ボード上の

LEDを光らせる.

2.1 ソースコードの作成

まずは Arduino IDEを起動し,以下のソースコードを入力する.

演習: ソースコードの入力 以下のソースコードを入力せよ.

リスト 1 ボード上の LEDを点滅させる1 void setup() 2 pinMode(13, OUTPUT);3 45 void loop() 6 digitalWrite(13, HIGH);7 delay(1000);8 digitalWrite(13, LOW);9 delay(1000);

10

ソースコードの作成が終わったら,一度保存しておくことにする. ファイル 名前を付けて保存で,ダイアログ「スケッチのフォルダの保存先...」が開くので,本実習用のフォルダ以下に保存する.名前は何でもよいが,例えば blinkとする.デフォルトだと日付入りの名前が付くが,後で何が何だったかわからなくなるので,適切な名前を付けるように心がけよ.

演習: スケッチの保存 スケッチ blinkを保存し,保存先のフォルダの内容を確認せよ.

2.2 Arduino IDEの設定確認

ソースコードが完成したら,Arduinoへの書き込みを行うために,Arduino IDEのボードとポートの設定を確認する.メニューの ツールをクリックすると,現在設定されているボード,プロセッサ (無い場合もある),ポートが表示される (図 6).それぞれ,

• ボード: Arduino Duemilanove 328• プロセッサ: ATmega328• ポート: COM5 (PCによって異なる可能性が高い)

となっていることを確認する.適切な設定がされていない場合は,各サブメニューから選択する.

演習: Arduino IDEの設定確認 Arduino IDEの設定が,手元の Arduino Duemilanove 328にプログラムの書き込みが行える状態 (上記設定)になっていることを確認せよ.

2.3 コンパイルと実行

Arduino IDEの上に並んでいるアイコンの,一番左端のボタン (チェックマーク)でプログラムに文法的誤りがないかを検証する. スケッチ 検証・コンパイル,あるいはショートカットキー Ctrl+Rでも行える.エラーが出なければ,Arduinoへのプログラムの書き込みを行う.書き込みは左から二つ目のアイコン (右向き矢印)で行う. ファイル マイコンボードに書き込む,あるいはショートカットキー Ctrl+Uでも行える.二つのアイコンの位置を図 7に示す.

6

Page 7: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 6 Arduino IDEの設定確認

コンパイル

書き込み

図 7 コンパイルと書き込み

書き込みが終了すると,自動的にプログラムが実行され,Arduino上の LEDが 1秒ごとに点滅する.

演習: プログラムのコンパイルと実行 作成したプログラムを実行し,正しく実行されることを確認せよ.

2.4 ソースコードの解説

簡単にソースコードの各関数について解説を行う.

• setup()には初期化の処理を記述する.起動時に一度だけ実行される.• pinMode()は,各ピンを入力に使用するか,出力に使用するかを設定する関数である.ここでは,13番のデジタルピンを出力モード OUTPUTに設定している.OUTPUTおよび INPUTは定義済みの定数である.13番のデジタルピンは,Arduinoのボード上にある Lのラベルが振られた LEDと直結しており,13番のデジタルピンへの出力によりこの LEDの点灯状態を制御することができる.

• loop() にはメインの処理を記述する.loop() は自動的に繰り返し実行されるので,中で for(;;) やwhile(1)といった無限ループを組む必要はない.

7

Page 8: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

• digitalWrite()はデジタルピンの出力状態を設定する関数である.HIGHを指定すると 5Vに,LOWを指定すると 0Vに設定される.HIGHおよび LOWは定義済みの定数である.

• delay()はプログラムの実行を指定時間だけ中断して待機する関数である.引数は待ち時間であり,単位はミリ秒 (ms)である.この例では LEDを点灯あるいは消灯してから,その状態を 1秒間維持する.

2.5 課題

演習: 点滅パターンの変更 LEDが点滅するパターンを変更してみよ.

1. 800ms点灯し,200ms消灯する.2. 800ms点灯し,200ms消灯し,200ms点灯し,800ms消灯する.

演習: 点滅パターンの変更 LEDが点滅する速さを非常に速くしてみると,ヒトの目にはどのように見えるか,確認せよ.

1. 1ms点灯し,1ms消灯する.2. 1ms点灯し,10ms消灯する.

演習: [発展]複雑なパターンで点滅させる int型の配列変数に点灯時間と消灯時間 (それぞれ単位はミリ秒)を交互に格納し,そのパターンに従って点滅させるようにせよ.

演習: [発展] delay()を使わずに点滅させる 1000ms間隔での LEDの点滅を,loop()内で delay()を使わずに実現せよ.ヒント: プログラム開始からの経過時間 (ミリ秒) は,millis() で取得できる.点灯,消灯を行った時刻を変数に保持し,プログラム開始からの経過時間との差が点滅間隔以上になれば,LED の点灯状態を切り替える.millis()の返値は unsigned long型の変数である.点灯,消灯を行った時刻も unsigned long型で保持する必要がある.

8

Page 9: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

3 ソースコードの可読性の向上よいソースコードを記述する原則の一つは,意味が把握しづらい生の数値による記述を避けることである.このようなソースコード内に埋め込まれた定数はしばしばマジックナンバーと呼ばれる.前節の例では,出力するデジタルピンの番号や点滅速度を変数に定義すると,ソースコードの見通しがよくなる.constは,変数が定数であることを示す接頭語である.

演習: ソースコードの可読性向上 ソースコードを以下のように書き換えて実行せよ.

リスト 2 ボード上の LEDを点滅させる (可読性向上版)

1 const int ledPin = 13;2 const int periodOn = 1000;3 const int periodOff = 1000;45 void setup() 6 pinMode(ledPin, OUTPUT);7 89 void loop()

10 digitalWrite(ledPin, HIGH);11 delay(periodOn);12 digitalWrite(ledPin, LOW);13 delay(periodOff);14

一見,タイプ量が増えるだけに見えるが,よいことがある.

• 出力ピンの番号が変わっても,1箇所変更するだけで済む.• 数字は見ても意味がわからないが,変数だと名前から意味がわかる.• constを付けることで,この変数に設定された値は変更できない値であることがわかる.• 定数の定義がソースコード内の一箇所に固まっているので,修正時にソースコードをあちこち眺める必要がない.

このサンプル程度であれば,delay()に直接数値が書いてある方が却って挙動がわかりやすいのではあるが,研究で使用するようなプログラムの場合は,規模が大きくなり,また時間が経った後に第三者が参照する可能性も高くなるため,普段からマジックナンバーを排除するように心がけるべきである.

変数や関数の名前の付け方 変数や関数の名前の付け方には,様々な流儀がある.

• very_very_long_name: スネークケース• veryVeryLongName: キャメルケース• VeryVeryLongName: パスカルケース (アッパーキャメルケース)

Arduinoでは変数名・関数名ともにキャメルケースを用いるのが一般的であるようだ.クラス名はパスカルケースである.変数,関数,クラス名などの命名規則については少なくともプロジェクトの中で統一しておくべきである.

9

Page 10: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

4 アナログ出力: LEDの明るさを連続的に変化させるデジタル出力ピンで設定できる値は,HIGH(5V)か LOW(0V)のいずれかに限られていた.これに対して,アナログ出力は,0から 255までの任意の値を指定することができる.ただし,アナログ出力と言われるものの,実際は PWM(Pulse Width Modulation)という,指定された値に応じた時間比率 (デューティー比)でデジタル出力のOnと Offを高速に切り替える出力を行っている.アナログ出力を使うと,LEDの明るさや DCモーターの回転数を変化させることができる.Arduinoでは PWM出力できるピンが限られている.Arduino Duemilanove 328の場合,横に PWMと書かれているデジタルピン (3,5,6,9,10,11)が PWMに対応している.*1デジタル出力のために用いていた 13番ピンは PWM出力ができないため,本節では別途 LEDを接続して実習を行う.

4.1 LEDの接続

11番ピンと GNDとの間に LEDを接続する.本来 LEDには適正な印加電圧および電流があり,適切な電圧と電流を得るために電流制限抵抗を直列に接続する必要があるのだが,本実習では電流制限抵抗が内蔵されていて5Vの電圧を直接印加可能な LEDを使用する.

LEDには極性がある.図 8に示すように,LEDの長い方の足をアノード,短い方の足をカソードと呼ぶ.電流はアノードからカソードに流れる.よって,アノードに電源電圧の高い方,カソードに低い方を接続する.本実習では,LEDの長い方の足を 11番ピンに,短い方の足を二つ間を離した GNDピンに接続する.接続した状態を図 9に示す.

4.2 アナログ出力

analogWrite() 関数を用いて,アナログ出力ピンと 0 から 255 の値を指定することで,アナログ出力ピンのPWMのデューティー比を変えることができる.

演習: LEDの明るさの連続的変化 LEDのアノード (長い足)を 11番ピンに,カソード (短い足)を GNDに接続し,以下のソースコードを入力・実行し,連続的に明るさが変化することを確認せよ.

長い方がアノード 短い方がカソード

アノード カソード

電流の向き

図 8 LED

*1 Arduino Unoは記号 ~が印刷されている.

10

Page 11: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 9 LEDを接続した状態

リスト 3 LEDの明るさを連続的に変化させる1 const int ledPin = 11;2 const int wait = 30;3 const int changeStep = 5;45 int brightness;6 int changeDir;78 void setup() 9 pinMode(ledPin, OUTPUT);

10 brightness = 0;11 changeDir = 1;12 1314 void loop() 15 analogWrite(ledPin, brightness);16 brightness += changeStep * changeDir;17 if (brightness >= 255) 18 brightness = 255;19 changeDir = -1;20 else if (brightness <= 0) 21 brightness = 0;22 changeDir = 1;23 24 delay(wait);25

4.3 ソースコード解説

• 変数のスコープは C言語と同じである.関数外で宣言すると,大域変数 (グローバル変数)となり,ソースコード全体で使用できる.この例では setup()で変数の初期化を行っているが,宣言時に代入してもよい.

• C言語の制御構文はそのまま使用できる.ここでは ifを使用している.

11

Page 12: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

0 1 2 3 4 5

0.0

0.2

0.4

0.6

0.8

1.0

0 1 2 3 4 5

0.0

0.2

0.4

0.6

0.8

1.0

0 1 2 3 4 5

0.0

0.2

0.4

0.6

0.8

1.0

(a)デューティー比 0.25 (b)デューティー比 0.5 (c)デューティー比 0.75

図 10 デューティー比の異なる PWM出力

4.4 演習

演習: LEDの逆差し LEDを Arduinoに接続するときに,アノードとカソードを逆に差してしまったとすると,どうなるだろうか.確認せよ.

演習: 点滅パターンの変化 LEDの点滅パターンを変更してみよ.

1. より高速に点滅するようにせよ.2. 点灯時はすばやく,消灯時はゆっくりと点滅するようにせよ.3. [発展]消灯時間を 1000msecずつ挟むようにせよ.

演習: [発展]正確な周期 周期 T = 3秒で,以下の式に従って輝度 I (t) を設定して点滅するようにせよ.

I (t) = 128 + 127 sin(2πt/T )

演習: [発展]二つの LEDを制御 ボード上の LEDと外付け LEDを異なる周期で点滅させる方法を考えよう.例えば,ボード上の LEDを 300msecごとに点滅させ (周期は 600msecになる),外付け LEDを 700msec周期で連続的に変化させるにはどうすればよいか.

Pulse Width Modulation Pulse Width Modulation (PWM) とは,一定の電圧 (オン) と 0V(オフ) の 2 状態を異なる時間幅で繰り返すことにより,定電力のアナログ信号を作り出す方法である.信号の周期に対するオンの時間の割合をデューティー比 (duty cycle) と言う.図にデューティー比の異なる PWM 出力例を示す.デューティー比を時間的に変化させることにより,正弦波のような出力波形を作り出すことも可能である.Arduinoが出力する PWM信号のデフォルトの周波数は,5,6番ピンが約 1kHz,3,9,10,11番ピンが約 500Hzである.

12

Page 13: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

LEDの電流制限抵抗 電子部品に印加可能な電圧や電流には上限があり,LEDも例外ではない.LEDには適正な順方向電圧降下と順方向電流がある.順方向電圧降下は半導体の特性に起因し,通常の動作範囲内では電流によらず一定として考えてよい.電源電圧が順方向電圧降下よりも高い場合には,LEDに直接電源電圧をかけると過負荷により破損する可能性がある.このため LEDと直列に適切な値の抵抗を接続し,LEDにかかる電圧を下げるとともに,流れる電流を適正値に保つのが一般的である.この抵抗のことを電流制限抵抗と呼ぶ.電源電圧を V,LEDの順方向電圧降下を VF,LEDに流したい電流を IF とすると,電流制限抵抗の抵抗値 R

は以下の手順で求められる.

1. 抵抗による電圧降下と LEDによる電圧降下の和が電源電圧に等しいことから,抵抗による電圧降下はV − VF である.

2. LEDと抵抗は直列に接続されているため,抵抗に流れる電流も IF である.3. したがってオームの法則より,R = (V − VF )/IF と計算できる.

例: 10mA, 1.8V の LED に 5V の電源を接続する場合,抵抗は (5 − 1.8)/0.01 = 320 であるから,近い値の330Ωを接続すればよい.

13

Page 14: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

5 PCと通信するArduinoと PCの間での通信を行うと,様々なアプリケーションとの連携が可能になる.また,Arduino単体では困難な,実行時の状態の確認を行うことができる.前述した通り,Arduinoと PCとの間は,USBケーブルを介したシリアル通信と呼ばれる通信方式で接続される.シリアル通信は,Serialライブラリを用いて行う.

5.1 Arduinoから PCへの情報送信

アナログ出力によって LEDの明るさを連続的に変化させるプログラムに,LEDの状態をシリアル通信を用いて PCに送信する機能を追加する.

リスト 4 LEDの明るさを連続的に変化させる (シリアル通信付き)

1 const int ledPin = 11;2 const int wait = 30;3 const int changeStep = 5;45 int brightness;6 int changeDir;78 void setup() 9 pinMode(ledPin, OUTPUT);

10 Serial.begin(9600);11 brightness = 0;12 changeDir = 1;13 1415 void loop() 16 analogWrite(ledPin, brightness);17 Serial.println(brightness);18 brightness += changeStep * changeDir;19 if (brightness >= 255) 20 brightness = 255;21 changeDir = -1;22 else if (brightness <= 0) 23 brightness = 0;24 changeDir = 1;25 26 delay(wait);27

Serial.begin()でシリアル通信を開始する.引数は通信速度である.9600は 1秒間に 9600ビットの帯域で通信するという指定である.このプログラムにおいて,Arduino側のシリアル通信設定は以下の通りとなる.

• ビット/秒: 9600• データビット: 8• パリティ: なし• ストップビット: 1• フロー制御: なし

Serial.println() で,変数の値などをシリアル通信で PC に送信する.値は文字列に変換されて送られる.整数型,浮動小数点型,文字列型,いずれでも適切に変換される.C言語の printf()と異なり,詳細なフォーマットの指定は行えない.

14

Page 15: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

シリアルモニタ

これでもOK

図 11 シリアルモニタの起動

図 12 シリアルモニタ

図 13 シリアルプロッタ

なおシリアル通信を使うと,機能を共用しているデジタルピンの 0番と 1番は使用できなくなるので,注意が必要である.

5.2 PCでの情報確認

Arduinoから PCに送信したデータを見るためには,Arduino IDEのシリアルモニタを使用する.図 11に示すように ツール シリアルモニタ (ショートカットキーは Ctrl+Shift+M) を選択するが一番右端のアイコンをクリックすると,図 12のようにシリアルモニタのウィンドウが開き,Arduinoと送受信した情報を表示することができる.シリアルモニタに brightnessの数値が表示されることを確認する.また,メニューの ツール シリアルプロッタ (ショートカットキーは Ctrl+Shift+L)を選択すると,送信されてきた時系列データが図 13に示すようなグラフとして表示される.

15

Page 16: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

なお,現時点では Arduino IDEのシリアルモニタでしか情報の送受信を確認できないが,自作のプログラムでもシリアル通信を行うことで,直接 Arduinoと情報の送受信を行うことができる.本実習では後半に Processingとの通信を行う.

演習: シリアル通信 ソースコードにシリアル通信部分を加え,実行し,Arduino IDEでシリアルモニタを開いて,LEDの明るさ情報が送信されてくることを確認する.

5.3 PCから Arduinoへの情報送信

PCから送信された情報を Arduinoで確認するためには,まず Serial.available()で受信データの有無を確認する.データが存在していた場合の処理にはいくつかの方法があるが,ここでは Serial.parseInt()で整数値を得て,LEDの明るさに反映させている.

リスト 5 キーボードから入力された値に LEDの明るさを設定する1 const int ledPin = 11;23 void setup() 4 pinMode(ledPin, OUTPUT);5 Serial.begin(9600);67 analogWrite(ledPin, 255);8 9

10 void loop() 11 if (Serial.available()) 12 int val = Serial.parseInt();13 analogWrite(ledPin, val);14 Serial.println(val);15 16

シリアルモニタの一番上の行に,数値を入力して Enterあるいは右端の「送信」を押すと,数値が送信される.本プログラムでは送信した値をそのまま返信するようになっているため,入力された値は少しの時間遅れの後にシリアルモニタに表示される.

5.4 演習

演習: シリアル通信機能の付加 ボード上の LEDを点灯させるプログラムに,シリアル通信機能を付加して,LEDの状態「HIGH」か「LOW」を確認できるようにせよ.

演習: 通信速度の不一致 シリアルモニタの右下にある通信速度を変えてみる.プログラム側で指定してい

16

Page 17: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

る速度 (9600)と,あえて異なる値に設定すると,何が起こるだろうか.なぜそうなるのか考えみよ.

シリアル通信の設定 シリアル通信は,1本の信号線を用いてデータを 1ビットずつ通信する方式である.送信側と受信側で,設定の統一がなされていないと,正しく情報を伝達できないため注意が必要である.

• 通信速度: 1秒間に送信するビット数.単位は bps(bit per second)である.

• データビット: 一つのデータが何ビットで構成されているか.8が一般的だが英数字記号のみ扱う場合には 7を使うこともある.

• パリティ: データの誤り検出を行うためのパリティチェックを使用するか.パリティチェックとは,あるデータ列の送信の際に,データの末尾に 1 ビットの情報 (パリティビット)を加えることで誤りを検出する手法である.送受信の双方で偶数パリティチェックを使用するか奇数パリティチェックを使用するかをあらかじめ取り決めておく.偶数パリティチェックの場合,加えるパリティビットの値を,データ列とパリティビットを合わせて 1 の個数が偶数になるように選ぶ.受信側では 1の個数を数え,偶数でない場合には通信の誤りが発生したとする.

• ストップビット: データの終端を表すビットの長さ.シリアル通信では,送受信するデータが 1列になっているため,データを送るだけではデータの区切りを判断できない.データの開始を識別するために,信号が無い状態 (アイドル時)に信号を 1として,通信の開始時に信号を 0とする (スタートビット)と規定されている.受信側は,スタートビットを確認すると (すなわち信号が 1から 0になる),そこからデータビットを設定個数,パリティがある場合はパリティビットを 1個サンプリングする.最後に受信したビットは 0か 1か不明であるため,次のスタートビットを検出するためには,一旦信号を 1にしておく必要がある.そのためデータの末尾には 1が付加される.このビットのことをストップビットと呼ぶ.ストップビットの長さを長くすると,時間的変動に強くなるが,単位時間内に送信できる情報量が少なくなる.通常 1が使用される.

• フロー制御: 送受信の確認を行うか.

9600bpsで 8ビットの情報をパリティ無し,ストップビット 1ビットで伝送する場合,スタートビットおよびストップビットが加わるため,10ビットの送信で 8ビットの情報が伝達される.よってこの設定における1秒間の伝送量は 960バイトとなる.例えば 1秒間に 30回情報を送信したい場合,1回あたりに送信できるのは高々 32バイトであり,大量の情報を送ることはできないため,注意が必要である.

17

Page 18: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

6 アナログ入力: 距離センサを使うこれまで Arduinoからの出力のみを扱ってきたが,各種センサの状態を Arduinoで取得することも頻繁に行われる.アナログ入力ピンは,Arduino Duemilanove 328の場合 6本あり,それぞれ A0から A5の名前が付いている.デジタルピンが並んでいる側と反対のボードの端に「ANALOG IN」と記されているものがそれである.ソースコード中でも,A0から A5といったシンボル名を使用することができる.アナログ入力ピンと GNDの間の電位差を,0から 1023の整数値として得ることができる.0Vの場合が 0,5Vの場合が 1023である.

A/D変換 連続的なアナログ信号を離散的なデジタル信号に変換することを,A/D変換 (Analog Digital Conversion)と呼ぶ.A/D変換は標本化 (サンプリング; sampling)と量子化 (quantization)の二つの過程から構成される.標本化は,ある時刻の信号を得ることであり,この段階では信号はまだアナログ値である,量子化は,得られた信号をデジタル値に変換する.Arduino Duemilanove 328では,量子化によって信号の値は 1024 = 210 段階のいずれかに離散化される.信号を表現に用いられるビット数のことを量子化ビット数と呼ぶ.つまり Arduino のアナログ入力の量子化ビット数は 10である.

6.1 距離センサ

距離センサとして,シャープ製の GP2Y0A21YK(図 14)を使用する.対象物に赤外光を照射し,その反射光の向きから三角測量の原理で距離を測定する.本センサに限らず,電子部品には特性などを記したデータシートが通常用意されている.本センサのデータシートは以下にある.https://www.sparkfun.com/datasheets/Components/GP2Y0A21YK.pdf

ケーブル赤を 5Vに,ケーブル黒を GNDに接続して,センサに電源を供給する.ケーブル黄の電位が対象物までの距離を反映した出力となる.このケーブル黄を Arduinoのアナログ入力ピンに接続することで,対象物までの距離を計測することができる (図 15).センサからのケーブル (より線)の末端は,Arduinoのピンに刺す前に十分ねじっておく (図 16).

6.2 アナログ入力を行うプログラム

アナログ入力ピン A0で得られた電圧を,PCのシリアルモニタに出力するプログラムを作成する.デジタルピンの場合は,入力と出力の双方に用いることができたため,いずれに用いるのか設定が必要だった

図 14 距離センサ

18

Page 19: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 15 距離センサと Arduinoの接続

図 16 より線の末端の処理 (上→下)

が,アナログ入力ピンは,入力専用であるため,pinMode()による入出力の設定は不要である.

リスト 6 赤外線距離センサ (その 1)

1 const int sensorPin = A0;2 const int wait = 30;34 void setup() 5 Serial.begin(9600);6 78 void loop() 9 int val = analogRead(sensorPin);

10 Serial.println(val);11 delay(wait);12

センサを上向きにして,手やノートなどの光を反射する物体をセンサの上で上下させると,シリアルモニタに出力される値が変わるのが確認できる (図 17).

演習: 距離センサの使用 距離センサを Arduinoに接続し,プログラムを作成・実行し,シリアルモニタを開いて,距離センサから出力されている値と,距離との関係について確認する.

19

Page 20: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 17 距離センサでの計測の様子

6.3 距離センサと LEDの連携

距離センサから得られる出力に応じて,LEDの点滅速度を変えてみよう.ここでは最も単純に,得られた値の時間間隔で,ボード上の LEDを点滅させてみよう.注意: 前節のプログラムと異なり,ここではボード上の LED(13番ピン)を点滅させている.

演習: 距離センサと LEDの連携 以下のプログラムを実行せよ.

リスト 7 赤外線距離センサと LEDの連携1 const int sensorPin = A0;2 const int ledPin = 13;34 void setup() 5 pinMode(ledPin, OUTPUT);6 78 void loop() 9 int val = analogRead(sensorPin);

10 digitalWrite(ledPin, HIGH);11 delay(val);12 digitalWrite(ledPin, LOW);13 delay(val);14

6.4 実習

演習: 距離センサと LED の連携 その 2 先程のプログラムでは,手が近付くと点滅が遅くなったが,手が近付く方が点滅が速くなる方がそれらしいように思われる.センサから得られた値が 0のときに delay(500),600のときに delay(20)となるような変換を考えて実装してみよ.ヒント: センサから得られた値を x,delay()の引数を y とする.2点を通る直線の式を考える.x = 0のとき

y = 500,x = 600のとき y = 20なので,傾きは (20 − 500)/(600 − 0) である.Arduinoでも int型を int型で除算した結果は int 型であることに注意が必要.また y 切片は x = 0 のときの y の値なので自明.図 18 を参照のこと.

演習: 距離センサと LEDの連携その 3 11番ピンに外付けの LEDを接続し,距離に応じて LEDの明る

20

Page 21: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

0 100 200 300 400 500sensor value

0

100

200

300

400

500

600

dela

y

図 18 センサから得られた値を変換する

さが変化するようにせよ.analogRead()で得られる値の範囲は 0-1023であるが,analogWrite()の出力範囲は 0-255の値であるため,そのまま出力するわけにはいかず,スケールの変換 (適切な定数を乗じる)が必要であることに留意せよ.

21

Page 22: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

7 距離センサのキャリブレーション距離センサから計測結果が得られるようになったが,得られた値は実際の距離そのものではなく,センサの出力電圧を更に量子化した値であり,そのまま使用するには直感的ではない.距離センサの出力電圧と距離との関係を図 19に示す.実用区間である 10cmから 80cmの間では,距離と出力電圧はほぼ反比例するように見受けられるが,出力電圧を距離に変換するためには係数まで含めた曲線の式を決定する必要がある.本節では,距離センサのキャリブレーション (calibration;較正)を行う.既知の距離に物体を置いたときのセンサの出力電圧を計測し,出力電圧と距離の組から,曲線の式を決定する.曲線の形を仮定して,係数を決定することが一般的である.キャリブレーションは本実験で使用する距離センサのみならず,あらゆるセンサの使用において重要である.

7.1 得られた値を電圧に変換する

A/D変換の量子化処理と逆の処理を行い,得られた値を電圧に変換する.すなわち,得られる値は 0から 1023の 1024段階で,0が 0V,1023が 5Vに対応していることから,5.0 / 1024を乗じることで,電圧に直すことができる.analogRead()の結果は int型であるが,変換後の電圧は float型に格納する必要があることに注意する.また

C言語は int型 / int型 = int型であるから,analogRead()の値を先に 1024で割ってしまうと正しい結果が得られない (常に 0になる).演算の順序に気を付ける.演算子には優先順位があり,*および /は同じ優先順位で,更に演算は左結合である.左結合とは,任意の演算子を としたときに,ABCを (AB)Cと解釈することである.以上を踏まえると,

1 int val = analogRead(A0);2 float voltage = val * 5.0 / 1024;

は正しく動作する.参考のため,以下に正しく動作しない例を示す.正しく動作しない例 1:

1 int val = analogRead(A0);2 float voltage = val / 1024 * 5.0;

正しく動作しない例 2:

図 19 距離センサのスペックシート

22

Page 23: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

1 int val = analogRead(A0);2 float voltage = val * 5 / 1024;

7.2 出力電圧の平均を求める

曲線の式を求めるためには,ある既知の距離に物体を置いた場合の出力電圧が必要である.これまで見てきてわかるように,センサの出力電圧にはばらつきがある.まず,電圧を複数回計測し,平均と分散と標準偏差を算出するプログラムを作成する.曲線を求めるという目的のためには,平均があれば事足りるが,誤差の程度も興味があるので,標準偏差も算出する.プログラムは以下のようになる.このプログラムは,まず連続して numData 回分のサンプリングされた信号を配列変数 buffer[] に保存する.サンプリングが終了すれば統計量を calcStats() で計算し,シリアル通信で PC に送信することを繰り返す.ここではサンプル数は 100 としている.統計量の計算はサンプルデータをxi (i = 1, 2, . . . , n) とすると以下の通りである.

• 平均 x

x =1n

n∑i=1

xi

• 分散 σ2

σ2 =1n

n∑i=1

(xi − x)2

• 標準偏差 σσ =

√σ2

リスト 8 複数回計測による統計量の算出1 const int numData = 100;2 float buffer[numData];3 int n;4 float average, variance, sd;56 const int sensorPin = A0;7 const int wait = 30;89 void calcStats()

10 float sum;1112 sum = 0.0;13 for (int i = 0; i < numData; i++) 14 sum += buffer[i];15 16 average = sum / numData;1718 sum = 0.0;19 for (int i = 0; i < numData; i++) 20 float d = buffer[i] - average;21 sum += d * d;22 23 variance = sum / numData;2425 sd = sqrt(variance);26 2728 void setup() 29 Serial.begin(9600);30 n = 0;

23

Page 24: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

31 3233 void loop() 34 int val = analogRead(sensorPin);35 float voltage = val * 5.0 / 1024;36 buffer[n] = voltage;37 n++;3839 if (n == numData) 40 calcStats();4142 Serial.println("");43 Serial.println(average);44 Serial.println(variance, 6);45 Serial.println(sd, 3);4647 n = 0;48 49 delay(wait);50

ソースコードに若干の解説を加える.

• 配列変数は C言語と同じように使用できる.• Serial.println()で float型の変数を出力する場合,第 2引数で小数点以下の桁数を指定できる.

7.3 最小二乗法により近似曲線を求める

曲線の式として,出力電圧 v と距離 d の間に,

d =av+ b (1)

の関係を仮定する.v と d が変数である.係数 aと bの値を求めたい.ここで,見通しをよくするために,x = 1/v, y = d と置くと,

y = ax + b (2)

となる.いま,距離 di に物体を置いたときの出力電圧を vi とすると,xi = 1/vi, yi = di であり,(xi, yi) の組が n

組計測されたときに,最良の 1次近似を与える直線のパラメータ a, bを決定する問題となる.これは最小二乗法により求めることができる.誤差の二乗和

e =n∑i=1yi − (axi + b)2 (3)

が最小になる十分条件である,∂e∂a= 0,∂e∂b= 0 (4)

を満たすための条件を書き下すと,a, bの連立方程式となり,(∑ni=1 x2

i

∑ni=1 xi∑n

i=1 xi∑n

i=1 1

) (ab

)=

(∑ni=1 xiyi∑ni=1 yi

)(5)

左辺の 2× 2行列の逆行列を左から乗じることによって,a, bが求まる.

7.4 演習

演習: 距離センサのキャリブレーション

24

Page 25: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 20 キャリブレーション用のデータを取得している様子

1. 複数回 (ここでは 100回とする)計測により平均と標準偏差を算出するプログラムを作成,実行せよ.2. 25cm定規を配布するので前に取りに来る.3. 配布した定規の左端に距離センサを置き,10cm,15cm,20cm,25cmの 4点に,順次何か光を反射する平面を置く (図 20).先程作成した 100回の計測結果の平均値を出力するプログラムを実行し,各距離における出力電圧の平均値を得る.

4. 得られた結果からパラメータ a, bの値を算出する.最小二乗法により係数 a, bの値を算出する excelファイルを配布するので,各自,a, bの値を求めよ.

5. 求めた a, bの値を用いて,距離をシリアル通信で出力するプログラムを作成せよ.テンプレートを以下に示す.___に適切なソースコードを記述すればよい (注意: _の数は実際の文字数とは一致しない).

6. 作成したプログラムによって,キャリブレーションに用いた距離以外に物体を置いたときも,正しく距離が計測できていることを確認する.例えば 12cmや 23cm,また定規をつなぎあわせてキャリブレーション範囲より遠方の 30cmや 40cmなどを試してみよ.

リスト 9 距離計測1 const int sensorPin = A0;2 const int wait = 30;34 float convertToDistance(int val)5 6 const float a = ______;7 const float b = ______;8 float v = val * _________; // vは電圧9 float d = _______________; // 電圧から距離に直す (式 1を使う)

10 return d;11 1213 void setup() 14 Serial.begin(9600);15 1617 void loop() 18 int val = analogRead(sensorPin);19 float distance = convertToDistance(val);20 Serial.println(distance);21 delay(wait);22

25

Page 26: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

8 基本的な信号処理手法センサから得られる信号に基づいて各種の処理を行う場合に必要となる,信号処理の基本的な知識を学ぶ.題材として,近付いたらライトが付くという人感センサを簡易的に実装する.ライトの代わりに,11番ピンに接続した外付けの LEDが点灯するものとする.

8.1 距離に応じて LEDが点灯・消灯

演習: 距離に応じて LEDが点灯・消灯 ある距離よりも近くに物体がある場合に,LEDが点灯するプログラムを作成せよ.このとき,距離は cm単位で表すものとし,任意の閾値を設定できるようにせよ.テンプレートを以下に示す.

閾値とは 一般的に,二つの状態が何らかの値に応じて切り替わる場合の,境界となる値を閾値と呼ぶ.「いきち」が本来の読み方だが,「しきいち」と読まれることも多い.

リスト 10 距離に応じて LEDを点灯・消灯1 const int sensorPin = A0;2 const int ledPin = 11;3 const int wait = 30;45 const float distanceThreshold = 20.0;67 float convertToDistance(int val)8 9 // [TODO] 前節で記述したコードをここに書く

10 1112 void setup() 13 pinMode(ledPin, OUTPUT);14 Serial.begin(9600);15 1617 void loop() 18 int val = analogRead(sensorPin);19 float distance = convertToDistance(val);2021 // [TODO] distanceの値に応じて LEDを点灯・消灯させる22 // ifと digitalWrite()を使う.2324 Serial.println(distance);25 delay(wait);26

8.2 閾値近辺での挙動の安定化: ヒステリシスの導入

前節のプログラムでは,閾値近辺に手をかざした場合,LEDが不規則に点滅する場合がある.センサには計測誤差があり,また手の微妙な動きによっても距離が変動するため,閾値近辺では挙動が安定しない.計測された距離と,閾値を 20cmに設定した LEDの状態の時間変化を図 21に示す.この問題を解決するためには,LEDの状態が ONから OFFになる閾値と OFFから ONになる閾値に,ヒステリシスと呼ばれる変化方法に応じた差を設ける.設定したい距離を d0 とした場合に,OFFから ONの場合には,

26

Page 27: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

17

18

19

20

21

22

23

0 500 1000 1500 2000 2500 3000 3500

dist

ance

[cm

]

time [ms]

distanceon/off

図 21 閾値近辺で状態が不安定になる例

ヒステリシス処理なし

ヒステリシス処理あり

図 22 ヒステリシス処理の導入効果

距離の値が小さくなる方向に変化するので,閾値 1: d0 − ∆を使い,ONから OFFの場合には,距離の値が大きくなる方向に変化するため,閾値 2: d0 + ∆を使う.∆の適切な大きさは問題により異なる.ヒステリシスを有する閾値処理を行った場合の,状態の変化を図 22に示す.緑色の線がヒステリシス処理無しの場合の閾値 (時間によらず一定),赤色の線がヒステリシス処理有りの場合の閾値 (状態により決まる) である.状態は黄色 (ON)と灰色 (OFF)の 2種類があり,各時刻での状態をグラフ下の横棒で示している.ヒステリシス処理が無い場合は,変化途中やノイズにより状態が不安定に変化する場合が見られるが,ヒステリシス処理を行うことによって安定化がなされることがわかる.

演習: ヒステリシスの導入 上記のヒステリシス処理を導入した閾値処理を実装せよ.テンプレートを以下に示す.

プログラム作成のヒント 現在の状態によって,異なる閾値を用いることから,プログラムには,現在の状態を保持する変数を追加する必要がある.テンプレートでは int 型の変数 state を用意している.state には HIGHと LOWのいずれかが格納され,HIGHの時が点灯,LOWの時が消灯とする.

リスト 11 ヒステリシスを導入した閾値処理1 const int sensorPin = A0;

27

Page 28: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

2 const int ledPin = 11;3 const int wait = 30;45 const float distanceThreshold = 20.0;6 const float delta = ______; // [TODO] 適切な値を設定78 int state;9

10 float convertToDistance(int val)11 12 // [TODO] 前節で記述したコードをここに書く13 1415 void setup() 16 pinMode(ledPin, OUTPUT);17 Serial.begin(9600);1819 state = LOW;20 2122 void loop() 23 int val = analogRead(sensorPin);24 float distance = convertToDistance(val);2526 // [TODO] stateと distanceの値に応じて LEDを点灯・消灯させる27 // ifと digitalWrite()を使う.28 // stateの更新も必要.2930 Serial.print(millis());31 Serial.print(" ");32 Serial.print(distance);33 Serial.print(" ");34 Serial.println(state);35 delay(wait);36

ソースコードに新しく登場した関数は以下の通り.

• millis(): 起動からの経過時間をミリ秒で返す.• Serial.print(): 改行しないでシリアル通信に出力する.

8.3 挙動の安定化 2: ノイズ除去 (この項は全て発展課題)

ヒステリシスの導入によって,閾値近辺での挙動が安定したが,状態の判定には依然としてセンサの生の値を用いているため,突発的なノイズに対しては不安定であることには変わりがない.この問題に対応するためには,複数のサンプル値を用いてフィルタ処理を行うことが有用である.画像処理におけるノイズ除去と同様の手法が効果的である.ここでは,移動平均 (moving average)と中央値 (median filter)を試してみる.

8.3.1 移動平均移動平均のうち最も基本的なものが単純移動平均である.単純移動平均とは,直近の n 個のサンプルの平均である.計測値を xi とすると,単純移動平均の出力 yi は,

yi =1n

i∑j=i−n+1

x j (6)

28

Page 29: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

となる.例えば n = 5とすると,yi =

15

(xi−4 + xi−3 + xi−2 + xi−1 + xi) (7)

である.よって,最近の複数のサンプル値を保持しておき,その都度平均を求めればよい.より効率を追求すると,サンプルごとに総和の計算を行う必要は無いことに気付く.yi−1 を考えると,

yi−1 =1n

i−1∑j=i−n

x j (8)

であるから,辺々引いて整理すると,yi = yi−1 +

1n

(xi − xi−n) (9)

となる.長い区間の移動平均を考える場合には検討する価値がある.

演習: [発展]移動平均 リアルタイムで得られている数値データについて,移動平均を実装せよ.

8.3.2 中央値移動平均は処理が単純で実装も容易であるが,極端に大きい/小さい値が突発的に計測された場合に,その影響が一定時間残るという問題がある.これに対応する方法として,移動平均と同じように一定時間内のサンプルを保持しておき,その中央値を採用する手法があり,中央値 (median)フィルタと呼ばれる.一定区間内の中央値を得るためには,区間内の値を保持し,ソート (並べ替え)を行った後,n/2番目の値を使うのがよい.arduinoにはソート関数は提供されていないので,各自,ソートアルゴリズムを調べて実装せよ.

演習: [発展]中央値 中央値に基づいたフィルタリング処理 (メディアンフィルタ)を実装せよ.)

8.4 更なる発展課題

• (節電) 点灯してから一定時間経過後に消灯するようにする.消灯した場合,点灯距離内に物体がある場合は,一定距離以上移動すれば再度点灯する.

• (誤検出対応)点灯するタイミングを,点灯距離内に物体が入ってから一定時間経過後にする.

29

Page 30: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

9 Processingとの連携センシングした結果を,Arduinoを経由して PCに送信し,PC側のプログラムで使用することができれば,活用の幅が大きく広がる.シリアル通信によって情報を送付することは学習済みであるので,PC側のプログラムを適切に作成することによって可能となる.ここでは,Processingを使用してプログラムを作成する.

9.1 通信の設計

Arduinoと PC(Processing)の間で通信を行うにあたっては,通信の方法を定めておかなければならない.

• データはバイナリで送るか? テキストで送るか?• データは Arduinoから連続的に送り続けるか? PCからの要求があったときのみ送るか?

バイナリで送る場合,関数 Serial.write()を使用する.1バイトのデータを送る場合:

1 int val = analogRead(sensorPin);2 Serial.write(val);

valの値が 0以上 255以下である必要がある.256以上,すなわち 2バイトの整数値をバイナリで送信したい場合は,上位バイトと下位バイトの値を明示的に取得し,順次送信する.この場合に,下位バイトから送信する場合 (リトルエンディアン)と,上位バイトから送信する場合 (ビッグエンディアン)の 2通りが考えられる.いずれの手順で送受信を行うかについて,送信側と受信側で事前に合わせておく必要がある.リトルエンディアンの場合:

1 int val = analogRead(sensorPin);2 Serial.write(val & 0xff);3 Serial.write(val >> 8);

テキストで送る場合は,既に何度か行っているように,関数 Serial.println()を使用する.1 int val = analogRead(sensorPin);2 Serial.println(val)

テキストの方が人間にとっての可読性が高い (シリアルモニタで値を確認するのが容易)が,通信量が増える点は留意する必要がある.例えば valの値が 120の場合に送信されるデータは,

• バイナリで送信: 120 (1バイト)• テキストで送信: ’1’(=49) ’2’(=50) ’0’(=48) 13 10 (5バイト)テキスト送信末尾の 13 10は改行コード (CR LF)である.

と差がある.例えば,計測を 1秒間に 100回行いたい場合,シリアル通信を 9600bps(bpsは Bit Per Secondの略.9600ビット/秒=1200バイト/秒)で行うと,1回の送信での最大通信量は 12バイトであるため,複数の計測結果を送信する場合,テキストでは帯域が不足する可能性がある.本節では最も単純な 1バイトのバイナリ通信を行う.

9.2 Arduino側のプログラム

ここでは,距離センサから得られた距離情報を,cm単位の整数に直し (これで 1バイトに収まる),1バイトずつバイナリ送信するものとする.

リスト 12 Arduinoと PCで通信: Arduino側1 const int sensorPin = A0;2 const int wait = 30;

30

Page 31: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 23 Processingの起動

34 float convertToDistance(int val)5 6 // [TODO] 以前記述したコードをここに書く7 89 void setup()

10 Serial.begin(9600);11 1213 void loop() 14 int val = analogRead(sensorPin);15 float distance = convertToDistance(val);16 byte distanceByte = byte(distance);17 Serial.write(distanceByte);18 delay(wait);19

※ 8.3節のノイズ除去を実装した人は,上記プログラムに加えてみよ.

ソースコードの説明 byte型は符号無し 1バイト整数で,C言語の unsigned char型に相当する.Arduinoではunsigned char型も使用できるが,byte型を使うことが一般的である.変数 distanceに代入するときに使用している関数 byte()は,型名と同じであるが,こちらは型の変換を行う関数である.C言語のキャストに相当する.送信は関数 Serial.write()で行うことは既に述べた.引数に byte型を与えることで,1バイトのバイナリ送信が行われる.

9.3 Processing側のプログラム

Processingの方では,受け取った値に応じて,さしあたり円の大きさを変えるようにしてみる.Processingの起動は, スタートボタン 3.マルチメディア processingにより行う (図 23).統合開発環境の

Processing Environmentが起動する.見た目は非常に Arduino IDEと似ている.アイコン一番左端のボタン (右向き矢印)でプログラムが実行される.

Processingに関連する情報は,本家のウェブサイトが詳しいが,やはり英語である.ただそれほど難しくはないしサンプルを見れば意味がわかるので,英語を参照する練習としてもよい題材であろう.

31

Page 32: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

• リファレンス: https://processing.org/reference/• ライブラリ: https://processing.org/reference/libraries/特にシリアル通信関係: https://processing.org/reference/libraries/serial/index.html

• サンプル: https://processing.org/examples/

また,Processing Environmentのメニュー File Examples...を開くと,大量のサンプルプログラムがすぐ実行できるようになっている.適宜実行してみると楽しい.日本語の各種情報はウェブを適宜検索せよ.

リスト 13 Arduinoと PCで通信: Processing側1 import processing.serial.*;23 Serial port;4 int val;56 void setup() 7 size(512, 512);89 printArray(Serial.list());

10 String arduinoPort = Serial.list()[0];11 port = new Serial(this, arduinoPort, 9600);12 val = 0;13 1415 void draw() 16 background(255, 255, 255);17 stroke(64, 64, 64);18 fill(0, 64, 255);19 int radius = (60 - val) * 8;20 if (radius < 0) 21 radius = 0;22 23 ellipse(width / 2, height / 2, radius, radius);24 2526 void serialEvent(Serial p)27 28 val = p.read();29

ソースコードの説明 Processing側のソースコードについて,解説する.

• プログラム全体の構造は,Arduinoと似ている.初期化は関数 setup()を定義して行う.描画処理は関数draw()で行う.関数 draw()は適宜自動的に呼び出されるため,draw()内でループを回す必要は無い (特に無限ループを回してはいけない).

• 1行目はシリアル通信を行うために必要である.ライブラリを importする.• シリアル通信は Serialクラスを利用して行う.3行目の Serial port;により宣言する.• Serial.list() は PC のシリアルポートの一覧を得る関数であり,printArray() によってコンソール

(IDEの下の黒い部分)に表示される.Arduinoが接続されているポートが何番目であるかを確認し,次の行の Serial.list()[0]の添字を適切に変更する.例えば

1 [0] "COM1"2 [1] "COM3"3 [2] "COM6"

と表示されて,Arduinoが接続されているポートが COM6であれば,0を 2に変更する.• 次の new Serial(...)でシリアルクラスのインスタンスを生成.

32

Page 33: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

• シリアル通信でデータが送られてくると,関数 serialEvent()が呼び出される.引数としてシリアルポートが与えられる.p.read()で値を得る (バイナリの場合).

• 描画関係のソースコードをまとめて解説する.– setup()中の size()は開くウィンドウのサイズを指定する.– 変数 width および height はウィンドウのサイズである.自動的に宣言され,自動的に値が設定される.

– background()で背景を初期化する.色は順に RGBである.– stroke()で線の色を指定する.線を描きたくない場合は noStroke()を指定する.– fill()で塗り潰しの色を指定する.塗り潰したくない場合は noFill()を指定する.– ellipse()は楕円を描く関数で,中心の X座標,Y座標,X方向直径,Y方向直径である.

演習: Arduinoと Processingの通信 上記二つのプログラムを実行して Arduinoと Processing間で通信を行い,手とセンサの距離に応じて円の大きさが変わることを確認する.

演習: バリエーション 円の大きさを変える代わりに,(1) 色を変える (2) 位置を変える (3) 円ではなく矩形 rect() を描画する等の変更を加えてみる.rect() については以下を参照.https://processing.org/reference/rect_.html他,適宜自由な発想で行うこと.

9.4 複数バイトからなる情報を送る

Serial.println()を使用して送信されたテキスト情報を Processingで受けるには,readStringUntil()を使用する.

演習: [発展] 1 バイトのバイナリ通信では,小数点以下の距離情報が失われてしまう.Arduino 側では Serial.println() で float 型の距離を小数点 2 桁で送信し,Processing 側では serialEvent() 中でreadStringUntil(10)を使用して数値を文字列として得て,float()を用いて float型に変換する.値を格納する float型の変数は,適宜グローバル変数として宣言せよ.Serial.readStringUntil() は読み込みに失敗すると null を返すため,返値が null でないことを確認し,nullではない場合のみ数値型に変換する.

9.5 発展課題

• 送信されてくる値の時間変化をグラフとして表示せよ.Processingで配列を使いたい場合,例えば 100要素の int型配列は以下のように宣言する.

1 int[] array = new int[100];

参照: https://processing.org/reference/Array.html• Processingのサンプル (メニュー File Examples...)の一つを,手とセンサの距離を使用するように変更してみよ.例えば Topics Simulate Springは相性がよさそうだ.

33

Page 34: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

10 様々なセンサ距離センサだけでは飽き足りない人向けに,様々なセンサを使用するための案内を記す.センサの出力様式としては,主に以下の種類がある.

• 電圧変化• 電流変化• 抵抗変化• 静電容量変化• パルス出力

既に使用した距離センサは,測定値に応じて電圧が変化するものであり,その変化範囲も Arduinoでの A/D変換に適したものであった.以下で紹介する加速度センサもこのタイプである.曲げセンサや光センサ (CdSセル)は,測定値に応じて抵抗が変化する.センサに単に電圧をかけるだけでは抵抗の変化を捉えることはできないため,別の抵抗をセンサと直列に接続して基準電圧を分圧することで,電圧の変化が生じ A/D変換可能になる.

10.1 加速度センサ

3 軸加速度センサモジュール KXM52-1050 を使ってみる.データシートはこちら: http://akizukidenshi.com/download/KXM-52.pdf *2

仕様に従って,センサモジュールの各ピンと Arduinoを,以下のように接続する.

• 1 Vdd: 電源入力 Arduino 3.3Vに接続 (5Vではないので注意)• 2 PSD:パワーシャットダウン 1番ピン (Vdd)と接続• 3 GND: Arduino GNDに接続• 4 Parity: どこにも接続しない• 5 SelfTest: 3番ピン (GND)と接続• 6 OutX: X軸加速度出力 Arduino A0に接続• 7 OutY: Y軸加速度出力 Arduino A1に接続• 8 OutZ: Z軸加速度出力 Arduino A2に接続

配線にはブレッドボードを利用する.ブレッドボードはハンダ付け不要で回路を組むことができるため試行錯誤には非常に便利である.ボード内部でピン間が電気的に接続されている (図 24).センサモジュールのピン配置を図 25に示す.また実体配線を図 26に示す.センサモジュール上に一部のピン番号が印刷されているので,確認しながら接続する.このセンサは最大 ±2Gまで計測できる (Gは重力加速度).推奨されている電源電圧 3.3Vで使用した場合,0Gで 1.65V(ちょうど電源電圧の半分)の出力となる.感度は 0.66V/Gである.以上を踏まえると,アナログピンからの入力 (0-1023)を,加速度に変換する式は,以下のようになる.a = (val * 5.0 / 1024 - 1.65) / 0.66;

静止状態では,Z軸方向に重力がかかっている他,X軸 Y軸の両方向も完全に 0にはならない.この誤差を取り除くためには,起動時に何サンプルか計測し,平均を零点として使用するのがよい.サンプルプログラムを以下に示す.

リスト 14 加速度センサ: 最初の 10サンプルの平均との差を出力

*2 現在販売されていない.後継は KXR94-2050.

34

Page 35: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

省略していますが途中も同様です

図 24 ブレッドボード

1 Vdd

2 PSD

3 GND

4 Parity

8 OutZ

7 OutY

6 OutX

5 SelfTest

図 25 3軸加速度センサのピン配置

図 26 加速度センサと Arduinoの接続

35

Page 36: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

1 const int xAccPin = A0;2 const int yAccPin = A1;3 const int zAccPin = A2;4 const int wait = 30;56 float convertToAcceleration(float val)7 8 float a;9 a = (val * 5.0 / 1024) / 0.66;

10 return a;11 1213 float xAverage, yAverage, zAverage;1415 void setup() 16 Serial.begin(9600);1718 const int numAverage = 10;19 int xSum = 0;20 int ySum = 0;21 int zSum = 0;22 for (int i = 0; i < numAverage; i++) 23 xSum += analogRead(xAccPin);24 ySum += analogRead(yAccPin);25 zSum += analogRead(zAccPin);26 delay(wait);27 28 xAverage = float(xSum) / numAverage;29 yAverage = float(ySum) / numAverage;30 zAverage = float(zSum) / numAverage;31 3233 void loop() 34 int x = analogRead(xAccPin);35 int y = analogRead(yAccPin);36 int z = analogRead(zAccPin);37 float xAcc = convertToAcceleration(float(x) - xAverage);38 float yAcc = convertToAcceleration(float(y) - yAverage);39 float zAcc = convertToAcceleration(float(z) - zAverage);40 Serial.print(xAcc, 3);41 Serial.print(" ");42 Serial.print(yAcc, 3);43 Serial.print(" ");44 Serial.println(zAcc, 3);45 delay(wait);46

計測時の誤差の除去には,移動平均や中央値フィルタによるノイズ除去も有効である.

10.2 曲げセンサ・光センサ

曲げセンサや光センサ (CdS セル) のように測定結果が抵抗値に反映されるセンサを使用する場合は,別の固定値の抵抗と直列に接続し,定電圧を分圧することで抵抗値を計測する.ここでは曲げセンサを題材にして説明する.曲げセンサの抵抗を Rb,直列に接続する抵抗を R0 とする.電圧 V を印加すると,曲げセンサでの電圧降下 Vb

は,Vb =

Rb

Rb + R0V (10)

となる.Rb の変化と Vb の変化は比例関係にはなく,またそもそも抵抗が計測対象の物理量と比例するとは限ら

36

Page 37: エルゴノミクスコンピューティング実習 入力インタ …imura-lab.org/wp/wp-content/uploads/2015/09/arduino.pdf1 Arduino で動作するプログラムの開発 本節では,Arduino

図 27 曲げセンサの使用

ないため,定量性を得るためにはキャリブレーションが必要である.曲げセンサの使用時の実体配線を図 27に示す.曲げセンサの抵抗は,曲げ無し時に約 10kΩ,曲げると最大約 20kΩ である.CdS セルの抵抗は,光無しで約

300kΩ,光ありで小さくなり,手で遮った程度だと約 20kΩ,ライトを近接させて照射すると 100Ω以下になる.いずれも 20kΩ程度の抵抗を直列に接続する程度がよいだろう.

直列に入れる抵抗の最適な値 直列に入れる抵抗を R として,最適な値を考えてみよう.センサの抵抗値がRmin から Rmax まで変化するとする.電圧 V0 を印加したときのセンサによる電圧降下を,それぞれ Vmin およびVmax とすると,

Vmin =Rmin

R + RminV0 (11)

Vmax =Rmax

R + RmaxV0 (12)

電圧降下の差 ∆V を考える.∆V = Vmax − Vmin =

(Rmax

R + Rmax− Rmin

R + Rmin

)V0 (13)

∆V は Rの関数であるから,どこかで極大となることが期待される.Rで微分すると,

∆V ′ =[− Rmax

(R + Rmax)2 +Rmin

(R + Rmin)2

]V0 (14)

であるから,極値を取るのは,

− Rmax

(R + Rmax)2 +Rmin

(R + Rmin)2 = 0 ⇐⇒ Rmin(R + Rmax)2 − Rmax(R + Rmin)2 = 0 ⇐⇒ R2 − RminRmax = 0 (15)

よって,R =

√RminRmax (16)

37