前々回、M5Stack GreyのLCDをオフにしたら、フル充電から約77分ほど動作しつづけたケースのグラフをお見せしました。しかし、M5.Powerモジュールの中にはもっと消費電力をケチれるAPIが準備されています。deepSleepとlightSleep。実際、lightSleep使ってみたら同様な条件で約397分も動作しつづけました。でもね、sleep、ちょっとクセがありそう。
※「IoT何をいまさら」投稿順Indexはこちら
闇雲に実験するのも何なので、最初にdeepSleepとlightSleepのソースを眺めてみました。まずどちらのsleepも、内部でLCDのバックライトとコントローラをオフにしています。消費電力削減の基本の基本。
M5.Lcd.setBrightness(0); M5.Lcd.sleep();
ところが、復帰のところが大きく違います。まずlightSleepを見ると、
ちゃんとLCDコントローラを起床させ、バックライトも設定して
復帰してきます。当然、画面表示が復活すると。ただし、バックライトの設定は固定値200になっていました。IOレジスタでリードオンリなので元の値を保存しておくことができないのですかね。つまり、関数呼ぶと、少なくともバックライトの設定は保存されない、ということです。もしかすると他のI/O関係も保存されない値があるかもしれません。
lightSleepのコードみると、
-
- 起床のためのタイマをしかけ、
- CPUをスリープに入れて、
- 所定の時間経過してCPUがタイマに叩き起こされると、
- 後始末をして、関数から抜け、呼び出し元に戻る、
というシナリオで動作していると思われます。まあ、これなら使いやすいんでないかい。
ところが、deepSleepのコードは一味違いますな。最後の「後始末」から「戻る」ところ、やる気がない感じ。それもその筈、実際、実験してみて確かめましたが、deepSleepからの脱出は、
RESET
がかかっていました。
setup()関数の前の方で、左のようなRESETのソースを識別して表示する関数を呼びだしておいたのです。そういう関数
isResetbyDeepsleep()
などというものがあることからも容易に推測されるように、脱出は通常のタイマ割り込みではなくてRESETでした。そのことから推測されるのは、deepSleepの場合、
大域変数などは初期化されてしまう
であろうことです。実際、上のコードの9行目に gval=1111;とありますが、ソフトウエアRESETの時だけ、大域変数gvalに1111を代入しているのです。
-
- ソフトウエアRESET
- lightSleep
の順番で実行した後、gvalの値を確認すると1111のまま、保存されています。lightSleepでは、メモリの値が保存されていることが分かります。しかし、
-
- ソフトウエアRESET
- deepSleep
の順番で実行すると、gvalは0になっていました。ソフトウエアRESET後、一端1111が代入されるものの、deepSleepからの脱出はRESETであるので、gval変数が再び初期状態に戻ってしまっているようです。ま、逆にBSS領域にある筈のローカル変数の値が維持されたように見えていましたが、これはBSS領域がRESETで初期化されず、関数フレームの位置がまったく同じところにくるために、未初期化でもそう見えただけでしょう。基本、deepSleepからの復帰では、RESETかかってプログラムの先頭から走り始めるので変数値などは注意しないとなりませんな。
なお、過去とのコンパチビリティからか、こんなコードもあることを見つけました。M5Stack.cppから引用させていただくと、
// quote from M5Stack.cpp void M5Stack::powerOFF() { M5.Power.deepSleep(); }
M5.powerOFF()関数の実体は、上のようにdeepSleep()そのものでした。この関数のコメントの中に「近い将来、削除される」とあるので、Powerモジュールの中の方の、M5.Power.powerOFF()の方を使うべきなのだと思います。こちらのpowerOFF()関数は、deepSleep()そのものということはなく、いろいろと下準備(WiFi止めたりとか)してから止まります。安全にシャットダウンするためには確かにこちらを使うべきでしょう。しかし、最終的に止まる実体関数は、結局のところdeepSleep()と共通のようです。M5Stackにおいては、deepSleep()とpowerOFFは親戚筋だと。
AボタンでdeepSleep()
BボタンでlightSleep()
CボタンでソフトRESET