ウォッチドッグタイマー割込みを使ってロータリーエンコーダーを読む
先にお断りしておきますが、この記事の内容ではArduinoを使ってロータリーエンコーダーを完璧に読むことは出来ません。この記事は、ウォッチドッグタイマー割り込みの使い方を解説するために書いています。
Arduino UNO(ATmega328P)には timer0,timer1,timer2 の3つのタイマーがあります。このうちtimer0はシステムが使っているので、勝手に使うことはできません。いや、使ってもいいのですが、それをやるといろいろ面倒なことが起きるはずです。
ということで、普通はTimer1とTimer2の割込みを使うことになります。普通のアプリならタイマー割り込みが二つあれば大丈夫でしょう。でももうすこし複雑なことをやらせたい場合、もう一つタイマー割り込みがあると助かります。
そんなことを考えていたら、居酒屋ガレージ店主さんからコメントで、ATtiny2313を使ったパルスジェネレーターを紹介していただきました。この作品はアセンブラを駆使してCPUの資源をしゃぶりつくして作られているのですが、特に参考になったのは、ウォッチドッグタイマー割込みを使ってデジスイッチの読み取りをやっていることです。
そうか、その手があったか。時間精度のいらない低頻度の割込みならウォッチドッグタイマーが使えます。このテクニックは組み込み系をやられている人には常識なのかも知れませんが、私にとっては目からウロコでした。
ということで、早速Arduino UNOでやってみました。
▼回路図

Arduino UNOにロータリーエンコーダーを接続します。
▼スケッチ
は使わないのでコメントアウトしています。
20行目の WDTimerStart(0); でウォッチドッグタイマー割り込みを起動しています。引数によって割込み間隔が変わり、0で呼ぶと最高速である約15ms間隔で割り込みが発生します。ちなみにこの関数は以前記事にしたdelayWDT関数のプログラムで使った、delayWDT_setup(unsigned int ii) 関数の使い回しです。この割り込みを停止させるために、WDTimerStop()という関数も作った方がいいのですが、とりあえず止めないでも困らないのでまだ書いていません。
これを動かした結果ですが、割り込みの速度が15ms間隔と遅いので、ロータリーエンコーダーをゆっくり廻さないと取りこぼしが発生します。まあこうなるのは想定の範囲内で、ゆっくりと動かすと正常に値が変化するので割り込みはうまくいっているようです。ちなみに、ロータリーエンコーダーを取りこぼし無く読むには、割込み間隔を1msくらいにする必要があります。
そういう問題はありますが、ともかくウォッチドックタイマー割込みを使うプログラムはこれでいけそうです。人間が操作するスイッチの読み取りや、バッテリー電圧の監視くらいなら、ウォッチドックタイマの割込みで充分対応出来ると思います。
あと、同じような機能はネットを探せばライブラリが出てくるので、それを使う手もあります。ただここからは私の意見ですが、Arduinoのユーザーライブラリには古いままメンテナンスされてないものがあって、最新版のIDEではコンパイラが通らないものが結構あります。ということで、よく確認しないでライブラリを使うと、かえってややこしいことになると思います。
◆まとめ
ウォッチドッグタイマーは、プログラムの暴走など予期しない動きから確実に脱出するために用意されている仕掛けだと思います。でも、さほど信頼性が必要ない場合はこれを割込み源として使うのもアリだと思います。
Arduino UNO(ATmega328P)には timer0,timer1,timer2 の3つのタイマーがあります。このうちtimer0はシステムが使っているので、勝手に使うことはできません。いや、使ってもいいのですが、それをやるといろいろ面倒なことが起きるはずです。
ということで、普通はTimer1とTimer2の割込みを使うことになります。普通のアプリならタイマー割り込みが二つあれば大丈夫でしょう。でももうすこし複雑なことをやらせたい場合、もう一つタイマー割り込みがあると助かります。
そんなことを考えていたら、居酒屋ガレージ店主さんからコメントで、ATtiny2313を使ったパルスジェネレーターを紹介していただきました。この作品はアセンブラを駆使してCPUの資源をしゃぶりつくして作られているのですが、特に参考になったのは、ウォッチドッグタイマー割込みを使ってデジスイッチの読み取りをやっていることです。
そうか、その手があったか。時間精度のいらない低頻度の割込みならウォッチドッグタイマーが使えます。このテクニックは組み込み系をやられている人には常識なのかも知れませんが、私にとっては目からウロコでした。
ということで、早速Arduino UNOでやってみました。
▼回路図

Arduino UNOにロータリーエンコーダーを接続します。
▼スケッチ
/* ウォッチドッグタイマー割り込みで、ロータリーエンコーダロータリーエンコーダーを廻すと現在の値をシリアルモニターに表示するプログラムになっています。ロータリーエンコーダーの読取りは、51行目以降の割込み処理ルーチンで行っていますが、アルゴリズムは、以前の記事に書いたビットパターン一致の監視法です。CPUのレジスタを直接いじっているので、6行目の #include
* を読取る実験。(15ms間隔サンプリングなので早回しはダメ)
* 2016/03/31 ラジオペンチ http://radiopench.blog96.fc2.com/
*/
//#include <avr/wdt.h> // ウォッチドッグタイマーを使用
#define ECA 9 // エンコーダーA相 = D9
#define ECB 10 // B相 = D10
volatile int X;
int data;
void setup() {
pinMode(ECA, INPUT_PULLUP); // ロータリーエンコーダーA
pinMode(ECB, INPUT_PULLUP); // ロータリーエンコーダーB
Serial.begin(9600);
Serial.println("start");
WDTimerStart(0); // 15ms間隔でウォッチドッグタイマー割込み開始
}
void loop() { // メインループ
if (X != 0) { // エンコーダーが動いていたら
data = data + X;
X = 0;
Serial.println(data);
}
}
void WDTimerStart(unsigned int ii) { // ウォッチドッグタイマーをセット。
// 引数はWDTCSRにセットするWDP0-WDP3の値。設定値と動作時間は概略下記
// 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
// 6=1sec, 7=2sec, 8=4sec, 9=8sec
byte bb;
if (ii > 9 ) ii = 9; // 変な値を排除
bb = ii & 7; // 下位3ビットをbbに
if (ii > 7) { // 7以上(8,9)なら
bb |= (1 << 5); // bbの5ビット目(WDP3)を1にする
}
bb |= ( 1 << WDCE );
MCUSR &= ~(1 << WDRF); // MCU Status RegのWatchdog Reset Flag ->0
// start timed sequence
WDTCSR |= (1 << WDCE) | (1 << WDE); // ウォッチドッグ変更許可(WDCEは4サイクルで自動リセット)
// set new watchdog timeout value
WDTCSR = bb; // 制御レジスタを設定
WDTCSR |= _BV(WDIE);
}
ISR(WDT_vect) { // WDT割込み発生時にロータリーエンコーダーを読む
static byte bp = 0; // ビットパターン記録バッファ
bp = bp << 1; // ピンの状態変化をbpに右詰めで記録 0b00ABABAB
if (digitalRead(ECA) == HIGH) bp |= 0x01;
bp = bp << 1;
if (digitalRead(ECB) == HIGH) bp |= 0x01;
bp &= 0x0F; // 下位4ビット残して上位を消す 0b0000ABAB
if (bp == 0b0111) X ++; // 一致していればインクリメント
if (bp == 0b1011) X --; // 一致していればデクリメント
}
20行目の WDTimerStart(0); でウォッチドッグタイマー割り込みを起動しています。引数によって割込み間隔が変わり、0で呼ぶと最高速である約15ms間隔で割り込みが発生します。ちなみにこの関数は以前記事にしたdelayWDT関数のプログラムで使った、delayWDT_setup(unsigned int ii) 関数の使い回しです。この割り込みを停止させるために、WDTimerStop()という関数も作った方がいいのですが、とりあえず止めないでも困らないのでまだ書いていません。
これを動かした結果ですが、割り込みの速度が15ms間隔と遅いので、ロータリーエンコーダーをゆっくり廻さないと取りこぼしが発生します。まあこうなるのは想定の範囲内で、ゆっくりと動かすと正常に値が変化するので割り込みはうまくいっているようです。ちなみに、ロータリーエンコーダーを取りこぼし無く読むには、割込み間隔を1msくらいにする必要があります。
そういう問題はありますが、ともかくウォッチドックタイマー割込みを使うプログラムはこれでいけそうです。人間が操作するスイッチの読み取りや、バッテリー電圧の監視くらいなら、ウォッチドックタイマの割込みで充分対応出来ると思います。
あと、同じような機能はネットを探せばライブラリが出てくるので、それを使う手もあります。ただここからは私の意見ですが、Arduinoのユーザーライブラリには古いままメンテナンスされてないものがあって、最新版のIDEではコンパイラが通らないものが結構あります。ということで、よく確認しないでライブラリを使うと、かえってややこしいことになると思います。
◆まとめ
ウォッチドッグタイマーは、プログラムの暴走など予期しない動きから確実に脱出するために用意されている仕掛けだと思います。でも、さほど信頼性が必要ない場合はこれを割込み源として使うのもアリだと思います。