NXP LPC810M で、Deep power-down モードを試してみる

(更新

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節によると、以下の設定が必要のようです。

  1. SYSCON の SYSAHBCLKCTRL レジスタを設定し、WKT 回路への制御クロックをオンにする。
  2. SYSCON の PRESETCTRL レジスタを設定し、WKT 回路のリセット状態をクリアする。
  3. PMU の DPDCTRL レジスタを設定し、low-power オシレターを有効にする。(LPOSCEN と LPOSCDPDEN をenable にする。)
  4. SYSCON の PDRUNCFG レジスタを設定し、IRC と IRCOUT を有効(powered)にしておく。(通常は powered になっているので、特に今回はいじりません。)
  5. 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)を参考にします。手順としては、以下のようになります。

  1. PMU の DPDCTRL レジスタを設定する。既に上述の通り設定しているのですが、実はここでマニュアルに明記されていない点として、WAKEPAD_DISABLE というビットに 1 を設定して WAKEUP ピンを disable にする必要があります。これをしないと、WFI 命令でスリープさせたつもりがすぐに wakeup してしまう、という現象が発生します。(私はこれでしばらく悩んだ。)
  2. PMU の PCON レジスタを設定し、NODPD をクリアし、PM には 0x3 を書き込む。
  3. 必要ならば、PMU の GPREG[0:3] レジスタに情報を書き込んでおく。この情報は、Deep power-down モードでも保持され、wakeup 後のリセットでもクリアされません。main() ルーチンの中で、その起動がパワーオンリセットなのか、Deep power-down モードからの wakeup なのか判断することができます。
  4. ARM コアの SCR レジスタを設定し、SLEEPDEEP ビットをオンにしておく。
  5. WKT の COUNT レジスタを設定し、どのくらいの時間が経過したら Deep power-down モードから wakeup するかを指定する。WKT カウンタは low-power オシレター(10kHz)でカウントさせるので、カウンタ値 10000で 1秒間のスリープになります。
  6. 最後に、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 遅延の秘密までは分かりませんでした。ということで、少しでも皆様の御参考になれば幸いです。

今日はここまで。