Arduinoプログラミング ものづくり (ATtiny4313版)

小山智史

コンピュータの仕組み ATtiny2313版 - - - - -
Arduinoでものづくり ATtiny4313版 ATmega328P版 NANO版 - ESP8266版 XIAO版
tinyBasicでものづくり - - NANO版 ProMicro版 ESP8266版 XIAO版
Pythonでものづくり - - - - - XIAO版
Javascriptでものづくり - - - ProMicro版 - XIAO版

目次



0. 準備
1. コマンド操作
2. デジタル出力(1): LEDの点滅
3. デジタル入力: スイッチ操作でLEDのオンオフ
4. アナログ出力: LEDの明るさを変える
5. アナログ入力
6. デジタル出力(2): 数字表示LED
7. デジタル出力(3): 液晶ディスプレイ
8. センサーの利用
9. ブザー音で演奏
10. 合成音声でおしゃべり
11. イルミネーション
12. 赤外線リモコン
13. 動くものを作る
14. 押しボタン信号機
(付録1) 入出力モジュール
(付録2) 使用する主な部品
(付録3) Arduinoプログラムの仕様の抜粋(ATtiny4313版)

 私達の身近にある電子機器の多くにコンピュータが内蔵されており、このようなコンピュータは「マイコン」と呼ばれます。マイクロコンピュータあるいはマイクロコントローラの略です。マイコンにはメーカーが開発したプログラムが書き込まれています。リモコンの中に入っているマイコンには、押したボタンに応じて決まったパターンで赤外線をオンオフするプログラムが書き込まれています。このプログラムはマイコン製造時に書き込まれ、後から書き換えることはできませんが、フラッシュメモリ(電気的に書き換え可能なプログラムメモリ)を搭載したマイコンを使えば、私達もさまざまな機器を作ることができます。
 マイコンを使った機器を設計・製作するには、電子回路、論理回路、コンピュータの仕組み、プログラミングなど広範囲の知識が要求されます。これは大変なことではありますが、一方ではこれらのことを学ぶ格好の教材であるということを意味しています。
 このテキストでは、AVRマイコン(ATtiny4313)[1]を使った実習を行いながら、「マイコンを使ったものづくり」について学習します。また、開発環境はArduino IDE[2]を使います。例題の多くは、ArduinoIDEの「スケッチの例」をそのまま、あるいは多少アレンジして取り上げています。

☆ コンピュータの仕組みを学びたい方は「AVRマイコンで学ぶコンピュータの仕組み」[3]を参考にしてください。
☆ AVRライターはArduinoISPを使います。「AVRライター」[4]をご覧ください。
☆ ここでは複雑な電子回路は登場していませんが、電気回路の最低限の知識は前提としています。
☆ 本テキストは実習時に適宜解説や補足を行うことを前提にしています。自学自習には適していないかもしれません。

0. 準備

0.1 ブレッドボードの使い方

 ここでは下図左のブレッドボード(EIC-301)を使います。ボードの内部は下図右のように接続されています。

(外観) (内部の接続)

 電子工作の第一歩として、下図左の回路図を組み立ててみましょう。実際の配線は下図右のようになります。これを実体配線図と言います。なお、結線はジャンパーワイヤを使って行います。配線を行う時は、必ず電池のスイッチを切るようにします。

(回路図)(実体配線図)

電池のスイッチを入れてLEDは点灯しましたか? 下の写真は実際の様子です。


(12cm×5cm 程の板を用意し、ブレッドボードと電池ケースを貼りつけています。)

 ここで、ブレッドボード内部の接続を念頭に、下図のような電流の流れをイメージすることが重要です。LEDは電流が流れるから点灯するのです。

 下図のように押しボタンスイッチを追加し、スイッチを押すとLEDが点灯するようにしてみましょう。スイッチを押すと電流が流れるわけですが、ここでも「電流が流れる経路」を(ブレッドボードの内部結線を含めて)イメージしてください。

(回路図)(実体配線図)

 次のステップへの準備として、AVRマイコン(ATtiny4313)を接続しましょう。電池のスイッチは切り、AVRマイコンの向きに気を付けて差し込んでください。

(回路図)(実体配線図)

 ここで、AVRマイコンにラベルを貼っておきます(テプラファイル)。

0.2 Arduino開発環境の準備

  1. Arduino 1.8.15(Windows ZIP file)をダウンロードし、マウスの右クリックで[すべて展開]します(時間がかかります)。
  2. 「c:/arduino」フォルダを作成し、この中に上で展開したArduino-1.8.15フォルダの中のArduino-1.8.15フォルダを移動します。また、c:/arduinoの下に「src」フォルダを作成し、ここに作成したプログラムを保存することにします。
  3. c:/arduino/Arduino-1.8.15/arduino.exeのショートカットを作り、デスクトップまたはスタートメニューに置きます。
  4. Arduinoを起動し、[ファイル][環境設定]で、「スケッチブックの保存場所」を「c:/arduino/src」にします。「エディタの文字の大きさ」は「20」程度にすると見やすいです。また、「追加のボードマネージャのURL」の右端のアイコンをクリックするとウィンドウが現れるので、ここに を追加します[5]。
  5. [ツール][ボード][ボードマネージャ]で「ATTinyCore」をインストールします。
  6. [ツール][ボード]で「ATtiny2313/4313」を選択し、[ツール][chip]で「ATtiny4313」を選択し、[ツール][clock]で「8MHz(internal)」ATtiny4313を選択ライタを接続した後、[ツール][シリアルポート]でポートを選択します。ポートはライタを接続した際には毎回確認してください。また、[ツール][書込装置]を「Arduino as ISP」にします。
  7. 初めて使用するマイコンチップ(ATtiny4313)は、ライタを接続して[ツール][ブートローダを書き込む]の操作を1度行う必要があります。

1. コマンド操作

* Arduinoは対話的に利用することはできません。


2. デジタル出力(1): LEDの点滅

2.1 LEDを点灯・消灯する

 以下の回路を組み立ててください。


回路図

実体配線図

 以下のプログラムは、Arduino IDE の [スケッチの例][01.Basics][Blink] の正味の部分を抜粋したものです。「//」以降行末まではコメントですので、入力しなくても構いません。

Blink.ino: ArduinoIDE [スケッチの例][01.Basics][Blink]

int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // { }内を繰り返し実行する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(1000); // 1秒(1000ms)待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(1000); // 1秒(1000ms)待つ }

 Arduinoを起動し、下図のようにプログラムを入力し、c:/arduinoフォルダにBlinkという名前で保存してください。そのフォルダにBlinkフォルダが作られ、中にBlink.inoというファイルが作られますので、確認してください。そして、電池のスイッチを入れてボタンの操作でマイコンに書き込み、LEDが1秒毎に点滅することを確かめてください。プログラムに誤りがあったり、うまく書き込みができない場合は、エラーメッセージが表示されます。もしも点滅が非常に遅い(8秒毎)場合は、「0.1 7. 初めて使用する...」を確認してください。

 以下、プログラムの内容について簡単に説明します。

 下図のようにマイコン内部のスイッチがプログラムで切り替えられ、LEDが点滅すると考えればわかりやすいと思います。スイッチがHIGHになるとLEDに電流が流れ、LOWになると電流が流れません。

 この例のように、Arduinoのプログラムは以下のように作ります。なお、Arduinoではプログラムのことをスケッチと呼びますが、このテキストでは一般的な「プログラム」という言葉を使います。

  1. プログラム全体を通して使う変数を冒頭に定義します(この例では1行目)。変数はデータ(値)を入れる入れ物のことです。中に入れるデータによって入れ物の大きさは異なります。データの型については付録1を参照してください。
  2. setup(){ }の中に、電源投入後に1度だけ実行したいプログラムを記載します(この例では2~4行目)。
  3. loop(){ }の中に、繰り返して実行したいプログラムを記載します(この例では5~10行目)。
  4. 各行の「//」以降はコメントとなります。また、この例にはありませんが、「/* */」で囲まれた箇所は改行も含めてコメントとなります。
  5. HIGHやLOWやOUTPUTは「組み込み定数」で、あらかじめ値が定義されています(付録1)。
  6. pinMode()やdigitalWrite()はあらかじめ用意された「組み込み関数」です(付録1)。関数を独自に定義することもできます。

(練習) LEDを1秒毎に交互に点滅させ、D13ピンの電圧をテスターで測りなさい。

(練習) LEDを0.5秒点灯、0.5秒消灯を繰り返すようにプログラムを変更しなさい。また、0.2秒点灯、1.8秒消灯を繰り返すように変更しなさい。

2.2 LEDを高速に点滅させる

 LEDの点灯時間と消灯時間を変更してみます。

 7行目と9行目を「delay(1000)」→「delay(100)」→「delay(20)」→「delay(10)」→「delay(1)」と変えて、点滅の様子を確認してください。「delay(10)」の時は10ms点灯、10ms消灯を繰り返し、50Hzの点滅になります。「delay(20)」(25Hz)の時は点滅しているのがわかりますが、「delay(10)」(50Hz)では点滅を感じなくなります。人間の目が感じる「点滅のちらつき」はフリッカと呼ばれ、高速の点滅(50~60Hz)ではちらつきを感じなくなります。

 通常の蛍光灯は100Hz(50Hzの2倍)で点滅しますが、蛍光管の状態によっては発光にムラが生じ50Hzで点滅し、その場合はちらつきが気になることがあります。インバーター式の蛍光灯はちらつきを抑えるために超高速(数10kHz)で点滅させています。また、ブラウン管テレビが毎秒30フレーム(実際にはインターレースで60Hzのフリッカ)、映画のフィルムが毎秒24フレーム(実際には同じフレームを2度映すことで48Hzのフリッカ)になっているのは、このような目の特性を考慮してのことです。

(練習) LEDの点滅速度を30Hz, 40Hz, 50Hz, 60Hzと変化させ、ちらつきが感じられるかどうかを、ブレッドボードを静止した場合と、左右に振った場合の両方で調べなさい。

(練習) 緑色LEDと1kΩの抵抗をD12ピンに追加接続するよう、下の回路図に書き加えなさい。また、blink.inoを参考に(1) 実際ブレッドボードに部品を追加し、追加した緑色LEDを1秒毎に点滅させなさい(blink1.ino)。(2) 赤色LEDと緑色LEDが1秒毎に交互に点灯するようにしなさい(blink2.ino)。


3. デジタル入力: スイッチ操作でLEDのオンオフ

3.1 スイッチが押されている時にLEDを点灯する

 スイッチと抵抗を追加し、下図の回路にします。

 以下のプログラムは、buttonピンがHIGHならば(スイッチが押されていれば)LEDを点灯し、LOWならばLEDを消灯します。

 8行目で、「digitalRead(button)」でbuttonピン(2ピン)がHIGH(高い電圧)かLOW(低い電圧)かを読み取り、HIGHであれば9行目が実行され、そうでなければ11行目が実行されます。

Button.ino: ArduinoIDE [スケッチの例][02.Digital][Button]
int button=2; // 変数buttonに2を代入する int led=13; // 変数ledに13を代入する void setup(){ pinMode(button, INPUT); // buttonピンを入力にする(この行はなくても良い) pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ if(digitalRead(button)==HIGH){//buttonピンがHIGHならば digitalWrite(led, HIGH); // ledピンにHIGHを出力する }else{ // そうでなければ digitalWrite(led, LOW); // ledピンにLOWを出力する } }
回路図

3.2 スイッチが押されている時にLEDを点灯する

 以下のプログラムは、buttonピンがHIGHならば(スイッチが押されていれば)LEDを点灯し、LOWならばLEDを消灯します。

 8行目で、「digitalRead(button)」でbuttonピン(0ピン)がHIGH(高い電圧)かLOW(低い電圧)かを読み取り、HIGHであれば9行目が実行され、そうでなければ11行目が実行されます。

Button.ino: ArduinoIDE [スケッチの例][02.Digital][Button]
int button=0; // 変数buttonに0を代入する int led=6; // 変数ledに6を代入する void setup(){ pinMode(button, INPUT_PULLUP); // buttonピンを入力(プルアップ抵抗有効)にする pinMode(led, OUTPUT); // ledピンを出力にする } void loop(){ if(digitalRead(button)==LOW){ // buttonが押されていれば digitalWrite(led, HIGH); // ledピンにHIGHを出力する }else{ // そうでなければ digitalWrite(led, LOW); // ledピンにLOWを出力する } }
回路図

3.3 スイッチ操作で交互に点灯・消灯する

 以下のプログラムは、スイッチを押すと交互にLEDが点灯・消灯します。考え方は、「スイッチが押された瞬間に(つまりさっき押されていなくて今押されたら)LED表示を反転する」というものです。変数buttonStateには「今のスイッチの状態(LOWまたはHIGH)」、変数lastStateには「さっきのスイッチの状態(LOWまたはHIGH)」、変数ledStateには「今のLEDの点灯状態(LOWまたはHIGH)」を保存します。

 11行目は、現在のbuttonピンの状態(HIGHまたはLOW)を読み取り、buttonStateに代入します。12行目で、それがlastState(直前のbuttonの状態)と違っていて、かつそれがHIGHなら、ledState(ledの状態)を反転します。13行目でledStateをledピンに出力します。

Debounce.ino: ArduinoIDE [スケッチの例][02.Digital][Debounce] の簡易版
int button=2; // 変数buttonに2を代入する int led=13; // 変数ledに13を代入する int ledState=HIGH; // 変数ledStateをHIGHにする(現在の点灯状態) int buttonState; // 現在のbuttonの状態 int lastState; // 直前のbuttonの状態 void setup(){ pinMode(button, INPUT); // buttonピンを入力にする pinMode(led, OUTPUT); // ledピンを出力にする } void loop(){ // 以下を繰り返す buttonState=digitalRead(button); // buttonピンを読みbuttonStateに代入 if(buttonState!=lastState && buttonState==HIGH) ledState=!ledState; // それが前の状態と違いかつHIGHならledStateを反転 digitalWrite(led, ledState);// ledピンにledStateの値を出力 lastState=buttonState; // lastStateを更新する delay(10); // チャタリングを考慮し少し待つ }
回路図

 15行目の「delay(10)」がないと、誤動作することがあります。それは、スイッチを押した時に、ごく短い時間にその接点がオンになったりオフになったりするからです。スイッチを離した時も同様です。スイッチによって異なりますが、10ms程で落ち着きます。この現象は「チャタリング」と呼ばれます。

(練習) 15行目のdelay(10)を極端に長くし、例えば待ち時間が2秒になるようにして、どのような動作になるか調べなさい。

(練習) 緑色LEDを増設し、スイッチを押すと赤色LEDと緑色LEDが交互に点灯するようにしなさい。

3.4 スイッチを4回押すと点灯する

 以下のプログラムは、スイッチ操作4回に一度LEDが点灯します。

 上の例ではledStateを記憶し、スイッチが押されるとその値を反転させていましたが、ここではCounterにスイッチが押された回数を記憶します。12行目で、buttonState(現在のbuttonの状態)がlastState(直前のbuttonの状態)と違っていて、かつそれがHIGHなら、Counterの値に1を足しこみます。14行目で、「Counter%4==0」がtrueつまりCounterの値を4で割った余りが0であればledにHIGHを出力し、そうでなければLOWを出力します。

StateChangeDetection.ino: ArduinoIDE [スケッチの例][02.Digital][StateChangeDetection] (シリアル通信に関係する行を除き、loop() の最後に「delay(10)」を挿入)
int button=2; // 変数buttonに2を代入する int led=13; // 変数ledに13を代入する int Counter=0; // 変数Counter(スイッチ操作回数)を0にする int buttonState; // 現在のbuttonの状態 int lastState=0; // 直前のbuttonの状態 void setup(){ pinMode(button, INPUT); // buttonピンを入力にする pinMode(led, OUTPUT); // ledピンを出力にする } void loop() { // 以下を繰り返す buttonState=digitalRead(button); // buttonピンを読みbuttonSteteに代入 if(buttonState!=lastState && buttonState==HIGH) Counter++; // それが前の状態と違いかつHIGHならCounterの値に+1 lastState=buttonState; // lastStateを更新する if(Counter%4==0) digitalWrite(led, HIGH); // Counterの値が4の倍数ならledピンにHIGHを出力し else digitalWrite(led, LOW); // そうでなければledピンにLOWを出力 delay(10); // チャタリングを考慮し少し待つ }
回路図

(練習) 14行目の「Counter%4==0」の箇所を「Counter%7==0」や「Counter%4!=0」などに変えて、どのような動作になるか調べなさい。

(練習) 2個の押しボタンスイッチを使い、一方のスイッチを押すとLEDが点灯し、もう一方のスイッチを押すと消灯するようにしなさい。

3.5 スイッチが押されるとLEDを3秒点灯する

 以下のプログラムは、スイッチが押されるとLEDを3秒点灯します。

3sec.ino:
int button=2; // 変数buttonに2を代入する int led=13; // 変数ledに13を代入する void setup(){ pinMode(button, INPUT_PULLUP);// buttonピンをプルアップ入力にする pinMode(led, OUTPUT); // ledピンを出力にする } void loop(){ if(digitalRead(button)==LOW){ // buttonピンがLOWならば digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(3000); // 3秒待つ }else{ // そうでなければ digitalWrite(led, LOW); // ledピンにLOWを出力する } }
回路図

4. アナログ出力: LEDの明るさを変える

 これまでは、LEDが点灯するか消灯するかでした。しかし、点灯時の明るさを変化させたいこともあります。

4.1 LEDの明るさを変える

 1.2ではLEDの点灯時間と消灯時間を変えてどのように見えるかを確認しました。ここでは7行目と9行目を「delay(1)」にしてみます。

Blink.ino: ArduinoIDE [スケッチの例][01.Basics][Blink] (7行目と9行目を1000→1に変更)
int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // { }内を繰り返し実行する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(1); // 1ms待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(1); // 1ms待つ }

 既に経験したように、点灯と消灯を高速に切り替えると、私達の目は点滅を認識できなくなります。代わりに、常時点灯している場合よりも少しだけ暗く感じます。「平均的に」半分の明るさになるからです。下図左は、この時のledピンの出力の波形を示したものです。8行目のLOWをHIGHにすれば常時点灯となりますから、LEDの明るさを比較してみてください。私達の目は、明るさが半分になっても、「ちょっと暗くなった」ぐらいにしか感じません。

PWM波形

 次に、7行目はそのままにして9行目を「delay(9)」としてみてください。こうすることにより、1ms点灯、9ms消灯を繰り返すようになり、明るさはより暗く感じられます。この時の波形が上図中央です。

 HIGHとLOWの時間が等しい時「デューティ比50%」、常時HIGHの時「デューティ比100%」、HIGHが1msでLOWが9msなら「デューティ比10%」といいます。この技術は「パルス幅変調(PWM)」と呼ばれ、モータのスピードコントロールや、デジタルオーディオアンプなどに利用されています。

 さて、Arduinoでは analogWrite( ) という関数が用意されていて、

とすると、ledピンにアナログ値val(0~255)が出力されます。正確に言えば、0/255~255/255のデューティ比で上記のようなPWM信号が出力されます。ただし、analogWrite()が利用できるピンは7, 11, 12, 13(tone()と併用する場合は7, 11のみ)に限られています。

pwm.ino:
int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する analogWrite(led, 10); // ledピンにアナログ値10を出力する } void loop(){ } // { }内は空

(練習) 3行目の「10」を0~255の間でいくつか変えて、LEDの明るさを観察しなさい。また、その時の波形をオシロスコープで観察して記録しなさい。

(練習) ボタンを押す度に「消灯→暗い→明るい→消灯→...」の動作をするプログラムを作りなさい。

4.2 LEDの明るさを連続的に変える

 デューティ比を連続的に変化させれば、下図のように任意の波形を表すことができます。

PWM波形

 以下は、デューティ比を0%から100%まで少しずつ変化させるプログラムです。暗い状態から次第に明るくなり、その後次第に暗くなり、これを繰り返します。プログラムの6行目でbrightの値をアナログ出力し、7行目でbrightの値をfadeだけプラスします。

Fade.ino: ArduinoIDE [スケッチの例][01.Basics][Fade] (led 9→13に変更)
int led=13; // 変数ledに13を代入する int bright=5; // 変数brightに5を代入する int fade=5; // 変数fadeに5を代入する void setup(){ } void loop(){ // 以下を繰り返す analogWrite(led, bright); // ledピンにbrightの値をアナログ出力する bright=bright+fade; // brightの値にfadeの値を足し込む if(bright==0 || bright==255) // brightの値が0または255だったら fade=-fade; // fadeの符号を逆にする delay(30); // 30ms待つ }

(練習) スイッチを押すとLEDがだんだん明るくなり、スイッチを離すだんだん暗くなるプログラムを作りなさい。


5. アナログ入力

5.1 アナログ値の入力

 analogRead(A0)は、A0ピンの電圧(0~3V)に応じて0~1023の値をとります。ただし、ここで用いている方法では正確な電圧値を読み取ることはできず、用途は限られます。

 以下のプログラムは、ボリュームの値に応じてLEDの点滅時間が変化します。

AnalogInput.ino: ArduinoIDE [スケッチの例][03.Analog][AnalogInput]
int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(val); // valの値だけ待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(val); // valの値だけ待つ }
回路図

5.2 アナログ値に応じてLEDを点灯・消灯する

 以下のプログラムは、ボリュームのアナログ値(0~1023)が600未満の時にLEDを点灯します。

AutoLight.ino:
int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する if(val<600) digitalWrite(led, HIGH); // valが600未満ならledピンをHIGH else digitalWrite(led, LOW); // そうでなければLOW }
回路図

5.3 アナログ値に応じて明るさを変える(調光)

 以下のプログラムは、ボリュームのアナログ値(0~1023)に応じてLEDの明るさを連続的に変えます。

AnalogLight.ino:
int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する analogWrite(led, val/4); // ledにアナログ値(0~255)を出力 }
回路図

5.4 アナログ値に応じて色を変える

 フルカラーLEDは中にRGB三色のLEDが入っています。以下のプログラムは、フルカラーLEDの色相(HUE)をボリュームのアナログ値(0~1023)に応じて変化させます。アナログ値を一旦0~360°に変換してHに代入し、その値に応じたRGB各LEDの明るさを0~255で求め、AnalogWrite()で出力しています。プログラム中のmap(H, 0,120, 255,0)はHの値0~120に対応する255~0の値を返します。明るさが不足する場合は、電池を1本追加し、3V→4.5Vにしてください。


Hue.ino:
int Rled=11, Gled=13, Bled=12; void setup(){ pinMode(Rled, OUTPUT); pinMode(Gled, OUTPUT); pinMode(Bled, OUTPUT); } void loop(){ int R, G, B; int H=(long)360*analogRead(A0)/1024; // H: 0-360 if(H<120){ R=map(H, 0,120, 255,0);G=255-R;B=0;} else if(H<240){ R=0; G=map(H, 120,240, 255,0); B=255-G;} else{ R=map(H, 240,360, 0,255); G=0; B=255-R;} analogWrite(Rled, R); analogWrite(Gled, G); analogWrite(Bled, B); }
回路図

(練習) ボリュームのアナログ値(0~1023)に応じて、LEDの明るさを変えなさい。


6. デジタル出力(2): 数字を表示する

6.1 7セグメントLEDに数字を表示する

 数字表示LED(7セグメントLED)に数字を表示してみます。下図の回路を組み立ててください。

 以下のプログラムは、数字表示LEDに「5」を表示するものです。プログラムの冒頭でchar型の配列を定義し、LED[0]には0x3f, LED[1]には0x06, ..., LED[6]には0x67が入ります。setupの中の2行目でその値を参照し、PORTDに出力しています。

 このプログラムはArduino風ではありません。Arduinoのピン番号は、マイコン本来のポート毎の番号 PB0~PB7, PD0~PD6, PA0,PA1 に通し番号を独自に割り振ったものです。この例では、LEDに接続された7本の信号線に一度に値を出力できるので、Arduino風ではないこの方法をとりました。

Count.ino:
char LED[]={ // 配列LEDに値を入れる // gfedcba 0b00111111, // 0x3f, LED[0] 0b00000110, // 0x06, LED[1] 0b01011011, // 0x5b, LED[2] 0b01001111, // 0x4f, LED[3] 0b01100110, // 0x66, LED[4] 0b01101101, // 0x6d, LED[5] 0b01111101, // 0x7d, LED[6] 0b00100111, // 0x27, LED[7] 0b01111111, // 0x7f, LED[8] 0b01100111 // 0x67, LED[9] }; void setup(){ DDRD=0b01111111; // 0x7f, PD0~PD6を出力にする PORTD=LED[5]; } void loop(){ }
回路図

 PD0~PD6はLEDのa~gのセグメントに接続されていて、それぞれ 1 を出力した時に点灯する回路となっています。下表は、各数字を表示する際にPD0~PD6の各々に何を出力すればよいかと、PORTDに出力すべき2進数値と16進数値を示したものです。

数字 PD6(g) PD5(f) PD4(e) PD3(d) PD2(c) PD1(b) PD0(a) 16進
0 0 1 1 1 1 1 1 0x3F
1 0 0 0 0 1 1 0 0x06
2 1 0 1 1 0 1 1 0x5B
3 1 0 0 1 1 1 1 0x4F
4 1 1 0 0 1 1 0 0x66
5 1 1 0 1 1 0 1 0x6D
6 1 1 1 1 1 0 1 0x7D
7 0 1 0 0 1 1 1 0x27
8 1 1 1 1 1 1 1 0x7F
9 1 1 0 0 1 1 1 0x67

(練習) 表示する数字を他の数字に変更してみなさい。

6.2 数字表示をカウントアップする

 表示される数字を1秒毎にカウントアップするには次のようにします。ポイントは配列を参照する9行目のLED[i]です。また、プログラム冒頭の配列LEDに値を入れる箇所は、簡潔にするために16進表記にしています。

Countup.ino:

char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする } void loop(){ for(int i=0;i<10;i++){ PORTD=LED[i]; delay(1000); } }
回路図

 以下は、スイッチを押すと数字表示がカウントアップするプログラムです。

ButtonCountup.ino:

int button=2; // 変数buttonに2を代入する int count=0; // 変数countに0を代入する char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ pinMode(button, INPUT_PULLUP);// buttonピンをプルアップ入力にする DDRD=0x7f; // PD0~PD6を出力にする } void loop(){ if(count<9&&digitalRead(button)==LOW){//countが9未満でbuttonがLOWなら count++; // countの値をプラス1する delay(300); // チャタリングを考慮して少し待つ } PORTD=LED[count]; }
回路図

(練習) 数字表示が9~0にカウントダウンするプログラム Countdown.ino を作りなさい。

(練習) Countup.ino を、1秒毎ではなく50ms毎に高速でカウントアップするようにしなさい。また、スイッチが押されるとカウントを停止するようにしなさい。

(練習) 1秒毎に「H」「E」「L」「L」「O」を表示し、その後3秒間消え、再びこれを繰り返すプログラムを作りなさい。また、「E」「r」「r」「o」「r」の表示にしてみなさい。また、自分の名前を表示できるか工夫してみなさい。

6.3 スイッチを押すと数字表示をカウントアップする

 スイッチを押すと数字表示をカウントアップするには次のようにします。

ButtonCountup.ino:

int button=0; // 変数buttonに0を代入する int count=0; // 変数countに0を代入する char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void led(val){ // LED[val]のビット0~6を順に調べて1なら点灯0なら消灯 int seg=LED[val]; // 7セグメントパターン for(int i=0; i<7; i++) digitalWrite(i+4, seg&(1<<i)?1:0); } void setup(){ pinMode(button, INPUT_PULLUP); // buttonピンをプルアップ入力にする for(int i=4; i<=10; i++) pinMode(i, OUTPUT); // ピン4~10を出力に } void loop(){ if(count<9&&digitalRead(button)==LOW){//countが9未満でbuttonがLOWなら count++; // countの値をプラス1する delay(300); // チャタリングを考慮して少し待つ } led(count); }
回路図

(練習) スイッチを追加し、一方のスイッチでカウントアップ、もう一方のスイッチでカウントダウンするプログラム Countupdown.ino を作りなさい。

(練習) Countup.ino を、1秒毎ではなく10ms毎に高速でカウントアップするようにしなさい。また、スイッチが押されるとカウントを停止するようにしなさい。

6.4 数字の2桁表示

 2桁以上の数字を表示するにはどうしたらいいでしょうか。マイコンから各LEDのa~gに別々につないでそれぞれ表示させたい値を出力すればいいのですが(スタティック点灯)、それではマイコンのピンが不足してしまいます。このような場合はダイナミック点灯と呼ばれる方法が用いられます。この方法は、身近にあるさまざまな表示装置に使われています。
 下の回路図(上)はこれまで用いた7セグメントLEDを2個用いたもので、回路図(下)は2桁LED(SN440502)を用いたものです。2桁LEDは、ひとつのパッケージの中に2個の7セグメントLEDが入っていて、中でa~gが接続されています。いずれの場合も、a~gに表示したい数字のパターンを出力し、D11ピンを「0(低い電圧)」にすると「10の桁」に、D12ピンを0にすると「1の桁」に表示されます。
 いずれかの回路を組み立て、動作を確認してください。

2digit.ino:

int digit1=11; // 変数digit1に11を代入する int digit2=12; // 変数digit2に12を代入する char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする pinMode(digit1, OUTPUT); // digit1(11ピン)を出力にする pinMode(digit2, OUTPUT); // digit2(12ピン)を出力にする } void loop(){ digitalWrite(digit1, HIGH);// 10の桁を消灯 digitalWrite(digit2, HIGH);// 1の桁を消灯 PORTD=LED[2]; // 「2」を出力(まだ表示されない) digitalWrite(digit1,LOW); // 10の桁に表示 delay(1000); // 1秒待つ digitalWrite(digit1,HIGH); // 10の桁を消灯 PORTD=LED[3]; // 「3」を出力(まだ表示されない) digitalWrite(digit2,LOW); // 1の桁に表示 delay(1000); // 1秒待つ digitalWrite(digit2,HIGH); // 1の桁を消灯 }
回路図
回路図

 ここで、2箇所の「delay(1000)」を「delay(100)」→「delay(20)」→「delay(10)」→「delay(1)」と変えてみてください。1.2の時と同様にフリッカが私たちの目にはわからなくなります。

 次に、0~99の数値を表示するにはどうしたらいいでしょうか。今、以下のように表示させたい数「23」を変数Tに入れておくことにします。10の位は「T/10」、1の位は「T%10(Tを10で割った余り)」で求め、出力しています。他は前と同じです。

int T=23; // 表示させたい数 char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする } void loop(){ digitalWrite(11,HIGH); // 10の桁を消灯 digitalWrite(12,HIGH); // 1の桁を消灯 PORTD=LED[T/10]; // 10の位を出力(まだ表示されない) digitalWrite(11,LOW); // 10の桁に表示 delay(1); // 1ms待つ digitalWrite(11,HIGH); // 10の桁を消灯 PORTD=LED[T%10]; // 1の位を出力(まだ表示されない) digitalWrite(12,LOW); // 1の桁に表示 delay(1); // 1ms待つ digitalWrite(12,HIGH); // 1の桁を消灯 }

6.5 アナログ値を数字で表示する

 以下のプログラムは、ボリュームのアナログ値(0~1023)に応じて、0~9の数字を表示させます。

AnalogDigital.ino
char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする } void loop(){ int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入 val=val/103; // valの値を103で割る(0~9の値になる) PORTD=LED[val]; // val(0~9)に応じたPD0~PD6をPORTDに出力 }
回路図

6.6 アナログ値を数字2桁で表示する

 以下のプログラムは、ボリュームのアナログ値(0~1023)に応じて、0~99の数字2桁を表示させます。

AnalogDigital2.ino
int digit1=11; // 変数digit1に11を代入する int digit2=12; // 変数digit2に12を代入する char LED[]={ // 配列LEDに値を入れる 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x27,0x7f,0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする pinMode(digit1, OUTPUT);// digit1(11ピン)を出力にする pinMode(digit2, OUTPUT);// digit2(12ピン)を出力にする } void loop(){ int val=analogRead(A0); // valにA0ピンのアナログ値を代入 val=map(val, 0,1023, 0,99);//0~1023の値を0~99の値にする digitalWrite(digit1, HIGH);// 10の桁を消灯 digitalWrite(digit2, HIGH);// 1の桁を消灯 PORTD=LED[val/10]; // 10の桁を出力(まだ表示されない) digitalWrite(digit1,LOW); // 10の桁に表示 delay(1); // 1ms待つ digitalWrite(digit1,HIGH); // 10の桁を消灯 PORTD=LED[val%10]; // 1の桁を出力(まだ表示されない) digitalWrite(digit2,LOW); // 1の桁に表示 delay(1); // 1ms待つ digitalWrite(digit2,HIGH); // 1の桁を消灯 }
回路図
回路図

7. デジタル出力(3): 液晶ディスプレイ

7.1 液晶ディスプレイに文字や数字を表示

7.2 液晶ディスプレイにカウントアップ表示

7.3 アナログ値を液晶ディスプレイに表示


8. センサーの利用

8.1 スイッチをセンサーとして使う

 押しボタンスイッチやスライドスイッチは、操作用のスイッチですが、センサーとしても活躍しています。例えば、CD/DVDデッキやコピー機やプリンタなど動きを伴う機器には、所定の位置に来たがどうかを検知するために、マイクロスイッチが必ずといっていいほど使われています。このような使われ方をするスイッチを「リミットスイッチ」と呼ぶこともあります。

 9.7のモーターカーでは車の先端にマイクロスイッチを使いました。ふたつの金属片を接触させるだけでスイッチ(センサー)となるので、オリジナルのスイッチを作ることも難しくはありません。所定の水位になったかどうかを検知するピンポン球を使った浮力スイッチ、傾けると金属ボールが移動する傾斜スイッチなど、用途に応じて工夫するのも楽しいでしょう。

8.2 明るさセンサー(CDS)

 5.2のボリュームの代わりに明るさセンサー(CDS)をつないでみましょう。明るくなるほどCDSの抵抗値が低くなり、A0ピンの電圧は高くなります。一定以下に暗くなるとLEDが点灯します。プログラムは5.2と同様です。ただし、7行目の「600」の値は適切な値に変更する必要があります。

AutoLight.ino:
int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する if(val<600) digitalWrite(led, HIGH); // valが600未満ならledピンをHIGH else digitalWrite(led, LOW); // そうでなければLOW }
回路図

 街路灯などでは、境界値付近でledが点灯したり消灯したり不安定になるのを避けるために、下図のような「ヒステリシス特性」を持たせます。

プログラムの8行目はledが消灯(ledStateがOFF)している時に600未満の明るさになったらledを点灯(ledStateをON)し、9行目はledが点灯(ledStateがON)している時に700以上の明るさになったらledを消灯(ledStateをOFF)します。

Hysteresis.ino:
int led=13; // 変数ledに13を代入する int ledState=LOW; // 変数ledStateにLOWを代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する if(ledState==LOW && val<600) ledState=HIGH; else if(ledState==HIGH && val>=700) ledState=LOW; if(ledState==LOW) digitalWrite(led, LOW); // ledStateがLOWならledピンをLOW else digitalWrite(led, HIGH);// そうでなければHIGH }
回路図

(練習) 8行目の600と9行目の700の値を変えて、適切な値をみつけなさい。

8.3 温度センサー(サーミスタ)を使う

 CDSの代わりに温度センサー(サーミスタ)をつないでみましょう。温度が高くなるほどサーミスタの抵抗値が低くなり、A0ピンの電圧は高くなります。温度がある値以上になるとLEDが点灯します。プログラムは上と同様です。ただし、7行目の「250」の値は適切な値に変更する必要があります。

AutoLight.ino:
int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する if(val<250) digitalWrite(led, HIGH); // valが250未満ならledピンをHIGH else digitalWrite(led, LOW); // そうでなければLOW }
回路図

(練習) LEDをエアコンに置き換えて考えてみましょう。街路灯の場合と同様、境界値付近で点灯したり消灯したり不安定になるのを避けるために、下図のような「ヒステリシス特性」を持たせます。

ヒステリシス特性を持たせた Hysteresis.ino でその動作を確かめなさい。また、600と700の値を変えて、適切な値をみつけなさい。

(練習) 5.4を参考に、温度が高ければ赤、温度が低ければ青というようにLEDの色が変わるようにしてみなさい。

8.4 明るさを計測する(数字表示)

 以下のプログラムは、明るさを0~9の数字で表示させます。プログラムは5.5と同様です。

AnalogDigital.ino
char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする } void loop(){ int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入 val=val/103; // valの値を103で割る(0~9の値になる) PORTD=LED[val]; // val(0~9)に応じたPD0~PD6をPORTDに出力 }
回路図

8.5 温度の計測(数字表示)

 以下のプログラムは、温度を0~9の数字で表示させます。プログラムは5.5と同様です。ただし、A0ピンの電圧は温度が変化してもあまり大きく変化しません。このような場合は、9行目を以下のように変更し、220~280の値valを0~9に変換します。

val=val/30;  ↓ val=map(val, 220,280, 0,9);
AnalogDigital.ino
char LED[]={ // 配列LEDに値を入れる 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67 }; void setup(){ DDRD=0x7f; // PD0~PD6を出力にする } void loop(){ int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入 val=map(val, 220,280, 0,9); // 220~280の値を0~9の値にする PORTD=LED[val]; // val(0~9)に応じたPD0~PD6をPORTDに出力 }
回路図

6.6 音を検知する: 音量センサー

 以下のプログラムは、音を検知したらLEDを3秒点灯するものです。

3sec.ino:
int TH=150; // 変数THに音量の境界値を代入する int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピンを出力にする } void loop(){ if(analogRead(A0)>=TH){ // 音量がTHよりも大きければ digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(3000); // 3秒待つ }else{ // そうでなければ digitalWrite(led, LOW); // ledピンにLOWを出力する } }
回路図

(練習) 4.2を参考に、手をたたくとカウントアップするプログラムSoundCountup.inoを作りなさい。

6.7 赤外線反射センサー

 赤外線の反射光で数mm程度の距離に反射物を検知するセンサーです。9.7のモーターカーと組み合わせると、ライントレースや衝突回避などが可能になります。以下のプログラムは、数mm程度の距離に反射物を検知したらLEDを点灯するものです。ただし、7行目の「250」の値は適切な値に変更する必要があります。

AutoLight.ino:
int led=13; // 変数ledに13を代入する void setup(){ pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // 以下を繰り返す int val=analogRead(A0); // valにA0ピンのアナログ値(0~1023)を代入する if(val<250) digitalWrite(led, HIGH); // valが250未満ならledピンをHIGH else digitalWrite(led, LOW); // そうでなければLOW }
回路図

9. ブザー音で演奏

 ここではブザー音で電子オルゴールを作ってみましょう。和音など音にこだわる方は「私だけの電子オルゴール」[6]を参考にしてください。

9.1 音を出す

 Blink.inoで、LEDと同じ箇所に圧電ブザー(Bzz)を接続し、「1秒ごとにLEDを点滅させるプログラム」Blink.inoを実行してください。するとブザーからカチカチと音が聞こえます。ここで、点灯時間と消灯時間を以下のように変えてみましょう。変更箇所は7行目と9行目です。

Blink.ino: ArduinoIDE [スケッチの例][01.Basics][Blink] (7行目と9行目を1000→1に変更)
int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // { }内を繰り返し実行する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(1); // 1ms待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(1); // 1ms待つ }

 この時の波形は下図のように500Hzの矩形波となり、聞こえるのは500Hzの音です(といっても倍音成分が含まれていますが)。

トーン波形

(練習) オシロスコープでトーン信号の波形を観測しなさい。また、WaveGeneなどの信号発生ソフトを用い、パソコンで500Hzの矩形波を出し、音を比べてみなさい。マイコンの内蔵クロックはそれほど正確ではありませんので多少の音程の違いがあるかもしれません。

9.2 音程を変える

 Arduinoでは、指定した周波数の音を、指定した時間、出力するtone()という関数が利用できます。以下は時報の音を出すプログラムです。

BzzTone.ino:
int bzz=13; // 変数bzzに13を代入する void setup(){ tone(bzz, 784, 200); // bzzピンに784Hzの音を200ms出力する(ポッ) delay(600); // 600ms待つ tone(bzz, 784, 200); // bzzピンに784Hzの音を200ms出力する(ポッ) delay(600); // 600ms待つ tone(bzz, 784, 200); // bzzピンに784Hzの音を200ms出力する(ポッ) delay(600); // 600ms待つ tone(bzz, 1046, 1200); // bzzピンに1046Hzの音を1200ms出力する(ポーン) delay(1200); // 1200ms待つ noTone(bzz); // toneを終了 } void loop(){ }

(練習) 440Hzのラの音(A4)に対し1オクターブ高いラ(A5)の音は880Hzで、その間は隣同士(半音)の周波数の比が一定(21/12)となるように音階が作られます。表計算ソフトを用い、A4~A6の各音の周波数を計算しなさい。上のプログラム中の784Hzが「ソ」、1046Hzが「ド」であることが確認できます。

(練習) 上のプログラムを参考に、簡単な曲を演奏させるプログラムを作りなさい。

(練習) 時計のアラーム音、信号機の音、風呂や電気ポットの湯沸かし音など、身近にある電子音を録音し、その音をプログラムで再現しなさい。

9.3 楽譜を演奏する

 以下のプログラムは、配列として記述した楽譜情報を参照しながら演奏します。NOTE_C4などの周波数値はpitches.hファイルで定義されています。

toneMelody.ino: ArduinoIDE [スケッチの例][02.Digital][toneMelody] (bzzPin 8→13に変更)
#include "pitches.h" // pitches.hファイルを読み込む int bzz=13; // 変数bzzに13を代入する int melody[]={ // 楽譜データ(音階) NOTE_C4, NOTE_G3,NOTE_G3, NOTE_A3, NOTE_G3,0, NOTE_B3, NOTE_C4 }; int noteDurations[]={ 4,8,8,4,4,4,4,4 }; // 楽譜データ(音符) void setup(){ for(int thisNote=0; thisNote<8; thisNote++){ int noteDuration = 1000/noteDurations[thisNote]; tone(bzz, melody[thisNote],noteDuration); int pauseBetweenNotes = noteDuration*1.30; delay(pauseBetweenNotes); noTone(bzz); // toneを終了 } } void loop(){ }

 楽譜はさまざまな方法で表現することができます。以下は、少し長い曲を演奏するプログラムです。

playNote.ino:
const PROGMEM int16_t notes[]={ // 楽譜データ #define C 262 #define D 294 #define E 330 #define F 349 #define G 392 #define A 440 #define B 494 #define c 523 #define d 587 #define e 659 #define f 698 #define g 784 #define R 1 // Rest R,D,D,B,A,G,D,R, R,D,D,B,A,G,E,R, R,E,E,c,B,A,F,R, R,d,d,d,c,A,B,R, R,D,D,B,A,G,D,R, R,D,D,B,A,G,E,R, R,E,E,c,B,A,d,d, d,d,e,d,c,A,G,R, d,R, B,B,B,R, B,B,B,R, B,d,G,A,B,R,R,R, c,c,c,c,c,B,B,B, B,A,A,B,A,R, d,R, B,B,B,R, B,B,B,R, B,d,G,A,B,R,R,R, c,c,c,c,c,B,B,B, d,d,c,A,G,R, g,R,0 }; int Tempo=300; // 変数Tempoに300を代入する int bzz=13; // 変数bzzに13を代入する void setup(){ pinMode(bzz, OUTPUT); // bzzピンを出力にする } void loop(){ // 以下を繰り返す for(int i=0;;i++){ // 楽譜データを順に int note=pgm_read_word(&notes[i]); // 変数noteにひとつ読み if(note==0) break; // noteが0なら1曲演奏終了 else if(note!=R) tone(bzz, note, Tempo); // noteがR(休符)でもなければ // bzzピンにnote[Hz]の周波数の音をTempo[ms]出力する delay(Tempo*1.1); // Tempoだけ待つ } delay(Tempo*8); // しばらくしてから演奏を繰り返す }

(練習) メロディー(notes[]の部分)を変え、必要な場合は音階を拡張し、好きな曲の電子オルゴールを作りなさい。

9.4 楽器を作る

 以下のプログラムは、ボタンを押すと、ボリュームで決まる周波数のブザー音がでます。

Instrument.ino:
int bzz=13; // 変数bzzに13を代入する int button=2; // 変数buttonに2を代入する void setup(){ pinMode(bzz,OUTPUT); // bzzピンを出力にする pinMode(button, INPUT_PULLUP); // buttonピンをプルアップ入力にする } void loop(){ // 以下を繰り返す if(digitalRead(button)==LOW){ // buttonがLOWなら int val=analogRead(A0);// valにA0ピンのアナログ値(0~1023)を代入する tone(bzz,val,200); // valの周波数を200ms delay(200); // 200ms待つ } }

 上のプログラムで一応楽器として使えるのですが、ドレミ...の音を丁度よく出すのは難しいと思います。そこで、0~1023のアナログ値をmap関数を使って0~11の値に変換し、notes[ ]配列の周波数を参照するようにしたのが次のプログラムです。

Instrument.ino:
int bzz=13; // 変数bzzに13を代入する int button=2; // 変数buttonに2を代入する // ド レ ミ ファ ソ ラ シ ド レ ミ ファ ソ int notes[]={262, 294, 330, 349, 392, 440, 494, 523, 587, 659, 698, 784}; void setup(){ pinMode(bzz,OUTPUT); // bzzピンを出力にする pinMode(button, INPUT_PULLUP); // buttonピンをプルアップ入力にする } void loop(){ // 以下を繰り返す if(digitalRead(button)==LOW){ // buttonがLOWなら int val=analogRead(A0);// valにA0ピンのアナログ値(0~1023)を代入する val=map(val, 0,1023, 0,11); // valを0~11の値に変換する int note=notes[val]; // noteにvalに対応する周波数を代入する tone(bzz,note,200); // noteを200ms delay(200); // 200ms待つ } }

(練習) ボリュームの代わりに明るさセンサーで音程が変わるようにしなさい。また、必要ならば音階を拡張しなさい。

9.5 「合いの手」楽器

 音量センサーと楽譜の演奏を組み合わせ、「合いの手」楽器を作ってみます。

MicTone.ino:
#define SOuL 207 #define RAL 220 #define RAuL 233 #define SIL 247 #define DO 262 #define DOu 277 #define RE 294 #define REu 311 #define MI 330 #define FA 349 #define FAu 370 #define SO 392 #define SOu 415 #define RA 440 #define RAu 466 #define SI 494 #define DOH 523 #define DOuH 554 #define REH 587 #define REuH 622 #define MIH 659 #define FAH 698 #define FAuH 740 #define SOH 784 #define R 0 // Rest #define Tempo 100 #define L1 32*Tempo #define L2_ 24*Tempo #define L2 16*Tempo #define L4_ 12*Tempo #define L4 8*Tempo #define L8_ 6*Tempo #define L8 4*Tempo #define L16 2*Tempo #define L32 1*Tempo int TH=150; // 変数THに音量の境界値を代入する(値は要調整) int bzz=13; int b(int note, int tempo){ if(note>0){ tone(bzz, note, tempo); delay(tempo*1.1); return 0; }else{ unsigned long lastmillis=millis(); int s=0; while(millis()<lastmillis+tempo) if(analogRead(A0)<TH) s=1; return s; } } void setup(){ pinMode(bzz, OUTPUT); } void loop(){ for(;;){ b(R,1000); // 1秒待つ while(analogRead(A0)>=TH); // チョ(音を待つ) b(R,L8); if(!b(R,L4)) continue; // チョイ if(b(R,L8)) continue; // の if(!b(R,L4)) continue; // チョイ if(b(R,L8)) continue; b(SIL,L16);b(RAL,L16);b(SOuL,L16);b(RAL,L32);// シラソ#ラ if(!b(R,L4)) continue; // チョン(ド) b(0,L32); b(RE,L16);b(DO,L16);b(SIL,L16);b(DO,L32); // レドシド if(!b(R,L4)) continue; // チョン(ミ) b(0,L32); b(FA,L16);b(MI,L16);b(REu,L16);b(MI,L16); // ファミレ#ミ b(SI,L16);b(RA,L16);b(SOu,L16);b(RA,L16); // シラソ#ラ b(SI,L16);b(RA,L16);b(SOu,L16);b(RA,L32); // シラソ#ラ if(!b(R,L4)) continue; // チョン(ド) b(0,L32); b(RA,L8);b(DOH,L8); // ラド b(SI,L8);b(RA,L8);b(SO,L8);b(RA,L8); // シラソラ b(SI,L8);b(RA,L8);b(SO,L8);b(RA,L8); // シラソラ b(SI,L8);b(RA,L8);b(SO,L8);b(FAu,L16); // シラソファ# if(!b(R,L4)) continue; // チョン(ミ) b(DO,L16);b(MI,L16);b(SO,L16); b(SO,L16);b(R,L16);b(MI,L16);b(SO,L4); } }

(練習) 違うメロディーやリズムにしてみなさい。


10. 合成音声でおしゃべり

10.1 合成音声でおしゃべり

 音声合成LSI(ATP3011F4)を使ってスピーカから合成音声を出します。

Talk.ino:
void setup(){ Serial.begin(9600); // 9600bpsで通信を開始 while(1){ Serial.write('?'); // '?'を送信する delay(300); // 300ms待つ if(Serial.available()>0&&Serial.read()=='>')break; } // '>'がきたらwhile終了 } void loop(){ // 以下を繰り返す Serial.println("ju'nbi/o'-kei");//「準備OK!」 delay(2000); // 2秒待つ }

(練習) 10行目を変えて、違う言葉をしゃべらせてみなさい。

10.2 「九九」の読み上げ

 以下の例は、「九九」を合成音声で読み上げるものです[音を聞く]。

10 for A=1 TO 9 20 for B=1 TO 9 30 talk A,"kakeru",B,"wa",A*B 40 next B 50 next A

 以下は、外部スイッチが押されると「九九」をランダムに読み上げるプログラムです。

10 A=1 20 B=dRead(1) 30 if A=1 if B=0 gosub 100 40 A=B 50 goto 20 100 X=1+rnd(9): Y=1+rnd(9) 110 talk X,"kakeru",Y,"wa",X*Y 120 return

(練習) 利用者に適したスイッチを使い、合成音声の読み上げで支援するさまざまなツール(VOCA)を作ることができます。2個のスイッチを接続し、それぞれ「はい」「いいえ」の合成音声が出るようなVOCAを作りなさい。


11. イルミネーション


12. 赤外線リモコン

 テレビなどのリモコンは、ボタンを押すと赤外線LEDが「決められたタイミングで点滅」するように作られています。どのように点滅しているかは、リモコン受光器に向けてリモコンのボタンを押し、受光器の信号をオシロスコープで観測するとわかります。

 実際に観測してみると、各社テレビのリモコン信号は以下のようになっています。図の着色した部分が信号が「オン」の箇所で、「オン」とは「赤外線LEDを13μs点灯し、13μs消灯し、これを一定時間繰り返す」ことです。また、「オフ」とは「一定時間消灯する」ことです。これを参考に、赤外線LEDを点滅させるプログラムを作ればよいわけです。

電源 ON/OFF 40 bf 12 ed
チャンネルUP 40 bf 1b e4
チャンネルDOWN 40 bf 1f e0
ボリュームUP 40 bf 1a e5
ボリュームDOWN 40 bf 1e e1
東芝製テレビのリモコン信号
電源 ON/OFF 02 20 80 00 3d bd
チャンネルUP 02 20 80 00 34 b4
チャンネルDOWN 02 20 80 00 35 b5
ボリュームUP 02 20 80 00 20 a0
ボリュームDOWN 02 20 80 00 21 a1
パナソニック製テレビのリモコン信号
電源 ON/OFF 8f 12 16 d1
チャンネルUP 8f 12 11 a1
チャンネルDOWN 8f 12 12 91
ボリュームUP 8f 12 14 f1
ボリュームDOWN 8f 12 15 e1
シャープ製テレビのリモコン信号
電源 ON/OFF 95 0
チャンネルUP 90 0
チャンネルDOWN 91 0
ボリュームUP 92 0
ボリュームDOWN 93 0
SONY製テレビのリモコン信号

 以下の回路とプログラムは、Upボタンを押すと各社リモコンの「チャンネルアップ」信号が次々出力され、Downボタンを押すと各社リモコンの「チャンネルダウン」信号が次々出力されます。実際には各社の信号を出す必要はありません。

Remocon.ino:
int led=13, UpButton=1, DownButton=5; void f38kHz(int n){ // 26μs × n周期 volatile char i; int j; for(j=0;j<n;j++){ digitalWrite(led, HIGH); i=0;i++; // 13μs点灯 digitalWrite(led, LOW); i=0; // 13μs消灯 } } void tx1(){f38kHz(300/26); delayMicroseconds(1800);}// 「1」の赤外線信号 void tx0(){f38kHz(300/26); delayMicroseconds(700);} // 「0」の赤外線信号 void sx1(){delayMicroseconds(600);f38kHz(1200/26); }// 「1」の赤外線信号(SONY) void sx0(){delayMicroseconds(600);f38kHz(600/26); } // 「0」の赤外線信号(SONY) void txbyte(uint8_t data){ // 1バイトのdataの赤外線信号 if(data&0x01) tx1(); else tx0(); // ビット0 if(data&0x02) tx1(); else tx0(); // ビット1 if(data&0x04) tx1(); else tx0(); // ビット2 if(data&0x08) tx1(); else tx0(); // ビット3 if(data&0x10) tx1(); else tx0(); // ビット4 if(data&0x20) tx1(); else tx0(); // ビット5 if(data&0x40) tx1(); else tx0(); // ビット6 if(data&0x80) tx1(); else tx0(); // ビット7 } void sxbyte(uint8_t data){ // 1バイトのdataの赤外線信号(SONY) if(data&0x01) sx1(); else sx0(); // ビット0 if(data&0x02) sx1(); else sx0(); // ビット1 if(data&0x04) sx1(); else sx0(); // ビット2 if(data&0x08) sx1(); else sx0(); // ビット3 if(data&0x10) sx1(); else sx0(); // ビット4 if(data&0x20) sx1(); else sx0(); // ビット5 if(data&0x40) sx1(); else sx0(); // ビット6 if(data&0x80) sx1(); else sx0(); // ビット7 } void setup(){ pinMode(led, OUTPUT); pinMode(UpButton, INPUT_PULLUP); pinMode(DownButton, INPUT_PULLUP); } void loop(){ if(digitalRead(UpButton)==LOW){ // UpButtonが押されると f38kHz(9000/26); delayMicroseconds(4500); // 東芝 CH UP txbyte(0x40); txbyte(0xbf); txbyte(0x1b); txbyte(0xe4); tx0(); delay(100); // ******************************* 少し時間をおいて f38kHz(3600/26); delayMicroseconds(1600); // パナソニック CH UP txbyte(0x02); txbyte(0x20); txbyte(0x80); txbyte(0x00); txbyte(0x34); txbyte(0xb4); tx0(); delay(100); // ******************************* 少し時間をおいて f38kHz(3600/26); delayMicroseconds(1600); // パナソニックBD CH UP txbyte(0x02); txbyte(0x20); txbyte(0xb0); txbyte(0x00); txbyte(0x34); txbyte(0x84); tx0(); delay(100); // ******************************* 少し時間をおいて txbyte(0x8f); txbyte(0x12); delay(42); // シャープ CH UP txbyte(0x11); txbyte(0xa1); tx0(); delay(100); // ******************************* 少し時間をおいて f38kHz(2500/26);sxbyte(0x90);sx0();sx0();sx0();sx0();delay(27);//SONY CH UP f38kHz(2500/26); sxbyte(0x90); sx0();sx0();sx0();sx0(); delay(27); f38kHz(2500/26); sxbyte(0x90); sx0();sx0();sx0();sx0(); delay(1000); }else if(digitalRead(DownButton)==LOW){ // DownButtonが押されると f38kHz(9000/26); delayMicroseconds(4500); // 東芝 CH DOWN txbyte(0x40); txbyte(0xbf); txbyte(0x1f); txbyte(0xe0); tx0(); delay(100); // ******************************* 少し時間をおいて f38kHz(3600/26); delayMicroseconds(1600); // パナソニック CH DOWN txbyte(0x02); txbyte(0x20); txbyte(0x80); txbyte(0x00); txbyte(0x35); txbyte(0xb5); tx0(); delay(100); // ******************************* 少し時間をおいて f38kHz(3600/26); delayMicroseconds(1600); // パナソニックBD CH DOWN txbyte(0x02); txbyte(0x20); txbyte(0xb0); txbyte(0x00); txbyte(0x35); txbyte(0x85); tx0(); delay(100); // ******************************* 少し時間をおいて txbyte(0x8f); txbyte(0x12); delay(42); // シャープTV CH DOWN txbyte(0x12); txbyte(0x91); tx0(); delay(100); // ******************************* 少し時間をおいて f38kHz(2500/26);sxbyte(0x91);sx0();sx0();sx0();sx0();delay(27);//SONY CH DN f38kHz(2500/26); sxbyte(0x91); sx0();sx0();sx0();sx0();delay(27); f38kHz(2500/26); sxbyte(0x91); sx0();sx0();sx0();sx0(); delay(1000); } }
回路図

13. 動くものを作る

 これまでの例では、マイコンの出力ピンにLEDや圧電ブザーを接続しました。AVRマイコンは最大20mAの電流を流すことができるので、この範囲であれば直接接続できます。この章では、モータなど大きな電流が流れる素子を駆動する例を紹介します。大きな電流が流れる回路を制御する場合は、「電流が流れる経路」を意識することが回路の誤動作を防ぐことにつながります。

13.1 リレーでさまざまなものをオンオフする

 さまざまな機器をオンオフ制御したい場合は、「リレー」が最も汎用性に富んでいます。ただし、ここでは交流100Vの機器ではなく、電池で動作する機器のみを考えます。おもちゃやラジオなど、制御対象の電圧や電流や極性などを気にせずに接続することができます。その際、「BDアダプタ」と組み合わせると、簡単に接続できるかもしれません(BDアダプタは製作も容易です)。
 下図に2種類のリレーSS1A05(5V 10mA 接点容量0.5A), Y14H-1C-3DS(3V 50mA 接点容量1A)を使った回路例を示します。マイコンに逆電圧がかからないよう、リレーと並列にダイオードを入れます。

Blink.ino: ArduinoIDE [スケッチの例][01.Basics][Blink] (7行目と9行目を1000→1に変更)
int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // { }内を繰り返し実行する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(1); // 1ms待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(1); // 1ms待つ }

(練習) 上の回路を組み立て、電池で動くオモチャをつないで、Blink.inoの動作を確かめなさい。また、2.2を参考に、スイッチを押すと交互に動作・停止するようにしてみなさい。

13.2 モータを動かす

 DCモータ(小型の直流モータ)は大きな電流が流れるので、マイコンに直接接続することはできず、トランジスタやFETを使って駆動します。「ミニドラムマシン」では、バチを動かすのにDCモータを使い、2SC2120で駆動しました。赤外線リモコン自動車ではDCモータを2SK3142で駆動しました。
 モータのオンオフだけでなく、正転逆転させたい場合は、Hブリッジ回路の専用IC(例えばTA7291P)を使います。IC内部での電圧降下には注意が必要です。

Blink.ino: ArduinoIDE [スケッチの例][01.Basics][Blink] (7行目と9行目を1000→1に変更)
int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // { }内を繰り返し実行する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(1); // 1ms待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(1); // 1ms待つ }

 回転数を制御したい場合は、analogWrite()を用います。以下は3.2のLEDの代わりにモーターを接続した例です。

Fade.ino: ArduinoIDE [スケッチの例][01.Basics][Fade]
int led=13; // 変数ledに13を代入する int bright=5; // 変数brightに5を代入する int fade=5; // 変数fadeに5を代入する void setup(){ } void loop(){ // 以下を繰り返す analogWrite(led, bright); // ledピンにbrightの値をアナログ出力する bright=bright+fade; // brightの値にfadeの値を足す if(bright==0 || bright==255)//brightの値が0または255だったら fade=-fade; // fadeの符号を逆にする delay(30); // 30ms待つ }

13.3 振動モータを動かす

 Midiコントローラ「ジングルベル」では、振動モータFM34F(100mA以下)を使い、「鈴」を鳴らました。2SC2120(最大800mA)や2SC735など、少し多目に電流を流せるトランジスタを使っています。

Blink.ino: ArduinoIDE [スケッチの例][01.Basics][Blink] (7行目と9行目を1000→1に変更)
int led=13; // 変数ledに13を代入する void setup(){ // { }内をはじめに1度だけ実行する pinMode(led, OUTPUT); // ledピン(13ピン)を出力にする } void loop(){ // { }内を繰り返し実行する digitalWrite(led, HIGH); // ledピンにHIGHを出力する delay(1); // 1ms待つ digitalWrite(led, LOW); // ledピンにLOWを出力する delay(1); // 1ms待つ }

13.4 サーボモータを動かす

 ラジコン用のサーボモーターは、右図のようなPWM波形で角度(0~180°)を制御するように作られています。
 サーボモータは5Vで動作しますが、4.5Vでも支障なく動作しますので、ここではそのようにします。なお、ここで用いている方法ではブザー音を出すtoneと一緒に使うことはできません。
 以下は、車のワイパーのような動きをするプログラム、ボリュームの値に応じた角度になるプログラムです。

Sweep4313.ino:
#include <servo4313.h> // servo4313.hを読み込む(12, 13ピンのみ対応) int pos=0; // 変数posに0を代入する(サーボモータの位置: 0~180) int d=1; // 変数dに1を代入する(変化は1度ずつ) void setup(){ servoAttach(13); // 13ピンにサーボモータを接続する } void loop(){ // 以下を繰り返す servoWrite(pos); // サーボモータをposの位置にする pos+=d; // posにdを足し込む if(pos==179) d=-1; // posが179になったらdを-1にする else if(pos==0) d=1; // posが0になったらdを1にする delay(15); // 少し待つ }
Knob4313.ino
#include <servo4313.h> // servo4313.hを読み込む(12, 13ピンのみ対応) void setup(){ servoAttach(13); // 13ピンにサーボモータを接続する } void loop(){ // 以下を繰り返す int val=analogRead(A0);// valにA0ピンのアナログ値(0~1023)を代入する val=val*179/1023; // valの値を0~179にする servoWrite(val); // サーボモータをvalの位置にする delay(15); // 少し待つ }

(練習) ボリュームの代わりに明るさセンサーを使い、明るさで位置が変わるようにしなさい。


14. 押しボタン信号機

 押しボタン信号機の制御プログラムです。

Signal.ino:
int buttonPin=2; int RPin=16, YPin=15, GPin=14, RRPin=10, GGPin=9, buttonMemo; void carsig(int r, int y, int g){ digitalWrite(RPin,r); digitalWrite(YPin,y); digitalWrite(GPin,g); } void mansig(int r,int g){digitalWrite(RRPin,r); digitalWrite(GGPin,g);} void setup(){ pinMode(RPin, OUTPUT); pinMode(YPin, OUTPUT); pinMode(GPin, OUTPUT); pinMode(RRPin,OUTPUT); pinMode(GGPin,OUTPUT); pinMode(buttonPin,INPUT_PULLUP); carsig(LOW,LOW,HIGH); mansig(HIGH,LOW); // 車青・歩赤 } void loop(){ int button=digitalRead(buttonPin); if(button!=buttonMemo && button==LOW){ delay(4000); carsig(LOW,HIGH,LOW); delay(4000); // 車黄4秒 carsig(HIGH,LOW,LOW); mansig(LOW,HIGH); // 車赤・歩青16秒 delay(16000); for(int i=0;i<8;i++){ // 歩青点滅8秒 mansig(LOW,HIGH); delay(500); mansig(LOW,LOW); delay(500); } mansig(HIGH,LOW); delay(4000); // 歩赤4秒 carsig(LOW,LOW,HIGH); // 車青 } buttonMemo=button; }

 以下は歩行者用の「カッコー」の音を付け加えた例です。

SignalKakko.ino:
int bzz=11; // 変数bzzに11を代入する int button=2; // 変数buttonに2を代入する int RPin=16; // 変数RPinに16を代入する int YPin=15; // 変数YPinに15を代入する int GPin=14; // 変数GPinに14を代入する int RRPin=10; // 変数RRPinに10を代入する int GGPin=9; // 変数GGPinに9を代入する int lastState=0; // 直前のbuttonの状態 void carsig(int r, int y, int g){ // 関数定義 digitalWrite(RPin, r); // RPinピンにrを出力する digitalWrite(YPin, y); // YPinピンにyを出力する digitalWrite(GPin, g); // GPinピンにgを出力する } void mansig(int r, int g){ // 関数定義 digitalWrite(RRPin, r); // RRPinピンにrを出力する digitalWrite(GGPin, g); // GGPinピンにgを出力する } void setup(){ pinMode(bzz, OUTPUT); pinMode(RPin, OUTPUT); pinMode(YPin, OUTPUT); pinMode(GPin, OUTPUT); pinMode(RRPin,OUTPUT); pinMode(GGPin,OUTPUT); pinMode(button,INPUT_PULLUP); carsig(LOW, LOW, HIGH); mansig(HIGH,LOW); // 車青・歩赤 } void loop(){ int i, Tempo=200, button=digitalRead(buttonPin), e=659, c=523; if(button!=buttonMemo && button==LOW){ delay(4000); carsig(LOW,HIGH,LOW); delay(4000); // 車黄4秒 carsig(HIGH,LOW,LOW); mansig(LOW,HIGH); // 車赤・歩青 for(i=0;i<5;i++){ // カッコー16秒 tone(bzzPin, e, Tempo/2); delay(Tempo*2.5); tone(bzzPin, c, Tempo); delay(Tempo*5.5); tone(bzzPin, e, Tempo/2); delay(Tempo); tone(bzzPin, e, Tempo/2); delay(Tempo); tone(bzzPin, c, Tempo); delay(Tempo*6); } for(i=0;i<8;i++){ // 歩青点滅8秒 mansig(LOW,HIGH); delay(500); mansig(LOW,LOW); delay(500); } mansig(HIGH,LOW); delay(4000); // 歩赤4秒 carsig(LOW,LOW,HIGH); // 車青 } buttonMemo=button; }

15. 応用例

15.1 I2Cデバイスを使う

 最近は、SDAとSCLの2本の信号線(GndとV+を加えれば4本)で接続するさまざまなI2Cデバイスが入手できます。例えば液晶ディスプレイやモータードライバーや温湿度センサーや加速度センサーなどがあります。

 ここでは、液晶ディスプレイに表示する温湿度計を紹介します。この例ではI2Cのプルアップ抵抗はHDC1000に内蔵されています。液晶ディスプレイのライブラリ(ST7032)は公開されているものを入手し、librariesフォルダに入れておきます。

Thermometer.ino:
#include <Wire.h> #include <ST7032.h> #define HDC 0x40 ST7032 lcd; void setup(){ lcd.begin(16, 2); // 16文字×2行の液晶 lcd.setContrast(45); // 0-63...10(5V), 30(3.3V), 45(3V) } void loop(){ Wire.beginTransmission(HDC); Wire.write(0x00); Wire.endTransmission(); delay(20); Wire.requestFrom(HDC, 4); uint16_t t=Wire.read(); t=(t<<8)|Wire.read(); // 温度を取得 uint16_t h=Wire.read(); h=(h<<8)|Wire.read(); // 湿度を取得 t=(((t>>8)*165)>>8)-40; // 温度の値を変換 h=(((h>>8)*100)>>8); // 湿度の値を変換 lcd.clear(); // 表示クリヤー lcd.setCursor(0,0); // 位置を指定し lcd.print(t); lcd.print("\337C"); // 温度を表示 lcd.setCursor(8,0); // 位置を指定し lcd.print(h); lcd.print("%RH"); // 湿度を表示 delay(1000); // 1秒待つ }

15.2 Midi楽器を作って演奏させる

 こちらの例では、Midi楽器を制御して演奏させています。振動モーターで鈴をシャンシャン鳴らしながら「ジングルベル」を演奏します。


15.3 ドラムの演奏

 こちらの例では、ドラムを演奏しながらブザー音のメロディーが流れます。ドラムの位置にバチの位置決めにサーボモータ(PICO/STD/F)、バチを打ち下ろすのにDCモータを使いました。DCモータは大きな電流が流れるので、2SC2120(最大800mA)を使いました。


15.4 鉄琴を演奏させる

 こちらの例では、鉄琴を演奏しながらブザー音のメロディーが流れます。バチ(マレット)の位置決めにサーボモータ(PICO/STD/F)、バチを打ち下ろすのにDCモータを使いました。DCモータは大きな電流が流れるので、2SC2120(最大800mA)を使いました。


9.7 モーターカーを動かす

 DCモータを2個使ったモーターカーです。車の先端のリミットスイッチがオンになると、少し後退し、右回転して、再び前進します。DCモータの駆動には専用のHブリッジIC(TA7291P)を使いました。

Motorcar.ino:
int motorR1=14, motorR2=13; // 右モーター 00:停止 01:後退 10:前進 int motorL1=16, motorL2=15; // 左モーター 00:停止 01:後退 10:前進 int sw=8; void lfwd(){ digitalWrite(motorL1, HIGH); digitalWrite(motorL2, LOW);} void lbwd(){ digitalWrite(motorL1, LOW); digitalWrite(motorL2, HIGH);} void lstop(){digitalWrite(motorL1, LOW); digitalWrite(motorL2, LOW);} void rfwd(){ digitalWrite(motorR1, HIGH); digitalWrite(motorR2, LOW);} void rbwd(){ digitalWrite(motorR1, LOW); digitalWrite(motorR2, HIGH);} void rstop(){digitalWrite(motorR1, LOW); digitalWrite(motorR2, LOW);} void Stop(){ lstop(); rstop(); delay(1000);} // 停止 void Fwd(){ Stop(); lfwd(); rfwd();} // 前進 void Bwd(){ Stop(); lbwd(); rbwd();} // 後退 void turnRight(){ Stop(); lfwd(); delay(200); Stop();} // 右旋回 void turnLeft(){ Stop(); rfwd(); delay(200); Stop();} // 左旋回 void setup(){ pinMode(motorL1, OUTPUT); pinMode(motorL2, OUTPUT); pinMode(motorR1, OUTPUT); pinMode(motorR2, OUTPUT); pinMOde(sw, INPUT_PULLUP); delay(1000); } void loop(){ fwd(); // 前進し while(digitalRead(sw)==HIGH); // ぶつかったら Bwd(); delay(300); // 少し後退し Right(); // 右旋回 }

 以下はヒダピオ制御カー(Topman)のcpuをATtiny4313に交換し、上と同様の動作をさせたものです。

hcc.ino:
int motorR1=4, motorR2=5; // 右モーター 01:後退 10:前進 11:停止 int motorL1=6, motorL2=7; // 左モーター 01:後退 10:前進 11:停止 int swR=10, swL=11; // 左右のタッチスイッチ 0:オン 1:オフ void lfwd(){ digitalWrite(motorL1, HIGH); digitalWrite(motorL2, LOW);} void lbwd(){ digitalWrite(motorL1, LOW); digitalWrite(motorL2, HIGH);} void lstop(){digitalWrite(motorL1, HIGH); digitalWrite(motorL2, HIGH);} void rfwd(){ digitalWrite(motorR1, HIGH); digitalWrite(motorR2, LOW); } void rbwd(){ digitalWrite(motorR1, LOW); digitalWrite(motorR2, HIGH);} void rstop(){digitalWrite(motorR1, HIGH); digitalWrite(motorR2, HIGH);} void Stop(){ lstop(); rstop();} // 停止 void Fwd(){ Stop(); lfwd(); rfwd();} // 前進 void Bwd(){ Stop(); lbwd(); rbwd();} // 後退 void turnRight(){Stop(); lfwd(); delay(300); Stop();} // 右旋回 void turnLeft(){ Stop(); rfwd(); delay(300); Stop();} // 左旋回 void setup(){ pinMode(motorL1, OUTPUT); pinMode(motorL2, OUTPUT); pinMode(motorR1, OUTPUT); pinMode(motorR2, OUTPUT); pinMode(swR, INPUT_PULLUP); pinMode(swL, INPUT_PULLUP); Stop(); } void loop(){ Stop();delay(500); Fwd(); // 前進し while(digitalRead(swR)==HIGH && digitalRead(swL)==HIGH);// ぶつかったら Stop();delay(500); Bwd();delay(500); // 少し後退し Stop();delay(500); turnRight(); // 右旋回 }
ヒダピオ制御カーのピン(参考)
機能ピン番号pinMode
DIPスイッチ10INPUT_PULLUP 0:オン 1:オフ
DIPスイッチ21INPUT_PULLUP 0:オン 1:オフ
ブザー2,3OUTPUT
右モーター4,5OUTPUT00:- 01:後退 10:前進 11:停止
左モーター6,7OUTPUT00:- 01:後退 10:前進 11:停止
LED8OUTPUT0:消灯 1:点灯
CDS9INPUT0:暗い 1:明るい
右タッチスイッチ10INPUT_PULLUP0:オン 1:オフ
左タッチスイッチ11INPUT_PULLUP0:オン 1:オフ
リモコン STARTボタン12INPUT_PULLUP0:オン 1:オフ
リモコン LEFTボタン13INPUT_PULLUP0:オン 1:オフ
リモコン RIGHTボタン14INPUT_PULLUP0:オン 1:オフ
リモコン BACKボタン15INPUT_PULLUP0:オン 1:オフ
リモコン FORWARDボタン16INPUT_PULLUP0:オン 1:オフ

 下の写真は下山大氏が考案した「ザリガニロボット」で、メカ部分が単純で簡単に製作できるのが特長です。ブレッドボードを使った製作例ではありませんので悪しからず。走り方を工夫したり、センサーと組み合わせると面白いでしょう。


(参考資料)

[1] ATtiny4313 データシート, http://www.atmel.com/Images/doc8246.pdf
[2] Arduino, http://www.arduino.cc
[3] AVRマイコンで学ぶコンピュータの仕組み, https://koyama.verse.jp/elecraft/avr/avrtext.html
[4] AVRライター, https://koyama.verse.jp/elecraft/avr/avrwriter.html
[5] ATTiny Core, https://github.com/SpenceKonde/ATTinyCore
[6] 私だけの電子オルゴール, https://koyama.verse.jp/elecraft/mymelo/


(付録1) 入出力モジュール

 ブレッドボードで利用できる入出力モジュールの製作例です。

モジュール回路図製作例
可変抵抗モジュール
明るさセンサーモジュール
赤外線反射モジュール
音量センサーモジュール

(付録2) 使用する主な部品

部品表
名称 外観 備考
ブレッドボード EIC-301 秋月 190円
ブレッドボード ジャンパーワイヤ 15cm   秋月 10本 300円
ブレッドボード ジャンパーワイヤ EIC-J-S   秋月 250円
LED 秋月 10個 120円
フルカラーLED OSTA5131A 秋月 50円
赤外線LED 秋月 10個 100円
数字表示LED C-551SR 秋月 40円
圧電スピーカー 秋月 2個 100円
プッシュスイッチ DS-660R-C 千石 84円
抵抗 1/4W 100Ω, 220Ω, 1kΩ, 10kΩ 秋月 100本 100円
フィルムコンデンサ 0.01μF秋月 10円
CDS(光センサー)秋月 30円
サーミスタ(温度センサー)秋月 50円
フォトリフレクタ(赤外線反射センサー)秋月 50円
AVRマイコン(ATtiny4313) 妙楽堂 200円
電池ケース(単3×2本 スイッチ付)   秋月 60円
サーボモーター SG-90   秋月 400円
振動モーター   秋月 2個 100円
2SC2120   秋月 20個 200円
リレー SS1A05   秋月 5個 280円
リレー Y14H-1C-3DS   秋月 80円
ベニア板(5.5cm×12cm)   ブレッドボードと電池ケースを貼り付けます。

(付録3) Arduinoプログラムの仕様の抜粋(ATtiny4313版)

(1) 入出力に利用できるピン(ATtiny4313の場合)

種類ピン番号
デジタル出力ピン0~16
デジタル入力ピン
アナログ出力ピン*7, 11, 12, 13
アナログ入力ピンA0
* ブログラム中でtone()を使っている場合は 7, 11を使う

(2) データの型

サイズ
char
int8_t
1バイト-128~127
unsigned char
uint8_t
byte
1バイト0~255
int
int16_t
2バイト-32768~32767
unsigned int
uint16_t
2バイト0~65535
long
int32_t
4バイト-2147483648~2147483647
unsigned long
uint32_t
4バイト0~4294967295
float4バイト3.4028235E+38~-3.4028235E+38

(3) 組み込み定数

定数名内容
HIGH, LOWデジタル出力するまたはデジタル入力されたピンの状態
OUTPUT, INPUT, INPUT_PULLUPpinMode()で設定するデジタルピンの入出力
A0アナログpin(ATtiny4313ではA0だけが利用できる)
DDRB, PORTB, PB0~PB7ATtiny4313固有のポートおよびビット指定
DDRD, PORTD, PD0~PD7ATtiny4313固有のポートおよびビット指定

(4) 組み込み関数

関数名内容
pinMode(pin, OUTPUT)pinをOUTPUTにする(OUTPUT, INPUT, INPUT_PULLUP)
digitalRead(pin)pinの値を読んでHIGHまたはLOWの値を返す
digitalWrite(pin, HIGH)pinにHIGHを出力する(値はHIGHまたはLOW)
analogRead(A0)A0ピンのアナログ値を読み、0~1023の値を返す
analogWrite(pin, value)pin(7,11,12,13のいずれか)にvalue(0~255)を出力する
map(val, fromL,fromH, toL,toH)fromL~fromHの値val(整数)をtoL~toHの値に変換する
delay(t)t[ms]待つ
delayMicroseconds(t)t[μs]待つ
tone(pin, freq, t)pinにfreq[Hz]の矩形波信号をt[ms]出力する
noTone()tone()を終了する
servoAttach(pin)pinをサーボ制御ピンにする(pinは12, 13のいずれか)
(toneと同時に利用することはできません)
servoWrite(pos)サーボの角度をpos(0~180)にする
(toneと同時に利用することはできません)
Serial.begin(speed)speed[bps]でシリアル通信を開始する
Serial.available()受信した文字数を返す
Serial.read()受信した1文字を返す
Serial.write(c)cを送信する
Serial.print(s)文字列sを送信する
Serial.println(s)文字列sと改行を送信する


koyama88@cameo.plala.or.jp