当前文档版本为 v1.2.0,您可以访问当前页面的 开发中 版本以获取最近可能的更新。

LowPower: Standby Mode 1 GPIO Key Wakeup

1 功能概述

本例程演示如何使 SoC 进入 Standby Mode 1 状态,然后通过 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\standby_m1_gpio_key_wakeup

使用 ZAL 工具擦除 Flash 程序。关于 ZAL 工具的详细介绍请参考:Zephyr APP Launcher 工具介绍

4 例程演示说明

  1. PC 上打开 PPK2 Power Profiler 软件,供电电压选择 3300 mV,然后打开供电开关:

    image

    PPK2 使能芯片供电

    测试芯片中目前还没有程序,所以看到此时芯片耗电保持在 3mA 左右。

  2. 使用 ZAL 工具将编译后的例程烧录至芯片

    烧录成功后,最好断开 JLink 与芯片的连接以防止芯片 P46 和 P47 两个引脚有漏电情况发生

  3. 从串口工具中看到如下的打印信息:

    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  ***
    
    Reset Reason: NVIC System Reset.
    
    Try to enter SoC sleep/deepsleep mode and wait for 3-times key pressing..
    

    由上述 Log 可得知以下信息:

    • 本次芯片 Reset 原因为 NVIC System Reset,这是因为本次芯片是程序烧录后由 JLink 触发的软件 Reset

    • 芯片进入 DeepSleep 模式,并等待按键将其唤醒

  4. 此时观察芯片电流波形,发现稳定在 6uA 左右(说明芯片成功进入了 DeepSleep 模式):

    image

    系统初始化后进入 DeepSleep 模式

  5. 分别尝试按下 EVB 底板上的 3 个按键:KEY1、KEY2 和 WKUP:

    P0_4 INT occurred.
    First key pressed.
    P0_5 INT occurred.
    Second key pressed.
    P5_6 INT occurred.
    Third key pressed.
    
    Busy wait 500ms to keep SoC in active mode..
    
    Now try to enter SoC standby mode 1..
    

    由上述 Log 可得知以下信息:

    • 3 个按键事件均成功触发了芯片唤醒

    • 第 3 次按键按下唤醒后,芯片在 Active 状态下等待了 500ms,然后进入了 Stnadby Mode 1 状态

  6. 此时再观察芯片电流波形,可以看到芯片触发了 3 次唤醒,最后进入 Standby Mode 1 状态等待下次按键唤醒:

    image

    分别使用 3 个按键唤醒芯片

    由电流波形可知:

    • 芯片前 2 次唤醒后均进入了 DeepSleep 模式,期间芯片电流在 6uA 左右

    • 芯片第 3 次唤醒后进入 Standby Mode 1 模式,期间芯片电流在 600nA 左右

  7. 再次尝试按下 EVB 底板上 3 个按键的任意一个(如 KEY1)以触发唤醒:

    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  ***
    
    Reset Reason: Standby Mode 1 GPIO Wakeup.
    
    Try to enter SoC sleep/deepsleep mode and wait for 3-times key pressing..
    

    由上述 Log 可得知以下信息:

    • 按键 KEY1 成功将芯片从 Standby Mode 1 唤醒,且本次芯片 Reset 原因显示为 Standby Mode 1 GPIO Wakeup

    • 程序初始化完成后立刻进入 DeepSleep 模式等待下一次按键唤醒

  8. 再观察芯片电流波形,可以看到芯片触发了复位,并重新进入 DeepSleep 模式:

    image

    芯片从 Standby Mode 1 模式下唤醒并重新进入 DeepSleep 模式

    由电流波形可知:

    • 芯片成功从 Standby Mode 1 模式下唤醒并触发了芯片 Reset

    • 系统重新初始化后先进入持续 520 ms 左右的 Sleep 模式,然后进入 DeepSleep 模式,期间芯片电流在 6uA 左右

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";
};

其中:

  • uart1status 属性配置为 disabled 以禁止系统上电后初始化 uart1 设备,确保不会因为 uart1 的引脚配置产生 IO 漏电

5.3 程序代码

5.3.1 主程序

主程序 main() 函数内容如下:

void main(void)
{
	uint8_t rst_reason;

	/* Initialize gpio irq semaphore */
	k_sem_init(&gpio_irq_sem, 0, 3);

	/* 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");
	}

	/* 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);

	/* Enable 3 GPIO keys low-level wakeup for standby mode 1 */
	wakeup_gpio_keys_init();

	printk("\nTry to enter SoC sleep/deepsleep mode and wait for 3-times key pressing..\n");
	k_sem_take(&gpio_irq_sem, K_FOREVER);
	printk("First key pressed.\n");
	k_sem_take(&gpio_irq_sem, K_FOREVER);
	printk("Second key pressed.\n");
	k_sem_take(&gpio_irq_sem, K_FOREVER);
	printk("Third key pressed.\n");

	printk("\nBusy wait 500ms to keep SoC in active mode..\n");
	k_busy_wait(500000);

	printk("\nNow try to enter SoC standby mode 1..\n\n");

#ifdef CONFIG_SERIAL
	/* Waiting for UART Tx done and re-set UART IO before entering standby mode 1 to avoid current leakage */
	reset_uart_io();
#endif
	soc_enter_standby_mode_1(STBM1_WAKEUP_SRC_GPIO, STBM1_RETENTION_SRAM_NONE, 0);

#ifdef CONFIG_SERIAL
	/* Set IO pinmux to UART again */
	set_uart_io();
#endif
	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 */
	}
}
  1. 初始化一个名为 gpio_irq_sem 的信号量

    在 GPIO 唤醒中断触发时通过 give 此信号量可以激活 main 线程的调度

  2. 获取本次系统复位的原因,本例程中仅检测 3 种情况(更多复位情况请参考 soc.h 文件中的相关定义):

    • 芯片 nRESET 引脚复位(nRESET 按键按下)

    • NVIC System Reset 软件复位(JLink 烧录后自动触发,或者软件调用 sys_reboot(0) 触发)

    • Standby Mode 1 GPIO Wakeup 唤醒复位

  3. 注册 GPIO IRQ 中断服务程序并使能 NVIC GPIO 中断

  4. 在 wakeup_gpio_keys_init() 函数中初始化 GPIO 配置

  5. 依次尝试 take 3 次 gpio_irq_sem 信号量,成功后使系统在 Active 状态下保持 500ms

  6. 将 UART Pinmux IO 恢复为默认的 GPIO 状态

  7. 使芯片进入 Standby Mode 1 模式,并配置为 GPIO 唤醒,且所有 SRAM 均不保持

  8. 若芯片未成功进入 Standby Mode 1 模式,则重新配置 UART Pinmux IO 状态并打印 Warning log

5.3.2 GPIO 初始化程序

GPIO 初始化程序 wakeup_gpio_keys_init() 函数内容如下:

static void wakeup_gpio_keys_init(void)
{
	/* Configure GPIO P04 (KEY1) / P05 (KEY2) / P56 (WKUP) as Falling Edge Interrupt/Wakeup */

	/* Set pinmux func as GPIO */
	SYS_SET_MFP(P0, 4, GPIO);
	SYS_SET_MFP(P0, 5, GPIO);
	SYS_SET_MFP(P5, 6, GPIO);

	/* Enable input debounce function */
	GPIO_SetDebounceTime(GPIO_DBCTL_DBCLKSRC_HCLK, GPIO_DBCTL_DBCLKSEL_32768);
	GPIO_EnableDebounce(P0, BIT4);
	GPIO_EnableDebounce(P0, BIT5);
	GPIO_EnableDebounce(P5, BIT6);

	/* Set GPIOs to input mode */
	GPIO_SetMode(P0, BIT4, GPIO_MODE_INPUT);
	GPIO_SetMode(P0, BIT5, GPIO_MODE_INPUT);
	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(P0, BIT4);
	GPIO_EnablePullupPath(P0, BIT5);
	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);

	/* Enable GPIO interrupts and set trigger type to Low Level */
	GPIO_EnableInt(P0, 4, GPIO_INT_FALLING);
	GPIO_EnableInt(P0, 5, GPIO_INT_FALLING);
	GPIO_EnableInt(P5, 6, GPIO_INT_FALLING);
}
  1. 此函数使用 Panchip HAL GPIO Driver 对 GPIO 进行配置

    实际上也可直接使用 Zephyr GPIO Driver 进行配置,具体可参考DeepSleep GPIO Key Wakeup例程中的相关介绍

  2. 由于 EVB 底板上有 3 个按键,分别对应核心板 P04/P05/P56 等 3 个引脚,因此这里仅配置这 3 个 GPIO 引脚:

    • 将三个引脚 Pinmux 配置 GPIO 功能

    • 将三个引脚的输入去抖功能打开,并将去抖时间配置为 32768 / 64M * 1M = 512 us

    • 将三个引脚配置为 GPIO 数字输入模式

    • 将三个引脚的内部上拉电阻打开(按键没有外部上拉电阻)

    • 等待一段时间,确保前面的上拉电阻状态稳定

    • 将三个引脚的 GPIO 中断功能打开,并将其配置为下降沿触发中断(即低电平唤醒)

5.3.3 GPIO 中断服务程序

GPIO 所有引脚共用一个中断服务程序:

__ramfunc void gpio_irq_handler(void)
{
	GPIO_T *gpio;

	for (size_t port = 0; port < 6; port++) {
		gpio = (GPIO_T *)(P0_BASE + 0x40 * port);
		if (GPIO_GetIntFlag(gpio, 0xFF) != 0) {
			for (size_t i = 0; i < 8; i++) {
				if (GPIO_GetIntFlag(gpio, BIT(i))) {
					GPIO_ClrIntFlag(gpio, BIT(i));
					printk("P%d_%d INT occurred.\r\n", ((uint32_t)gpio - (uint32_t)P0) / 0x40, i);
					k_sem_give(&gpio_irq_sem);
				}
			}
			GPIO_ClrAllIntFlag(gpio);
		}
	}

	/* To indicate zephyr to schedule new ready thread if there is */
	z_arm_int_exit();
}
  1. 函数定义使用 __ramfunc 前缀,将函数编译在 RAM 上以提升执行效率

    若需要节约 RAM 且对 GPIO 中断执行时间要求不高,此前缀也可以不写

  2. 在中断 Handler 中检测,当发现任何中断均先将当前 GPIO 引脚号打印出来,然后 give 信号量,当函数返回后将会触发线程调度, 而对于此例程来说则是重新调度至 main 线程的 main() 函数的 k_sem_take(gpio_irq_sem) 处继续执行

5.3.4 UART 引脚 Pinmux 配置与恢复

若芯片在使用过程中有诸如 UART 等外设模块配置了 IO,则芯片在进入低功耗状态前,要考虑将其 Pinmux 功能切换为 GPIO 功能; 若有必要,还需在芯片唤醒后将其功能切换回 UART 等功能以继续使用。

#ifdef CONFIG_SERIAL
__ramfunc static void reset_uart_io(void)
{
	/* 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.
	 */
	SYS_SET_MFP(P0, 0, GPIO);
	SYS_SET_MFP(P0, 1, GPIO);
	GPIO_DisableDigitalPath(P0, BIT1);
}

__ramfunc static void set_uart_io(void)
{
	/* Resume UART PIN Configurations to reenable UART function */
	SYS_SET_MFP(P0, 0, UART0_TX);
	SYS_SET_MFP(P0, 1, UART0_RX);
	GPIO_EnableDigitalPath(P0, BIT1);
}
#endif
  1. reset_uart_io() 函数用于:

    • 等待串口 Log 数据都打印完毕(即 UART0 Tx FIFO 应为空)

    • P00 引脚 Pinmux 功能由 UART0 Tx 切换回 GPIO

    • P01 引脚 Pinmux 功能由 UART0 Rx 切换回 GPIO,并将其数字输入功能关闭

  2. set_uart_io() 函数用于:

    • P00 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Tx

    • P01 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Rx,并将其数字输入功能重新打开

5.3.5 与低功耗相关的 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
	reset_uart_io();
#endif
}

__ramfunc void z_power_hw_deep_sleep_exit_hook(void)
{
#ifdef CONFIG_SERIAL
	set_uart_io();
#endif
}
  1. Zephyr 有一个优先级最低的 idle 线程,当系统调度到此线程后会立刻触发 k_idle_thread_hook() 钩子函数。系统默认实现了一个 weak 属性的 k_idle_thread_hook() 函数,而在 App 中可以重新实现此函数。若令此函数返回 false,则表示允许程序 run 进 idle 线程后续的低功耗流程中; 而若令此函数返回 true,则表示阻止系统执行 idle 线程的低功耗流程(此情况下程序在 idle 线程中空转)。

    对于本例程来说,在 main 线程中 take 信号量之后,由于系统没有其他的 ready 线程等待调度,因此会直接调度至 idle 线程中。

  2. 当程序执行到 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() 函数中,调用了 reset_uart_io() 函数以防止 UART IO 在 DeepSleep 模式下漏电

    • 本例程在 z_power_hw_deep_sleep_exit_hook() 函数中,调用了 set_uart_io() 函数以恢复串口 Log 打印功能

6 RAM/Flash资源使用情况

Memory region         Used Size  Region Size  %age Used
FLASH:       20556 B       384 KB      5.23%
SRAM:        7160 B        64 KB     10.93%