23.Arduino固有の仕様とライブラリ
23.1 Arduino固有の仕様とライブラリ
Arduino言語はC/C++をベースにしており、C言語のすべての仕様といくらかのC++の機能をサポートしています。また、AVR Libcにリンクされていているのでそれらの関数を利用することができます。
AVR Libcは、Atmel社が開発環境”AVR Studio”用にフリーで提供しているC/C++の標準ライブラリ群です。ArduinoがAVRのチップを採用しているフリーなシステムということで、これらのライブラリも無償で使用することができます。
Arduinoのプログラム記述における大方の仕様はC又はC++と同一ですが、いくつかの仕様についてはArduinoシステム固有のものになっています。特に、周辺装置へのアクセスライブラリやI/O処理の記述に関しては全くという程の一般性はありません。
23.1.1 ArduinoにおけるStringクラス
ArduinoにおけるStringクラスの使用には特に注意が必要です。C/C++にも標準的なクラスライブラリとして同様なstringがありますが、ArduinoにおけるStringクラスは実装内容が全く同じというわけではないのです。ArduinoのStringクラスのメンバ関数を使用した部分に関しては、クラスの概念は同じなのですがメンバー関数の実装が同一ではないので、移植に際して全く同じ結果になるとは補償できなくなってしまいます。
さらに、ArduinoでのStringクラスのメンバ関数をを使うことは生成されるオブジェクトサイズ的にも不利になります。もしも、ArduinoではStringクラスのメンバ関数を一つでも使おうものなら、使わない他の関数のライブラリオブジェクトまで一体となって付加されてしまいます。メモリサイズ的に厳しいファームウエアでは、このようなメモリの消費は他の機能のサポートに大きな影響を与える可能性があるので余り進められません。C/C++の標準関数はオブジェクトサイズ的にも比較的小さくて有利なのです。ArduinoのStringクラスはできれば使用しない方が良いというのが結論ですが、C++が標準になりつつある昨今、メモリの制約も緩和されつつある現状においては、Stringクラスを使うか使わないかの選択を迷うところです。しかし、システム移植という観点からすると、文字列操作のStringクラスはとても便利な機能なのですが、互換性の面とオブジェクトサイズでの難点から使用しない方が良いというのが結論です。
もともと、Stringクラスは文字列の操作を容易にしてくれるものなのですが、Cが多少分かれば少し面倒でもこのクラスのメンバ関数が行なっていることは全てC標準のライブラリ関数で置き換えが可能な事は分かるはずです。更に言うならば、自らの作成するプログラムで、Stringクラスのメンバ関数の全てを必要とすることはほとんどの場合あり得ないということも事実なのです。むしろ、ひとつかふたつの関数さえあれば事が足りるということの方が多いというのも経験から分かることです。
C/C++の標準ライブラリ関数はシステム依存しない共通仕様(注 1 )なので全てこのような関数を用いて文字操作ができるのですが、そもそもStringクラス自体がこれらの標準関数を使用して作成されているので必ずしも使用する必要がないということも当然のことなのです。それでも、標準ライブラリ関数はStringクラスのメンバ処理に比較すればより低位に位置する関数群なので、これらを使ってStringクラスと同様なことをさせるには、若干のコーディングスキルが必要なことは言うまでもありません。従って、何度も言うようですが、移植を考えないArduinoのスケッチではこれらのメンバ関数を利用する方が良いのです。プログラムの作成において文字列の操作は最も面倒であるとともに暴走を引き起こす危険な処理ということもまた事実なのです。
(注 1 )システム依存しない共通仕様
システム依存しない関数というのは、関数の入力と出力の仕様が同じことを言うのであって、全く同じオブジェクトである必要はありません。
・関数の名称と引数の数と型が同じ
・関数の戻り値が同じ型と結果を与えること
表 ArduinoでサポートされているStringクラスのメンバ関数
<Stringクラス関数> String.charAt(n)
<関数の処理> 文字列の先頭からn番目(インスタンスはゼロから)の文字を返す
String s = "abcdefgh"; //文字列はa=0,B=1で、引数はa=1,B=2と指定
Serial.println(s.charAt(1)); // bとシリアル表示
<Stringクラス関数> String.compareTo(str2)
<関数の処理内容> 2つの文字列を比較します。ABC文字順で見たとき、ストリングsにストアされている文字列に対し、str2の方が後順なら負の値、前順なら正の値を返します。
インスタンスsとstri2が一致するときは0を返します。
String s = "abc";
Serial.println(s.compareTo("abb")); // 1とシリアル表示されます
Serial.println(s.compareTo("abd")); // -1とシリアル表示されます
<Stringクラス関数> <関数の処理内容> String.concat(str2)
文字列を連結します。Stringの末尾にstr2が付け加えられます。
String s = "abcd";
s.concat("efgh");
Serial.println(s); // abcdefghとシリアル表示されます
<Stringクラス関数> String.endsWith(str2)
<関数の処理> Stringの末尾がstr2のときtrueを返し、そうでなければfalseを返します。
String.equals(str2)
Stringとstr2の2つの文字列を比較し、一致するときtrueを返し、そうでなければfalseを返します。大文字小文字を区別します。
<Stringクラス関数> String.equalsIgnoreCase(str2)
<関数の処理>大文字小文字の区別をせず比較し、一致していればtrueを、そうでなければfalseを返します。helloとHELLOは一致するとみなされます。
<Stringクラス関数> String.getBytes(buf, len)
<関数の処理> String文字列をbyte型の配列(buf)にコピーします。lenはbufのサイズです(int)。
<Stringクラス関数>String.indexOf(val, from)
<関数の処理> String文字列を先頭から検索し、検索されたらその位置を返し(1文字目が0です)、検索されないときは-1を返します。valは探したい文字('a')または文字列("abc")です。
fromは検索を始める位置で、省略も可能です。
String s = "abcdefgh";
Serial.println(s.indexOf("fg")); // 5とシリアル表示されます
<Stringクラス関数> String.lastIndexOf(val, from)
String文字列を末尾から検索し、検索されたらその位置を返し(1文字目が0です)、検索されない時は-1を返します。valは探したい文字('a')または文字列("abc")です。
fromは検索を始める位置で、省略可能です。
String s = "abc_def_ghi_";
Serial.println(s.lastIndexOf('_', 10)); // iから検索し、7とシリアル表示される
<Stringクラス関数> String.length()
<関数の処理> String文字の数を返します。
String s = "abcdefg";
Serial.println(s.length()); // 7とシリアル表示されます
String.replace(substr1, substr2)
String文字列のsubstr1文字列をsubstr2に置換したString文字列を返します。
String s = "abcd";
Serial.println(s.replace("cd", "CD")); // abCDとシリアル表示されます
<Stringクラス関数> String.setCharAt(index, c)
i<関数の処理> ndexで指定した位置の文字をcに置き換えます。
Stringの長さより大きいindexを指定した場合はなにも変化しない
String s = "abcdefg";
s.setCharAt(3, 'D'); // S=”abcDefg” になります
<Stringクラス関数> String.startsWith(str2)
<関数の処理> Stringの先頭がstr2のときtrueを返し、そうでなければfalseを返します。
<Stringクラス関数> String.substring(from, to)
<関数の処理> String文字列の一部を返します。fromからtoまでの文字を返します。toは省略可能で、fromのみ指定されているときは、from文字目から末尾までの文字列を返します。
String s = "abcdefgh";
Serial.println(s.substring(3)); // defghとシリアル表示される
Serial.println(s.substring(3, 6)); // defとシリアル表示される
<Stringクラス関数> String.toCharArray(buf, len)
<関数の処理> String文字列をbyte型の配列(buf)にコピーします。lenはbufのサイズ(int)。
<Stringクラス関数> String.toLowerCase()
<関数の処理> 大文字を小文字に変換します。もとの文字列は変化しない
<Stringクラス関数> String.toUpperCase()
<関数の処理> 大文字を小文字に変換します。もとの文字列は変化しない
<Stringクラス関数> <関数の処理内容> String.trim()
<関数の処理> String文字列の先頭と末尾のスペースを取り除きます
String s = "\n abcd \n"; // 文字列の前後に改行とスペースが入っている
String s2 = s.trim(); //
Serial.print(s2); // abcdとシリアル表示される
23.1.2 I/Oポートのアクセス
I/Oポートのアクセスに関してはデジタル入出力の記述そのものはシステムにより異なりますが、ポートアクセスに対する考え方はほぼ同じようになっています。つまり、GPIOピンの動作(入力/出力)を指定した後に、そのピンに対して特定な関数でポート値(0/1)を設定することになる訳です。要は一般にMCUが異なると、このI/Oポートの操作に関するステートメント記述そのものが異なることに注意が必要です。
以下で示すように、ArduinoにはGPIOに対する操作関数が3種類あります。
表 GPIOの処理と入出力関数
<GPIO処理の内容> <Arduinoの入出力>
インクルード システム標準の機能として提供されているので記述不要
pin番号の pinMode( pin, mode )
入出力mode設定 pin : ピン番号
mode : 入出力mode
pin番号に digitalWrite( pin, value )
value出力 pin : ピン番号
value : ビット値 0: Low、 !=0: High
pin番号の digitalRead( pin )
状態入力 pin : ピン番号
<入出力関数の機能詳細>
<構文> pinMode( pin, mode )
<関数の機能> pin番号の入出力モード(mode)を設定する
Arduino Unoにおけるアナログピン(A0~A5)は、本関数を使用すればデジタルピンとして使用することができるようになります。その場合、アナログピンA0がデジタルピン14とし、順次A5ピンがデジタルピン19として扱えます
<引数> pin : 処理対象となるpinの番号
mode : INPUT :ピン番号(pin)を入力ピンに設定する
OUTPUT :ピン番号(pin)を出力ピンに設定する
INPUT_PULLUP :ピン番号(pin)を内部プルアップした入力ピンに設定する
<戻り値> なし
<構文> digitalRead( pin )
<関数の機能> 指定するpin番号の値を入力する。
入力される値は、デジタル的な閾値によりHIGH又はLOWが返ります
この関数を使用する前に必ずpinMode( pin, mode ) を実行して処理対象のpin番号の処理モードを入力に指定しておかなければなりません。
<引数> pin : 処理対象となるpinの番号
<戻り値> HIGHか LOWが入力される
<構文> digitalWrite( pin, value )
<関数の機能> pin番号にvalue値を出力する
この関数を使用する前に必ずpinMode( pin, mode ) を実行して処理対象のpin番号の処理モードを出力に指定しておかなければなりません。
HIGHまたはLOWを、指定した番号のピンに出力します。指定したピンがpinMode()関数でOUTPUTに設定されている場合は、次の電圧にセットされます。
HIGH = 5V (3.3Vのボードでは3.3V) 、LOW = 0V (GND)
指定したピンがINPUTに設定されている場合は、HIGHを出力すると20KΩから50KΩの内部プルアップ抵抗が有効になります。LOWでプルアップは無効になります。
<引数> pin : 処理て対象となるpin番号
value : HIGHか LOWを出力指定する
<戻り値> なし
<使用例>
int ledPin = 13; // LEDtが接続されているデジタルピン pin 13
void setup()
{
pinMode(ledPin, OUTPUT); // デジタルピン pin 13を出力に設定
}
void loop()
{
digitalWrite(ledPin, HIGH); // デジタルピン pin 13をHIGHにして LED点灯
delay(1000); // 1秒遅延
digitalWrite(ledPin, LOW); // デジタルピン pin 13をLOW にして LED消灯
delay(1000); // 1秒遅延
}
23.1.3 周辺機器へのアクセスライブラリ
以下で説明することはArduinoにおける周辺機器に対するアクセスライブラリについてです。本項はプログラム移植にかかわる周辺装置のアクセス方法と両システムの相違と移植の要点を述べています。したがって、全ての周辺機器に対するアクセスに関する移植の方法については述べていませんが、可能な限り多くのデバイス処理を明確にそして分かりやすく記述してみたいと思います。
Arduinoでサポートされている周辺機器のアクセスライブラリには、Arduino IDEに標準的に提供されているものとそれ以外のものとに分けることができます。
標準的に提供されているライブラリはArduinoプロジェクトの一部として提供されるものでライブラリオブジェクトだけでなく、そのライブラリを使用するためのサンプルプログラムも同時に提供されています。
Arduino IDEにインストール済みの標準的なライブラリを使用するときは、IDEの[スケッチ Sketch]メニューの[ライブラリを使用 Import Library]を開き、表示されたリストから目的のライブラリを選択します。すると、”#include”文がスケッチの先頭に自動的に挿入され、ライブラリが使用可能になります。
この”#include < >”文が挿入されると、ライブラリはスケッチとともにArduinoボードにアップロードされてしまうためメモリが消費されます。必要としないライブラリの#include文は削除しないと、実行オブジェクトのサイズも自動的に増大します。
以下で説明する項目はArduinoの全ての標準ライブラリについてではなく、実際に使用して分かったことのみです。また、同時にArduinoにあって他ボードの開発環境に入っていないライブラリについても記述しません。現状においてArduinoのものではSoftwareSerialライブラリでしょうか。
ソフトウェアシリアルライブラリはArduinoボードの1,2番ピン以外を使ってソフト的な擬似シリアル通信を行うために開発されたものです。
<EEPROMライブラリ>
Arduinoボードが使用しているマイクロコントローラには、EEPROM(Electrically Erasable Programmable Read-Only Memory)と呼ばれる不揮発性メモリが搭載されています。
近年のMCUは概ねそのようなデータ領域を有しており、そのデータはまるでPCにおける小さなハードディスクにストアされた情報のように、電源を切っても内容が消えることがありません。
その容量はMCUの種別によって異なり、それほど多くはありませんが、今回のシステム開発に関連するものとしては下表に示すようなっています。
表 MCUの種別によるEEPROMのサイズの違い
<MCUの種別> <開発システム> <EEPROMサイズ> <Flashメモリサイズ>
AVR ATmega168 Arduino 512バイト 16kB
AVR ATmega328 Arduino 1kB 32kB
AVR ATmega1280 Arduino 4kB 128kB
AVR ATmega2560 Arduino 4kB 256kB
このEEPROMは、一般的なシステムでは稼働するために必要な基本情報を保持したり、特定なパラメータなどを保持するために利用されます。
Ejan赤道儀制御システムでは、望遠鏡の制御、サイト情報、ステッピングモータの情報及び赤道儀の回転速度などの情報領域があり、およそ1kB程の領域を必要としています。
EEPROMライブラリは、下表 で示すようにこのデバイスに対する書き込みと読み込みを可能にする関数が提供されています。
表 EEPROMへの操作関数
<EEPROMへの操作>
<インクルード> システム標準の機能として提供されているので記述不要
<情報の読み込み> #include <EEPROM.h>
EEPROM.read(address)
<情報の書き込み> #include <EEPROM.h>
EEPROM.write(address,value)
<構文> EEPROM.write( address, value )
<関数の機能> EEPROMの指定アドレスに1バイトを書き出します
EEPROMへの書き込みには概ね3.3ミリ秒かかります。
EEPROMの書込/消去サイクルは100,000回程で寿命に達します。頻繁に書き込みを行う場合はこの点を考慮して使用してください。
EEPROMのサイズは使用されているMCUの種別により異なります。そのため指定できる引数adressの値の最大値は機種個度に異なります。
<引数> Address : 読み出し対象となるEEPROMのアドレス(>=0)を指定します
EEPROMの先頭アドレスはゼロ(0)です。
Value : 読み出しデータ(byte) 、0から255までのバイトデータ
<戻り値> なし
<構文> EEPROM.read( address )
<関数の機能> EEPROMの指定アドレスから1バイトを読み出します
EEPROMのサイズは使用されているMCUの種別により異なります。そのため指定できる引数adressの値の最大値は機種個度に異なります。
<引数> Address : 読み出し対象となるEEPROMのアドレス(>=0)を指定します
EEPROMの先頭アドレスはゼロ(0)です。
<戻り値> 指定したEEPROMアドレスの1バイトデータ
<使用例>
#include <EEPROM.h>
int romAddress = 0; // EEPROM アドレス
int romValue; // EEPROM データ
void setup() {
Serial.begin( 9600 ); // シリアル通信開始 9600bps
}
void loop() {
romValue = EEPROM.read( romAddress ); // EEPROM データ読み込み
Serial.print( romAddress ); // 読み込みEEPROM アドレス出力
Serial.print( "\t" ); // タブ
Serial.print( romValue ); // 読み込みEEPROM データ出力
Serial.println( "" ); // 改行復帰
romAddress = romAddress + 1; // EEPROM アドレスのインクリメント
if (a == 512) romAddress = 0; // 読み込みEEPROM が512になったら0にもどす
delay(500); // 遅延時間500ms
}
<SPIライブラリ>
SPI(Serial Peripheral Interface)は、マイクロ・コントローラからひとつ又は複数のデバイスを制御する目的で使用される短距離用の簡便な同期通信を行うシリアルバスの規格です。SPIによる接続では、マスタデバイス(通常はマイコン)が必ず1つ存在し、このマスタデバイスが1つ以上の周辺デバイスをスレーブとして制御します。各デバイス間を次表 で示す4本の線で接続するのが典型的な使い方です。 このうち、MISO、MOSI、SCKの3つの信号線は各デバイスに共通なものなので必ず結線されます。SS信号も同様ですが、マスタに対する信号線の接続形態は規定されていず、単に”low”信号が通信要求、”high”がペンディングというように規定されているだけです。
表 SPIの信号線
<SPIの信号線 > < 信号線の意味合い >
MISO (Master In Slave Out) スレーブ(周辺デバイス)からマスタへデータライン
MOSO(Master Out Slave In) マスタからスレーブへデータライン
SSK(シルアルクロック) データ転送を同期用、マスタからの同期クロック信号
SS(Slave Select pin) スレーブ機器選択ピン :
スレーブ機器選択ピン :各デバイスは、上記の信号線以外に自デバイスをSPIの動作機器として有効にするか否かを指定するためのピンです。マスタがこのピンをlowにすると、マスタとの通信が有効になります。自デバイスのSS信号がhighの場合、マスタからのデータは全て無視されます。これにより複数のSPIデバイスが3本の信号線を共有していても自デバイスへの情報を選別できます。
SPIデバイスに対応するコードを書くときは、いくつかの決まり事を理解して設定する必要があります。
・データ転送のビットオーダーがあり、LSBFIRST(最下位)かMSBFIRST(最上位)の設定
・アイドリング状態を示すクロック信号はhighかlowかの設定
・サンプリングはクロックの立ち上がりエッジか、立ち下がりエッジかの設定
・SPIの動作スピードの設定
SPIの規格はあまり厳密なものではなく、デバイスごとの実装はわずかながら異なっています。新たなデバイスに対応するコードを書くときは、データシートをよく読む必要があるかもしれません。 一般的には3種類の転送モードが使われます。これらのモードは、クロック位相(clock phase)とクロック極性(clock polarity)という2つの要素で決定されます。
クロック位相はシフトされたデータを読み取るタイミング(立ち上がりエッジか立ち下がりエッジか)を決定します。
クロック極性はアイドリング状態のときのクロックの状態(highかlowか)を示します。
これらの設定はSPIを利用するシステムのライブラリにより設定の方法がことなるかもしれません。
これらの設定はArduinoでは、SPI.setDataMode()を使って行います。パラメータの組み合わせは次の表のとおりです。
表 SPIの信号線の特性とモード
<Mode> <Clock Polarity(CPOL)> <Clock Phase(CPHA)>
0 0 0
1 0 1
2 1 0
3 1 1
SPIパラメータを正しくセットしたあとは、データシートを見ながら、デバイスの機能とそれを制御するレジスタの使い方を調べていくことになるでしょう。
表 SPIの信号とピン番号の対応
<Arduino Duemilanove ,
<SPIの信号> UNO(Atmega168/328搭載)> < Arduino Mega>
SS(Slave Select Pin) 10ピン 53ピン
MOSI(Master Out Slabe In) 11ピン 51ピン
MISO(Maste In Slabe Out) 12ピン 50ピン
SCK(シルアルクロック ) 13ピン 52ピン
SSピンは使わない場合でも出力状態のままにしておく必要があります。そうしないと、SPIインタフェイスがスレーブモードに移行し、ライブラリが動作しなくなります。
Arduinoの場合は、10ピン番以外のGPIOピンをスレーブ選択(SS)ピンとして使うことができます。例えば、イーサネット・シールド(Ethernet shield)はピン4をオンボードのSDカードの制御に使い、ピン10をイーサネット (Ethernet)コントローラに割り当てています。
表 SPIの操作関数の対応表
<インクルード> #include <SPI.h>
<SPI操作> SPIバスの初期化
<SPI関数> SPI.begin()
<操作内容>
SCK、MOSI、SSの各ピンは出力に設定され、SCKとMOSIはlowに、SSはhighとなります。
SCK、MOSI、SCK、SSの各ピン番号は固定ですが、SS信号は他のピンでも代用できます。
その場合でも、SSはhighにしておかないとライブラリが正しく動作しなくなります。
<戻り値> なし
<SPI操作> SPIバスの無効化
<SPI関数> SPI.end()
<操作内容> 各ピンの設定は変更されません。
<戻り値> なし
<SPI操作> SPIバスの入出力のビットオーダーを設定する
<SPI関数> SPI.setBitOrder(order)
<引数> order: LSBFIRST(least-significant bit first) or
MSBFIRST(most-significant bit first)
<戻り値> なし
<SPI操作> SPIクロック分周器(divider)を設定する
<SPI関数> SPI.setClockDivider(divider)
<引数> divider: 分周値は2、4、8、16、32、64、128のいずれか
<SPI操作> SPIの転送モードを設定する
<SPI関数> SPI.setDataMode(mode)
<引数> mode: 前述のSPIの信号線の特性とモードを参照する
SPI_MODE0, SPI_MODE1, SPI_MODE2 , SPI_MODE3
<SPI操作> SPIバスを通じてデータを転送する
<SPI関数> SPI.transfer(value)
<引数> value: 転送する1バイトデータ、送信と受信の両方で使用
<戻り値> 受信したバイト
<シリアル通信関数>
ArduinoボードはSerialという名前で参照される1組のハード的なシリアルポート(RXとTX)を持っています。Arduino Megaは4組のポートを持っていて、Serial0、Serial1、Serial2、Serial3が使用可能です。
Arduinoのシリアル通信関数はシステム一部として独自に実装されたものです。したがって、その種類は多くありませんが、通信に関する基本的な下表で示すような関数が存在しています。
以下ではArduinoのシリアル通信関数の詳細について記述します。関数の数はさほど多くはありませんが、使用に際しては手順かあり、手順を守らなくてはなりません。その手順の内容は以下の通りです。
1.シリアル通信の開始・初期化 Serial.begin(speed) speed : 通信ボーレートの設定
2.受信データのバイト数を確認する Serial.available()
3.受信データを1バイト読み込む Serial.read()
4.データをシリアルポートへ出力 Serial.print(data, format) Serial.println(data, format)
5.シリアル通信の終了 Serial.end()
このうち、2.から4.の項目はユーザサイドのプログラム内容によって異なります。1.シリアル通信の開始・初期化 の処理は、シリアル通信を開始する前に必ず実行しなくてはなりません。また、終了時も5.シリアル通信の終了を実行しておきます。
<シリアル通信の操作関数の詳細>
<関数の機能> シリアル通信の開始・初期化
シリアル通信データの転送レートをbps(baud)で指定します
Arduino Megaでは4つの独立したポート(Serial,Serial1,Serial2,Serial3)を初期化し使用することができます
<構文> Serial.begin ( speed )
Serial.begin ( speed, config )
<引数> speed : 転送レート (long)
シリアル通信データの転送レートをspeed で指定します。
コンピュータと通信する際は、次のレートから1つを選びます。
300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 bps
bps(baud)はビット/秒です。
Config : 設定データ長、パリティー、ストップビットの内容を指定します
SERIAL_5N1 データ長5,ノンパリティー、ストップビット1
SERIAL_6N1 データ長6,ノンパリティー、ストップビット1
SERIAL_7N1 データ長7,ノンパリティー、ストップビット1
SERIAL_8N1 データ長8,ノンパリティー、ストップビット1
SERIAL_5N2 データ長5,ノンパリティー、ストップビット2
SERIAL_6N2 データ長6,ノンパリティー、ストップビット2
SERIAL_7N2 データ長7,ノンパリティー、ストップビット2
SERIAL_8N2 データ長8,ノンパリティー、ストップビット2
SERIAL_5E1 データ長5,偶数(even)パリティー、ストップビット1
SERIAL_6E1 データ長6,偶数(even)パリティー、ストップビット1
SERIAL_7E1 データ長7,偶数(even)パリティー、ストップビット1
SERIAL_8E1 データ長8,偶数(even)パリティー、ストップビット1
SERIAL_5R2 データ長5,偶数(even)パリティー、ストップビット2
SERIAL_6R2 データ長6,偶数(even)パリティー、ストップビット2
SERIAL_7E2 データ長7,偶数(even)パリティー、ストップビット2
SERIAL_8E2 データ長8,偶数(even)パリティー、ストップビット2
SERIAL_5O1 データ長5,奇数(odd)パリティー、ストップビット1
SERIAL_6O1 データ長6,奇数(odd)パリティー、ストップビット1
SERIAL_7O1 データ長7,奇数(odd)パリティー、ストップビット1
SERIAL_8O1 データ長8,奇数(odd)パリティー、ストップビット1
SERIAL_5O2 データ長5,奇数(odd)パリティー、ストップビット2
SERIAL_6O2 データ長6,奇数(odd)パリティー、ストップビット2
SERIAL_7O2 データ長7,奇数(odd)パリティー、ストップビット2
SERIAL_8O2 データ長8,奇数(odd)パリティー、ストップビット2
<戻り値> なし
<使用例>
void setup() {
Serial.begin(9600); // シリアルポートを 9600 bps でオープン
}
void loop() {}
<関数の機能> シリアル通信の終了
シリアル通信処理を終了して、Pin0(RX)とPin1(TX)を汎用の入出力ピンとして使えるようにします。
再びシリアル通信を可能にするには、Serial.begin() を呼び出します。
Arduino Megaでは4つの独立したポート(Serial.end,Serial1.end,Serial2.end,
Serial3.end)に対してこの処理を実行することができます
<構文> Serial. End( )
<引数> なし
<戻り値> なし
<関数の機能> シリアルのバッファ到着しているデータのバイト数を調べます
バッファには128バイトまで保持できます
<構文> Serial. available ()
<引数> なし
<戻り値> シリアルバッファにあるデータのバイト数が返ります。1以上ならば受信データを読み出し可能です。
使用例
int incomingByte = 0; // 受信データ
void setup() {
Serial.begin(9600); // 9600bpsでシリアルポートを開く
}
void loop() {
if (Serial.available() > 0) { // 受信したデータが存在する(>0)か否か
incomingByte = Serial.read(); // 1バイト受信データを読み込む
Serial.print("I received: "); //"I received: "をシリアルに出力する
Serial.println(incomingByte, DEC); // 10進形式受信データをシリアルに出力し改行する
}
}
<関数の機能> 受信バッファの内容をクリアします
Serial.read()やSerial.available()が返すデータは、Serial.flush()を実行したあとのものです。Serial.flush()はデータの送信が終わるのを待ってから、バッファをクリアします。
Arduino Megaでは4つの独立したポート(Serial.flush ,Serial1.flush,Serial2. flush, Serial3.flush)に対してこの処理を実行することができます
<構文> Serial. flash ()
<引数> なし
<戻り値> なし
<関数の機能> 受信バッファからシリアルデータを1バイト読み出すが、読み出し位置を更新しません Arduino Megaでは4つの独立したポート(Serial.peek,Serial1.peek,Serial2.peek,Serial3.peek)に対してこの処理を実行することができます
<構文> Serial. peek()
<引数> なし
<戻り値> 受信バッファの次読み込み位置のデータを読み出します
Serial.peek()を繰り返し出しても同じ(受信バッファの)データが返るだけです
!=-1:読み出した1データ
==-1:有効なデータはない
<関数の機能> 受信バッファからシリアルデータを1バイト読み出します
読み出しと同時に受信バッファの読み出し位置を更新します
Arduino Megaでは4つの独立したポート(Serial.read,Serial1.read,Serial2.read,Serial3.read)に対してこの処理を実行することができます
<構文> Serial. read()
<引数> なし
<戻り値> 受信バッファの次読み込み位置のデータを読み出します
!=-1:読み出した1データ
==-1:有効なデータはない
<関数の機能> 受信バッファからシリアルデータを1バイト読み出します
読み出しと同時に受信バッファの読み出し位置を更新します
Arduino Megaでは4つの独立したポート(Serial.read,Serial1.read,Serial2.read,
Serial3.read)に対してこの処理を実行することができます
<構文> Serial. read()
<引数> なし
<戻り値> 受信バッファの次読み込み位置のデータを読み出します
!=-1:読み出した1データ
==-1:有効なデータはない
<関数の機能> シリアルに改行コード付きでデータの送信
Serial.print()との違いは、データの末尾にキャリッジリターン(ASCIIコード13あるいは'\r')とニューライン (ASCIIコード10あるいは'\n')を付けて送信することです
<構文> Serial. Println( data )
Serial. Println( data, format )
<引数> Serial.print()と同じフォーマットを使います
print()の説明を参照してください
<戻り値> なし
<使用例> print()の説明を参照してください
<関数の機能> シリアルにバイナリデータを出力します。
シリアルポートにバイナリデータを1バイトずつ、又は複数バイトの送信が可能です。 数値(を表す)文字として送信したい場合は、print()を使います。
構文> Serial. write( val )
Serial. write( str )
Serial. write( buf, len )
<引数>
<関数の機能>
<戻り値>
<関数の機能> ソフトウエア・シリアルにデータの送信
<構文> Serial. print( data )
Serial. print( data, format )
<引数>
Data : 出力する値。全ての型に対応する
format: 基数または有効桁数(浮動小数点数の場合) の指定文字列
この命令は多くのデータ形式に対応しています。整数値は1桁ずつASCII文字に変換され、浮動小数点数の場合は、小数点以下第2位まで出力するのがデフォルトの動作です。バイト型のデータは1文字として送信されます。文字列はそのまま送信されます。
Serial.print(78) - "78"
Serial.print(1.23456) - "1.23"
Serial.print('N') - "N"
Serial.print("Hello ") - "Hello"
第2パラメータによって基数(フォーマット)、又は数値によって有効桁数を指定できます。
BIN(2進数)、OCT(8進数)、DEC(10進数)、HEX(16進数)に対応します。
浮動小数点数を出力する場合は、第2パラメータの数値によって有効桁数を指定できます。
Serial.print(78, BIN) - "1001110"
Serial.print(78, OCT) - "116"
Serial.print(78, DEC) - "78"
Serial.print(78, HEX) - "4E"
Serial.println(1.23456, 0) - "1"
Serial.println(1.23456, 2) - "1.23"
Serial.println(1.23456, 4) - "1.2346"
<戻り値> 送信したバイト数 (byte)
<使用例>
void setup() {
Serial.begin(9600); // 9600bpsでシリアルポートを開く
}
void loop() {
for(int x=0; x< 64; x++) { // ASCIIコード表を出力
Serial.print(x); // ASCIIコードを十進数で出力
Serial.print("\r\n"); // 改行 復帰
Serial.print(x, DEC); // ASCIIコードを十進数で出力
Serial.print("\r\n"); // 改行・復帰
Serial.print(x, HEX); // ASCIIコードを十六進数で出力
Serial.print("\r\n"); // 改行 復帰
Serial.print(x, OCT); // ASCIIコードを八進数で出力
Serial.print("\r\n"); // 改行 復帰
Serial.println(x, BIN); // ASCIIコードを二進数で出力し改行
delay(ぬ00);
}
Serial.println(""); // 改行・復帰
}
<タイマー関数>
一般的なCPUのタイマー処理には、タイマー処理とチック処理の二つのものがあります。
タイマー処理は、Arduinoのタイマー処理と同じ考えに基づいた処理で、プログラム設定によるタイマー割り込み時間を基準にして、所定の割り込みプログラムを実行するものです。
また、millis()関数やmicrps()関数のように、タイマー割り込みを用いて時間を計測するような所定の割り込み処理をプログラムすればプログラムされた時間の計測や処理を行うことができます。
チック処理は、一般的なオペレーティング・システム(OS)のタスク切り替えなどの処理に必要な定常的な割り込みを提供するものです。この処理のためにARMのCortex-M0などではシステム・チック・タイマー(SysTick timer)がMCUの一部としてタイマー処理とは別途に提供されています。システム・チック・タイマー(SysTick timer)はオペレーティング・システムや他の制御システムで使用される10mSec毎の固定的な割り込みを生成するために設定されています。
このシステム・チック・タイマーはMCUの一部機能として構成されるもので、これを標準タイマーとして使用することにより、他の割り込みに影響を受けない正確な時間計測やOSソフトウエア処理が可能になっています。
残念ながら、Arduinoで提供されるタイマー関数には、システム・チック・タイマーに相当するような機能はなく、非常に貧弱で実処理にはほとんど利用できません。また、外部割り込み等の処理を行うと、次のような不具合が発生します。
・delay()関数は機能しません
・millis()関数の戻り値は増加しません
・シリアル通信により受信したデータは、失われる可能性があります
このため、Ejan赤道儀制御システム開発には別途に任意時間で発生する割り込み関数ライブラリ関数を作成しなくてはなりません。赤道儀においては実時間に対する割り込みを定義できなくてきなりませんが、これらの関数は特定な時間単位(1ms,1μs)でしか割り込みを定義でないことが問題となります。
< タイマー関数の詳細 >
<関数の機能> ミリ秒単位の経過時間の取得
Arduinoボードのプログラムが実行を開始した時から現在までの経過時間をミリ秒単位で返します。約50日間で内部データがオーバーフローし、ゼロに戻ります。
<構文> millis()
<引数> なし
<戻り値> 実行中のプログラムがスタートしてからの時間 (unsigned long)
<関数の機能> マイクロ秒単位の経過時間の取得
Arduinoボードのプログラムが実行を開始した時から現在までの経過時間をマイクロ秒単位で返します。
約70分間で内部データがオーバーフローし、ゼロに戻ります。16MHz動作のArduinoボードでは、この関数の分解能は4マイクロ秒で、戻り値は常に4の倍数となります。8MHzのボード(たとえばLilyPad)では、8マイクロ秒の分解能です。
1,000マイクロ秒は1ミリ秒、1,000,000 (1000ミリ)マイクロ秒は1秒です。
<構文> micros()
<引数> なし
<戻り値> 実行中のプログラムが動作し始めてからの時間をマイクロ秒単位で返します (unsigned long)
<関数の機能> プログラムをミリ秒単位(1,000ミリ秒=1秒)で指定した時間だけ止めます。
このパラメータに32767より大きい整数を指定するときは、値の後ろにULを付け加えます。例 delay(60000UL);
<構文> delay(ms)
<引数> ms: 一時停止する時間 (unsigned long)。単位はミリ秒
<戻り値> なし
<関数の機能> プログラムをマイクロ秒単位で指定した時間だけ一時停止します。
数千マイクロ秒を超える場合はdelay関数を使ってください。
現在の仕様では、16383マイクロ秒以内の値のとき、正確に動作します。この仕様は将来のリリースで変更されるはずです。
<構文> delayMicroseconds(us)
<引数> us: 一時停止する時間。単位はマイクロ秒。
1マイクロ秒は1ミリ秒の1/1000 (unsigned int)
<戻り値> なし
<MsTimer2 ミリ単位でのタイマー関数>
前述のようにチックタイマーは通常のマイクロプロセッサにはありますが、Arduinoに搭載されているMCUには同様な機能がありません。それは、チックタイマーが主にオペレーティングシステム(OS)のタスク切り替えやシステム時間に使用するためのタイマーということに由来します。
小規模な組み込みシステムにおいては、オペレーティングシステムが必要ないばかりか、逆に実行時間の増加や、メモリ領域の消費という点においてデメットが多いために一般には使用されません。
この点においてArduinoに搭載されているMCUはメモリサイズからしても必然的に小規模な組み込みシステムがターゲットになって設計されていることによります。
このため、Arduinoでシステムタイムを動作させるためには外部タイマーライブラリを作成しなくてはなりません。
前述のように、OSにおいてはタスク処理のための更に多くの処理を実行するのですが、OSを利用しないArduinoシステムなどでは単にシステムタイムの更新というような簡単な処理のためにタイマー割り込みを使用することになってしまいます。
Arduinoのタイマー割り込みは、8ビット タイマ/カウンタ0,2と16ビット・タイマ/カウンタがありますが、ライブラリで提供されるMsTimer2は内蔵タイマー(Timer2)を利用したものです。MsTimer2という名前は、8ビット タイマ/カウンタ2(timer2)を1ミリ秒(ms)単位で扱えることに由来しています。 このため、このライブラリを使用するときは 8ビット タイマ/カウンタ2は他の用途では使用できなくなります。
< MsTimer2 タイマー関数の詳細 >
<インクルード> #include <MsTimer2.h>
<割り込み操作> < Arduino>
<関数の機能> タイマー2割り込みの実処理ルーチン設定
<構文> MsTimer2::set( unsigned long ms, void (*f)() )
<引数> ms :タイマー時間 mS単位
(*f)() :割り込みタイマ処理の関数
<戻り値> なし
<関数の機能> タイマー2の開始
<構文> MsTimer2::start()
<関数の機能> タイマー2の停止
<構文> MsTimer2::stop()
<関数の機能> タイマー2の開始
タイマー割り込みとは別個に定義され、割り込み処理も異なっています。
<構文> MsTimer2::start()
<引数> なし
<戻り値> なし
<使用例>
// 500ms毎に割り込みが発生し、flash ()が呼び出されるように設定
MsTimer2::set( 500, flash );
MsTimer2::start( ); //
<関数の機能 > タイマー2割り込みの実処理ルーチンの設定処理
Arduinoではユーザ作成のタイマー割り込み処理関数を定義します。
void (*f)()のfはユーザ作成作成した関数の名前を指定します。この関数の中で割り込みを計数(1000回)して一秒を得ることになります。
<構文> MsTimer2::set( unsigned long ms, void (*f)() )
<引数> ms :タイマー割り込み時間 mS単位
( *f)() :割り込みタイマ処理の関数(f) へのポインタ
<戻り値> なし
<使用例>
#include <MsTimer2.h>
static boolean ledCH = LOW; // LEDの点灯処理の更新
static boolean output = LOW; // HIGH : LED 点灯、 LOW:消灯
void flash() {
output = !output; // output を HIGH/LOW で反転処理
ledCH = HIGH; // LEDの状態変更要求
}
void setup() {
// 500ms毎に割り込みが発生し、flash ()が呼び出されるように設定
MsTimer2::set( 500, flash );
MsTimer2::start( ); //
}
void loop( ) {
if (ledCH == HIGH) {
digitalWrite( 13, output ); // LEDの点灯/消灯
ledCH = LOW; // LEDの状態変更処理完了
}
}
<関数の機能> タイマー2の開始
タイマー割り込みとは別個に定義され、割り込み処理も異なっています。
<構文> MsTimer2::start()
<引数> なし
<戻り値> なし
<使用例>
// 500ms毎に割り込みが発生し、flash ()が呼び出されるように設定
MsTimer2::set( 500, flash );
MsTimer2::start( ); //
<SecTimer1 任意秒単位でのタイマー関数>
Arduinoのライブラリで提供されているMsTimer2は内蔵タイマー(Timer2)のカウンタ溢れ割り込みを利用したものです。この割り込みはタイマ/カウンタ2のカウンタ溢れ割り込みが発生する度にカウンタ値をプロラムで再クリア設定しなくてはなりません。
このカウンタ値の再設定処理は本来ライブラリ内で行うものですから余計な時間がかかっています。この問題を解消するためには、カウンタを自動更新してくれるCTCモードを採用する方が単純なタイマなどでは有効ということになります。
また、Arduinoのライブラリで提供されているMsTimer2は、タイマー割り込み時間がmS単位になっています。この割り込み時間の設定では、天体などのステッピングモータ駆動用のステップ割り込みには到底使用することができません。
以上のような理由で、本ライブラリは割り込みのイベントをCTC割り込みにして作成してあります。
そのため、現状ではMsTimer2とMsTimer2CTCのルーチン仕様は全く同じです。どちらを使っても良いですが、同時に二つのライブラリを使用することができないことに注意してください。
< SecTimer1 任意時間 単位でのタイマー関数 >
<関数の機能> タイマー1の開始
<構文> SecTimer1::start()
<引数> なし
<戻り値> なし
<使用例>
// 500ms毎に割り込みが発生し、flash ()が呼び出されるように設定
SecTimer1::set( flash , 0.5 );
SecTimer1::stop( ); //タイマーの停止
<関数の機能> タイマー1の停止
<構文> SecTimer1::stop()
<引数> なし
<戻り値> なし
<使用例>
SecTimer1::set( 500, flash ); // 500ms毎に割り込みが発生し、flash ()が呼び出されるように設定
SecTimer1::stop( ); //タイマーの停止
<使用例 2>
/*
* Interrupt utilities for 16 bit Timer1 on ATmega168/328
* Created Novenber 2011 by Yoshihiro Odanaga for ATmega328 support
* This is free software. You can redistribute it and/or modify it.
*/
#ifndef SECTIMER1_h
#define SECTIMER1_h
#include <avr/io.h>
#include <avr/interrupt.h>
#define RESOLUTION 65536 // Timer1 is max count on 16 bit
class SecTimerOne
{
public:
// properties
unsigned int ctcTimerCount;
unsigned char clockSelectBits;
// methods
unsigned int initialize(float seconds);
unsigned int setTimerCount(float seconds);
unsigned int attachInterrupt(void (*isr)(), float seconds=-1);
void start();
void stop();
void restart();
unsigned long read();
void detachInterrupt();
void (*isrCallback)();
};
*
* Interrupt utilities for 16 bit Timer1 o
* Created Novenber 2011 by Yoshihiro Odan
*
* This is free software. You can redistrib
*
*/
ifndef SECTIMER1_cpp
define SECTIMER1_cpp
include "SecTimer1.h"
ecTimerOne Timer1; // preinstati
/
/ interrupt service routine that wraps a us
/
SR(TIMER1_CAPT_vect)
Timer1.isrCallback();
/ Device Initialize
nsigned int SecTimerOne::initialize(float s
TCCR1A = 0; // cl
TCCR1B = _BV(WGM13) | _BV(WGM12); // se
return( Timer1.setTimerCount(seconds) );
/ Set CTC Timer : Top data : BOTTOM =0
/
nsigned int SecTimerOne::setTimerCount(floa
long ctcCount = (long)(seconds * F_CPU);
if(ctcCount < RESOLUTION) clo
else if((ctcCount >>= 3) < RESOLUTION) clo
else if((ctcCount >>= 3) < RESOLUTION) clo
else if((ctcCount >>= 2) < RESOLUTION) clo
else if((ctcCount >>= 2) < RESOLUTION) clo
else if (ctcCount >= RESOLUTION) ret
ICR1 = ctcTimerCount = ctcCount;
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS
TCCR1B |= clockSelectBits;
return((unsigned int)ctcTimerCount);
/ Device Attach : device intialize and Time
/
nsigned int SecTimerOne::attachInterrupt(vo
unsigned int ctcTimerCount;
ctcTimerCount = 0; // no operation
if(seconds > 0)
{
if ((ctcTimerCount = Timer1.initialize(s
Timer1.isrCallback = isr; //
TIMSK1 = _BV(ICIE1); //
sei(); //
start();
}
}
return( ctcTimerCount );
/ Device Dettach : device intialize and Tim
/
oid SecTimerOne::detachInterrupt()
TIMSK1 &= ~_BV(ICIE1); //
/ Start Interrupt
/
oid SecTimerOne::start()
TCCR1B |= clockSelectBits;
/ Stop Inerrupt
/
oid SecTimerOne::stop()
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS
/ reset CTC counter
/
oid SecTimerOne::restart()
TCNT1 = 0;
/
/
*
nsigned long SecTimerOne::read() //re
//rember
unsigned int tmp=TCNT1;
char scale=0;
switch (clockSelectBits)
{
case 1:// no prescalse
scale=0;
break;
case 2:// x8 prescale
scale=3;
break;
case 3:// x64
scale=6;
break;
case 4:// x256
scale=8;
break;
case 5:// x1024
scale=10;
break;
}
while (TCNT1==tmp) //if the timer has no
{
//do nothing -- max delay here is ~1
}
tmp = ( (TCNT1>tmp) ? (tmp) : (ICR1-TCN
return ((tmp*1000L)/(F_CPU /1000L))<<sca
/
#endif
<外部割り込み処理と割り込み操作 >
Arduinoシステムで使用することのできる割り込みは、特定なピンに対する外部信号要因による”外部割り込み処理”です。例えば、Arduino UNOでは表 で示す通りピン番号2,3がそれに該当し、そのほかのピンでは割り込み処理を実行できません。
この外部割り込み処理というのは、特定なピンに接続された外部からの信号状態により、その信号の変化に対する条件に合致した場合にのみ割り込みが発生し、予め AttachInterrupt()関数で指定した割り込み処理関数を実行させようとするものです。
このような処理は、loop()関数内で実行している主(メイン)処理の状態に関係なく、特定なピンの信号状態が所定の条件が満たされた時に発生する例外的な処理で、主処理に割り込んで実行されるのでこれを”割り込み処理 interrupt”と呼ばれています。
割り込み処理を使わないで不定期に発生する信号変化に対する処理を行なおうとすると、loop()関数内などで定期的に信号の変化を検出するようにしなくてはなりません。例えば特定なデジタルピンやレジスタなどの状態を定期的に検査することを”ポーリング”とよんでいますが、このような不定期の信号に対するポーリング処理は大方何もしなくてすむものなので、信号変化を定期的に行なうこと自体が非常に大きな無駄を生むのです。このような無駄は、主処理の処理能力を阻害し、一般に”処理が遅い”だの”計算に時間がかかる”だのとかいったMCUの能力に無関係な不具合の事態が発生します。
組み込みプログラムを作成したことのないエンジニアやアプリケーションを作成したことしかないプログラマは、一般的にこのようなことが理解できていないことが原因で自らの作成したプログラムの処理がひじ用に遅いと嘆いて、その原因をプロセッサ能力の無さの所為にすることが多いものです。でも実際は、設計者自身の設計能力が足りないことに気づいていません。
0コメント