以前の投稿で、一定時間内に入ったパルスの数を数えて、ワイヤレスモジュール(nRF21L01+)を使って通信するという実験をしましたが、いくつかの不具合があったので、修正してみることにしました。
目次
【不具合の色々】
- 10秒毎のタイマー割り込みを設定していたつもりだったが、正確に10秒になっていない。
- パルス入力に「チャタリング除去」を使っていたが、測定対象のパルスの幅が非常に短く、場合によっては捕まらない事がある。
- 測定結果をUSB経由のシリアル通信でパソコンに取り込んで使う必要があるが、何らかの理由でパソコン側のシリアルバッファにデータが溜まってしまった際(吐き出す処理が滞った場合)、実際に計測された時刻がいつだったのかわからなくなる。
さらに、この際、余分な機能(温度測定とか)を全部取り除いて、コードをきれいに書き直すことにしました。
【不具合への対応】
上記の各不具合に対して、次のような対応をしました。
- Timer2割り込みに設定する値を修正して、ほぼ正確に10秒毎に割り込みが掛かるようにした。
- パルス入力を”INT0”ピンを使ったエッジ割り込み(立上り)に変更した。
- パルス計測結果をRFで(nRF24L01経由で)送信する際に、RTCモジュールを使って「送信時刻」を送信パケットのデータに含めることにした。
【1: Timer2割り込みを正確に10秒毎に】
まずは、以下の単純コードでTimer2とSerial.printだけのコードで、確認してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <MsTimer2.h> //タイマー割り込み用のヘッダー #define TMR2_INT 10000 //10秒なら10000 msecと設定すれば良いはずなのだが・・・A volatile bool isTimer2Int = false; //割り込みあり無しをメインループへ伝えるフラグ void timer2ISR() //Timer2割り込みの処理ルーチン。出来るだけ早く抜ける { isTimer2Int = true; // ISR for Timer2 } void setup() { Serial.begin(115200); MsTimer2::set(TMR2_INT, timer2ISR); //割り込み周期と割り込みルーチンの設定 MsTimer2::start(); // Timer2割り込み開始 } void loop() { if (isTimer2Int) { //割り込みが入った次のメインループで実際の処理を実施する isTimer2Int = false; //割り込みフラグをクリア Serial.println(millis()); } } |
このコードを走らせてみると、設定した通り、10000msec毎に正確に 10000ずつ差のある mills()の数字が表示されます。ということは、ATmega328のコントローラー自体は、ちゃんと正確に 10000ms毎に割り込みをかけているつもりになっている事になります。
ただ、実際の時計と比較して長時間走らせると、ずれが発生しているのは事実ですので、要するにArduino Nanoのクロック精度がその程度ということだと思われます。
Timer2の設定を10000 (すなわち10.000秒)にして、RTC (DS3231のReal Time Clock)の時計出力と比較実験すると、約114回毎、すなわち1140秒毎に約1秒遅れていくことが観測されていますので、ズレは、1/1140 (=0.0877%) 程度となります。 Arduino Nanoに搭載されているクロック源は、16MHzのセラミック振動子(セラロック)であり、仕様書によると初期周波数の偏差は±0.5%にもなりますので、今回の実験結果は 0.09%程度というのは、充分仕様内と言えます。 これ以上の精度をセラロックに求めるのは無理ですので、まぁ、今回のプロジェクトでは、「現物合わせ」の手法で対応するのが妥当という事になりますね。
もっと、精度が必要な場合は、水晶振動子を使うしか無いですね。
現物合わせとしては、Timer2の割り込み周期を ずれている分だけ長くしてあげることにしました。10000 * (1/1140 + 1) = 約10008.7719 (四捨五入して 10009) を設定して実験してみたところ、(実用的なレベルで)正確に10秒ごとに割り込みが入るようになりました。
追記: 周波数カウンタとかオシロとかが手元にあれば、確実に検証できるでしょう。安いUSBのオシロが欲しくなってきました。
【2.パルス入力をINT0割り込みへ変更】
最初に設計した際、パルスは通常のDigital Readで読んで、20msec毎にチャタリング処理をした上で、パルスの有り無しを判断するように作りました。実験中に普通のタクトスイッチでパルス検討する際には、これが必須でした(チャタリングを取らないと、まともなパルス計測ができません)
ところが、実際に測定したい装置に導入してみた際、パルスのデューティー比が非常に大きく、”H”レベルが50msec から 70msec程度、”L”レベルが800msec程度であったため、チャタリング処理が逆に邪魔をして、パルスを取り込めない場合が時々ありました。この装置に於けるパルスを現場で確認したところ、チャタリングは無く、きれいな波形でしたので、この際、割り込みを使ったパルス検出方法へ変更することにしました。 (チャタリングがある場合には、割り込みによるパルス入力検出は難しい気がします)
Arduinoに於けるパルス割り込みは、INT0 (D2 ピン) で簡単に使えます。単純化したコードはこんな感じです。10秒の間に入ったパルスの数を数えて、10秒ごとにシリアルに書き出すソフトです。前述した通り、10秒の割り込みには、Timer2を使いますが、クロック精度の現物合わせで 10009という補正したカウントで割り込みをかけます。
常識として、「割り込みの中で時間のかかる処理は、避けるべき」という鉄則がありますので、どちらの割り込みも、フラグを設定したり、カウンターをインクリメントするだけにして、時間のかかる処理は、メインループの中でTimer2の割り込み毎に処理します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <MsTimer2.h> #define TMR2_INT 10009 // 0.09%の補正後 volatile bool isTimer2Int = false; void timer2ISR() // Timer2割り込み処理ルーチン { isTimer2Int = true; // ISR for Timer2 フラグを立てるだけで抜ける。 } #define INPUT_PIN_1 2 // 割り込みピンはD2 (INT0)を使用。 volatile uint16_t pulse_count = 0; //パルス計数用のカウンタ (Volatile宣言・・・勉強中・・・) void risingISR() // パルス立上りエッジの割り込み処理ルーチン { ++pulse_count; // カウンターをIncrementするだけで抜ける。 } /**************************************** Setup *****************************************/ void setup() { Serial.begin(115200); // 115200 baud MsTimer2::set(TMR2_INT, timer2ISR); MsTimer2::start(); // interrupt start pinMode(INPUT_PIN_1, INPUT_PULLUP); // pulse input pin attachInterrupt(0, risingISR, RISING); //pin D2 (INT0) RISING EDGE detect // この記述で、「INT0信号の立上りエッジ検出し、指定されたルーチンを開始する」になります } /************************************************** Main Loop **************************************************/ void loop() { if (isTimer2Int) { // 10秒毎 isTimer2Int = false; //フラグをクリア c = pulse_count; // この瞬間までに計測された数字をコピーしてクリアする。 pulse_count = 0; // この2つの文の間に割り込みが掛かるとずれるリスクがあるので、 // 本当は割り込みを禁止すべき・・・と思う。 Serial.println(c); // 数をシリアルに出力 } } |
これで、D2ピン(INT0)に接続された信号の、すべての立上りエッジを割り込みで取り込んで、カウントできるので、いくらパルスのデューティーが大きくても(どれだけ、パルスの”H”期間が短くても)正確に、パルス数を数えられます。逆に、副作用としては、パルスにチャタリングがある場合は、正しく数えられません。(チャタリングを全部数えてしまうから・・・)
【パルスを計測した時刻を正確に出力できるようにする】
【次ページへ続きます】