3.29. 低功耗與睡眠模式

電池供電的相機與間歇運作的感測器,並不需要 CPU 一直以全速運行。machine 模組提供四種逐步加深的省電狀態 -- activeidle()lightsleep()deepsleep()。每加深一步,就會關閉更多晶片功能並節省更多電力,代價則是更長的喚醒時間。挑選正確的狀態,是在相機能節省多少電力、以及在事件發生時能多快做出反應之間取得平衡。

3.29.1. Active

預設狀態。CPU 正在執行 Python,每個周邊裝置都有時脈,電流消耗也最高 -- 相機邏輯電源軌上達數十毫安培,再加上任何外接配件透過它所拉取的電流。

3.29.2. idle()

machine.idle() 會閘控 CPU 時脈,直到任何中斷觸發(周邊裝置、計時器或接腳 IRQ)。RAM 維持運作、周邊裝置保持開啟、時脈持續運行 -- 只有 CPU 本身被暫停,並在有工作要做時於微秒內喚醒。

可在任何等待外部事件發生的緊密輪詢迴圈中使用它:

import machine

while not button_pressed():
    machine.idle()

CPU 不再為了 while 檢查本身而空耗週期,並在下一個事件到來時自然喚醒 -- 這是一筆小小的節省,但在執行數百萬次的迴圈中累積起來相當可觀。

3.29.3. lightsleep()

machine.lightsleep() 是更深一層的狀態。CPU 完全停止,晶片內部大多數時脈被關閉,但 RAM 與周邊裝置狀態仍被保留。當喚醒來源觸發時,指令碼會從它呼叫 lightsleep 的確切位置繼續執行 -- 變數、開啟的控制代碼與待處理資料全部完好無缺 -- 約在數毫秒之後。

import machine
from machine import Pin

wake_pin = Pin("P0", Pin.IN, Pin.PULL_UP)
wake_pin.irq(lambda _: None, trigger=Pin.IRQ_FALLING, wake=machine.SLEEP)

while True:
    do_work()
    machine.lightsleep()   # wakes on a falling edge on P0

喚醒來源 -- 此處為接腳 IRQ -- 必須在睡眠呼叫之前設定好。功耗相對於 active 模式大幅下降;確切數值取決於板子以及仍在設定中的周邊裝置。

3.29.4. deepsleep()

machine.deepsleep() 是最深的狀態。CPU 停止、周邊裝置斷電,RAM 內容可能會遺失。唯一仍在耗電的,只有喚醒電路與一小部分常開邏輯。

當喚醒來源觸發時,晶片會從主指令碼的開頭開機 -- deepsleep 不會回傳。指令碼可使用 machine.reset_cause() 來區分這是 deepsleep 喚醒,還是全新上電或硬重置:

import machine

if machine.reset_cause() == machine.DEEPSLEEP_RESET:
    # Woke from deepsleep -- restore state from non-volatile storage,
    # take a measurement, etc.
    pass
else:
    # Fresh boot
    pass

do_work()
machine.deepsleep(60_000)    # arm RTC wake for 60 s, sleep, then restart

傳給 deepsleep() 的毫秒參數會在內部設定晶片內建的 RTC 鬧鐘 -- RTC 正是在睡眠期間負責掌握喚醒計時的元件,因為大多數其他計時器都已斷電。不帶參數呼叫 deepsleep() 則會將喚醒交由你另外設定的來源負責(接腳 IRQ、外部設定的 RTC 鬧鐘)。

由於指令碼會重新啟動,下一次迭代所需的任何東西,都必須在 main.py 開頭重新建構,或持久化到快閃記憶體(或在具備該功能的零件上,存到 RTC 的備援暫存器)。Deepsleep 帶來最大的省電效果,卻也要求最多的程式重構 -- 應用程式必須表現為一連串以睡眠分隔的短促「測量爆發」,而非一個狀態保存在 RAM 中的長時間運行迴圈。

3.29.5. 選擇狀態

正確的狀態取決於相機在等待什麼:

  • 緊密輪詢迴圈,等待數毫秒。 使用 idle()。每個週期的節省雖小,但總和可觀,而且喚醒過程無感。

  • 事件之間閒置數秒或數分鐘。 使用 lightsleep()。狀態被保留、喚醒迅速,功耗只是 active 模式的一小部分。

  • 在短暫的工作爆發之間閒置數分鐘或更久。 使用 deepsleep()。事件之間晶片實際上等同關閉,指令碼結構則轉變為「喚醒、測量、睡眠」的迴圈。

無論處於哪種狀態,喚醒來源與狀態本身同等重要 -- 只靠計時器喚醒的 deepsleep 是一個工作週期化的測量迴圈;靠接腳 IRQ 喚醒的 lightsleep 則是一個事件驅動的感測器。machine 模組的睡眠函式、RTC 鬧鐘與 irq() 共同提供了這些構件。