今回は、スケッチの構造や、これからの連載で使う予定のライブラリについて、予備知識として使い方や注意事項などを簡単に説明します。
●Arduinoのスケッチの構造
スケッチとは、プログラムのソース・ファイルのことです。拡張子は"pde"ですが、通常のテキスト・ファイルですので、Windowsメモ帳などのテキスト・エディタでも開けます。スケッチは一つのフォルダに一つだけ保存されます。
フォルダ名はスケッチと同じ名称にする必要があります。外部にヘッダ・ファイルを持たせて、スケッチから読み込むこともできますが、そのヘッダ・ファイルは同一のフォルダ内にあるものに限られます(ライブラリのヘッダ・ファイルを除き、ほかの階層やサブフォルダにあるヘッダ・ファイルは使用不可)。
スケッチを新規に作成する場合、まず、"setup()"と"loop()"という二つのvoid型関数を定義します。これらの関数は、Arduinoで予約されているもので、必ずこの名称でなければなりません。
"setup()"には、起動時に一度だけ実行される初期化処理を記述します。"loop()"はメイン・ループの中身の処理です。ループからコールされているため、通常は、"loop()"の中でループさせてはいけません。スケッチ上には現れませんが、実際は次のようなコードが実行されているはずです。
main() {
setup();
while(1) {
loop();
}
}
●ライブラリのリンク
Arduinoのライブラリには、標準で、EEPROM、Ethernet、LiquidCrystal(LCD)、Servo、Wire(I2C)といったライブラリが用意されていますので、ハードウェアさえ接続されていれば、それらを簡単に扱うことができます。
これらのライブラリはC++のクラスとしてコーディングされています。C++言語になじみがない人でも例題などを真似すればわりと簡単に使えますので、それほど心配はいりません。なんでそうなるの?と考えるとC++も奥深いですから、パターンを覚えて、そういうものだ、と考えればよいでしょう。
Arduinoはもともと、プログラムに無縁の人でも使えるように、という設計思想の製品ですから、そういう考えは間違いではないと思います。
C言語の構文もほとんどそのまま使えますので、C言語を知っている人には抵抗なく使えるはずです。
ライブラリの使用方法は簡単で、ライブラリのオブジェクトにより多少違いはありますが、基本的には、各ライブラリのヘッダ・ファイルをスケッチの最初のほうでインクルードし、オブジェクトをインスタンス(実体)化するだけです。ライブラリによっては、ピンをアサインするなどの初期化処理を"setup()"内に記述する場合もあります。
"Wire"(I2C)のライブラリのように、ヘッダ・ファイル内で、すでにインスタンス化が行われているものもあります。このようなものは、あらためてインスタンス化の処理は不要です。
●WinAVRのライブラリの流用
WinAVRで用意されているランタイム・ライブラリも使用することができます。たとえば、文字列フォーマットの"sprintf()"は"stdio.h"をインクルードすることで使用できます。
AVRのレジスタを直接アクセスすることもできます。この場合は、"AVR/io.h"をインクルードすれば、AVRのレジスタ"PORTA"や"DDRA"といったシンボルも使えます。
I/Oをビット単位でアクセスする関数も用意されていますが、CAN制御では、直接レジスタを操作している場合があります。
●A-Dコンバータ
A-Dコンバータは初期化処理なしに、すぐにアナログ値を読み出すことができます。リファレンス電圧はデフォルトで5V(AVR内部で"ARef"ピンが+5Vに接続)になっています。これは、AVRのリセット直後の本来の状態とは異なります。
"ARef"ピンにリファレンス電圧を与える場合は、"analogReference()"関数でソースを外部入力に設定します。#256基板でシャント・レギュレータを使う場合は外部入力に設定してください。
次に外部リファレンス電圧に設定して、アナログ値を読み出すコードの例を示します。
// AREFピン 外部REF電圧
analogReference(EXTERNAL);
// アナログ・ポート"0"の変換値を読み出す
val = analogRead(0);
なお、"ARef"ピンをGNDレベルにした状態でArudioを立ち上げると、GNDと+5VがAVR内部でショートすることになり、デバイスにダメージを与える可能性があるので、注意してください。
●LiquidCrystal(LCD;液晶表示器)
"LiquidCrystal"は、パーツ・ショップに出回っているキャラクタ表示LCD用の制御オブジェクトです。制御信号のほか、データ信号も個別にディジタル・ポートにアサインすることができます(連続でなくても可)。
このオブジェクトを使用する場合は、ヘッダ・ファイル"LiquidCrystal.h"のインクルードが必要です。また、使用する前にインスタンス化が必要です。このとき、ポートのアサインを指定できます。初期化時のコードの例を示します。ポートのアサインは#256にあわせてあります。
#include <LiquidCrystal.h>
#define LCD_RS 8
#define LCD_RW 2
#define LCD_E 3
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D6 6
#define LCD_D7 7
// LCDのインスタンス化
LiquidCrystal lcd(LCD_RS, LCD_RW, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
実際に文字列を表示する場合は、次のようにします。このコードはなんらかの関数の中で使用します。
lcd.print("LCD TEST");
書式付で数値を文字列として表示する場合は次のようにします。C言語のライブラリの"sprintf()"を使った例です。
int i = 100;
char StrBuf[10];
sprintf(StrBuf, "%03d", i);
lcd.print(StrBuf);
"sprintf()"を使う場合は、"stdio.h"のインクルードが必要です。
このオブジェクトは、LCDコントローラのBUSYチェック機能を使っていません。したがって、LCD側のデータ信号は常に入力です。
#256基板では、LCDのデータ信号をシフトレジスタHC595の制御信号出力やスイッチ・ステータスの入力と兼用で使っていますが、シフトレジスタのアクセスやスイッチ入力側にちょっとした切り替え処理を追加するだけで、"LiquidCrystal"オブジェクトがそのまま利用できます。
●Wire(I2C)
"Wire"はI2Cで通信するためのオブジェクトです。AVR内蔵のTWIモジュールを使用するため、"SCL"、"SDA"にアサインされるピンはアナログ・ポートの"4"と"5"に決められています。
このオブジェクトを使用するには、"Wire.h"のインクルードが必要です。インスタンス化は"Wire.h"の中で記述されているため、改めて行う必要はありません。
I2Cでアクセスする前に、初期化時などに"Wire.begin()"関数を実行しておく必要があります。この関数は、引数を省略すると、I2Cマスタとして初期化されます。また、引数にI2Cアドレスを与えるとI2Cスレーブとして初期化されます。
次にI2Cマスタとして初期化して、RTCから「秒」データを読み出す例を示します。
#include <Wire.h>
#define I2C_RTC_ADRS 0x51 // スレーブ・アドレス(7bit)
void setup() {
Wire.begin();
}
void loop() {
int sec;
Wire.beginTransmission(I2C_RTC_ADRS); // (1) スタート
Wire.send(0x02); // (2) "秒"の レジスタアドレス
Wire.endTransmission(); // (3) ストップ
Wire.requestFrom(I2C_RTC_ADRS, 1); // (4) スタート、受信
sec = Wire.receive() & 0x7F; // (5) データ取り出し
:
}
(1) の"Wire.beginTransmission()"関数は、スタート・コンディションを発行してコントロール・バイト(スレーブのI2CアドレスとWフラグ)を送信するメソッドです。
(2) の"Wire.send()"関数は1バイトをマスタ送信するもので、この場合は、RTCのレジスタ・アドレスを送信して「秒」レジスタを指定しています。
(3) でいったん送信を終了します(ストップ・コンディションの発行)。
(4) で受信要求を出します。このメソッドはスタート・コンディションを発行して、コントロール・バイト(Flag="R")を送信して、指定バイト数のマスタ受信を行います。おそらく、受信したデータはリング・バッファに格納されているものと思われます。
(5) でバッファから受信データを取り出して変数へ代入しています。
このWireオブジェクトは、リピート・スタート・コンディションを発行させることができません。上記の例や、EEPROMのデータ読み出しの際など、アドレスを設定(Write)してからデータを読み出す(Read)ような場合、通常は、データ読み出しの直前にリピート・スタート・コンディションを発行しますが、Wireの関数を組み合わせてもうまくはいきませんでした。
TWIモジュールでは、スタート・コンディションを発行したあと、ストップ・コンディションを発行せずに、再び、スタート・コンディションを発行すると、結果的にリピート・スタート・コンディションになりますが、それを期待して、(3) の"Wire.endTransmission()"の処理をはずしてみたところ、データがくっついて無茶苦茶なことになりました。このオブジェクトを使う限り、リピート・スタートは使えないようです。
幸い、RTCもEEPROMもアドレス設定でいったんストップして続けてリードすると、前回のアドレスの設定が残っていて、そこから読み出しが始まるようになっています。そのため実用上は問題ありませんが、リピート・スタートが必要なデバイスには"Wire"オブジェクトは使えないということになります。
なお、I2Cについては、AVRのI2C制御で連載していますので、そちらも参照してください。
RTCの使い方については、後ほどのクロックの製作時に説明します。
●Servo
RCサーボ・モータを制御するオブジェクトです。AVR内蔵のPWMモジュールを使用しています。
このオブジェクトを使用するには、"Servo.h"をインクルードしておきます。また、オブジェクトをインスタンス化して、接続するディジタル・ポートをアサインするために"attach()"関数を実行しておく必要があります。
初期化して、サーボの角度を設定するコードの例を示します。
#include <Servo.h>
Servo Servo1; // インスタンス化
void setup() {
Servo1.attach(9); // ディジタル・ポートの"9"にアサイン
Servo1.write(90); // 角度を設定
}
"write()"メソッドの引数は角度で、0~180°で指定できます。
次回は、拡張基板上のシフトレジスタやスイッチ・ステータスの読み出しなど、拡張基板上のデバイスのアクセス方法などについて説明します。
