Evaluated “Deep power-down mode” of NXP LPC810M.
以前、LPC810M で「L チカ」(LED チカチカ)をしてみましたが、今回は定期的にスリープモードに入り消費電力を抑える実験をしてみました。特に、もっとも消費電流の小さい Deep power-down モードというものを評価してみました。うまく実装すると、スリープ期間中の消費電流を 1 uA(マイクロアンペア)程度に抑えられるようです。(ただし、メモリ回路への電流もカットしてしまうので、復帰(以下 wakeup と呼ぶ)時はリセット動作と「ほぼ」同等になります。つまり、main() ルーチンから実行し直しになります。ただし、パワーオンリセットなのか Deep power-down モードからの wakeup なのかは、レジスタを見ると分かるようになっています。)
世の中にはいろいろと参考になるサイトがありますが、今回は勉強を兼ねて、まずはユーザーマニュアルだけを元に実装してみました。
まずはマニュアルを読む
LPC81x User manual(UM10601)(以下、ユーザーマニュアルと呼びます)の 5.5節(LPC81x Reduced power modes and Power Management, General description)を見ると、以下のような説明があります。
3. Deep power-down mode:
For maximal power savings, the entire system is shut down except for the general purpose registers in the PMU and the self wake-up timer. Only the general purpose registers in the PMU maintain their internal states. The part can wake up on a pulse on the WAKEUP pin or when the self wake-up timer times out. On wake-up, the part reboots.
つまり、このモードでは外部から WAKEUP ピンをトリガしてやるか、wake-up タイマというものを使ってスリープから wakeup することになります。
今回はタイマを使ってみたいので、今度は 13節(LPC81x Self wake-up timer (WKT))を読んでみましょう。この WKT を使うには、13.3節によると、以下の設定が必要のようです。
- SYSCON の SYSAHBCLKCTRL レジスタを設定し、WKT 回路への制御クロックをオンにする。
- SYSCON の PRESETCTRL レジスタを設定し、WKT 回路のリセット状態をクリアする。
- PMU の DPDCTRL レジスタを設定し、low-power オシレターを有効にする。(LPOSCEN と LPOSCDPDEN をenable にする。)
- SYSCON の PDRUNCFG レジスタを設定し、IRC と IRCOUT を有効(powered)にしておく。(通常は powered になっているので、特に今回はいじりません。)
- 5.7.1節を参考に、power down モードを設定する。
なお、3節(LPC81x Nested Vectored Interrupt Controller (NVIC))の Table 3 を見ると、WKT 割込は NVIC の #15 に接続されているようですが、今回は WKT 割込は使いません。WFI 命令を実行するや否や、システムが Deep power-down モードに入ってしまい、wakeup したときにはリセットから再開します。(つまり、WKT 割込が発生するタイミングがない。)
また、13.3節には書かれていないのですが、実際には上記手順の 4 と 5 の間で、WKT 自身の設定が必要です。具体的には以下の通りです。
- WKT の CTRL レジスタを設定し、CLKSEL を low power clock に、また ALARMFLAG をクリアし、CLEARCTR でカウンタをクリアしておく。
さて、5.7.1節に移りましょう。今回、使いたいのは Deep power-down モードなので、さらに 5.7.7 節に進みます。ちなみにここを読むと、/RESET ピンを外部的にプルアップしておく必要があることが分かります。後述のアプリケーションノートによると、10〜47kΩ でプルアップするのが適切のようです。
今回は Deep power-down モードの中でも WKT による wakeup をしたいので、5.7.7.4節(Programming Deep power-down mode using the self-wake-up timer)を参考にします。手順としては、以下のようになります。
- PMU の DPDCTRL レジスタを設定する。既に上述の通り設定しているのですが、実はここでマニュアルに明記されていない点として、WAKEPAD_DISABLE というビットに 1 を設定して WAKEUP ピンを disable にする必要があります。これをしないと、WFI 命令でスリープさせたつもりがすぐに wakeup してしまう、という現象が発生します。(私はこれでしばらく悩んだ。)
- PMU の PCON レジスタを設定し、NODPD をクリアし、PM には 0x3 を書き込む。
- 必要ならば、PMU の GPREG[0:3] レジスタに情報を書き込んでおく。この情報は、Deep power-down モードでも保持され、wakeup 後のリセットでもクリアされません。main() ルーチンの中で、その起動がパワーオンリセットなのか、Deep power-down モードからの wakeup なのか判断することができます。
- ARM コアの SCR レジスタを設定し、SLEEPDEEP ビットをオンにしておく。
- WKT の COUNT レジスタを設定し、どのくらいの時間が経過したら Deep power-down モードから wakeup するかを指定する。WKT カウンタは low-power オシレター(10kHz)でカウントさせるので、カウンタ値 10000で 1秒間のスリープになります。
- 最後に、WFI 命令を発行する。
実際にコードを書いてみる
以下にコードをまとめておきます。Keil uVision で LPC810M 用の PACK をインストールしておくと、LPC8xx.h というヘッダファイルの定義でコンパイルできると思います。前回の記事も御参考ください。
#include <LPC8xx.h> // ... /* * delay() 関数は、LPC810M のサンプルコード Blinky より借用 */ void delay(uint32_t delay_ticks) { uint32_t cur_ticks; cur_ticks = ms_ticks; while ((ms_ticks - cur_ticks) < delay_ticks) __NOP(); } // ... int main(void) { int from_deep_power_down = 0; if (LPC_PMU->PCON & (1 << 11)) from_deep_power_down = 1; // Deep power-down からの wakeup かどうか確認 NVIC_DisableIRQ(WKT_IRQn); // 念のため /* * この辺は、LPC810M のサンプルコード Blinky より */ SystemCoreClockUpdate(); LED_Initialize(); SysTick_Config(SystemCoreClock / 1000); /* * Refer 13.3 of UM10601 */ LPC_SYSCON->SYSAHBCLKCTRL |= 1 << 9; // enable WKT (wake-up timer) LPC_SYSCON->PRESETCTRL |= 1 << 9; // clear WKT reset /* * ここで 10 msec 程度の遅延を入れておかないと、なぜか最初のパワーオンリセット時の * WFI で、すぐに起きてきてしまう。マニュアルとか調べたけど分かりませんでした。 */ delay(10); LPC_PMU->DPDCTRL = 0 << 0 // (wakeup pin is not used) | 1 << 1 // need to disable wakeup pin | 1 << 2 // enable LPO for WKT | 1 << 3; // also in deep power-down // (refer 5.7.7.4) /* * Configure for Deep power-down mode using self-wake-up timer * * Refer 5.7.1 of UM10601 * Refer 5.7.7.4 of UM10601 */ LPC_WKT->CTRL = 1 << 0 // select LPO clock | 1 << 1 // clear timeout | 1 << 2; // clear the WKT LPC_PMU->PCON = 3; // Deep power-down mode SCB->SCR |= 1 << 2; // SLEEPDEEP = 1 /* * ここで、必要な仕事(処理)をします。L チカとか。 */ something_to_do(); /* * 少し時間をおかないと、あとでデバッガからフラッシュメモリを * 消したりできなくなると困るので、デバッグ中はウェイトを入れておきます。 * (Deep power-down モードに入ると、デバッガ接続が切れてしまう。) * LED とか点灯しておくと、ウェイト中かどうかが分かりやすいです。 */ delay(1000); /* * それでは、10秒間、お休みなさいzzz */ LPC_WKT->COUNT = 10 * 10000; __WFI(); }
回路上の注意点
LPC810M は通常、/RESET ピンをオープンのままでも動いてしまいますが、Deep power-down モード中に /RESET ピンがオープンだと消費電流が増えるそうなので(実測 10 uA 程度)、10〜47kΩ程度でプルアップしておくことをお勧めします。そうすると、だいたい 1 uA 程度に収まります。(デバッガが繋がっていると電流値が落ちないことがあるので、デバッガ等を外して測定することをお勧めします。)
もう一点。WKT(タイマ)を使って wakeup させる場合には、WAKEUP ピンはオープン(無接続)にしておかないとまずいようです。WAKEPAD_DISABLE を 1 に設定しておいても、WAKEUP ピンを V_DD や GND に接続したりすると、外部的に wakeup してしまうことがあります。
実はアプリケーションノートがあった
だいだいそういうものなのですが、後になってアプリケーションノートを見つけました。このページの Application note “LPC800 low power modes and wake-up times” というところにあります。ただし、先ほどのコード中の、10 msec 遅延の秘密までは分かりませんでした。ということで、少しでも皆様の御参考になれば幸いです。
今日はここまで。