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

DeepSleep SleepTimer Wakeup

1 功能概述

本例程演示如何使 SoC 进入 DeepSleep 状态,然后通过 SleepTimer 定时器将其唤醒。

2 环境准备

  • 硬件设备与线材:

    • PAN107X EVB 核心板底板各一块

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

    • 逻辑分析仪(Logic Analyzer, LA,用于观察各个 Timer 定时器超时中断的触发时间)

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

  • PC 软件:

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

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

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

3 编译和烧录

例程位置:<PAN10XX-NDK>\01_SDK\nimble\samples\low_power\deepsleep_slptmr_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
    [Uptime: 83 ms] Main Thread sleep..
    [Uptime: 583 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 1083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 1084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 1583 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 2083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 2084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 2583 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 3083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 3084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 3583 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 4083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 4084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 4583 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 5082 ms] Main Thread waked up.
    [Uptime: 5083 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 5084 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 5085 ms] Main Thread sleep..
    [Uptime: 5584 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 6083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 6084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 6584 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 7083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 7084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 7584 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 8083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 8084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 8584 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 9083 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 9084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 9584 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 10082 ms] Main Thread waked up.
    [Uptime: 10083 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 10084 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 10083 ms] Main Thread sleep..
    [Uptime: 10584 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 11084 ms] SleepTimer 2 IRQ triggered.
    [Uptime: 11084 ms] SleepTimer 1 IRQ triggered.
    [Uptime: 11584 ms] SleepTimer 1 IRQ triggered.
    ...
    

    由 Log 中的时间戳可以看出,每隔 500ms 触发 SleepTimer 1 中断,每隔 1s 触发 SleepTimer 2 中断,每隔 5s 触发 Main Task 调度

  3. 观察逻辑分析仪波形,发现芯片 P13 引脚每隔 5s 左右拉低一次;P14 引脚每隔 500ms 左右拉低一次; P15 引脚每隔 1s 左右拉低一次:

    image

    使用逻辑分析仪抓取 GPIO 波形

    P13 引脚指示 Main Task 执行时刻,P14 引脚只是 SleeTimer 1 中断触发时刻,P15 引脚只是 SleeTimer 2 中断触发时刻,可见它们与 Log 中的时间戳正好对应

  4. 将逻辑分析仪从芯片上断开(避免影响电流观测),再观察芯片电流波形:

    image

    使用电流计抓取电流波形

    可见芯片低功耗底电流为 4uA 左右,并且每隔一段时间芯片被某个 SleepTimer 定时器从 DeepSleep 状态下唤醒。

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)
{
    /* Init specific GPIO as output for ISR indication use */
    indication_gpio_init();
    /* Init Sleep Timers for wake up use */
    slptmr_init();

    while (1) {
        printf("[Uptime: %d ms] Main Thread sleep..\n", soc_lptmr_uptime_get_ms());
        vTaskDelay(pdMS_TO_TICKS(5000));
        P13 = 0;
        P13 = 1;
        printf("[Uptime: %d ms] Main Thread waked up.\n", soc_lptmr_uptime_get_ms());
    }
}

  1. 在 indication_gpio_init() 函数中初始化 GPIO,用于指示各个 SleepTimer 唤醒时刻

  2. 在 slptmr_init() 函数中初始化 SleepTimer 1/2 的配置

  3. 在 while (1) 主循环打印当前 Task 的睡眠唤醒 Log,并通过 vTaskDelay() 接口触发系统进出 DeepSleep 状态(注意通过此方式进入低功耗,实际上 OS 内部使用的是 SleepTimer 0 进行定时)

5.2.3 Indication GPIO 初始化程序

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

static void indication_gpio_init(void)
{
    /* Configure GPIO P13/P14/P15 to push-pull output mode */
    GPIO_SetMode(P1, BIT3 | BIT4 | BIT5, GPIO_MODE_OUTPUT);
}
  1. 此函数使用 Panchip Low-Level GPIO Driver 对 GPIO 进行配置

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

  2. 这里我们直接使用 GPIO_SetMode() 接口将 P13/P14/P15 配置为推挽输出模式(初始输出高电平)

5.2.4 SleepTimer 初始化程序

SleepTimer 1/2 初始化程序 slptmr_init() 函数内容如下:

static void slptmr_init(void)
{
    /* Configure timeout of SleepTimer 1&2 with interrupt and wakeup enabled */

    /*
     * Configure timeout:
     * timeout1 = SLPTMR1_TIMEOUT_CNT / PAN_RCC_CLOCK_FREQUENCY_SLOW
     *         = (32000 / 2) / 32000 (s)
     *         = 0.5 s = 500 ms
     * timeout2 = SLPTMR2_TIMEOUT_CNT / PAN_RCC_CLOCK_FREQUENCY_SLOW
     *         = 32000 / 32000 (s)
     *         = 1 s
     */
    const uint32_t SLPTMR1_TIMEOUT_CNT = soc_32k_clock_freq_get() / 2;
    const uint32_t SLPTMR2_TIMEOUT_CNT = soc_32k_clock_freq_get();

    /* Set timeout of SleepTimer 1 */
    LP_SetSleepTime(ANA, SLPTMR1_TIMEOUT_CNT, 1);
    /* Set timeout of SleepTimer 2 */
    LP_SetSleepTime(ANA, SLPTMR2_TIMEOUT_CNT, 2);
}
  1. 此函数功能是:

    • 将 SleepTimer 1 超时时间配置为 500ms

    • 将 SleepTimer 2 超时时间配置为 1s

  2. 配置时间的具体公式计算请参考代码注释

5.2.5 SleepTimer 1/2 中断服务回调函数

SleepTimer 1/2 的中断服务回调函数分别如下:

/* This function overrides the reserved weak function with same name in os_lp.c */
void sleep_timer1_handler(void)
{
    P14 = 0;
    P14 = 1;
    printf("[Uptime: %d ms] SleepTimer 1 IRQ triggered.\n", soc_lptmr_uptime_get_ms());
}

/* This function overrides the reserved weak function with same name in os_lp.c */
void sleep_timer2_handler(void)
{
    P15 = 0;
    P15 = 1;
    printf("[Uptime: %d ms] SleepTimer 2 IRQ triggered.\n", soc_lptmr_uptime_get_ms());
}
  1. 芯片共有 3 个 SleepTimer,分别为 SleepTimer 0、SleepTimer 1 和 SleepTimer 2,它们共用一个中断服务函数 SLPTMR_IRQHandler(),此函数是在 os_lp.c 中实现的;其中 SleepTimer0 被用作 OS Tick,因此在 App 层只可通过回调函数使用 SleepTimer 1 和 SleepTimer 2

  2. 在 App 中实现 sleep_timer1_handler()sleep_timer1_handler() 两个中断回调函数,在其中拉 Indication IO 并打印 Log,这样当对应 SleepTimer 超时后,即可唤醒系统并执行回调函数中的代码

5.2.6 与低功耗相关的 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 例程中的相关介绍