3.5秒极速启动!树莓派 Zero 2 W 的极端启动优化技巧!

描述

不久前,我们构建了一个名为 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

3.USB-UART转换器

 

树莓派

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%的能耗,这仅仅是通过在更理想的工作点操作开关模式调节器实现的!这当然需要进一步测试稳定性/可靠性(因为这在技术上是不符合规格的),但这是一个非常令人印象深刻的结果。

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分