MicroPython的午睡(28) ラズパイPico、lightとdeepなsleep

Joseph Halfmoon

Raspberry Pi Pico上のMicroPythonで以前から気になっていたのが、電力制御関係の関数です。パソコンに接続してソフトを書いている分には必要性は無いですが、バッテリに接続して動作させようとする場合には非常に重要です。それらしい関数が存在するので、きっと効果あるに違いないと思い込んできたのですが、そこには闇がありました。

※「MicroPython的午睡」投稿順 Indexはこちら

ちょっといかぶしく思う点はあったのです。ラズパイPicoのMicroPythonの拠り所ともいうべき文書である “Raspberry Pi Pico Python SDK” には電力制御に関する言及が見当たりません。しかし、ラズパイPicoのマイコンであるRP2040のデータシートには電力制御関係の記述があります。そして MicroPythonのmachineモジュールには電力制御に使えそうな関数が含まれています。当然、なんらかの効果があるものと期待しました。machineモジュールの中から以下の2つの関数を使ってみました。

lightsleep -- <function>
deepsleep -- <function>

lightなsleepとdeepなsleepです。以下の一般的なMicroPython実装のドキュメントでは「電力関連の関数」として上記の関数についての記述があります。

machine — ハードウェア関連の関数

上記のページから1行引用させていただきます。

実行を停止して、低電力状態に入ります。

一般的な実装では、lightsleepは周辺回路を止めることなく、またメモリを保持したまま、MicroPythonを処理するプロセッサだけを低電力状態に入れるようです。周辺の処理状況によっては即座に起きて処理を再開できる関数として記述されています。それに対してdeepsleepは周辺回路を止め、メモリ(RAM)への電力供給も止め(当然メモリ内容は失われる)より深い「眠り」につかせる、と。このためdeepsleepからの「起床」には、RESETかけるような再初期化が必要。ただしこれら関数は「実装依存」で機種毎に異なる、と。このような理解をいたしました。

ラズパイPicoのMicroPythonのmachineモジュールを調べたところ、deepsleepについては標準的な実装と以下の部分が異なることが分かりました。

  • 標準的な実装ではdeepsleepに対してRESETをかけて起床したことを、通常のPower On Resetなどとソフトウエアで識別するための定数が定義されている(識別できればdeepsleepからの復帰時に、なんらかの処理を再開するようなコードが書ける。)
  • ラズパイPicoのMicroPythonでは、RESET要因はPower OnかWatch Dogのどちらかしか識別できないようである。

とりあえず、そのくらいいんじゃね、ということで末尾に掲げた測定用のMicroPythonコードを作成いたしました。

測定のためのハードウエア構成

関数の主たる期待効果は消費電力の削減です。USBから電力を供給しているのでは電流を測りずらいので、以下の構成といたしました。

  • USBは接続しない
  • VBUS端子に外部から5Vを供給する。電源線にはDMM(mA測定レンジ)を入れて置き、電流を読み取れるようにしておく。

5Vの電源端子にはVBUSとVSYSの2本がありますが、VBUSはUSBのV+そのものです。VBUSからダイオード(逆流防止用?)を介してVSYSとなり、オンボードのレギュレータで3v3が作られます。単に外部電源ということであればVSYS利用する方が良いかね。

測定のためのソフトウエア構成

反応が遅いDMMでの測定なので、以下のようにしました(ソース全文末尾にありますのでご参照ください。)

  1. 10秒間特定の測定条件を継続する(変化点で電流値がバタバタ動きますが、ちょっと待てば落ち着くのでそこで電流を読み取ります。)
  2. 各測定条件の切り替えを示すためにLEDで知らせる
  3. LEDの消費電流を明確にするため、LED点灯(10s)で知らせた後、LEDを消灯(10s)する
  4. 赤色LEDのみ点灯は「待ち」で頻繁に使ってきた time.sleep()
  5. 青色LEDのみ点灯はmachine.lightsleep()
  6. 赤色+青色両点灯はmachine.deepsleep()
  7. 1コアのみの時と、2コアの時と両方測定する
  8. 1コア目がLEDの制御とsleepを行う
  9. 2コア目を動作させるときは、「無駄な計算」を継続実行させる
測定結果

結果は、ちょっと意外。

sleep Current Data2コア目を動作させるかどうかで、約3.4mAの差(オレンジ)がつきます。これは2コア目で無駄な計算をつづけるのにそれだけの電流がかかると解釈していいでしょう。しかし、意外にも3種のsleep()関数がほぼ同じ電流値(黄色、縦方向)を示す、という結果を得ました。勿論、LEDの点灯消灯の差は明確です。

deepsleep()については周辺を止めてしまう、という効果があることは確認できました。実際、deepsleep()からの復帰後、RESETなどの初期化をしないとUARTは止まってしまってます。よってlightとdeepの間には機能の差があります。しかし、今回ケースでは周辺回路を明示的に動作させていないので、もともと周辺回路は電気を食っておらず、deepだろうとlightだろうと差はない、ということと理解できます。周辺回路をガンガン動かしていたら差が出そうではありますが、走っている最中にパッツんと一気に停止みたいなことをするとは思えない(普通は何か止めるための処理するよね)のでdeepを使う局面限られるでしょう。また、動作的にはtime.sleep()とlightsleep()にはもともと差が無いのかもしれません。

この数字見ちゃうとね、ご利益ないじゃん。そんなもんなのかね。

まあ、こういうことはMicroPythonのような処理系でなく、C/C++でもっとハードウエアに近いところで制御する方が良いのでしょう。再トライは大分先だな。

MicroPython的午睡(27) ラズパイPico、DHT11接続、ソフト現物合わせ へ戻る

MicroPython的午睡(29) ラズパイPico、PIOでパルス幅測定 へ進む

測定に使用したコード(Dualコア測定用)
import machine 
import time
import _thread

led0RED  = machine.Pin(14, machine.Pin.OUT)
led0BLUE  = machine.Pin(15, machine.Pin.OUT)
led0RED.low()
led0BLUE.low()
led0Stat = 0

sum = 0

def task1(n):
    global sum
    while True:
        if sum > n:
            sum = 0
        else:
            sum += 1

_thread.start_new_thread(task1, (0x7FFFFFFF,))

while True:
    led0RED.high()
    time.sleep(10)
    led0RED.low()
    time.sleep(10)
    led0BLUE.high()
    time.sleep(10)
    led0BLUE.low()
    machine.lightsleep(10000)
    led0RED.high()
    led0BLUE.high()
    time.sleep(10)
    led0RED.low()
    led0BLUE.low()
    machine.deepsleep(10000)