不久前,我們構建了一個名為 SolarCamPi 的項目,這是一個離網太陽能供電的WiFi攝像頭。
在這個項目中,我們使用了Raspberry Pi Zero 2 W,它在啟動時進入Linux系統,拍攝一張照片,建立WiFi連接,然后再次關閉(以節省電量)。這個過程每幾分鐘重復一次,以便持續向云服務發送最新圖像。

Raspberry Pi Zero 每開機一秒鐘都會消耗寶貴的電力,這對于太陽能供電的設備來說是一種稀缺資源(至少在西歐的冬天是這樣……)。用戶空間的應用程序(服務器連接、圖片上傳等)已經盡可能進行了優化。電子設備的設置也特意設計為在休眠時盡可能少地消耗電力。
有兩種方法可以進一步降低總能耗:
1.降低功耗/電流
2.減少運行時間
然而,在某些情況下,需要在兩者之間找到平衡。例如,僅僅為了節省一些電流而禁用CPU渦輪加速并不是一個好主意,因為由此產生的額外時間將消耗比快速完成任務并關閉更多的能量。我們想要的是電流與時間圖下的面積盡可能小。
硬件設置
在優化嵌入式啟動過程時,能夠在做出更改后迅速看到效果至關重要。在工作中頻繁更換SD卡、擺弄讀卡器和電源供應器既分散注意力又令人厭煩。
為了避免這種情況,存在一些有用的工具:
1.Nordic Power Profiler Kit II
2.USB-SD-Mux Fast
Power Profiler Kit
Power Profiler Kit II(現在稱為PPK)可以為被測設備(DUT)供電,并隨時間準確測量其功耗。您可以啟用/禁用DUT,查看任何時間點的功耗,以及查看8個數字輸入的狀態!我們將其中一個數字輸入連接到Raspberry Pi的GPIO引腳上。
這樣,“我們的應用程序”的第一個動作(即終點線)將是切換GPIO引腳。然后,我們只需測量從開機到GPIO切換之間的時間。
USB-SD-Mux
USB-SD-Mux是硬件黑客們非常有用的工具,它是microSD卡和帶有USB-C接口的DUT之間的轉換器。計算機可以從DUT“竊取”microSD卡,重寫其內容,然后將microSD卡插回DUT,而無需觸摸設備。
這大大簡化了測試更改的工作流程,避免了拔下卡片、將其插入microSD讀卡器、刷新、將卡片插回DUT等繁瑣步驟。它甚至可以使用板載GPIO來自動重置或供電DUT。
USB-UART轉換器
幾乎需要某種形式的UART接口。這些更改將在某個時刻破壞系統啟動、WiFi連接等,而如果沒有UART控制臺,我們將無法看到發生了什么。標準的CP2102、FTDI等轉換器都能很好地工作。
測量/測試設置
在干凈的Debian 12(bookworm)arm64 Lite映像上,修改了/boot/firmware/cmdline.txt 文件以包含init=/init.sh。這意味著內核將在用戶空間的第一件事就是執行/init.sh腳本(在運行systemd或任何其他內容之前)。
這樣的init.sh腳本可能如下所示:
#!/bin/bashgpioset 0 4=0sleep 1gpioset 0 4=1sleep 1gpioset 0 4=0exec /sbin/init
這將切換GPIO4,然后用/sbin/init(即systemd)替換自己以恢復正常啟動。
在Nordic的Power Profiler軟件中,您可以看到Raspberry Pi在啟動過程中的電流消耗(以5V計算)。大約12秒后,數字輸入0變為低電平,表明我們的init.sh已執行。
在此過程中,總共使用了1.90庫侖(庫侖和安培秒是等價的)的電量。計算1.9As * 5.0V得出此啟動過程的能耗為9.5Ws。
作為參考:一節AA堿性電池可以提供約13500Ws的能量。
降低電流
首先,我們來做簡單的事情,盡可能降低工作電流。
禁用HDMI
我們可以完全禁用HDMI編碼器。由于我們需要GPU來編碼攝像頭數據,因此無法禁用GPU。如果您的應用程序不需要攝像頭/GPU支持,請嘗試完全禁用GPU。
這可以將電流消耗從136.7mA降低到122.6mA(超過10%?。?。
相關的config.txt參數:
# disable HDMI (saves power)dtoverlay=vc4-kms-v3d,nohdmimax_framebuffers=1disable_fw_kms_setup=1disable_overscan=1# disable composite video outputenable_tvout=0
禁用活動LED
僅通過禁用活動LED,我們就可以節省2mA(從122.6mA降低到120.6mA)。
dtparam=act_led_trigger=nonedtparam=act_led_activelow=on
禁用攝像頭LED
對攝像頭LED重復相同的操作(如果存在)。這還將減少LED反射回圖像的機會。
disable_camera_led=1
渦輪調整
如前所述,在浪費時間的同時節省電流可能并不理想。
在當前的更改下,Pi可以在使用1.62As的情況下啟動。
force_turbo=0initial_turbo=10arm_boost=0
在沒有強制渦輪模式的情況下,使用了1.58As:
出于某種未知原因,禁用渦輪/增強模式也會反轉GPIO4的默認狀態(因此我在init.sh中切換了極性)。
減少時間
電流降低了約13%,這很有幫助,但仍有很長的路要走。
Pi在出現Linux控制臺上的第一行輸出之前需要8秒鐘(同時消耗約1As)。
幸運的是,有多種方法可以獲取有關這8秒鐘的更多信息。
調試啟動
在Raspberry Pi家族的啟動過程中,GPU首先初始化。
它與SD卡通信并查找bootcode.bin文件(Pi 4及更新版本使用EEPROM代替)。
我們可以修改此bootcode.bin以啟用詳細的UART日志記錄:
sed -i -e "s/BOOT_UART=0/BOOT_UART=1/" /boot/firmware/bootcode.bin
首先備份原始的bootcode.bin,因為此過程可能是破壞性的。
使用啟用的BOOT_UART重啟后,我們會獲得大量有用的信息:
Raspberry Pi BootcodeFound SD card, config.txt = 1, start.elf = 1, recovery.elf = 0, timeout = 0Read File: config.txt, 1322 (bytes)Raspberry Pi BootcodeRead File: config.txt, 1322Read File: start.elf, 2981376 (bytes)Read File: fixup.dat, 7303 (bytes)MESS000: brfs: File read: /mfs/sd/config.txtMESS000: brfs: File read: 1322 bytesMESS000: HDMI0:EDID error reading EDID block 0 attempt 0[..]MESS000: HDMI0:EDID error reading EDID block 0 attempt 9MESS000: HDMI0:EDID giving up on reading EDID block 0MESS000: brfs: File read: /mfs/sd/config.txtMESS000: gpioman: gpioman_get_pin_num: pin LEDS_PWR_OK not definedMESS000: gpioman: gpioman_get_pin_num: pin LEDS_PWR_OK not definedMESS000: *** Restart loggingMESS000: brfs: File read: 1322 bytesMESS000: hdmi: HDMI0:EDID error reading EDID block 0 attempt 0[..]MESS000: hdmi: HDMI0:EDID error reading EDID block 0 attempt 9MESS000: hdmi: HDMI0:EDID giving up on reading EDID block 0MESS000: hdmi: HDMI0:EDID error reading EDID block 0 attempt 0[..]MESS000: hdmi: HDMI0:EDID error reading EDID block 0 attempt 9MESS000: hdmi: HDMI0:EDID giving up on reading EDID block 0MESS000: hdmi: HDMI:hdmi_get_state is deprecated, use hdmi_get_display_state insteadMESS000: HDMI0: hdmi_pixel_encoding: 162000000MESS000: brfs: File read: /mfs/sd/initramfs8MESS000: Loaded 'initramfs8' to 0x0 size 0xb0898eMESS000: initramfs loaded to 0x1b4e7000 (size 0xb0898e)MESS000: dtb_file 'bcm2710-rpi-zero-2-w.dtb'MESS000: brfs: File read: 11569550 bytesMESS000: brfs: File read: /mfs/sd/bcm2710-rpi-zero-2-w.dtbMESS000: Loaded 'bcm2710-rpi-zero-2-w.dtb' to 0x100 size 0x8258MESS000: brfs: File read: 33368 bytesMESS000: brfs: File read: /mfs/sd/overlays/overlay_map.dtbMESS000: brfs: File read: 5255 bytesMESS000: brfs: File read: /mfs/sd/config.txtMESS000: dtparam: audio=onMESS000: brfs: File read: 1322 bytesMESS000: brfs: File read: /mfs/sd/overlays/vc4-kms-v3d.dtboMESS000: Loaded overlay 'vc4-kms-v3d'MESS000: dtparam: nohdmi=trueMESS000: dtparam: act_led_trigger=noneMESS000: dtparam: act_led_activelow=onMESS000: brfs: File read: 2760 bytesMESS000: brfs: File read: /mfs/sd/cmdline.txtMESS000: Read command line from file 'cmdline.txt':MESS000: 'console=serial0,115200 console=tty1 root=PARTUUID=26bbce6b-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=DE init=/init.sh'MESS000: gpioman: gpioman_get_pin_num: pin EMMC_ENABLE not definedMESS000: brfs: File read: 146 bytesMESS000: brfs: File read: /mfs/sd/kernel8.imgMESS000: Loaded 'kernel8.img' to 0x200000 size 0x8d8bd7MESS000: Device tree loaded to 0x1b4de900 (size 0x8605)MESS000: uart: Set PL011 baud rate to 103448.300000 HzMESS000: uart: Baud rate change done...MESS000: uart: Baud rate[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
禁用HDMI探測
在啟動過程中,引導加載程序會花費大量時間嘗試自動檢測可能連接的HDMI顯示器的視頻參數。然而,由于我們沒有HDMI(而且它已經被禁用了),因此等待I2C響應以獲取EDID(包括分辨率、幀率等信息)信息并不明智。
通過簡單地硬編碼一個EDID字符串,我們可以禁用任何探測:
# don't try to read HDMI eepromhdmi_blanking=2hdmi_ignore_edid=0xa5000080hdmi_ignore_cec_init=1hdmi_ignore_cec=1
禁用HAT、PoE和LCD探測
啟動過程還會嘗試檢測HAT上的I2C EEPROM,嘗試檢測需要風扇的PoE HAT以及其他一些內容。我們可以安全地禁用這些探測:
# all these options cause a wait for an I2C bus response, we don't need any of them, so let's disable them.force_eeprom_read=0disable_poe_fan=1ignore_lcd=1disable_touchscreen=1disable_fw_kms_setup=1
禁用攝像頭和顯示器探測
探測連接的MIPI攝像頭或顯示器也會花費一些時間。我們知道連接了哪個攝像頭(在這個案例中是HQ Camera,IMX477),因此我們可以硬編碼這個信息:
# no autodetection for anything (will wait for I2C answers)camera_auto_detect=0display_auto_detect=0# load HQ camera IMX477 sensor manuallydtoverlay=imx477
禁用initramfs
上述更改將(自報告的)啟動時間從5.38秒縮短到4.75秒。我們可以通過移除auto_initramfs=1來完全禁用initramfs,這取決于initramfs的大小,但可以將啟動時間縮短到4.47秒。
經過測試,沒有顯著差異
盡管網上經常推薦將SD外設超頻到100 MHz,但這在啟動性能上并沒有產生可測量的差異
# not recommended! data corruption risk!dtoverlay=sdtweak,overclock_50=100
而且,在高速下操作SD外設還存在數據損壞的風險(在寫入訪問時),這在遠程物聯網設備中是非常不希望的。
內核加載
此時,加載內核是最慢的操作之一。
MESS000: Loaded 'kernel8.img' to 0x200000 size 0x8d8bd7MESS000: Device tree loaded to 0x1b4de900 (size 0x8605)
加載9276375字節大約需要1.54秒,即大約6 MiB/s的傳輸速度
內核加載由GPU(使用其內部的VideoCoreIV處理器)完成,這可能是加載代碼效率低下或使用了非常保守的設置。由于這是一個黑盒,我們無法直接操作寄存器或修改參數。
理論上,GPU處理器內核超頻是可行的
# Overclock GPU VideoCore IV processor (not recommended!)core_freq_min=500core_freq=550
這確實減少了20%的內核加載時間。但是帶來了未知的副作用(可靠性等。)
Buildroot/自定義內核
是時候將系統從Raspbian/Debian遷移到自定義構建的Buildroot發行版了(特別是為了獲取自定義內核)。使用 buildroot 2024.02.1,我們配置了一個非常精簡的系統。原生的 aarch64 工具鏈,仍然使用完整的 glibc 和 Raspberry Pi 用戶區工具(如相機實用程序)。
內核已配置:
- 無聲音支持
- 無大多數塊設備和文件系統驅動(除了SD/MMC和ext4)
- 無RAID支持
- 無USB支持
- 無HID支持
- 無DVB支持
- 無視頻和幀緩沖支持(HDMI已被禁用)
- 無高級網絡功能(隧道、橋接、防火墻等)
- 未壓縮(不使用Gzip)
- 模塊未壓縮(不使用Gzip)
測試表明,內核和模塊均未壓縮可以帶來正的能量結果(即使GPU加載內核時花費了更多時間)。Gzip解壓縮需要消耗大量能量(并且實際上涉及另一個重定位步驟)。
一個名為KASLR的安全功能也被禁用。
KASLR將內核在內存中的加載地址隨機化,使得編寫漏洞利用代碼更加困難(因為內核的內存位置是未知的)。這要求內核在被GPU加載后重新定位。在我們的用例中,網絡攻擊面非常有限,所以可以禁用KASLR(反正所有應用軟件都以root身份運行)。投機性執行漏洞(如Spectre)的緩解也被禁用。

最終的內核大小為8.5兆字節(未壓縮),4.1兆字節壓縮為Gzip(這里沒有使用,只是為了比較)。最初的Raspbian內核是25 MiB(未壓縮),8.9 MiB壓縮為Gzip
最終結果
現在,我們可以在不到3.5秒的時間內啟動到Linux用戶空間程序!
Linux內核占用時間約為400毫秒(從引腳0到引腳1的差值)。總能耗為0.364 As * 5.0 V = 1.82 Ws,與原始Debian相比,能耗降低了5倍(原始Debian直到用戶空間需要9.5 Ws)。
降低輸入電壓
在發表這篇博文后,Graham Sutherland / Polynomial 指出,Pi Zero 中的調節器在5.0V輸入下效率不是很高。
這可能不適用于所有情況,但在我們的測試場景和成品中,我們可以將輸入電壓降至4.0V。
在5.0V下運行:
好好注意這里正在進行的單元。通過切換到4.0V(因為電流更高),mC(毫庫侖/毫安培秒)增加,但是總能量顯著降低!
350.94mAs * 5.0V = 1.754 Ws
在4.0V下運行:
390.77mAs * 4.0V = 1.563 Ws
我們可以更進一步:
在3.6V運行:
399.60mAs * 3.6V = 1.438 Ws
我們剛剛又降低了20%的能耗,這僅僅是通過在更理想的工作點操作開關模式調節器實現的!這當然需要進一步測試穩定性/可靠性(因為這在技術上是不符合規格的),但這是一個非常令人印象深刻的結果。
-
攝像頭
+關注
關注
60文章
4897瀏覽量
97005 -
樹莓派
+關注
關注
118文章
1882瀏覽量
106257
發布評論請先 登錄
相關推薦
評論