LowPower: Multiple Wakeup Source¶
1 功能概述¶
本例程演示多种唤醒源、多种低功耗模式的切换,包括以下功能:
DeepSleep 状态下,通过 UART Rx 引脚唤醒芯片,并完成 UART 数据接收(对应 SoC GPIO 唤醒)
DeepSleep 状态下,通过 Zephyr Kernel Timer 定时唤醒(对应 SoC LP Timer 唤醒)
Standby Mode 1 状态下,通过按键唤醒芯片(对应 SoC GPIO 唤醒)
2 环境准备¶
硬件设备与线材:
PAN1080 EVB 核心板与底板各一块
JLink 仿真器(用于烧录例程程序)
USB 转 UART 模块(用于查看 QDEC 唤醒例程的串口 Log)
USB-TypeC 线一条(用于底板供电和查看串口打印 Log)
杜邦线数根(用于连接各个硬件设备)
硬件接线:
将 EVB 核心板插到 EVB 底板上
使用 USB-TypeC 线,将 PC USB 插口与 EVB 底板 USB->UART 插口相连
使用杜邦线或跳线帽将 EVB 底板上的 TX0 引脚与核心板上的 P00 引脚相连, EVB 底板上的 RX0 引脚与核心板上的 P01 引脚相连,用于 UART0 输出 Log
使用杜邦线将 USB 转 UART 模块的 RX 引脚与核心板上的 P06 引脚相连,USB 转 UART 模块的 TX 引脚与核心板上的 P07 引脚相连,用于 UART1 接收 PC 发送的数据
使用杜邦线将 JLink 仿真器的:
SWD_CLK 引脚与 EVB 核心板的 P46 引脚相连
SWD_DAT 引脚与 EVB 核心板的 P47 引脚相连
SWD_GND 引脚与 EVB 核心板的 GND 引脚相连
PC 软件:
串口终端工具(SecureCRT),波特率 921600(用于接收 SoC UART0 发送的 Log)
串口调试助手(UartAssist),波特率 9600(用于发送串口数据至 SoC UART1)
3 编译和烧录¶
例程位置:zephyr\samples_panchip\low_power\multiple_wakeup_source
使用 ZAL 工具可以对其进行编译、烧录、打开 VS Code 调试等操作。关于 ZAL 工具的详细介绍请参考:Zephyr APP Launcher 工具介绍。
4 例程演示说明¶
使用 ZAL 工具将编译后的例程烧录至芯片,并确认两个 PC 串口工具配置正确:
由 Log 可知程序初始化后进入了 DeepSleep 模式,等待唤醒
以 9600 的波特率向 SoC UART1 Rx 引脚发送十六进制数据
00 11 22 33 44
:由 Log 可知芯片成功被 UART1 Rx 引脚接收到的数据唤醒,并成功收到预期数据:
第一个数据 0x00 是唤醒前导码,在 9600 的波特率下,实际上是一个持续时间为 937us (1/9600*9s) 的低电平
在接收第二个数据前,程序将 P06/P07 引脚的 PINMUX 从 GPIO 重新切回 UART1 功能,用于接收后面的数据(即 0x11 0x22 0x33 0x44)
等待 30 秒,可观察到芯片再次被唤醒并立刻进入 Standby Mode 1:
UART1 Rx (P07) GPIO wakeup triggered.. wakeup_sem is taken. standby_mode_enter_work is canceled. uart actual_read_len: 4, rx_index: 0 0x11 0x22 0x33 0x44 Try to take wakeup_sem.. Soc has stayed in deepsleep mode more than 30s, now try to enter standby mode 1..
按下 EVB 底板上的 WKUP 按键,可以看到芯片复位,并提示复位原因为 Standby Mode 1 GPIO Wakeup:
Try to load HW calibration data.. DONE. - Chip Type : 0x80 - Chip CP Version : None - Chip FT Version : 7 - Chip MAC Address : D0000C06FB74 - Chip Flash UID : 31373237304A6D074330FFFFFFFFFFFF - Chip Flash Size : 1024 KB *** Booting Zephyr OS build zephyr-v2.7.0-1653 *** Reset Reason: Standby Mode 1 GPIO Wakeup. Try to take wakeup_sem..
5 开发者说明¶
5.1 App Config 配置¶
本例程的 App Config(对应 prj.conf 文件)配置如下:
# Low Power
CONFIG_PM=y
CONFIG_BT_CTLR_SLEEP_CLOCK_SOURCE=1
# Enable GPIO Input Sentinel
CONFIG_PM_GPIO_INPUT_SENTINEL=y
# Enable DC/DC
CONFIG_SOC_DCDC_PAN1080=y
其中:
CONFIG_PM=y
:使能低功耗流程CONFIG_BT_CTLR_SLEEP_CLOCK_SOURCE=1
:低功耗时钟相关配置(目前必须固定配置为1)CONFIG_PM_GPIO_INPUT_SENTINEL=y
:开启 GPIO 输入电平检测功能,开启后当系统试图进入 DeepSleep 模式时,会先检查当前是否有使能中断的 GPIO,若有,则接着检查其输入电平是否与中断配置冲突,若冲突则阻止系统进入 DeepSleep 模式此处 “输入电平与中断配置冲突” 的含义是:中断配置为下降沿触发而此时输入电平已经为低电平,或者中断配置为上升沿触发而此时输入电平已经为高电平; PAN1080 SoC 的低功耗模式不支持这种冲突情况,因此在配置 GPIO 唤醒的时候为安全起见可将此开关打开。
CONFIG_SOC_DCDC_PAN1080=y
:使能芯片的 DCDC 供电模式,以降低芯片动态功耗
5.2 App DeviceTree 配置¶
本例程的 App DeviceTree(对应 app.overlay 文件)配置如下:
&uart1 {
current-speed = <9600>;
pinctrl-0 = <&p0_6_uart1_tx &p0_7_uart1_rx>;
status = "okay";
};
&clk_xtl {
/* Frequency of XTL clock, DO NOT CHANGE THIS ITEM */
clock-frequency = <32768>;
/* Enable/Disable the external 32768 Hz low speed crystal oscillator */
status = "disabled";
};
&clk_rcl {
/* Frequency of RCL clock, DO NOT CHANGE THIS ITEM */
clock-frequency = <32000>;
/* Enable/Disable the internal 32 KHz Low Speed RC */
status = "okay";
};
&rcc {
clocks = <&dpll &clk_rcl>;
clock-frequency-slow = <32000>;
};
其中:
将 uart1 的:
current-speed
属性配置为9600
以将其初始波特率配置为 9600pinctrl-0
属性配置为<&p0_6_uart1_tx &p0_7_uart1_rx>
以将其 tx 重定向到 P06 引脚,rx 重定向到 P07 引脚status
属性配置为okay
以在系统初始化阶段使能 UART1 模块
将 clk_xtl 的
status
属性配置为disabled
以禁用 32768Hz XTL 低速晶振(此为 SDK 默认配置)将 clk_rcl 的
status
属性配置为okay
以打开 32kHz RCL 内部低速时钟(此为 SDK 默认配置)将 rcc 的:
clocks
属性的第 1 项(clk_system)配置为dpll
以将系统高速时钟(AHB 总线时钟)源选择为 DPLL 时钟(此为 SDK 默认配置)clocks
属性的第 2 项(clk_slow)配置为clk_rcl
以使系统使用 32kHz RCL 内部低速时钟(此为 SDK 默认配置)clock-frequency-slow
属性配置为32000
以告诉系统当前使用的低速时钟频率为 32000 Hz(此为 SDK 默认配置)
5.3 程序代码¶
5.3.1 主程序¶
主程序 main() 函数内容如下:
void main(void)
{
uint8_t rst_reason;
/* Get the last reset reason */
printk("\nReset Reason: ");
rst_reason = soc_reset_reason_get();
switch (rst_reason) {
case SOC_RST_REASON_PIN_RESET:
printk("nRESET Pin Reset.\n");
break;
case SOC_RST_REASON_SYS_RESET:
printk("NVIC System Reset.\n");
break;
case SOC_RST_REASON_STBM1_GPIO_WAKEUP:
printk("Standby Mode 1 GPIO Wakeup.\n");
break;
default:
printk("Unhandled Reset Reason, refer to more reason define in soc.h!\n");
}
/* Initialize wakeup semaphore */
k_sem_init(&wakeup_sem, 0, 1);
/* Init specific GPIOs to input mode for wake up use */
wakeup_gpio_init();
/* Init uart1 device */
if (!zephyr_uart1_device_init()) {
printk("Error: Failed to init uart1 device.\n");
return;
}
/* Init a delayable work for switching from deepsleep mode to standby mode */
k_work_init_delayable(&standby_mode_enter_work, standby_mode_enter_work_handler);
while (1) {
/* Busy wait a while to simulate cpu busy status */
k_busy_wait(1000000);
/* Try to take wakeup_sem */
printk("Try to take wakeup_sem..\n");
k_sem_take(&wakeup_sem, K_FOREVER);
printk("wakeup_sem is taken.\n");
/* Cancel the previous scheduled delayable standby-mode-enter work */
k_work_cancel_delayable(&standby_mode_enter_work);
printk("standby_mode_enter_work is canceled.\n");
}
}
获取本次系统复位的原因,本例程中仅检测 3 种情况(更多复位情况请参考 soc.h 文件中的相关定义):
芯片 nRESET 引脚复位(nRESET 按键按下)
NVIC System Reset 软件复位(JLink 烧录后自动触发,或者软件调用
sys_reboot(0)
触发)Standby Mode 1 GPIO Wakeup 唤醒复位
初始化一个名为 wakeup_sem 的信号量
在系统从 DeepSleep 状态下唤醒时通过 give 此信号量可以激活 main 线程的调度
在 wakeup_gpio_init() 函数中初始化 GPIO 配置
在 zephyr_uart1_device_init() 函数中初始化 UART1 配置
使用 k_work_init_delayable() 接口初始化一个 Delayable Work,用于定时触发二级休眠特性(从 DeepSleep 状态下唤醒后立刻进入 Standby 状态)
在
while (1)
循环中等待wakeup_sem
信号量,然后立刻取消正在排队的 Delayable Work
5.3.2 GPIO 初始化程序¶
GPIO 初始化程序 wakeup_gpio_init() 函数内容如下:
static void wakeup_gpio_key_init(void)
{
/* Set pinmux func as GPIO */
SYS_SET_MFP(P5, 6, GPIO);
/* Set GPIOs to input mode */
GPIO_SetMode(P5, BIT6, GPIO_MODE_INPUT);
CLK_Wait3vSyncReady(); /* Necessary for P56 to do manual 3v sync */
/* Enable internal pull-up resistor path */
GPIO_EnablePullupPath(P5, BIT6);
CLK_Wait3vSyncReady(); /* Necessary for P56 to do manual 3v sync */
/* Wait for a while to ensure the internal pullup is stable before entering low power mode */
SYS_delay_10nop(0x10000);
/* Disable P56 interrupt at this time */
GPIO_DisableInt(P5, 6);
}
static void wakeup_gpio_uart_rx_pin_init(void)
{
/* Set GPIO to input mode */
GPIO_SetMode(P0, BIT7, GPIO_MODE_INPUT);
/* Enable internal pull-up resistor path */
GPIO_EnablePullupPath(P0, BIT7);
/* Disable P07 interrupt before entering deepsleep */
GPIO_DisableInt(P0, 7);
}
static void wakeup_gpio_init(void)
{
/* Enable customized GPIO ISR to override the default one written in zephyr gpio driver */
IRQ_DIRECT_CONNECT(GPIO_IRQn, GPIO_IRQ_PRIO, gpio_irq_handler, 0);
irq_enable(GPIO_IRQn);
/* Configure gpio wakeup key (P56) */
wakeup_gpio_key_init();
/* Configure gpio wakeup pin for uart rx */
wakeup_gpio_uart_rx_pin_init();
}
此函数使用 zephyr 的 GPIO Driver 对 GPIO 进行配置
此部分包括两部分:GPIO 唤醒按键(P56)初始化,以及 GPIO UART Rx 引脚(P07)初始化
5.3.3 GPIO 中断服务程序¶
GPIO 中断服务程序如下:
static void gpio_irq_handler(void)
{
/* Check if WKUP (P56) button pressed */
if (GPIO_GetIntFlag(P5, BIT6)) {
GPIO_ClrIntFlag(P5, BIT6);
printk("\nWKUP key (P56) pressed..\n");
}
/* Check if UART1 Rx (P07) GPIO wakeup triggered */
if (GPIO_GetIntFlag(P0, BIT7)) {
/* Clear gpio pin irq flag */
GPIO_ClrIntFlag(P0, BIT7);
printk("\nUART1 Rx (P07) GPIO wakeup triggered..\n");
while (P07 == 0) {
/* Busy wait until P07 pulled high, which means the 1st wakup
* trigger character (0x00) send done.
*/
}
/* Resume UART1 PIN configs after P07 pulled high */
SYS_SET_MFP(P0, 6, UART1_TX);
SYS_SET_MFP(P0, 7, UART1_RX);
GPIO_DisableInt(P0, 7);
}
/* To indicate zephyr to schedule new ready thread if there is */
z_arm_int_exit();
}
判断 WKUP(P56)按键是否按下,若是则:
清除此引脚的中断 Flag
打印按键按下的 Log
判断是否为 P07(UART1 Rx)引脚产生的中断,若是则:
清除此引脚的中断 Flag
打印 UART1 Rx Trigger 的log
等待 P07 引脚拉低(即 P07 接收的唤醒前导数据 0x00 接收完成)
恢复 P06/P07 引脚为 UART1 功能
执行 z_arm_int_exit() 以通知 OS 当前中断执行完毕,允许 OS 在中断退出后立刻启动线程调度
5.3.4 UART1 中断响应回调函数¶
UART1 中断响应回调函数如下:
static void uart_rx_isr(const struct device *dev)
{
static uint8_t rx_index;
int actual_read_len;
/* Handle received data */
actual_read_len = uart_fifo_read(dev, uart_data + rx_index, UART_DATA_BUFF_SIZE - rx_index);
printk("uart actual_read_len: %d, rx_index: %d\n", actual_read_len, rx_index);
for (size_t i = 0; i < actual_read_len; i++) {
printk("0x%x ", uart_data[rx_index + i]);
}
printk("\n");
rx_index += actual_read_len;
if (rx_index >= UART_DATA_BUFF_SIZE) {
rx_index = 0;
}
}
static void app_uart_callback(const struct device *dev, void *user_data)
{
ARG_UNUSED(user_data);
/* Update uart event status */
if (!uart_irq_update(dev)) {
printk("%s(): Error occurs\n", __func__);
return;
}
/* Check if uart rx ready */
if (uart_irq_rx_ready(dev)) {
uart_rx_isr(dev);
}
}
此函数用于判断 UART1 Rx 数据是否 Ready,若是则将 Rx FIFO 数据读空,并将接收到的数据打印出来
5.3.5 Kernel Delayable Work 回调函数¶
当 Delayable Work 超时时间到来,会触发对应的回调函数:
static void standby_mode_enter_work_handler(struct k_work *work)
{
printk("\nSoc has stayed in deepsleep mode more than %ds, now try to enter standby mode 1..\n\n",
INTERVAL_FOR_SWITCH_LOWPOWER_MODE);
/* Wait until all UART0 data sending done before entering standby mode */
while (!(UART_GetLineStatus(UART0) & UART_LINE_TXSR_EMPTY)) {
/* Busy wait */
}
/* Enable P56 interrupt before entering standby mode for wakeup */
GPIO_EnableInt(P5, 6, GPIO_INT_FALLING);
/* Try to enter standby mode 1 */
soc_enter_standby_mode_1(STBM1_WAKEUP_SRC_GPIO, STBM1_RETENTION_SRAM_NONE, 0);
printk("WARNING: Failed to enter SoC standby mode 1 due to unexpected GPIO IO level detected.\n");
printk(" The wakeup level of GPIO is configured as low-level-wakeup, however the\n");
printk(" actual GPIO level right before entering standby mode is already low. The\n");
printk(" SoC does not support this case, and the API soc_enter_standby_mode_1()\n");
printk(" just returns when this case is detected.\n");
while (1) {
/* Busy wait */
}
}
等待 UART0 Tx FIFO 空,防止进入 Standby Mode 之前 UART Tx FIFO 还有数据
使能 P56 中断,确保进入 Standby Mode 1 后可以通过 P56 按键唤醒系统
调用
soc_enter_standby_mode_1()
接口,进入 Standby Mode 1 低功耗模式
5.3.6 与低功耗相关的 Hook 函数¶
目前有 3 个与低功耗密切相关的 Hook 函数:
/*
* Idle thread hook function for doing something before pm flow.
* return true to avoid entering the following pm (low power) flow
* return false otherwise.
*/
bool k_idle_thread_hook(void)
{
/* Schedule the delayable standby-mode-enter work */
k_work_schedule(&standby_mode_enter_work, K_SECONDS(INTERVAL_FOR_SWITCH_LOWPOWER_MODE));
/* Allow entering pm flow */
return false;
}
__ramfunc void z_power_hw_deep_sleep_enter_hook(void)
{
/* Handle logging uart (UART0) */
/* Wait until all UART0 data sending done before entering deepsleep mode */
while (!(UART_GetLineStatus(UART0) & UART_LINE_TXSR_EMPTY)) {
/* Busy wait */
}
/*
* Reset UART0 PINs to GPIO function and disable digital input path of UART Rx PIN
* to avoid possible current leakage.
*/
SYS_SET_MFP(P0, 0, GPIO);
SYS_SET_MFP(P0, 1, GPIO);
GPIO_DisableDigitalPath(P0, BIT1);
/* Handle communication uart (UART1) */
/* Wait until all UART1 data sending done before entering deepsleep mode */
while (!(UART_GetLineStatus(UART1) & UART_LINE_TXSR_EMPTY)) {
/* Busy wait */
}
/* Reset UART1 PINs to GPIO function and enable gpio interrupt for uart rx pin. */
SYS_SET_MFP(P0, 6, GPIO);
SYS_SET_MFP(P0, 7, GPIO);
GPIO_EnableInt(P0, 7, GPIO_INT_FALLING);
/* Enable P56 interrupt before entering deepsleep mode for wakeup */
GPIO_EnableInt(P5, 6, GPIO_INT_FALLING);
}
__ramfunc void z_power_hw_deep_sleep_exit_hook(void)
{
/* Resume UART0 PIN Configurations to reenable UART function */
SYS_SET_MFP(P0, 0, UART0_TX);
SYS_SET_MFP(P0, 1, UART0_RX);
GPIO_EnableDigitalPath(P0, BIT1);
/* Resume UART1 PIN Configurations here to reenable UART function if is not waked up by P07 */
if (!GPIO_GetIntFlag(P0, BIT7)) {
SYS_SET_MFP(P0, 6, UART1_TX);
SYS_SET_MFP(P0, 7, UART1_RX);
GPIO_DisableInt(P0, 7);
}
/* Re-disable P56 interrupt after deepsleep wakeup */
GPIO_DisableInt(P5, 6);
/* Trigger main thread reschedule after wakeup from deepsleep mode */
k_sem_give(&wakeup_sem);
}
Zephyr 有一个优先级最低的 idle 线程,当系统调度到此线程后会立刻触发
k_idle_thread_hook()
钩子函数。系统默认实现了一个 weak 属性的k_idle_thread_hook()
函数,而在 App 中可以重新实现此函数。若令此函数返回 false,则表示允许程序 run 进 idle 线程后续的低功耗流程中; 而若令此函数返回 true,则表示阻止系统执行 idle 线程的低功耗流程(此情况下程序在 idle 线程中空转)。对于本例程来说,在 main 线程中 take 信号量之后,由于系统没有其他的 ready 线程等待调度,因此会直接调度至 idle 线程中。在 Idle Hook 函数中, 我们直接 schedule 了一个 delayable work,当设定时间到来后,会触发对应的 handler,使系统进入 standby mode 1 状态
当程序执行到 idle 线程中低功耗流程的 DeepSleep 子流程中后,会在 SoC 进入 DeepSleep 模式之前执行
z_power_hw_deep_sleep_enter_hook()
函数,在 SoC 从 DeepSleep 模式下唤醒后执行z_power_hw_deep_sleep_exit_hook()
函数。本例程在
z_power_hw_deep_sleep_enter_hook()
函数中:为防止 UART0 IO 漏电,在进入 DeepSleep 状态前:
确保串口 Log 数据都打印完毕(即 UART0/UART1 Tx FIFO 应为空)
将 P00 引脚 Pinmux 功能由 UART0 Tx 切换回 GPIO
将 P01 引脚 Pinmux 功能由 UART0 Rx 切换回 GPIO,并将其数字输入功能关闭
为使得 UART1 Rx 收到数据后能唤醒,在进入 DeepSleep 状态前:
将 P06 引脚 Pinmux 功能由 UART0 Tx 切换回 GPIO
将 P07 引脚 Pinmux 功能由 UART0 Rx 切换回 GPIO,并使能此引脚的中断(下降沿触发)
为确保 P56 按键唤醒功能正常,使能 P56 引脚的中断(下降沿触发)
本例程在
z_power_hw_deep_sleep_exit_hook()
函数中:恢复串口 Log 打印功能:
P00 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Tx
P01 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Rx,并将其数字输入功能重新打开
当不是 P07 引脚 GPIO 唤醒(即 UART1 Rx)时,重新设置 P06/P07 引脚为 UART1 功能
关闭 P56 引脚的 GPIO 中断功能
调用 k_sem_give() 接口,释放
wakeup_sem
信号量,使程序从低功耗状态下唤醒后,能够调度到 main() 函数中
6 RAM/Flash资源使用情况¶
Memory region Used Size Region Size %age Used
FLASH: 22284 B 384 KB 5.67%
SRAM: 8792 B 64 KB 13.42%