LowPower: DeepSleep GPIO Key Wakeup¶
1 功能概述¶
本例程演示如何使 SoC 进入 DeepSleep 状态,然后通过 GPIO 按键将其唤醒。
2 环境准备¶
硬件设备与线材:
PAN1080 EVB 核心板与底板各一块
JLink 仿真器(用于烧录例程程序)
电流计(本文使用电流可视化测量设备 PPK2 [Nordic Power Profiler Kit II] 进行演示)
USB-TypeC 线一条(用于底板供电和查看串口打印 Log)
杜邦线数根(用于连接各个硬件设备)
硬件接线:
为确保能够准确地测量 SoC 本身的功耗,排除底板外围电路的影响,请勿将 EVB 核心板插到 EVB 底板上
使用 USB-TypeC 线,将 PC USB 插口与 EVB 底板 USB->UART 插口相连
使用杜邦线将(根据核心板芯片的型号不同,以下两种接法二选一):
EVB 底板上的 TX0 引脚与核心板上的 P00 引脚相连, EVB 底板上的 RX0 引脚与核心板上的 P01 引脚相连(若核心板芯片为 QFN32 或 LQFP64 封装)
EVB 底板上的 TX0 引脚与核心板上的 P30 引脚相连, EVB 底板上的 RX0 引脚与核心板上的 P31 引脚相连(若核心板芯片为 QFN48 封装)
使用杜邦线将 JLink 仿真器的:
SWD_CLK 引脚与 EVB 核心板的 P46 引脚相连
SWD_DAT 引脚与 EVB 核心板的 P47 引脚相连
SWD_GND 引脚与 EVB 核心板的 GND 引脚相连
使用杜邦线将 EVB 底板上的:
P04 引脚(对应按键 KEY1)与 EVB 核心板的 P04 引脚相连
P05 引脚(对应按键 KEY2)与 EVB 核心板的 P05 引脚相连
P56 引脚(对应按键 WKUP)与 EVB 核心板的 P56 引脚相连
将 PPK2 硬件的:
USB DATA/POWER 接口连接至 PC USB 接口
VOUT 引脚连接至 EVB 核心板的 VBAT 引脚
GND 引脚连接至 EVB 核心板的 GND 引脚
PC 软件:
串口调试助手(UartAssist)或终端工具(SecureCRT),波特率 921600(用于接收串口打印 Log)
nRF Connect Desktop(用于配合 PPK2 测量 SoC 电流)
3 编译和烧录¶
例程位置:zephyr\samples_panchip\low_power\deepsleep_gpio_key_wakeup
使用 ZAL 工具擦除 Flash 程序。关于 ZAL 工具的详细介绍请参考:Zephyr APP Launcher 工具介绍。
4 例程演示说明¶
PC 上打开 PPK2 Power Profiler 软件,供电电压选择 3300 mV,然后打开供电开关:
测试芯片中目前还没有程序,所以看到此时芯片耗电保持在 3mA 左右。
使用 ZAL 工具将编译后的例程烧录至芯片
烧录成功后,最好断开 JLink 与芯片的连接以防止芯片 P46 和 P47 两个引脚有漏电情况发生
从串口工具中看到如下的打印信息:
Try to load HW calibration data.. DONE. - Chip Type : 0x80 - Chip CP Version : None - Chip FT Version : 5 - Chip MAC Address : D0000C0293CA - Chip Flash UID : 31373237300A29494330FFFFFFFFFFFF - Chip Flash Size : 1024 KB *** Booting Zephyr OS build zephyr-v2.7.0-1346-g181eea9cf7ca *** Try to take wakeup_sem..
此时观察芯片电流波形,发现稳定在 6.3uA 左右(说明芯片成功进入了 DeepSleep 模式):
分别尝试按下 EVB 底板上的 3 个按键:KEY1、KEY2 和 WKUP,由串口打印信息可知 3 个按键事件均触发了芯片唤醒:
GPIO key pressed: P04. wakeup_sem is taken. Try to take wakeup_sem.. GPIO key pressed: P05. wakeup_sem is taken. Try to take wakeup_sem.. GPIO key pressed: P56. wakeup_sem is taken. Try to take wakeup_sem..
此时再观察芯片电流波形,可以看到芯片触发了 3 次唤醒,最后又进入 DeepSleep 状态等待下次按键唤醒:
由电流波形可知芯片每次唤醒后均重新进入了 DeepSleep 模式,此模式下芯片电流保持在 6.3 uA 左右。
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
# CONFIG_PM_GPIO_INPUT_SENTINEL_INVERT_CTRL=y
# Disable Serial Uart & Log.
# CONFIG_SERIAL=n
# CONFIG_UART_INTERRUPT_DRIVEN=n
# CONFIG_CONSOLE=n
# CONFIG_UART_CONSOLE=n
# CONFIG_PRINTK=n
# 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 {
status = "disabled";
};
&gpioctrl {
input-debounce-clksrc = "clock-ahb";
input-debounce-cycle = <8192>;
};
其中:
将 uart1 的
status
属性配置为disabled
以禁止系统上电后初始化 uart1 设备,确保不会因为 uart1 的引脚配置产生 IO 漏电将 gpioctrl 的
input-debounce-cycle
配置为 8192,即所有 GPIO 去抖时间为 8192 / 64M * 1M = 128 us
5.3 程序代码¶
5.3.1 主程序¶
主程序 main() 函数内容如下:
void main(void)
{
k_sem_init(&wakeup_sem, 0, 1);
pinmux = device_get_binding("PINMUX");
if (pinmux == NULL) {
printk("get device PINMUX error!\n");
return;
}
wakeup_gpio_keys_init();
while (1) {
printk("Try to take wakeup_sem..\n");
k_sem_take(&wakeup_sem, K_FOREVER);
printk("wakeup_sem is taken.\n");
}
}
初始化一个名为 wakeup_sem 的信号量
在 GPIO 唤醒中断触发时通过 give 此信号量可以激活 main 线程的调度
获取 PINMUX 的设备指针,用于后面修改 pinmux 引脚配置的时候使用
在 wakeup_gpio_keys_init() 函数中初始化 GPIO 配置
在 while (1) 主循环中尝试 take 信号量,并打印相关的状态信息
5.3.2 GPIO 初始化程序¶
GPIO 初始化程序 wakeup_gpio_keys_init() 函数内容如下:
void wakeup_gpio_keys_init(void)
{
/* Configure GPIO P04 (KEY1) and P05 (KEY2) as Falling Edge Interrupt/Wakeup */
gpio_port_key1_key2 = device_get_binding(KEY1_GPIO_PORT_LABEL);
gpio_pin_configure(gpio_port_key1_key2, KEY1_GPIO_PIN, GPIO_INPUT | GPIO_PULL_UP | GPIO_INT_DEBOUNCE);
gpio_pin_configure(gpio_port_key1_key2, KEY2_GPIO_PIN, GPIO_INPUT | GPIO_PULL_UP | GPIO_INT_DEBOUNCE);
gpio_init_callback(&gpio_cb_data_p0, gpio_irq_callback_p0, BIT(KEY1_GPIO_PIN) | BIT(KEY2_GPIO_PIN));
gpio_add_callback(gpio_port_key1_key2, &gpio_cb_data_p0);
gpio_pin_interrupt_configure(gpio_port_key1_key2, KEY1_GPIO_PIN, GPIO_INT_EDGE_FALLING);
gpio_pin_interrupt_configure(gpio_port_key1_key2, KEY2_GPIO_PIN, GPIO_INT_EDGE_FALLING);
/* Configure GPIO P56 (WKUP) as Falling Edge Interrupt/Wakeup */
gpio_port_wkup = device_get_binding(WKUP_GPIO_PORT_LABEL);
gpio_pin_configure(gpio_port_wkup, WKUP_GPIO_PIN, GPIO_INPUT | GPIO_PULL_UP | GPIO_INT_DEBOUNCE);
gpio_init_callback(&gpio_cb_data_p5, gpio_irq_callback_p5, BIT(WKUP_GPIO_PIN));
gpio_add_callback(gpio_port_wkup, &gpio_cb_data_p5);
gpio_pin_interrupt_configure(gpio_port_wkup, WKUP_GPIO_PIN, GPIO_INT_EDGE_FALLING);
}
此函数使用 zephyr 的 GPIO Driver 对 GPIO 进行配置
实际上也可直接使用 Panchip HAL GPIO Driver 进行配置,具体可参考Standby Mode 1 GPIO Key Wakeup例程中的相关介绍
由于 EVB 底板上有 3 个按键,分别对应核心板 P04/P05/P56 等 3 个引脚,因此这里仅配置这 3 个 GPIO 引脚:
使能 GPIO 数字输入模式
使能内部上拉电阻(按键没有外部上拉电阻)
使能去抖功能(去抖时间在当前工程目录的 app.overlay 文件中配置)
使能 GPIO 中断,将其配置为下降沿触发中断(即低电平唤醒),并实现 isr callback 函数
5.3.3 GPIO 中断服务回调函数¶
GPIO P0 和 P5 的中断服务程序的回调函数内容分别如下:
__ramfunc static void gpio_irq_callback_p0(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins)
{
for (size_t i = 0; i < 8; i++) {
if (BIT(i) & pins) {
printk("GPIO key pressed: P0%d.\n", i);
}
}
k_sem_give(&wakeup_sem);
}
__ramfunc static void gpio_irq_callback_p5(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins)
{
for (size_t i = 0; i < 8; i++) {
if (BIT(i) & pins) {
printk("GPIO key pressed: P5%d.\n", i);
}
}
k_sem_give(&wakeup_sem);
}
函数定义使用
__ramfunc
前缀,将函数编译在 RAM 上以提升执行效率若需要节约 RAM 且对 GPIO 中断执行时间要求不高,此前缀也可以不写
每个 GPIO Port 均需注册自己的 IRQ Callback 函数,其内部可通过
pins
参数判断触发中断的是当前 port 的哪根 pin在回调函数值 give 信号量,当函数返回后将会触发线程调度,而对于此例程来说则是重新调度至 main 线程的 main() 函数的
k_sem_take(wakeup_sem)
处继续执行
5.3.4 与低功耗相关的 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)
{
#if CONFIG_PM_GPIO_INPUT_SENTINEL_INVERT_CTRL
/* Delay 1ms to make sure GPIO IRQ could successfully be triggered */
k_busy_wait(1000);
#endif
/* Allow entering pm flow */
return false;
}
__ramfunc void z_power_hw_deep_sleep_enter_hook(void)
{
#ifdef CONFIG_SERIAL
/* Wait until all UART0 data sending done before entering deepsleep mode */
while (!(UART_GetLineStatus(UART0) & UART_LINE_TXSR_EMPTY)) {
/* Busy wait */
}
/*
* Reset UART PINs to GPIO function and disable digital input path of UART Rx PIN
* to avoid possible current leakage.
*/
pinmux_pin_set(pinmux, PAN_PIN_P00, PAN1080_PIN_FUNC_P00_GPIO);
pinmux_pin_set(pinmux, PAN_PIN_P01, PAN1080_PIN_FUNC_P01_GPIO);
pinmux_pin_input_enable(pinmux, PAN_PIN_P01, PAN_PIN_DIG_INPUT_PATH_DISABLE);
#endif
}
__ramfunc void z_power_hw_deep_sleep_exit_hook(void)
{
#ifdef CONFIG_SERIAL
/* Resume UART PIN Configurations to reenable UART function */
pinmux_pin_set(pinmux, PAN_PIN_P00, PAN1080_PIN_FUNC_P00_UART0_TX);
pinmux_pin_set(pinmux, PAN_PIN_P01, PAN1080_PIN_FUNC_P01_UART0_RX);
pinmux_pin_input_enable(pinmux, PAN_PIN_P01, PAN_PIN_DIG_INPUT_PATH_ENABLE);
#endif
}
Zephyr 有一个优先级最低的 idle 线程,当系统调度到此线程后会立刻触发
k_idle_thread_hook()
钩子函数。系统默认实现了一个 weak 属性的k_idle_thread_hook()
函数,而在 App 中可以重新实现此函数。若令此函数返回 false,则表示允许程序 run 进 idle 线程后续的低功耗流程中; 而若令此函数返回 true,则表示阻止系统执行 idle 线程的低功耗流程(此情况下程序在 idle 线程中空转)。对于本例程来说,在 main 线程中 take 信号量之后,由于系统没有其他的 ready 线程等待调度,因此会直接调度至 idle 线程中。
当程序执行到 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()
函数中,为防止 UART IO 漏电,编写了相关代码以确保在进入 DeepSleep 模式前:串口 Log 数据都打印完毕(即 UART0 Tx FIFO 应为空)
P00 引脚 Pinmux 功能由 UART0 Tx 切换回 GPIO
P01 引脚 Pinmux 功能由 UART0 Rx 切换回 GPIO,并将其数字输入功能关闭
本例程在
z_power_hw_deep_sleep_exit_hook()
函数中,编写了相关代码以恢复串口 Log 打印功能:P00 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Tx
P01 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Rx,并将其数字输入功能重新打开
6 RAM/Flash资源使用情况¶
Memory region Used Size Region Size %age Used
FLASH: 19240 B 384 KB 4.89%
SRAM: 7 KB 64 KB 10.94%