NCS 低功耗日志打印
本文第一节直接给出配置,第二节介绍原理,第三节介绍踩的一个坑
低功耗日志打印配置
【注意】本文介绍的方法,仅支持NCS
v2.8.0
之后的版本。
软件配置
以下代码与config直接在zephyr/samples/hello_world
中进行配置即可。
配置文件:prj.conf
# 开启LOG功能,并选择后端为串口 |
设备树overlay文件(没有可自己新建app.overlay
):
&uart0 { |
代码main.c
:
|
硬件配置
以nRF52840DK为例:
首先切开焊盘SB40,这个是MCU供电流过的地方。
- 如果用电流表测试,则接电流流出和电流流入
- 如果用电源测试,则接电流流入和GND
- 如果使用PPK II,支持以上两种模式,可以3个引脚都接
- 如果使用示波器,可以在焊盘R90上焊接一个10欧姆高精度电阻,然后用示波器双通道2个探头分别接入电流流入和电流流出,再接地。示波器双通道相减获得电流。
PPK II推荐使用电流表模式。如果要使用电源模式,一定要确保供电电压和板子本身供电电压相同。否则MCU引脚和板子上其他器件会有压差,导致漏电。
这里使用nRF52840DK,如果用电压源模式需要选择3V供电。
此外,即使你使用电源模式,USB也是必须要接的:
否则电源供电会通过一些开关芯片漏到板子上的其他器件上,造成功耗评估严重偏高。
运行结果
板子上JLINK (Interface MCU)自带串口,直接电脑打开JLINK上的串口即可:
日志:
*** Booting nRF Connect SDK v2.8.0-a2386bfc8401 *** |
printk
和printf
以及LOG
都连续输出2次。这说明这三种方式内部分别各自存在buffer,buffer满了才会输出。
底电流:
IDLE线程的底电流2.54uA,符合手册中System ON条件下,CPU不工作时的功耗:
相对的,System OFF的功耗非常低,但是只能用reset或GPIO唤醒,且唤醒后必定reset。
配置解析
异步串口
根据我之前的文章《Zephyr驱动与设备树实战——串口》,我们知道异步串口只要不是正在发送或接收,就是低功耗的。RX时的功耗比较高,因为需要一直等待。但好在日志是一个不需要RX的功能,因此我们关闭所有RX:
CONFIG_SHELL=n |
其他关于异步串口的配置,可以看那篇文章解释。
Zephyr Device Power Management
接下来是关于低功耗的:
CONFIG_PM_DEVICE_RUNTIME=y |
这是Zephyr Device Power Management Subsystem的功能。Zephyr中把电源管理分为System Power Management 和 Device Power Managment。System Power Management的强大功能我们已经见识过了:只要其他线程都在阻塞(不论是在sleep还是在等待信号量),进入IDLE线程后,系统会自动让CPU休眠。当有EVENT到来时,CPU自动唤醒,无需开发者操心CPU的休眠。
而Device Power Managment管理的是外设的功耗,包括片上外设和外挂的总线外设。如果你开发了一段时间Zephyr,会发现Zephyr的外设API是没有init和uninit的。因为外设的初始化是在main线程运行之前就已经被驱动程序做好了。
CONFIG_PM_DEVICE=y
就让你可以在应用层手动去开关这些外设,具体的API为:
|
驱动层已经实现了PM_DEVICE_ACTION_SUSPEND
和PM_DEVICE_ACTION_RESUME
对应的功能,开发者无需再关心其初始化的细节。
通过这种方式你可以手动地全局开关一些外设。
但是有时候手动去开关还是太麻烦,可能你会想这样做来实现低功耗:
... |
但是这么做不是线程安全的,如果这段代码在不同线程里运行会出问题(常见于SPI,I2C等总线外设)。另外可能这个外设被Zephyr系统用到了,但是却在那之前被你关掉了,那就会出问题(尤其常见于QSPI外挂Flash)。
CONFIG_PM_DEVICE_RUNTIME=y
就是Zephyr提供的另外一个库,它相当于对Device Management的API进行了一层封装:
当应用层调用外设的API时,驱动层先调用pm_device_runtime_get()
函数,使得usage
变量+1。当usage变量大于1时,PM Subsystem就会调用pm_device_action_run(dev, PM_DEVICE_ACTION_RESUME)
来让驱动层打开这个外设。
当这个外设不再使用时(发送完毕/接收完毕),驱动层则调用pm_device_runtime_put()
函数,使得usage
变量-1。当usage == 0
时,PM Subsystem就会调用pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND)
来让驱动层关闭这个外设。
这种实现是线程安全的,并且不需要应用层手动控制这个外设。此外,即使应用层重复地进行get
或者put
,也不会影响它实际的运行逻辑。
NCS v2.8.0之后,Nordic串口驱动才加入了Runtime的支持。
Runtime电源管理的功能,要看驱动程序是否支持,主要是看它有没有调用pm_device_runtime_put
和pm_device_runtime_get
。
另外,Runtime的功能一定要对每个外设分别使能。你可以在设备树里添加一个配置,让它自动使能:
&uart0 { |
此外,Nordic串口驱动做了更多事情。如果
CONFIG_PM_DEVICE
和CONFIG_PM_DEVICE_RUNTIME
都不打开,此串口驱动也实现了自己的私有runtime低功耗:
# 见/zephyr/drivers/serial/Kconfig.nrfx_uart_instance
config UART_$(nrfx_uart_num)_NRF_ASYNC_LOW_POWER
bool "Low power mode"
depends on HAS_HW_NRF_UARTE$(nrfx_uart_num)
depends on UART_ASYNC_API
depends on UART_NRFX_UARTE_LEGACY_SHIM
default y if !PM_DEVICE
help
When enabled, UARTE is enabled before each TX or RX usage and disabled
when not used. Disabling UARTE while in idle allows to achieve lowest
power consumption. It is only feasible if receiver is not always on.
This option is irrelevant when device power management (PM) is enabled
because then device state is controlled by the PM actions.因此,两个都不打开也是可以低功耗的,只要其他配置与本文保持一致即可(异步串口)。
但是,很多NCS例程是只开了
CONFIG_PM_DEVICE
,没有开CONFIG_PM_DEVICE_RUNTIME
,导致两种低功耗场景都沾不上边,这种情况下,底电流约为15uA。
踩坑记录
记录一个坑中坑的情况,当打开以下配置时:
CONFIG_LOG_PRINTK=y |
printk会从LOG的后端进行输出,也就是利用异步串口DMA进行输出,而不是串口阻塞API一个字节一个字节输出,这听起来很美好。
但是,当我们只用printk,且输出字符为刚好33字节时(含\n
):
|
功耗会来到惊人的367uA!!!
并且,多一个字节或者少一个字节,功耗都是正常的!
|
我猜想这可能是因为33字节会刚好触发了Power Management打开串口,但是却又差一个字节才能发出去,导致实际没发出去,要等下一次数据来才能发出去。但是发送完毕后,又马上打开了串口。
这个问题最坑的点在于…
printk("Hello World! %s\n", CONFIG_BOARD_TARGET); |
"Hello World! nrf52840dk/nrf52840\n"
刚好就是33字节…
并且,在printk后面加一个LOG_INF输出,也不会出现这个问题。
这个Bug的出现让我一度怀疑我的其他配置出现了问题,各种修改尝试、切换SDK、翻阅源码,都无法找出原因,直到有次随手修改了打印的内容…
总之分享出来,让其他人别踩这个坑吧。