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

DeepSleep GPIO PWM Wakeup

1 功能概述

本例程演示如何使 SoC 进入 DeepSleep 状态,然后使用外部 PWM 波形通过 GPIO 将其唤醒。

2 环境准备

  • 硬件设备与线材:

    • PAN107X EVB 核心板底板

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

    • 逻辑分析仪(Logic Analyzer, LA,用于观察 PWM 波形信息和 GPIO 中断信息)

    • 电流计(本文使用电流可视化测量设备 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 排针相连

    • 将逻辑分析仪硬件的:

      • USB 接口连接至 PC USB 接口

      • 三个通道的信号线分别连接至 EVB 底板的 P13 / P14 / P15 排针

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

    • 将 PPK2 硬件的:

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

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

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

  • 硬件接线(PWM Waveform Generator 例程):

    • 参考 DeepSleep PWM Waveform Generator 例程中的介绍搭建 PWM 波形输出环境

    • 将 PWM 波形输出例程的 P03 引脚与 GPIO 唤醒例程的 P13 引脚相连

    • 将 PWM 波形输出例程的 P04 引脚与 GPIO 唤醒例程的 P14 引脚相连

  • PC 软件:

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

    • Logic(用于配合逻辑分析仪抓取 IO 波形)

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

3 编译和烧录

例程位置:<PAN10XX-NDK>\01_SDK\nimble\samples\low_power\deepsleep_gpio_pwm_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   : 6
    - Chip MAC Address  : E11000000FF8
    - Chip UID          : 6D0001465454455354
    - Chip Flash UID    : 4250315A3538380B005B7B4356037D78
    - Chip Flash Size   : 512 KB
    APP version: 255.255.65535
    Wait for Task Notifications..
    
  3. 将 PWM 波形输出板卡上电,使能 PWM 输出,此时观察 GPIO 唤醒例程的 Log,可以看到触发了多次的唤醒过程:

    [Uptime: 90 ms] GPIO IRQ triggered: P14.
    A notification received, value: 1.
    
    Wait for Task Notifications..
    [Uptime: 100 ms] GPIO IRQ triggered: P14.
    A notification received, value: 1.
    
    Wait for Task Notifications..
    [Uptime: 109 ms] GPIO IRQ triggered: P14.
    A notification received, value: 1.
    
    Wait for Task Notifications..
    [Uptime: 116 ms] GPIO IRQ triggered: P13.
    A notification received, value: 1.
    
    Wait for Task Notifications..
    [Uptime: 118 ms] GPIO IRQ triggered: P14.
    A notification received, value: 1.
    ...
    
  4. 此时观察芯片电流波形,可以更清晰地看出芯片多次睡眠唤醒的切换过程:

    image

    系统被外部 PWM 波唤醒电流波形

  5. 再观察逻辑分析仪波形,可以看出 GPIO P13/P14 分别被各自输入的 PWM 波形的上升沿唤醒并触发中断:

    image

    系统被外部 PWM 波唤醒LA波形

    程序中的两个 GPIO 均被配置为上升沿唤醒,从 LA 波形中可以看出,两个 IO 中的任意一个检测到 GPIO 上升沿,均在经过 470us 左右的时间后触发芯片唤醒并执行 GPIO 中断(由 P15 引脚指示)

5 开发者说明

5.1 App Config 配置

本例程的 App Config(对应 app_config_spark.h 文件)配置与 DeepSleep GPIO Key Wakeup 例程完全相同:

image

App Config File

5.2 程序代码

5.2.1 主程序

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

void app_main(void)
{
    BaseType_t r;

    print_version_info();

    /* Create an App Task */
    r = xTaskCreate(app_task,               // Task Function
                    "App Task",             // Task Name
                    APP_TASK_STACK_SIZE,    // Task Stack Size
                    NULL,                   // Task Parameter
                    APP_TASK_PRIORITY,      // Task Priority
                    NULL                    // Task Handle
    );

    /* Check if task has been successfully created */
    if (r != pdPASS) {
        printf("Error, App Task created failed!\n");
        while (1);
    }
}
  1. 打印 App 版本信息

  2. 创建 App 主任务 “App Task”,对应任务函数 app_task

  3. 确认线程创建成功,否则打印出错信息

5.2.2 App 主任务

App 主任务 app_task() 函数内容如下:

void app_task(void *arg)
{
    uint32_t ulNotificationValue;

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

    /*
     * Init specific GPIOs:
     * - to input mode for wake up use
     * - to output mode for ISR indication use
     */
    gpio_init();

    while (1) {
        printf("Wait for Task Notifications..\n");

        /*
         * Wait to be notified that gpio gpio irq occured. Note the first parameter is pdTRUE,
         * which has the effect of clearing the task's notification value back to 0, making
         * the notification value act like a binary (rather than a counting) semaphore.
         */
        ulNotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        printf("A notification received, value: %d.\n\n", ulNotificationValue);
    }
}
  1. 获取当前任务的 Task Handle,用于后续中断中给次任务发送通知使用

  2. 在 gpio_init() 函数中初始化 GPIO 配置

  3. 在 while (1) 主循环中尝试获取任务通知(Task Task Notify),并打印相关的状态信息

5.2.3 GPIO 初始化程序

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

static void gpio_init(void)
{
    /* Configure GPIO P13/P14 as Rising Edge Interrupt/Wakeup */

    /* Set pinmux func as GPIO */
    SYS_SET_MFP(P1, 3, GPIO);
    SYS_SET_MFP(P1, 4, GPIO);

    /* Set GPIOs to input mode */
    GPIO_SetMode(P1, BIT3 | BIT4, GPIO_MODE_INPUT);

    /* Enable GPIO interrupts and set trigger type to Falling Edge */
    GPIO_EnableInt(P1, 3, GPIO_INT_RISING);
    GPIO_EnableInt(P1, 4, GPIO_INT_RISING);

    /* Configure debounce clock to 32K clock so that GPIO edge could wake up SoC */
    GPIO_SetDebounceTime(GPIO_DBCTL_DBCLKSRC_RCL, GPIO_DBCTL_DBCLKSEL_4);

    /* Enable GPIO IRQs in NVIC */
    NVIC_EnableIRQ(GPIO1_IRQn);

    /* Configure GPIO P15 to push-pull output mode (with init low level) */
    P15 = 0;
    GPIO_SetMode(P1, BIT5, GPIO_MODE_OUTPUT);
}
  1. 此函数使用 Panchip Low-Level GPIO Driver 对 GPIO 进行配置

    实际上也可使用更上层的 Panchip HAL GPIO Driver 进行配置,具体可参考GPIO Digital Input Interrupt例程中的相关介绍

  2. 配置流程如下:

    • 配置 P13 和 P14 引脚 Pinmux 至 GPIO 功能(此步骤可省略,原因是这两个引脚默认就是 GPIO 功能)

    • 使能 P13 和 P14 的数字输入模式

    • 使能 P13 和 P14 的中断,将其配置为上升沿触发中断(即上升沿唤醒)

    • 配置 GPIO 去抖时钟为 32K Clock,以支持边沿唤醒(注意此处并不使能各个 IO 的去抖功能)

    • 使能 GPIO1 NVIC IRQ

    • 将 P15 配置为推挽输出功能

5.2.4 GPIO 中断服务程序

GPIO P1 的中断服务程序如下:

void GPIO1_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdTRUE;

    for (size_t i = 0; i < 8; i++) {
        if (GPIO_GetIntFlag(P1, BIT(i))) {
            GPIO_ClrIntFlag(P1, BIT(i));
            P15 = 1;
            P15 = 0;
            printf("[Uptime: %d ms] GPIO IRQ triggered: P1%d.\n", soc_lptmr_uptime_get_ms(), i);
        }
    }

    /* Notify the task that gpio IRQ occured. */
    vTaskNotifyGiveFromISR(xTaskToNotify, &xHigherPriorityTaskWoken);
}
  1. GPIO 中断服务函数内部可通过 GPIO_GetIntFlag() 接口判断触发中断的是哪根引脚

  2. 在中断服务函数中需注意调用 GPIO_ClrIntFlag() 接口清除中断标志位

  3. 使用 soc_lptmr_uptime_get_ms() 接口获取系统的上电时间戳并打印到串口

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

5.2.5 与低功耗相关的 Hook 函数

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

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

CONFIG_RAM_CODE void vSocDeepSleepExitHook(void)
{
#if CONFIG_LOG_ENABLE
    set_uart_io();
#endif
}
  1. 上述两个 Hook 函数用于在进入 DeepSleep 前和从 DeepSleep 唤醒后做一些额外操作,如关闭和重新配置 IO 为串口功能,以防止 DeepSleep 状态下 IO 漏电

  2. 详细解释请参考 DeepSleep GPIO Key Wakeup 例程中的相关介绍