当前页面为 开发中 版本,查看特定版本的文档,请在页面左下角的下拉菜单中进行选择。

Standby Mode1 GPIO Key Wakeup

1 功能概述

本例程演示如何使 SoC 进入 Standby Mode 1 状态,然后通过 GPIO 按键将其唤醒。

2 环境准备

  • 硬件设备与线材:

    • PAN107X EVB 核心板底板各一块

    • JLink 仿真器(用于烧录例程程序)

    • 电流计(本文使用电流可视化测量设备 PPK2 [Nordic Power Profiler Kit II] 进行演示)

    • USB-TypeC 线一条(用于底板供电和查看串口打印 Log)

    • 杜邦线数根或跳线帽数个(用于连接各个硬件设备)

  • 硬件接线:

    • 将 EVB 核心板插到底板上

    • 为确保能够准确地测量 SoC 本身的功耗,排除底板外围电路的影响,请确认 EVB 底板上的:

      • Voltage 排针组中的 VCC 和 VDD 均接至 3V3

      • POWER 开关从 LDO 档位拨至 BAT 档位(并确认底板背部的电池座内没有纽扣电池)

    • 使用 USB-TypeC 线,将 PC USB 插口与 EVB 底板 USB->UART 插口相连

    • 使用杜邦线将 EVB 底板上的 TX 引脚接至核心板 P16,RX 引脚接至核心板 P17

    • 使用杜邦线将 JLink 仿真器的:

      • SWD_CLK 引脚与 EVB 底板的 P00 排针相连

      • SWD_DAT 引脚与 EVB 底板的 P01 排针相连

      • SWD_GND 引脚与 EVB 底板的 GND 排针相连

    • 将 PPK2 硬件的:

      • USB DATA/POWER 接口连接至 PC USB 接口

      • VOUT 连接至 EVB 底板的 VBAT 排针

      • GND 连接至 EVB 底板的 GND 排针

  • PC 软件:

    • 串口调试助手(UartAssist)或终端工具(SecureCRT),波特率 921600(用于接收串口打印 Log)

    • nRF Connect Desktop(用于配合 PPK2 测量 SoC 电流)

3 编译和烧录

例程位置:<PAN10XX-NDK>\01_SDK\nimble\samples\low_power\standby_m1_gpio_key_wakeup\keil_107x

双击 Keil Project 文件打开工程进行编译烧录,烧录成功后断开 JLink 连线以避免漏电。

4 例程演示说明

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

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

    Try to load HW calibration data.. DONE.
    - Chip Info         : 0x1
    - Chip CP Version   : 255
    - Chip FT Version   : 7
    - Chip MAC Address  : E110000052E3
    - Chip UID          : 060300465454455354
    - Chip Flash UID    : 4250315A3538380B00CE12435603C678
    - Chip Flash Size   : 512 KB
    [I] App started..
    
    [I] Reset Reason: nRESET Pin Reset.
    [I] Try to enter SoC sleep/deepsleep mode and wait for 3-times key pressing..
    
  3. 此时观察芯片电流波形,发现稳定在 4uA 左右(说明芯片成功进入了 DeepSleep 模式):

    image

    系统初始化后进入 DeepSleep 模式

    芯片低功耗状态下的底电流(漏电流)与环境温度相关,温度越高,漏电流越大。

  4. 尝试按下 EVB 底板上的 3 个按键中的任意一个或几个:KEY1、KEY2 和 WKUP,由串口打印信息可知触发 3 次 DeepSleep 唤醒后,芯片会延时 500ms,然后进入 Standby Mode 1 状态:

    [I] ==== KEY1 Pressed! (P0_6 falling edge detected) ====
    [I] First key pressed, notify value = 1.
    [I] ==== KEY2 Pressed! (P1_2 falling edge detected) ====
    [I] Second key pressed, notify value = 1.
    [I] ==== WKUP Pressed! (P0_2 falling edge detected) ====
    [I] Third key pressed, notify value = 1.
    [I] Busy wait 500ms to keep SoC in active mode..
    [I] Now try to enter SoC standby mode 1 (wakeup reset)..
    
  5. 此时再观察芯片电流波形,可以看到芯片触发了 3 次 DeepSleep 唤醒,最后一次唤醒持续了约 500ms 时间,随后进入 Standby Mode 1 状态等待下次按键唤醒:

    image

    使用按键将芯片从 DeepSleep 状态下唤醒 3 次后,芯片进入 Stnadby Mode 1 状态

    由电流波形可知芯片前两次唤醒后均重新进入了 DeepSleep 模式,此模式下芯片电流保持在 4uA 左右;第三次唤醒后,芯片最终进入了 Standby Mode 1 模式,此模式下芯片电流保持在 1.2uA 左右。

  6. 再次尝试按下 EVB 底板上的 3 个按键中的任意一个(如 KEY1),由串口打印信息可知芯片从 Standby Mode 1 状态下唤醒并复位,复位的原因为 GPIO P06 唤醒(对应按键 KEY1),随后芯片重新进入 DeepSleep 状态继续等待按键唤醒:

    Try to load HW calibration data.. DONE.
    - Chip Info         : 0x1
    - Chip CP Version   : 255
    - Chip FT Version   : 7
    - Chip MAC Address  : E110000052E3
    - Chip UID          : 060300465454455354
    - Chip Flash UID    : 4250315A3538380B00CE12435603C678
    - Chip Flash Size   : 512 KB
    [I] App started..
    
    [I] Reset Reason: Standby Mode 1 GPIO Wakeup (cnt = 1).
    [I] gpio wakeup src flag = 0x00000040
    [I] SoC is waked up by GPIO P0_6.
    [I] Try to enter SoC sleep/deepsleep mode and wait for 3-times key pressing..
    

5 开发者说明

5.1 SDK Config 配置

与本例程相关的 SDK Config (sdk_config.h) 配置有:

  • SoC Platform : Chip Power Mode

    • 芯片供电选择 DCDC 模式,以降低芯片动态功耗

    • 对应宏配置 CONFIG_SOC_DCDC_PAN1070 = 1

  • Power Management : Enable Low Power Mode

    • 使能系统低功耗功能

    • 对应宏配置 CONFIG_PM = 1

  • Power Management : Enable Low Power Mode : Continue Run After Standby M1 Wakeup

    • 配置 Standby Mode 1 唤醒后 CPU 的行为:CPU 复位,而不是继续执行 WFI 后面的指令

    • 对应宏配置 CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET = 0

  • Log & Debug Config : Enable App Log

    • 使能 App Log 功能

    • 对应宏配置 APP_LOG_EN = 1

  • Log & Debug Config : Enable App Log : Log to UART

    • 将 App Log 输出至串口

    • 对应宏配置 CONFIG_UART_LOG_ENABLE = 1

5.2 程序代码

5.2.1 主程序

本例程中,OS 为使能状态,因此主程序 main() 函数也是 OS Main Task 的入口函数,其内容如下:

int main(void)
{
    /* Application initialization */
    app_setup();

    /* Application main infinite loop */
    app_main_loop();

    return 0;
}

5.2.2 App Setup 初始化

App 初始化 app_setup() 函数内容如下:

void app_setup(void)
{
    APP_LOG_INFO("App started..\n\n");

    uint8_t rst_reason;

    /* Get the last reset reason */
    APP_LOG_INFO("Reset Reason: ");
    rst_reason = soc_reset_reason_get();
    switch (rst_reason) {
    case SOC_RST_REASON_POR_RESET:
        APP_LOG("Power On Reset.\n");
        break;
    case SOC_RST_REASON_PIN_RESET:
        APP_LOG("nRESET Pin Reset.\n");
        break;
    case SOC_RST_REASON_SYS_RESET:
        APP_LOG("NVIC System Reset.\n");
        break;
    case SOC_RST_REASON_STBM1_GPIO_WAKEUP:
        APP_LOG("Standby Mode 1 GPIO Wakeup (cnt = %d).\n", ++wkup_cnt);
        parse_stbm1_gpio_wakeup_source();
        break;
    default:
        APP_LOG("Unhandled Reset Reason (%d), refer to more reason define in soc_api.h!\n", rst_reason);
    }
}
  1. 打印 App 初始化 Log

  2. 获取并打印本次芯片启动的复位原因

5.2.3 App Main Task Loop 任务循环

App Main Task 循环 app_main_loop() 函数内容如下:

void app_main_loop(void)
{
    uint32_t ulNotificationValue;

    /* Store the handle of current task. */
    xTaskToNotify = xTaskGetCurrentTaskHandle();
    if(xTaskToNotify == NULL) {
        app_assert("Error, get current task handle failed!\n");
    }

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

    APP_LOG_INFO("Try to enter SoC sleep/deepsleep mode and wait for 3-times key pressing..\n");
    /*
     * Wait to be notified that gpio key is pressed (gpio irq occured). Note the first parameter
     * is pdFALSE, which has the effect of decrease the task's notification value by 1, making
     * the notification value act like a counting semaphore.
     */
    ulNotificationValue = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
    APP_LOG_INFO("First key pressed, notify value = %d.\n", ulNotificationValue);
    ulNotificationValue = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
    APP_LOG_INFO("Second key pressed, notify value = %d.\n", ulNotificationValue);
    ulNotificationValue = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
    APP_LOG_INFO("Third key pressed, notify value = %d.\n", ulNotificationValue);

    APP_LOG_INFO("Busy wait 500ms to keep SoC in active mode..\n");
    soc_busy_wait(500000);

#if !CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET
    APP_LOG_INFO("Now try to enter SoC standby mode 1 (wakeup reset)..\n\n");
#if CONFIG_UART_LOG_ENABLE
    /* Waiting for UART Tx done and re-set UART IO before entering standby mode 1 to avoid current leakage */
    reset_uart_io();
#endif
    /* Enter standby mode1 with all sram power off */
    soc_enter_standby_mode_1(STBM1_WAKEUP_SRC_GPIO, STBM1_RETENTION_SRAM_NONE);

    APP_LOG("WARNING: Failed to enter SoC standby mode 1 due to unexpected interrupt detected.\n");
    APP_LOG("         Please check if there is an unhandled interrupt during the standby mode 1\n");
    APP_LOG("         entering flow.\n");

    while (1) {
        /* Busy wait */
    }
#else
    while (1) {
        APP_LOG_INFO("Now try to enter SoC standby mode 1 (wakeup continuous run)..\n\n");
#if CONFIG_UART_LOG_ENABLE
        /* Waiting for UART Tx done and re-set UART IO before entering standby mode 1 to avoid current leakage */
        reset_uart_io();
#endif
        /* Enter standby mode1 with all common 48KB sram retention */
        soc_enter_standby_mode_1(STBM1_WAKEUP_SRC_GPIO, STBM1_RETENTION_SRAM_BLOCK0 | STBM1_RETENTION_SRAM_BLOCK1);
        /* Restore hardware peripherals manually */
        restore_hw_peripherals();
        APP_LOG_INFO("Waked up from SoC standby mode 1 and continue run (cnt = %d)..\n", ++wkup_cnt);
    }
#endif /* CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET */
}
  1. 获取当前任务的 Task Handle,用于后续中断中给次任务发送通知使用

  2. 在 wakeup_gpio_keys_init() 函数中初始化按键 GPIO 配置

  3. 尝试获取任务通知(Task Task Notify)3 次,使系统 3 次进入 DeepSleep 状态并等待 GPIO 按键唤醒

  4. 第三次从 DeepSleep 状态下唤醒后,调用 soc_busy_wait() 接口使芯片在 Active 状态下全速运行 500ms

  5. 根据 CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET 配置,决定进入 Standby Mode 1 的流程,这里由于我们将此配置设置为 0,因此会执行唤醒后复位的 Standby Mode 1 流程

  6. 进入 Standby Mode 1 前,先将 Log UART IO 配置还原为 GPIO 模拟输入状态,以避免 Standby 模式下 IO 漏电

  7. 调用 soc_enter_standby_mode_1() 接口,使系统进入 Standby Mode 1 低功耗状态,此接口接受两个参数:第一个参数用于配置唤醒源,第二个参数用于配置低功耗下保电的 SRAM,这里我们将唤醒源配置为 GPIO,且低功耗状态下所有 SRAM 均掉电以节约功耗(由于当前低功耗配置为唤醒后复位,因此所有 SRAM 均没有保电的必要)

5.2.4 GPIO 初始化程序

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

static void wakeup_gpio_keys_init(void)
{
    /* Configure GPIO P06 (KEY1) / P12 (KEY2) / P02 (WKUP) as Falling Edge Interrupt/Wakeup */

    /* Set pinmux func as GPIO */
    SYS_SET_MFP(P0, 6, GPIO);
    SYS_SET_MFP(P1, 2, GPIO);
    SYS_SET_MFP(P0, 2, GPIO);

    /* Configure debounce clock to 32K clock */
    GPIO_SetDebounceTime(GPIO_DBCTL_DBCLKSRC_RCL, GPIO_DBCTL_DBCLKSEL_4);

    /*
     * Construct GPIO init structure and Init GPIO P06/P12/P02:
     * - Set IO to digital input mode
     * - Enable internal pull-up resistor
     */
    HAL_GPIO_InitTypeDef GPIO_InitStruct = {
        .mode   = HAL_GPIO_MODE_INPUT_DIGITAL,
        .pull   = HAL_GPIO_PULL_UP,
    };
    HAL_GPIO_Init(P0_6, &GPIO_InitStruct);
    HAL_GPIO_Init(P1_2, &GPIO_InitStruct);
    HAL_GPIO_Init(P0_2, &GPIO_InitStruct);

    /* Construct GPIO interrupt init structure */
    HAL_GPIO_IntInitTypeDef GPIO_IntInitStruct = {
        .intMode        = HAL_GPIO_INT_FALLING,
        .debounce       = ENABLE,
    };
    /* Init GPIO P06 interrupt */
    GPIO_IntInitStruct.callbackFunc = gpio_p06_input_callback;
    HAL_GPIO_InterruptInit(P0_6, &GPIO_IntInitStruct);
    /* Init GPIO P12 interrupt */
    GPIO_IntInitStruct.callbackFunc = gpio_p12_input_callback;
    HAL_GPIO_InterruptInit(P1_2, &GPIO_IntInitStruct);
    /* Init GPIO P02 interrupt */
    GPIO_IntInitStruct.callbackFunc = gpio_p02_input_callback;
    HAL_GPIO_InterruptInit(P0_2, &GPIO_IntInitStruct);

    /* Wait for a while to ensure the internal pullup is stable */
    soc_busy_wait(10000);

    /*
     * Clear possible GPIO pending IRQs to avoid unexpected GPIO isr run
     * after NVIC GPIO IRQ enabled.
     * [NOTE] GPIO pending IRQs would be generated right after SoC waking
     *        up from Standby Mode 1 by GPIO interrupt.
     */
    NVIC_ClearPendingIRQ(GPIO0_IRQn);
    NVIC_ClearPendingIRQ(GPIO1_IRQn);

    /* Enable GPIO IRQs in NVIC */
    NVIC_EnableIRQ(GPIO0_IRQn);
    NVIC_EnableIRQ(GPIO1_IRQn);
}
  1. 使用 HAL GPIO Driver 对 GPIO 进行配置

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

    • 配置引脚 Pinmux 至 GPIO 功能

    • 选择去抖时钟源为 32K Clock,以支持边沿唤醒,并配置去抖时间

    • 配置 GPIO 为输入模式,并使能内部上拉电阻(注意 EVB 底板按键没有外部上拉电阻)

    • 使能 GPIO 中断,将其配置为下降沿触发中断(即下降沿唤醒),并使能去抖功能

    • 使能 NVIC GPIO IRQs

5.2.5 GPIO 中断回调函数

static void gpio_p06_input_callback(HAL_GPIO_IntMode intMode)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if (intMode == HAL_GPIO_INT_FALLING) {
        /* Notify the task that KEY1 is pressed (P06 falling edge detected). */
        vTaskNotifyGiveFromISR(xTaskToNotify, &xHigherPriorityTaskWoken);
        APP_LOG_INFO("==== KEY1 Pressed! (P0_6 falling edge detected) ====\n");
    }

    /* Yield if xHigherPriorityTaskWoken is true. */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

static void gpio_p12_input_callback(HAL_GPIO_IntMode intMode)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if (intMode == HAL_GPIO_INT_FALLING) {
        /* Notify the task that KEY1 is pressed (P06 falling edge detected). */
        vTaskNotifyGiveFromISR(xTaskToNotify, &xHigherPriorityTaskWoken);
        APP_LOG_INFO("==== KEY2 Pressed! (P1_2 falling edge detected) ====\n");
    }

    /* Yield if xHigherPriorityTaskWoken is true. */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

static void gpio_p02_input_callback(HAL_GPIO_IntMode intMode)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if (intMode == HAL_GPIO_INT_FALLING) {
        /* Notify the task that KEY1 is pressed (P06 falling edge detected). */
        vTaskNotifyGiveFromISR(xTaskToNotify, &xHigherPriorityTaskWoken);
        APP_LOG_INFO("==== WKUP Pressed! (P0_2 falling edge detected) ====\n");
    }

    /* Yield if xHigherPriorityTaskWoken is true. */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
  1. 由于使用 HAL GPIO Driver,因此无需实现 GPIO 中断服务程序,而是要为每个 GPIO 引脚实现自己的中断触发 callback

  2. 使用 FreeRTOS vTaskNotifyGiveFromISR() 接口向 App Task 发送通知,表示有 GPIO 中断产生(对应按键按下),此接口中 xHigherPriorityTaskWoken 变量被配置为 pdTRUE,表示当中断返回后将会触发线程调度,而对于此例程来说则是重新调度至 App Task 中的 ulTaskNotifyTake() 处继续执行

5.2.6 与低功耗相关的 Hook 函数

本例程还用到了 2 个与低功耗密切相关的 Hook 函数:

CONFIG_RAM_CODE void vSocDeepSleepEnterHook(void)
{
#if CONFIG_UART_LOG_ENABLE
    reset_uart_io();
#endif
}

CONFIG_RAM_CODE void vSocDeepSleepExitHook(void)
{
#if CONFIG_UART_LOG_ENABLE
    set_uart_io();
#endif
}
  1. FreeRTOS 有一个优先级最低的 Idle Task,当系统调度到此任务后会对当前状态进行检查,以判断是否允许进入芯片 DeepSleep 低功耗流程

  2. 若程序执行到 Idle Task 的 DeepSleep 子流程中,会在 SoC 进入 DeepSleep 模式之前执行 vSocDeepSleepEnterHook() 函数,在 SoC 从 DeepSleep 模式下唤醒后执行 vSocDeepSleepExitHook() 函数

    • 本例程在 vSocDeepSleepEnterHook() 函数中,为防止 UART IO 漏电,编写了相关代码(reset_uart_io(),具体实现见例程源码)以确保在进入 DeepSleep 模式前:

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

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

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

    • 本例程在 vSocDeepSleepExitHook() 函数中,编写了相关代码(set_uart_io(),具体实现见例程源码)以恢复串口 Log 打印功能:

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

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

5.2.7 其他功能函数

本例程还编写了 2 个比较常用的功能函数:

static void parse_stbm1_gpio_wakeup_source(void)
{
    uint32_t src;

    src = soc_stbm1_gpio_wakeup_src_get();

    printf("gpio wakeup src flag = 0x%08x\n", src);

    for (size_t i = 0; i < 32; i++) {
        if (src & (1u << i)) {
            printf("SoC is waked up by GPIO P%d_%d.\n", i / 8, i % 8);
        }
    }
}

#ifdef CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET
void restore_hw_peripherals(void)
{
#if CONFIG_UART_LOG_ENABLE
	UART_InitTypeDef Init_Struct = {
		.UART_BaudRate = 921600,
		.UART_LineCtrl = Uart_Line_8n1,
	};
	/* Re-init UART */
	UART_Init(UART0, &Init_Struct);
	UART_EnableFifo(UART0);
	/* Set IO pinmux to UART again */
	set_uart_io();
#endif
	/* Re-enable NVIC GPIO IRQs */
	NVIC_EnableIRQ(GPIO0_IRQn);
	NVIC_EnableIRQ(GPIO1_IRQn);
	/* Re-init GPIO keys */
	wakeup_gpio_keys_init();
}
#endif /* CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET */
  1. parse_stbm1_gpio_wakeup_source() 用于获取当前 GPIO 中断引脚,本例程中在初始化阶段检测到当前为 Standby Mode 1 GPIO 唤醒后,调用此接口即可获取并打印唤醒 IO 是哪个引脚

  2. CONFIG_PM_STANDBY_M1_WAKEUP_WITHOUT_RESET 配置被设置为 1 时, 芯片从 Standby Mode 1 下唤醒后,将不会触发复位,而是从睡眠之前的代码处接着执行,但由于 Standby Mode 1 状态下芯片内部大部分模块均已掉电,因此醒来后需通过函数 restore_hw_peripherals() 重新初始化一些用到的硬件模块

6 RAM/Flash资源使用情况

PAN107x:

Flash Size:  20.24k
RAM Size:  9.95 k