【星闪】Hi2821 | 低功耗开发 + 低功耗管理及按键唤醒例程
1. 简介
1.1 低功耗模式
Hi2821 一共有 3 种低功耗模式——WFI、浅度睡眠和深度睡眠,它们的区别如下:
模式 | 芯片状态 | 特点 |
---|---|---|
WFI |
|
|
浅度睡眠 |
|
|
深度睡眠 |
|
|
1.2 低功耗条件
首先,系统中会有一个投票系统(veto),系统初始时否决票数为 0。如果有某个外设或用户调用 uapi_pm_add_sleep_veto 函数,就会投入否决票,此时系统在空闲时只能进入 WFI 模式。如果不存在否决票,那么系统空闲时会关闭 osTimer 并进入浅睡模式;如果进入浅睡的时间超过了深睡的阈值,那么系统将进入深睡模式。
1.3 唤醒源
浅度睡眠下支持以下唤醒源:
#define PM_LPM_MCPU_CWDT_INT_WAKEUP 14
#define PM_LPM_MCPU_ULP_INT_WAKEUP 13
#define PM_LPM_MCPU_BT_OSC_EN_WAKEUP 12
#define PM_LPM_MCPU_DAP_WAKEUP 11
#define PM_LPM_MCPU_SSI_WAKEUP 10
#define PM_LPM_MCPU_GPIO_WAKEUP 9
#define PM_LPM_MCPU_M_RTC_WAKEUP 8
#define PM_LPM_MCPU_SPI1_INT_WAKEUP 7
#define PM_LPM_MCPU_UART_L1_RX_WAKEUP 6
#define PM_LPM_MCPU_UART_H0_RX_WAKEUP 5
#define PM_LPM_MCPU_UART_L0_RX_WAKEUP 4
#define PM_LPM_MCPU_SPI2_INT_WAKEUP 3
#define PM_LPM_MCPU_QDEC_INT_WAKEUP 2
#define PM_LPM_MCPU_KEYSCAN_INT_WAKEUP 0
深度睡眠下支持以下唤醒源:
#define PM_LPM_MCPU_WKUP_MASK (BIT(PM_LPM_MCPU_CWDT_INT_WAKEUP) | \BIT(PM_LPM_MCPU_ULP_INT_WAKEUP) | \BIT(PM_LPM_MCPU_BT_OSC_EN_WAKEUP) | \BIT(PM_LPM_MCPU_M_RTC_WAKEUP))
1.4 外设下电
外设 | 深睡下电说明 | 唤醒恢复说明 |
---|---|---|
Pinctrl | 输出模式失能,上下拉和驱动能力正常 | 使用 uapi_pin_set_mode 恢复各个外设的管脚模式 |
GPIO | 下电,进入睡眠时 SDK 自动将配置同步到 ULP GPIO | 用户自行恢复 |
UART | 下电 | SDK 自动恢复(使能 CONFIG_UART_SUPPORT_LPM 宏) |
I2C | 用户自行恢复 | |
ADC | 用户自行恢复 | |
DMA | 用户自行恢复 | |
PWM | 用户自行恢复 | |
WDT | SDK 自动恢复(使能 CONFIG_WATCHDOG_SUPPORT_LPM 宏) | |
TIMER | SDK 自动恢复(使能 CONFIG_TIMER_SUPPORT_LPM 宏) | |
RTC | SDK 自动恢复(使能 CONFIG_RTC_SUPPORT_LPM 宏) | |
SysTick | SDK 自动恢复(使能 CONFIG_SYSTICK_SUPPORT_LPM 宏) | |
TCXO | SDK 自动恢复(使能 CONFIG_TCXO_SUPPORT_LPM 宏) | |
SFC | SDK 自动恢复(使能 CONFIG_SFC_SUPPORT_LPM 宏) | |
SPI | 用户自行恢复 | |
QDEC | 用户自行恢复 | |
KEYSCAN | 用户自行恢复 | |
RAM & Flash | 不下电 | |
ULP GPIO | ||
ULP RTC | ||
ULP WDT |
1.5 低功耗调试
1.5.1 中断和任务
在 config.py 中添加 OS_DFX_SUPPORT 全局宏定义,主任务会定时打印最近触发的中断号和运行的任务。
使用此功能时确保没有重定义 app_main 函数;如果重定义了,可以通过调用 print_os_task_id_and_name 函数查看系统任务信息,调用 os_dfx_print_info 函数查看最近触发的中断和运行的任务。
1.5.2 工作和空闲时间统计
在 product_evb_standard.h 中使能 PM_MCPU_MIPS_STATISTICS_ENABLE 宏。
包含 pm_porting.h 头文件,调用 pm_get_time_before_sleep 函数获取进入睡眠前的时刻;调用 pm_get_total_work_time 函数获取工作的总时间;调用 pm_get_time_after_sleep 函数获取唤醒时的时刻;调用 pm_get_total_work_time 函数获取上一次工作的总时长;调用 pm_get_total_idle_time 获取上一次休眠的总时长。
1.5.3 唤醒原因
在 product_evb_standard.h 文件中使能 PM_SLEEP_DEBUG_ENABLE 宏。
唤醒源的的各位说明如下:
- bit0:较为特殊,一般是由于在睡眠过程中被其他唤醒源唤醒导致,定位原因仍需看其他位;
- bit1:ulp_gpio;
- bit2:ulp_rtc(包含任务中的 osal_msleep 行为定时调度和 osal_timer 软调度);
- bit3:osc_en(和蓝牙业务有关,收发数据前需提前唤醒);
- bit4:NFC。
1.5.4 VETO投票信息
包含 pm_veto.h 头文件,调用 uapi_pm_veto_get_info 函数,返回的结构体定义如下:
typedef struct {uint8_t total_counts;uint8_t sub_counts[PM_VETO_ID_MAX];
} pm_veto_counts_t;typedef struct {pm_veto_counts_t veto_counts;uint16_t last_veto_id;uint32_t last_veto_lr;uint64_t veto_timeout_timestamp;
} pm_veto_t;
1.5.5 睡眠信息
需在 Kconfig 中使能 CONFIG_PM_SLEEP_RECORD_ENABLE。
包含 pm_sleep.h 头文件,调用 uapi_pm_get_sleep_info 函数,返回的结构体定义如下:
typedef struct sleep_veto {uint16_t last_veto_count;uint16_t last_veto_id;
} sleep_veto_t;typedef struct sleep_event {uint16_t slp_event;uint16_t wkup_event;
} sleep_event_t;typedef struct sleep_history {uint64_t total_slp_time : 48;uint64_t total_slp_count : 16;
} sleep_history_t;typedef struct sleep_info {uint64_t sleep_base_time;sleep_veto_t sleep_veto;sleep_event_t event;sleep_history_t sleep_history[PM_SLEEP_MAX];
} sleep_info_t;
1.6 Kconfig
- Power supply by ldo:当主控供电采用 LDO 时使能;
- Reduce the frequency of low-speed peripgerals:降低低速外设的工作频率至 8MHz,包括TIMER、UART_L0、UART_L1、I2C、KEYSCAN、QDEC;
- Reduce frequency during wfi:在 WFI 模式下降低外设频率;
- Enable ultra-deep sleep:使能超级低功耗;
- Enable the xo fast start:使能外部晶振快速起振;
- Enable the 32k xo clock:使能外部晶振;
- Support close ulp-wdt during sleep:睡眠时关闭看门狗;
- Prevents pin 18/26/27/31 from mis-triggering interrupts during sleep:避免特定管脚在睡眠时的中断误触发。
- ENABLE SLEEP RECORD:使能睡眠信息记录;
- ENABLE POWERGATING:使能睡眠掉电功能;
- VETO ENABLE TRACK:使能 veto 跟踪;
- The light sleep time threshold:浅睡阈值;
- The deep sleep time threshold:深睡阈值;
- Enable wakeup interrupt:使能唤醒中断;
- Pm sys support:使能低功耗管理任务;
2. 例程
例程中将配置一个 GPIO 管脚为输入中断,演示低功耗应用中的管脚配置和中断管理。
2.1 代码
#include <stdint.h>#include "app_init.h"
#include "pinctrl.h"
#include "gpio.h"
#include "soc_osal.h"
#include "ulp_gpio.h"
#include "pm_sys.h"#define PM_SAMPLE_GPIO_NUM 2static void pm_gpio_irq_func(pin_t pin, uintptr_t param)
{unused(param);osal_printk("[pm_sys]gpio%d irq.\r\n", pin);uapi_gpio_clear_interrupt(pin);uapi_pm_work_state_reset();
}static void pm_ulpgpio_wkup_handler(uint8_t ulp_gpio)
{uapi_pm_wkup_process(0);osal_printk("ulp_gpio%d wakeup\r\n", ulp_gpio);
}static ulp_gpio_int_wkup_cfg_t g_pm_wk_cfg = {0, PM_SAMPLE_GPIO_NUM, true, ULP_GPIO_INTERRUPT_FALLING_EDGE, pm_ulpgpio_wkup_handler
};static void pm_gpio_slp_cfg(void)
{uapi_gpio_deinit();ulp_gpio_init();ulp_gpio_int_wkup_config(&g_pm_wk_cfg, 1);
}void pm_gpio_wkup_cfg(void)
{ulp_gpio_deinit();uapi_gpio_init();uapi_pin_set_mode(PM_SAMPLE_GPIO_NUM, 0);uapi_pin_set_pull(PM_SAMPLE_GPIO_NUM, PIN_PULL_UP);uapi_gpio_set_dir(PM_SAMPLE_GPIO_NUM, GPIO_DIRECTION_INPUT);uapi_gpio_register_isr_func(PM_SAMPLE_GPIO_NUM, GPIO_INTERRUPT_FALLING_EDGE, pm_gpio_irq_func);
}static int32_t pm_state_work_to_standby(uintptr_t arg)
{unused(arg);uint32_t irq_status = osal_irq_lock();pm_gpio_slp_cfg();osal_irq_restore(irq_status);return 0;
}static int32_t pm_state_standby_to_sleep(uintptr_t arg)
{unused(arg);return 0;
}static int32_t pm_state_standby_to_work(uintptr_t arg)
{unused(arg);uint32_t irq_status = osal_irq_lock();pm_gpio_wkup_cfg();osal_irq_restore(irq_status);return 0;
}static int32_t pm_state_sleep_to_work(uintptr_t arg)
{unused(arg);uint32_t irq_status = osal_irq_lock();pm_gpio_wkup_cfg();osal_irq_restore(irq_status);return 0;
}void pm_low_power_entry(void)
{pm_state_trans_handler_t handler = {.work_to_standby = pm_state_work_to_standby,.standby_to_sleep = pm_state_standby_to_sleep,.standby_to_work = pm_state_standby_to_work,.sleep_to_work = pm_state_sleep_to_work,};uapi_pm_state_trans_handler_register(&handler);uapi_pm_work_state_reset();uapi_pm_set_state_trans_duration(5000, 10000);
}app_run(pm_low_power_entry);void app_main(void *unused)
{(void) unused;#if defined(CONFIG_PINCTRL_SUPPORT_IE)uapi_pin_set_ie(PM_SAMPLE_GPIO_NUM, PIN_IE_1);
#endif /* CONFIG_PINCTRL_SUPPORT_IE */pm_gpio_wkup_cfg();app_tasks_init();
}
1. 初始化 GPIO
例程中使用的是 IO2 管脚,调用 uapi_pin_set_ie 函数使能管脚输入(如果使能了 CONFIG_PINCTRL_SUPPORT_IE 宏)。
调用 ulp_gpio_deinit 去初始化 ULP GPIO。
其余的就是基本的 GPIO 初始化,配置管脚为输入上拉,使能中断,下降沿触发。中断服务函数里面打印管脚号,清除中断位,调用 uapi_pm_work_state_reset 去复位低功耗定时器。
2. 初始化低功耗管理
调用 uapi_pm_state_trans_handler_register 函数初始化低功耗管理回调,结构体定义如下:
typedef struct pm_state_trans_handler {pm_state_trans_func_t work_to_standby; /* 工作态转换到待机状态(如interval调大) */pm_state_trans_func_t standby_to_sleep; /* 工作态转换到深睡状态(如断连、关广播) */pm_state_trans_func_t standby_to_work; /* 待机态转换到工作状态(如interval调小) */pm_state_trans_func_t sleep_to_work; /* 深睡态转换到工作状态(如开广播) */
} pm_state_trans_handler_t;
4 个回调里面的操作都是大同小异的,如果是从低功耗到工作状态就初始化 GPIO,如果是从工作到低功耗状态就初始化 ULP GPIO。
初始化 ULP GPIO 时,先调用 uapi_gpio_deinit 去初始化 GPIO,再调用 ulp_gpio_init 初始化 ULP GPIO,最后调用 ulp_gpio_int_wkup_config 配置 ULP GPIO 的中断,结构体定义如下:
typedef enum {ULP_GPIO_INTERRUPT_LOW,ULP_GPIO_INTERRUPT_HIGH,ULP_GPIO_INTERRUPT_FALLING_EDGE,ULP_GPIO_INTERRUPT_RISING_EDGE,ULP_GPIO_INTERRUPT_DEDGE
} ulp_gpio_interrupt_t;typedef struct ulp_gpio_int_wkup_cfg {/* ulp_gpio共有8个(0-7) */uint8_t ulp_gpio;/*** 0-31:IO管脚,参考 pin_t 。* 32:swd_clk管脚* 33:swd_io管脚* 34:gpio或mask0,低电平:0,高电平:1,可以将多个低电平管脚绑定成或mask0,中断触发选上升沿或高电平,一个脚被拉高即触发中断。* 35:gpio与mask0,低电平:0,高电平:1,可以将多个高电平管脚绑定成与mask0,中断触发选下降沿或低电平,一个脚被拉低即触发中断。* 36:gpio或mask1,低电平:0,高电平:1,可以将多个低电平管脚绑定成或mask1,中断触发选上升沿或高电平,一个脚被拉高即触发中断。* 37:gpio与mask1,低电平:0,高电平:1,可以将多个高电平管脚绑定成与mask1,中断触发选下降沿或低电平,一个脚被拉低即触发中断。* 补充:mask管脚绑定可以使用接口 ulp_gpio_mask_pin_set。*/uint8_t wk_mux;bool int_enable;ulp_gpio_interrupt_t trigger;ulp_gpio_irq_cb_t irq_cb;
} ulp_gpio_int_wkup_cfg_t;
中断服务函数里面也是打印管脚号,然后调用 uapi_pm_wkup_process 函数让底层处理唤醒。
调用 uapi_pm_set_state_trans_duration 函数设置浅睡阈值和深睡阈值,单位毫秒。
2.2 测试
系统启动后打印 pm_sys_entry 表示低功耗管理任务正在运行。mail.state switch 指示当前切换的低功耗模式,定义如下:
typedef enum pm_state_trans {PM_STATE_WORK_TO_STANDBY = 0,PM_STATE_STANDBY_TO_SLEEP = 1,PM_STATE_STANDBY_TO_WORK = 2,PM_STATE_SLEEP_TO_WORK = 3,PM_STATE_TRANS_NUM = 4,
} pm_state_trans_t;
系统启动后,等待 5 秒后进入浅睡模式;再次等待 5 秒,进入深睡模式;此时将 IO2 接地,ULP_GPIO0 中断触发,芯片切换到工作模式;此时再次将 IO2 接地,现在就是 GPIO2 的中断触发。