小嶋秀樹 | 研究室
日本語 | English
Miyagino を C でプログラムする

ここでは Miyagino を C (C++) でプログラムする方法を解説します.プロセッサが直接実行できるコードにコンパイルし,Miyagino にアップロードすることで,PC との接続がなくても(電池などを電源とすることで)Miyagino 単体で動作するアプリケーションを制作することができます.

【スケッチの作成】

Arduino 開発環境を起動すると,まず空のスケッチ(名前は今日の日付から生成)が開きます.スケッチとは Arduino のソースプログラムのことです.ここに setup() という関数と,loop() という関数を作ることで,Miyagino で動作するプログラムを作成します.下図(クリックして拡大)は,"A" のモールス符号(・ー)を 13番の LED の明滅パターンで繰り返すものです.

関数 setup() はプログラム起動直後に一度だけ呼び出され,その後,関数 loop() が繰り返し呼び出されます.Miyagino(あるいは Arduino 一般)のスケッチは,基本的にこの2つの関数(あるいはこれらから呼び出されるユーザ定義の関数)のみから構成されます.これをフローチャートで書くと下図のようになります.

Miyagino3 main flow
【スケッチのコンパイルとアップロード】

作成したスケッチをコンパイルし,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」のページを参照してください.

【ディジタル入出力】

スイッチを押しているあいだ LED が点滅するスケッチ+回路

Miyagino3 dio example
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);
    }    
}
【PWM 出力】

スイッチをクリックするたびに LED の強弱が変わるスケッチ+回路

Miyagino3 pwm example
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 という名前のインスタンス)をつくると,以下のようなメソッドを利用できるようになります.

3つのサーボモータを ±45deg 振動させるスケッチ+回路

Miyagino3 servo example
#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);
}
【アナログ入力】

アナログ入力のレベルを4桁の LED で表示するスケッチ+回路

Miyagino3 analog example
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);
}
タイマ処理
【Metro によるタイマ処理】

一定の時間間隔で,あるタスクを実行したいことがあります.そのために利用するのがタイマです.Arduino では Metro ライブラリを利用します.まず,下のリンクから "Downlad here: Metro.zip" をクリックして,"Metro.zip" をダウンロードしてください.

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 ライブラリのページをご覧ください.

タイマ処理のスケッチ+回路

Miyagino3 timer1 example
#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 に対応し,それぞれ独立した割り込み処理に利用できます.

スイッチ入力による割り込みのスケッチ+回路

Miyagino3 interrupt example
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つの関数呼び出しによって挟まれた部分は,外部入力あるいはタイマによる割り込みの影響を受けません.

なお,割り込み関数の内部では,新しい割り込みの受け付けは自動的に一時中止されているため,基本的に「本業」の中でのみ割り込みがけ付けられます.(割り込み関数の内部で interrupts() を呼び出すことで,入れ子になった割り込み受け付けも可能ですが,あまりおすすめしません.)

その他の関数

Arduino では,C (C++) で利用できる標準的な関数やマクロを利用することができます.その他にも便利な関数やマクロが用意されていますので,よく使うものを以下に紹介します.より詳しい解説は,Arduino Reference のページ(英語)を参照してください.

なお Arduino では,int 型データは 16bit (−32768〜32767),long (= long int) 型データは 32bit (−2147483648〜2147483647) で表現されます.プログラム中の定数(たとえば 1234 や 0x3FF)は基本的に int 型として扱われますが,たとえば 12345678L のように "L" を加えることで,long 型の定数とすることができます.

【乱数】
【時刻取得・ディレイ】
【便利なマクロ】