概要
ATtiny85で、TC(タイマーカウント)ユニットのタイマーオーバーフロー割込みの代わりに、WDT(ウォッチドッグタイマー)割込みを使ってみる。WDT割込みを使えば、アイドルスリープに比べて消費電流が極めて小さいパワーダウンスリープを使うことができる。
動作の確認のため、WDT割込みによるスリープ復帰のタイミングでLEDを点けたり消したりしてチカチカさせてみた。
ウォッチドッグタイマーについて
ATtiny5シリーズのウォッチドッグタイマー(WDT)は、専用の128kHzオシレータを備えているので、他のクロックを止めているときも動作を持続する。128kHzをWDT専用のプリスケーラを使って分周することで、タイムアウトするまでの時間を10通りに設定できる(16msec~8.0sec)。なお、TCユニットとは違ってWDTのある時点でのカウンタ値を得たり、任意の値をセットすることはできない(wdr命令によってクリアすることはできる)。
WDT本来の使い方は、タイムアウト時にATtinyをリスタートすることでプログラムが(無限ループなどにより)停止するのを防ぐこと。たとえば通常は1秒以内に処理が終わるようなループ処理ならば、WDTタイムアウトを1秒間としループの先頭でwdr(Watchdog Reset)命令を実行するようにしておく。何かの異常があって1秒経ってもループの先頭に戻らなければ、WDTリセットがかかることになる。
ATtinyでは、リセットの代わりに割込みを起こすように設定することもできる。この割込みは、パワーダウンスリープからの復帰要因となるので、今回はそのような使い方を試してみることにした。
調べたかぎり、ATtiny45/85の出荷時のFUSE設定ではWDTONビット(FUSEハイバイトのビット4)は”1″ (Unprogrammed)になっており、リセット時のWDTは動作禁止になっている。必要ならばデータシートの「セーフティーレベル1」という手順にしたがってタイムアウト時のリセット動作を許可することができるが、今回はタイムアウト割込みだけを使い、リセットを伴う使い方はしない。そのため、特別なシーケンスを使わなくてもタイムアウト時間や割込み許可を行うことができる。
WDTの使い方
スリープ復帰用のタイマーとして使う際には、WDTCR ( Watchdog Timer Control Register ) に対して、割込み許可とWDTのプリスケーラを設定してやればよい。データシートから関係する項目を抜き出すと以下のようになる。
1 2 3 4 5 6 7 |
(MSB) WDIF WDIE WDP3 WDCE WDE WDP2 WDP1 WDP0(LSB) | | | | | +--- | | | | +-------- WDP[3:0] WDTプリスケーラ設定 | | | +------------- (設定内容は後述) | | +--------------------------- | +--- Watchdog Timeout Interrupt Enable. 1にするとタイムアウト割込み許可。 +-------- Watchdog Timeout Interrupt Flag. タイムアウト割込みが起きると1になる。 |
WDTタイムアウト割込みの割込みベクタ番号は13で、 ISR(WDT_vect) {} のように宣言しておく。タイムアウト割込みが起きて ISR(WDT_vect) に実行が移るとWDIFはクリアされる。
タイムアウト割込みを使うためにやるべきことは、WDP[3:0]の4ビットに必要なタイムアウト時間に導くプリスケーラ値を設定し、WDIEを1にする。そしてwdr()命令によってカウンタをクリアすれば、所定の時間が経過すればタイムアウト割込みが発生する。
タイムアウト時間とプリスケーラの設定
タイムアウト時間は、以下のように16msec~8.0秒の範囲で10通りに設定可能になっている(データシートのTable 8-3. Watchdog Timer Prescale Select による)。
WDP3 | WDP2 | WDP1 | WDP0 | タイムアウト |
---|---|---|---|---|
0 | 0 | 0 | 0 | 16msec |
0 | 0 | 0 | 1 | 32msec |
0 | 0 | 1 | 0 | 64msec |
0 | 0 | 1 | 1 | 0.125sec |
0 | 1 | 0 | 0 | 0.25sec |
0 | 1 | 0 | 1 | 0.5sec |
0 | 1 | 1 | 0 | 1.0sec |
0 | 1 | 1 | 1 | 2.0sec |
1 | 0 | 0 | 0 | 4.0sec |
1 | 0 | 0 | 1 | 8.0sec |
この記述方法からして、あまり正確さを期待してはいけないのかもしれない。設定する際には、
1 |
WDTCR = _BV(WDIE) | _BV(WDP2) | _BV(WDP1); |
のようにする。この例では1秒ごとにWDTタイムアウト割込みが発生する。
テスト回路
Arduino PRO MINI ISPボードにATtinyを装着して、PB3にLEDを接続。PB3を1とすると点灯し、0で消灯する。
スケッチ
以下のようなスケッチを書いてみた。
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 48 49 50 51 |
#include <avr/wdt.h> #include <avr/sleep.h> const uint8_t WDT_PRSCL_125 = (_BV(WDP1) | _BV(WDP0)); const uint8_t WDT_PRSCL_250 = _BV(WDP2); const uint8_t WDT_PRSCL_500 = (_BV(WDP2) | _BV(WDP0)); const uint8_t WDT_PRSCL_1000 = (_BV(WDP2) |_BV(WDP1)); const uint8_t WDT_PRSCL_2000 = (_BV(WDP2) |_BV(WDP1) | _BV(WDP0)); void start_wdt(int8_t prscl) { cli(); WDTCR = _BV(WDIE) | prscl; wdt_reset(); // == wdr sei(); } void setup() { cli(); DDRB = _BV(3); // LED PORTB = 0; ACSR |= 0x80; // disable Analog Comparator. ADCSRA &= 0x7f; // disable ADC TIMSK = 0; // disable Timer0/1 overflow interrupt. PRR = 15; sei(); } ISR(WDT_vect) {} int8_t prcl_index = 0; const uint8_t wdt_prcl[] = { WDT_PRSCL_125, WDT_PRSCL_250, WDT_PRSCL_500, WDT_PRSCL_1000, WDT_PRSCL_2000 }; void loop() { static int wdt_count = 0; if (wdt_count == 0) { start_wdt(wdt_prcl[prcl_index++]); if (prcl_index >= sizeof(wdt_prcl)) prcl_index = 0; } set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_cpu(); sleep_disable(); if (PINB & _BV(3)) PORTB = 0; else PORTB = _BV(3); wdt_count++; if (wdt_count > 10) wdt_count = 0; } |
このスケッチの意図は以下のとおり。
- WDTタイムアウトにより、パワーダウンスリープから復帰するごとにLEDの点灯/消灯を行う。
- 5回点滅するごとに、wdt_prcl[]の内容にしたがってタイムアウト値を変更。最後の2秒間になったら、最初(125msec)に戻って繰り返す。
- もしもWDTCRが更新できないならば、スリープから復帰しないだろう。また、WDTタイムアウトごとにリセットがかかっているとしたら、prcl_index は維持されないので点滅間隔は変化しないだろう。
スケッチ内容について
- WDT_PRSCL_125 ~ WDT_PRSCL_2000 までのマクロは、プリスケーラの各ビットの組み合わせによってWDTタイムアウト値を表現している。正直なところ、もうちょっと細かく設定できた方がうれしかった。
- 印象としては10%程度かそれ以上の誤差があるようなので、正確な計時には向かない。
- パワーダウンスリープからの復帰のみが目的なので、ISR(WDT_vect) 内での処理はない。
- wdt_reset() は、wdr命令を実行するためのマクロで、avr/wdt.h 内で定義されている。
- 前回の投稿では、Arduinoライブラリをリンクしないようにしたが、今回は特に気にする必要もない(TC0の割込みを使わない)ので、main() は記述しなかった。
- ATtinyを使うための要素はだいたい確認できたので、そろそろカタチのあるものを作る予定です。
以上)