1.今回の開発テーマ
次のような新たな仕組みを追加して自動計測システムを完成させます。
①タイマー割り込み処理
設定した時間間隔で計測・記録処理をキックする仕組み。
NTPによる時刻自動補正を定期的にキックする仕組み。
②一連の動作の連係処理
計測指示~計測結果取得~SDカードへの記録など一連の計測関連処理を統合する。
③測定処理条件の整理
測定開始時刻、測定間隔、時刻補正条件など外部からの設定・変更を要するファクターの整理。
2.ソフト開発のポイント
今回の最終的なスケッチはかなり大きなものになります。しかし、多くは今までに作成したスケッチの組み合わせなので、新たなコードの量はわずかです。その新たなコードの大半はタイマー割り込み処理に関するものです。
(1)タイマー割り込みの利用
では、なぜタイマー割り込みを利用するのでしょうか。
『Ⅱ.システムクロックの製作』では、NTPサービスを利用して時刻調整をするために、loop処理中で調整時刻が到来しているかを常時監視していて、到来すれば時刻合わせを行っていました。一定間隔で計測する仕組みを同じ方法で行うとすれば、時刻調整と測定処理が重なった場合の計測モレなどが起きないような処置が必要になります。またこのシステムは最終的に、ネットからの照会に対して計測データを配信する機能をもたせる予定なので、常時監視でMCUを占有してしまう方法ではムリがあります。
これらはタイマー割り込みを利用することで解決できます。指定時刻に自動的に割り込みが起きるので、一連のタイミング制御が不要になりコードを簡素化できます。またWi-Fi通信中でも測定モレや時刻調整抜けが起きる心配がなくなります。ただし、時刻合わせや計測の間隔はすべてタイマー任せになるので、タイマーの精度に影響されることになります。これを補正する仕組みも考えられますが、ここではふれないことにします。
(2)利用上の注意
タイマー割り込みを利用するには、標準ライブラリーのTickerを使用します。
Arduino IDEを起動して[ファイル]-[スケッチ例]で表示される一覧から[Ticker]を探すと、簡単なサンプルスケッチを表示できるので参考にしてください。
さて、新規スケッチの作成準備ができたら、[ファイル]-[スケッチ]-[ライブラリをインクルード]で一覧を表示させて[Ticker]を選択してください。先頭行に"#include "が入力されて、Tickerを使える準備が整いました(わざわざこの手順を踏まなくても、直接#include文をキー入力しても同じです)。
サンプルスケッチを眺めると、Tickerの利用は実にシンプルであることが分かります。Tickerのインスタンスを宣言しておき(例えば「Ticker ticker;」のように)、初期化処理部分で何秒ごとにどんなメソッド(コールバック関数)を起動するかを指示するだけです。例えば以下のように指示します。
ticker.attach(10, doAnything);
こうすると、10秒ごとにメソッドdoAnything()が起動されます。ここで気をつけなければならないのは、doAnythingの中ではネットワーク通信、シリアル通信、ファイル読み書きなどのブロッキングI/O処理を行ってはならない点です。実はうっかりこれをやってしまいました。コールバック関数内にWi-Fiを利用して時刻合わせのNTPサービスを呼び出す処理を書いてしまい、誤動作が発生して右往左往。ネットで次のページを見つけて参考にさせていただきました。
SG Labs 「ESP-WROOM-02で定期的割り込み処理」
なおこれに関連して、英文ページですが、次の文書を見つけました。これはTickerだけでなく、ESP-WROOM-02に内蔵されているESP8266利用上の要点が簡潔に解説されているので、一読をお勧めします。
ESP8266 Arduino Core: Documentation for ESP8266 Arduino Core.
サンプルスケッチを眺めると、Tickerの利用は実にシンプルであることが分かります。Tickerのインスタンスを宣言しておき(例えば「Ticker ticker;」のように)、初期化処理部分で何秒ごとにどんなメソッド(コールバック関数)を起動するかを指示するだけです。例えば以下のように指示します。
ticker.attach(10, doAnything);
こうすると、10秒ごとにメソッドdoAnything()が起動されます。ここで気をつけなければならないのは、doAnythingの中ではネットワーク通信、シリアル通信、ファイル読み書きなどのブロッキングI/O処理を行ってはならない点です。実はうっかりこれをやってしまいました。コールバック関数内にWi-Fiを利用して時刻合わせのNTPサービスを呼び出す処理を書いてしまい、誤動作が発生して右往左往。ネットで次のページを見つけて参考にさせていただきました。
SG Labs 「ESP-WROOM-02で定期的割り込み処理」
なおこれに関連して、英文ページですが、次の文書を見つけました。これはTickerだけでなく、ESP-WROOM-02に内蔵されているESP8266利用上の要点が簡潔に解説されているので、一読をお勧めします。
ESP8266 Arduino Core: Documentation for ESP8266 Arduino Core.
(3)タイマー割り込み部のコード
タイマー割り込み関連のコードは次のとおりです。
〔ポイント説明〕
・1行目: 割り込みを利用するためにTickerのヘッダーファイルをインクルードします。
・4~5行: 計測用と時刻調整用の2つのTickerのインスタンスを作成します。
・6行目: 時刻調整の割り込みが発生した場合、コールバック関数内ではNTPサーバーを呼べないので
(ブロッキングI/Oはダメなので)、この変数をフラグとして利用します。
・9行目: 初期化処理で、開始時刻(分)が指定されていればシステムクロックの分の値が一致するまで
待ちます。これで指定時刻から測定を開始できるわけです
・23行目: 初期化処理内で初回の時刻合わせを実行させます。
・26行目: 初期化処理内で初回の計測・編集・記録処理を実行させます。
・29~30行: 初期化処理内で計測用と時刻調整用の2つのタイマー割り込み条件を指定します。
・34~41行: フラグbReadyTickerが立っていれば時刻調整を行ってフラグを下ろします。
このフラグは、時刻調整割り込み時にtrueにセットされます。
ブロッキングI/O処理やそれを含むメソッドの実行はここに書きます。
・48行目: 計測処理のタイマー割り込みで呼ばれるコールバック関数です。
・51~53行: 計測処理を実行し、結果を編集してSDカードのファイルに記録します。
・61行目: 時刻調整のタイマー割り込みで呼ばれるコールバック関数です。
・63行目: フラッグbReadyTickerをセットするだけで、実行はloop()内に委ねます。
・69行目: 時刻調整メソッドです。
・72~73行: NTPサーバーからJSTの時刻を取得します。
・75行目: システムクロックの時刻を更新します(時刻を合わせます)。
#include <Ticker.h>
// Timer interruption
Ticker ticker1; // For measurement
Ticker ticker2; // For time adjustment
bool bReadyTicker = false;
void setup() {
// Wait until the specified time and start up.
if (strStartTime != "") {
Serial.print("Just wait until next time(minuets) = "); Serial.println(strStartTime);
while (1) {
getDateTime(&dtClock);
sTime = editTime(dtClock);
if (sTime.substring(3, 5) == strStartTime)
break;
delay(100);
}
}
Serial.println("It is just in time!!");
// Adjust time.
adjustTime();
// Do 1'st measurement.
kickRoutineWork();
// Timer: interrupt time and event setting.
ticker1.attach(iIntervalTime, kickRoutineWork);
ticker2.attach(iNtpInterval, kickTimeAdjust);
}
void loop() {
if (bReadyTicker) {
/*
* [Timer interrupt process]
* Match measurement timing at time correction.
*/
adjustTime();
bReadyTicker = false;
}
}
/*
* Timer interrupt event handler1
* <Start measurement>
*/
void kickRoutineWork()
{
// Measurment & write file
doMeasurement();
String buf =editMeasuredResult(rstMeasured);
writeMeasurementResult(DATA_FILE, buf);
Serial.println(buf);
}
/*
* Timer interrupt event handler2
* <Start time adgustment>
*/
void kickTimeAdjust()
{
bReadyTicker = true;
}
/*
* Adjust the clock time.
*/
void adjustTime()
{
while(1) {
if (timeClient.update()) {
String strJST = timeClient.getFormattedTime();
Serial.print("JST time = "); Serial.println(strJST);
setTime(strJST);
return;
}
delay(50);
}
}
(4)外部設定条件の検討
計測開始のタイミングや計測間隔、時刻合わせの間隔など、コード内で記述している定数類には外部から指定できた方が便利なものがあります。次のフィールドはSDファイルに設定したパラメータファイルから読み込むよう、最終回までには具体化したいと思います。
// WiFi connection
const char* ssid = "your_ssid";
const char* password = "your_password";
// Time adjust
const char* sNtpUrl = "ntp.nict.jp";
int iNtpOffset = 32400; // UTC + 9h (3600sec * 9h)sec.
int iNtpInterval = 180; // Time adjust interval(sec.)
// Measurement conditions
String strStartTime = "00"; // "00"~"59" (min.)
int iIntervalTime = 10; // Measurement time interval(sec.)
3.スケッチの実行
(1)実行結果と評価
下図は、動作開始時刻に"00"分を指定して起動したシリアルモニターの表示です。
先のコードで見たように、まずSDカード装置が有効であることを確認してWi-Fiに接続します。その後、指定された00分まで待ち続け、時刻が到来するとその旨を表示して初回の時刻調整と計測を行っています。1秒の時間差で調整が行われ、このズレを含んだまま指定された10秒間隔の測定を行って結果を表示しています。
指定された時刻調整間隔の3分が経過すると、再度時刻調整を行っている様子が分かります。次の計測から1秒のズレが補正されていますが、なぜこうなったかは定かでありません。
このように、所定の計測条件にしたがって計測してSDカードに記録する「自動計測システム」が完成しました。
(2)スケッチ全景