ここでは Miyagino を C (C++) でプログラムする方法を解説します.プロセッサが直接実行できるコードにコンパイルし,Miyagino にアップロードすることで,PC との接続がなくても(電池などを電源とすることで)Miyagino 単体で動作するアプリケーションを制作することができます.
Arduino 開発環境を起動すると,まず空のスケッチ(名前は今日の日付から生成)が開きます.スケッチとは Arduino のソースプログラムのことです.ここに setup() という関数と,loop() という関数を作ることで,Miyagino で動作するプログラムを作成します.下図(クリックして拡大)は,"A" のモールス符号(・ー)を 13番の LED の明滅パターンで繰り返すものです.
関数 setup() はプログラム起動直後に一度だけ呼び出され,その後,関数 loop() が繰り返し呼び出されます.Miyagino(あるいは Arduino 一般)のスケッチは,基本的にこの2つの関数(あるいはこれらから呼び出されるユーザ定義の関数)のみから構成されます.これをフローチャートで書くと下図のようになります.
作成したスケッチをコンパイルし,USB 接続した Miyagino にアップロード(内蔵 Flash にプログラムを焼き込む)には,Tools > Board を "Arduino Duemilianove or Nano w/ ATmega328" に,また Tools > Serial Port を Miyagino に接続する通信ポート(参考:通信ポート名の確認)に正しく設定した上で,Upload アイコンをクリック(または File > Upload to I/O Board を選択)するだけです.自動的にコンパイラが起動し,とくにエラーがなければ,実行可能プログラムが Miyagino にアップロードされます.アップロードが完了すると,プログラムは自動的にスタートします.
Miyagino (Arduino) のスケッチは C (C++) で書きますが,main() もインクルード文もありません.じつは Arudino 開発環境は,ユーザが書いたスケッチをつぎのような C (C++) ソースプログラムに自動変換し,それを g++ などのコンパイラで,Arduino (ATMega プロセッサ) をターゲットとしてクロスコンパイルしています.
#include "Arduino.h" int main (void) { init(); setup(); for (;;) { loop(); } return 0; } void setup () {...} void loop () {...}
init() は Miyagino (Arduino) を初期化するための関数で,Arduino 開発環境によって用意されたものです.main() は 0 を返すようになっていますが,実際には return 0; まで制御が進むことはありません.
Arduino の C (C++) プログラミングでは,int が 16bit,short int も 16bit となります。値の範囲に十分注意してください。long int とすれば 32bit,long long int とすれば 64bit となります。もちろん,char や byte (= unsigned char) は 8bit です.
ここではスケッチから使える Arduino のシステム関数のなかで,ディジタル・アナログ信号の入出力を扱う関数について解説します.LED やスイッチなどの接続については「Miyagino + Pd」のページを参照してください.
-
void pinMode (pin, mode);
- pin : ピン番号 2〜13 (0〜13), A0〜A5
- mode : 動作モード INPUT, OUTPUT, INPUT_PULLUP
- 返値 : なし
pin 番のディジタル入出力ピンを入力モード(INPUT)あるいは出力モード(OUTPUT)に設定します.INPUT_PULLUP は INPUT と同様に入力モードになりますが,内蔵プルアップ抵抗が有効化される点が異なります.ピンの番号は,通常は 2〜13 となりますが,PC と通信しないスタンドアロン動作時は 0〜13 に広げることができます.また,アナログ入力ピン A0〜A5 についても,この関数で動作モードを設定することで,ディジタル入出力ピンとして利用できます.
-
void digitalWrite (pin, value);
- pin : ピン番号 2〜13 (0〜13), A0〜A5
- value (出力モードの場合) : ディジタル出力値 LOW (0), HIGH (1)
- 返値 : なし
pin 番のピンが出力モードの場合,オフ(LOW)あるいはオン(HIGH)を出力します.
-
int digitalRead (pin);
- pin : ピン番号 2〜13 (0〜13), A0〜A5
- 返値 : ディジタル入力値 LOW (0), HIGH (1)
pin 番のピンの電圧が,オフ(約 0V)のときは LOW を返し,オンのときは HIGH を返します.入力モードだけでなく,出力モードでも使えます.
スイッチを押しているあいだ LED が点滅するスケッチ+回路
void setup () { // 入力12番+プルアップ有効 pinMode(12, INPUT_PULLUP); digitalWrite(12, HIGH); // 出力13番(標準 LED 利用) pinMode(13, OUTPUT); } void loop () { if (digitalRead(12) == LOW) { // 押す (LOW) > LED 点滅 (5Hz) digitalWrite(13, HIGH); delay(100); digitalWrite(13, LOW); delay(100); } }
-
void analogWrite (pin, value);
- pin : ピン番号 3, 5, 6, 9, 10, 11
- value : PWM デューティ比 (0〜255)
- 返値 : なし
pin 番のピンから PWM 信号(約 490Hz)を出力します.そのデューティ比(オンになっている時間の割合)は value ⁄ 255.0 となります.つまり value = 0 のときは常にオフ,128 のときは 50%,255 では常にオンとなります.analogWrite() を実行すると,ピンは自動的に出力モードになります.
スイッチをクリックするたびに LED の強弱が変わるスケッチ+回路
int state = 0; // 直前の SWT 状態 int bright = 0; // 明るさ void setup () { // 入力12番+プルアップ有効 pinMode(12, INPUT); digitalWrite(12, HIGH); } void loop () { if (digitalRead(12) == LOW) { // SWT が押された state = 1; } if ((state == 1) && (digitalRead(12) == HIGH)) { // SWT が放された state = 0; // 32 だけ平均電圧を上げる(上限 255) bright += 32; if (bright > 255) bright = 0; // PWM 出力 analogWrite(11, bright); } }
まず,Servo ライブラリを読み込みます.Sketch > Import Library... > Servo を読み込んでください.スケッチの先頭に #include <Servo.h> が挿入されます.Servo クラスの変数(たとえば servo という名前のインスタンス)をつくると,以下のようなメソッドを利用できるようになります.
-
void servo.attach (pin);
- pin : ピン番号 2〜13
- 返値 : なし
Servo クラスのインスタンス servo を,pin 番のピンに対応づけます.これでサーボ制御信号(回転位置は中央 90deg)が出力されるようになります.すでに他のインスタンスに対応づけたピンは指定しないほうがよいでしょう.(いずれかのピンが attach() されているとき,9番・10番ピンからの PWM 出力が出来なくなります.バグではなく仕様です.)
-
void servo.write (deg);
- deg : 回転位置 (deg)
- 返値 : なし
Servo クラスのインスタンス servo を,回転位置 deg まで回転させます.単位は度 (deg) で,中央位置を 90deg とし,0〜180deg の範囲で指定することができます.(あらかじめ servo をいずれかのピンに attach() しておく必要があります.)
-
void servo.detatch ();
- 返値 : なし
Servo クラスのインスタンス servo について,それまで attach() されていたピンとの対応づけを解除します.
3つのサーボモータを ±45deg 振動させるスケッチ+回路
#include <Servo.h>
Servo servo1, servo2, servo3;
float degree = 0.0;
void setup () {
// 10, 11, 12番からサーボ制御出力
servo1.attach(10);
servo2.attach(11);
servo3.attach(12);
}
void loop () {
// -45〜+45deg で(位相をずらして)サーボ制御出力
// sin() は正弦関数.radians() は deg>rad の単位変換
servo1.write(sin(radians(degree )) * 45.0 + 90.0);
servo2.write(sin(radians(degree + 30.0)) * 45.0 + 90.0);
servo3.write(sin(radians(degree + 60.0)) * 45.0 + 90.0);
// およそ 3.6s 周期
degree += 1.0;
delay(10);
}
-
int analogRead (pin);
- pin : アナログピン番号 0〜5 (a0〜a5 に対応)
- 返値 : アナログ入力値 (0〜1023)
pin 番のアナログピン(a0〜a5)の電圧をよみとり,マイナス電源の電圧 (0V) を 0 とし,プラス電源の電圧 (5V) を 1023 とする整数値 (10bit) にアナログ-ディジタル変換します.たとえばポテンシオメータ(可変抵抗器)のツマミの角度を読み取ったり,あるいは何らかのセンサの出力信号を読み取るために使います.アナログ信号をディジタル値に変換するには,およそ 0.1ms かかります.
アナログ入力のレベルを4桁の LED で表示するスケッチ+回路
void setup () { // 2,3,4,5番ピンから LED 出力 pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); } void loop () { // a0 からアナログ読み取り 10bit int value = analogRead(0); // 上位 4bit を LED に出力 digitalWrite(2, (value & 512)? HIGH: LOW); digitalWrite(3, (value & 256)? HIGH: LOW); digitalWrite(4, (value & 128)? HIGH: LOW); digitalWrite(5, (value & 64)? HIGH: LOW); }
一定の時間間隔で,あるタスクを実行したいことがあります.そのために利用するのがタイマです.Arduino では Metro ライブラリを利用します.まず,下のリンクから "Downlad here: Metro.zip" をクリックして,"Metro.zip" をダウンロードしてください.
- Metro ライブラリ:http://www.arduino.cc/playground/Code/Metro
Windows であれば,Arduino フォルダの中にある libraries フォルダに,ダウンロード・展開した Metro フォルダを移動あるいはコピーすれば,インストール完了です.
Mac OS の場合は,コンソール端末上で,Arduino.app が置かれたフォルダ(たとえば /Applications)から,Contents/Resources/Java/libraries に入り,ここにダウンロード・展開した Metro フォルダを移動あるいはコピーします.これでインストール完了です.
Mac OS での Metro ライブラリのインストール例
Mac> cd /Applilcations Mac> cd Arduino.app/Contents/Resources/Java/libraries Mac> cp -r ~/Downloads/Metro .
Metro ライブラリを利用するには,Arduino 開発環境を起動し,Sketch > Import Library... > Metro を選択してください.スケッチの先頭に #include <Metro.h> が挿入されます.使い方は,つぎの例を参考にしてください.詳細については,Metro ライブラリのページをご覧ください.
タイマ処理のスケッチ+回路
#include <Metro.h> // 周期 123ms/789ms, 自動更新 Metro metro1 = Metro(123, 1), metro2 = Metro(789, 1); int state1 = LOW, state2 = LOW; void setup() { pinMode(9, OUTPUT); digitalWrite(9, state1); pinMode(13, OUTPUT); digitalWrite(13, state2); } void loop() { if (metro1.check() == 1) { // トグル動作 if (state1 == LOW) state1 = HIGH; else state1 = LOW; // LED 出力 digitalWrite(9, state1); } if (metro2.check() == 1) { // トグル動作(上の if...else... と同じ) state2 = state2? 0: 1; // LED 出力 digitalWrite(13, state2); } }
上の例では,9番ピンの LED を 123ms 周期で点滅させ,また 13番ピンの LED を 789ms 周期で点滅させています.check() メソッドを呼び出すことで,Metro オブジェクトごとにタイマ時刻に達したか否かを判定します.
Metro は,次節で紹介する「割り込み処理」ではなく,いわゆる「ポーリング処理」によってタイマ機能を実現しています.したがって,毎回の loop() の実行時間が長い場合,Metro の時間精度は悪くなります.
より高い時間精度が必要な場合は,Timer1 ライブラリを使うとよいでしょう.これは「割り込み処理」を利用しています.使い方は,つぎの例を参考にしてください.
#include <TimerOne.h> volatile int state = false; void setup () { pinMode(13, OUTPUT); pinMode(9, OUTPUT); // タイマ設定 500000us = 500ms = 0.5s Timer1.initialize(); Timer1.attachInterrupt(wakeup, 500000); } void loop () { // 本業:10Hz で点滅 digitalWrite(13, HIGH); delay(50); digitalWrite(13, LOW); delay(50); } void wakeup () { // 副業:9番の出力を反転 state = !state; digitalWrite(9, (state)? HIGH: LOW); }
なお,Timer1 は TimerOne.h の中で生成された TimerOne クラスのオブジェクトです.Timer1 ライブラリで利用できるのは,この Timer1 オブジェクトのみ(つまり1チャネルのみ)です.
割り込み (interruption) とは,たとえば外部のスイッチが押されるたびに,あらかじめ設定しておいた関数を呼び出すための仕組みです.Miyagino (Arduino) は,通常は loop() を繰り返し呼び出します.これが「本業」の処理フローとなります.割り込み処理を設定しておくと,スイッチやタイマからのキッカケによって,「本業」の処理フローを一時中断し,あらかじめ設定しておいた「副業」関数を呼び出し,それが終了すると「本業」の処理フロー(一時中断した箇所)に戻ります.
割り込みのキッカケとして,入力モードに設定した 2番ピンあるいは 3番ピンの電圧変化を使います.2番ピンは割り込み番号 0,3番ピンは割り込み番号 1 に対応し,それぞれ独立した割り込み処理に利用できます.
-
void attachInterrupt (intId, func, cond);
- intId : 割り込み番号 0 (2番ピンを使用), 1 (3番ピンを使用)
- func : 呼び出される関数名(void something(); の形式)
- cond : 割り込み条件 LOW, CHANGE, RISING, FALLING
- 返値 : なし
割り込みを有効にします.intId が 0 のときは 2番ピン,1 のときは 3番ピンを割り込み入力に使います.このピンの電圧が,LOW(オフのあいだ)・CHANGE(変化したとき)・RISING(オフからオンに上がったとき)・FALLING(オンからオフに下がったとき)に,func を名前とする関数が呼び出されます.void func() {...} のように定義される関数です.割り込み入力ピンは,あらかじめ入力モードに設定しておきます.外部にプルアップ抵抗器(あるいはプルダウン抵抗)をつけるとよいでしょう.
-
void detachInterrupt (intId);
- intId : 割り込み番号 0 (2番ピンを使用), 1 (3番ピンを使用)
- 返値 : なし
割り込み番号 intID の割り込みを無効にします.
スイッチ入力による割り込みのスケッチ+回路
volatile int state = false; void setup () { // LED 13番・9番 pinMode(13, OUTPUT); pinMode(9, OUTPUT); // 割り込み入力(外部でプルアップ) pinMode(2, INPUT); // 割り込み有効(RISING->pressed()) attachInterrupt(0, pressed, RISING); } void loop () { // 本業:1Hz で点滅 digitalWrite(13, HIGH); delay(500); digitalWrite(13, LOW); delay(500); } void pressed () { // 副業:9番の出力を反転 state = !state; digitalWrite(9, (state)? HIGH: LOW); }
上の例では,13番ピンの LED を 1Hz で点滅させることを本業として,スイッチが押されたときに,9番ピンの出力を反転させるという副業もこなしています.スイッチが押されると,この本業を(おそらく delay(500) の実行途中で)中断し,割り込み関数 pressed() を呼び出します.それが終了すると,本業を(中断した箇所から)再開します.副業が短時間で終わるのであれば,本業にはほとんど影響ありません.
なお,割り込み関数の内部から書き換える大域変数(上の例では state)には,volatile というキーワードをつけるように心がけてください.これは,state の値を参照するとき,プロセッサ内部のレジスタではなく,state に割り当てられた RAM にアクセスさせるためです.
厳密な実時間処理を行なうなど,「本業」を「副業」への割り込みによって中断させたくない場合,noInterrupts() によって割り込みを受け付けなくすることができます.割り込みの受け付けを再開するには interrupts() を呼び出します.これら2つの関数呼び出しによって挟まれた部分は,外部入力あるいはタイマによる割り込みの影響を受けません.
-
void noInterrupts ();
- 返値 : なし
すべての割り込みの受け付けを停止します.
-
void interrupts ();
- 返値 : なし
割り込みの受け付けを再開します.
なお,割り込み関数の内部では,新しい割り込みの受け付けは自動的に一時中止されているため,基本的に「本業」の中でのみ割り込みがけ付けられます.(割り込み関数の内部で interrupts() を呼び出すことで,入れ子になった割り込み受け付けも可能ですが,あまりおすすめしません.)
Arduino では,C (C++) で利用できる標準的な関数やマクロを利用することができます.その他にも便利な関数やマクロが用意されていますので,よく使うものを以下に紹介します.より詳しい解説は,Arduino Reference のページ(英語)を参照してください.
なお Arduino では,int 型データは 16bit (−32768〜32767),long (= long int) 型データは 32bit (−2147483648〜2147483647) で表現されます.プログラム中の定数(たとえば 1234 や 0x3FF)は基本的に int 型として扱われますが,たとえば 12345678L のように "L" を加えることで,long 型の定数とすることができます.
-
long random(max);
long random(min, max);- min : 発生させる乱数の最小値(デフォルト 0)
- max : 発生させる乱数の最大値 + 1
- 返値 : 発生させた乱数
min 〜 max−1 の乱数を返します.Arduino を起動するたび,同じ乱数列が生成されますので,適宜 randomSeed() を使って,乱数列を「乱して」ください.
-
randomSeed(unsigned int seed);
- seed : 乱数の「タネ」
- 返値 : なし
乱数列に「タネ」を設定します.以降,random() は,設定したタネから導出される乱数列を生成していきます.たとえば未接続のアナログ入力ピンの値を読み取り,それを「タネ」とするとよいでしょう.
-
unsigned long millis ();
- 返値 : 起動時からの経過時間(単位は ms)
Arduino を起動(あるいはリセット)してからの経過時間をミリ秒 (ms) を単位として返します.およそ 50日で桁あふれし,0 に戻ります.(割り込み関数の内部では使えません.)
-
unsigned long micros ();
- 返値 : 起動時からの経過時間(単位は ms)
Arduino を起動(あるいはリセット)してからの経過時間をマイクロ秒 (µs) を単位として返します.およそ 70分で桁あふれし,0 に戻ります.(割り込み関数の内部では使えません.)
-
void delay (unsigned long msec);
- 返値 : なし
Arduino の「本業」の動作を msec ミリ秒 (ms) だけ中断(スリープ)します.中断している間も,割り込みの受け付けは可能です.指定できる中断時間は,最大で約50日です.(割り込み関数の内部では使えません.)
-
void delayMicroseconds (unsigned int usec);
- 返値 : なし
Arduino の「本業」の動作を usec マイクロ秒 (µs) だけ中断(スリープ)します.中断している間も,割り込みの受け付けは可能です.指定できる中断時間は,最大で約 16383µs です.(割り込み関数の内部では使えません.)
-
min(a, b);
a, b のうち小さい方を返します
-
max(a, b);
a, b のうち大きい方を返します
-
abs(x);
x の絶対値を返します
-
round(x);
x を小数点以下四捨五入した整数値を返します.
-
sq(x);
x の二乗を返します.
-
radians(deg);
度 deg(deg) をラジアン (rad) に単位変換して返します.
-
degrees(rad);
ラジアン rad(rad)を度 (deg) に変換して返します.
-
constrain(x, low, high);
x の値を low で足切り,high で頭切りにして返します.
-
map(value, fromLow, fromHigh, toLow, toHigh);
fromLow から toLow に写像し,fromHigh から toHigh に写像するように,入力値 value を新しい値に変換して返します.(返値は toLow 〜 toHigh に制限されるわけではありません.範囲外の value は範囲外の値に変換されます.(必要に応じて constrain() を使ってください.)
-
PI = 3.14159265...
HALF_PI = 1.57079632...
TWO_PI = 6.28318530...それぞれ,π,π ⁄ 2,2π の近似値です.