LowPower: DeepSleep QDEC Wakeup¶
1 功能概述¶
本例程演示如何使 SoC 进入 DeepSleep 状态,然后通过 QDEC 模块输入信号将其唤醒。
2 环境准备¶
硬件设备与线材:
PAN1080 EVB 核心板 2 块,底板 1 块,其中:
一块核心板1 + 底板用作正交编码信号发生器,具体操作请参照 LowPower: DeepSleep Quadrature Encoder 例程文档
另一块核心板2用作正交解码器(QDEC),演示正交信号输入唤醒功能
JLink 仿真器(用于烧录例程程序)
USB 转 UART 模块(用于查看 QDEC 唤醒例程的串口 Log)
USB-TypeC 线一条(用于底板供电和查看串口打印 Log)
杜邦线数根(用于连接各个硬件设备)
硬件接线:
给 EVB 核心板2供电
根据核心板2芯片的型号,选择对应的 UART Log 线接法:
若核心板2芯片为 QFN32 或 LQFP64 封装,则使用杜邦线将 USB 转 UART 模块的 RX 引脚与核心板上的 P00 引脚相连,USB 转 UART 模块的 TX 引脚与核心板上的 P01 引脚相连
若核心板2芯片为 QFN48 封装,则使用杜邦线将 USB 转 UART 模块的 RX 引脚与核心板上的 P30 引脚相连,USB 转 UART 模块的 TX 引脚与核心板上的 P31 引脚相连
使用杜邦线将 JLink 仿真器的:
SWD_CLK 引脚与 EVB 核心板2的 P46 引脚相连
SWD_DAT 引脚与 EVB 核心板2的 P47 引脚相连
SWD_GND 引脚与 EVB 核心板2的 GND 引脚相连
使用杜邦线将核心板1(底板)的:
P30 引脚(输出正交编码波形 Signal A)连接至核心板2的 P20 引脚(QDEC Z0)
P31 引脚(输出正交编码波形 Signal B)连接至核心板2的 P21 引脚(QDEC Z1)
PC 软件:
终端工具(SecureCRT),波特率 921600(用于接收串口打印 Log)
3 编译和烧录¶
例程位置:zephyr\samples_panchip\low_power\deepsleep_qdec_wakeup
使用 ZAL 工具可以对其进行编译、烧录、打开 VS Code 调试等操作。关于 ZAL 工具的详细介绍请参考:Zephyr APP Launcher 工具介绍。
4 例程演示说明¶
使用 ZAL 工具分别将 QENC 和 QDEC Wakeup 例程编译烧录至芯片,程序运行成功后可分别在 2 个串口终端上看到如下界面:
QENC Shell 界面下:输入
help
命令可以查看帮助信息;按Tab
键可以列出所有实现的 Shell 命令。在 QENC Shell 界面输入命令
qenc forward 100
,输出 100 个正向相位变化的正交编码波形,观察 QDEC 唤醒与解码情况:由 QDEC 端可看出,在 QENC 波形变化了 4 个相位的时候,SoC 成功被唤醒,并最终成功检测出 QENC 正向变化的 100 个相位。
在 QENC Shell 界面输入命令
qenc backward 150
,输出 150 个反向相位变化的正交编码波形,观察 QDEC 唤醒与解码情况:由 QDEC 端可看出,在 QENC 波形变化了 4 个相位的时候(由 100 变为 96),SoC 成功被唤醒,并最终成功检测出 QENC 反向变化的 150 个相位。
5 开发者说明¶
5.1 App Config 配置¶
本例程的 App Config(对应 prj.conf 文件)配置如下:
# Enable Low Power Flow
CONFIG_PM=y
CONFIG_BT_CTLR_SLEEP_CLOCK_SOURCE=1
# Forcely calibrate 32K RCL Clock
CONFIG_SOC_FORCE_CALIB_RCL_CLK=y
# Enable DC/DC
CONFIG_SOC_DCDC_PAN1080=y
其中:
CONFIG_PM=y
:使能低功耗流程CONFIG_BT_CTLR_SLEEP_CLOCK_SOURCE=1
:低功耗时钟相关配置(目前必须固定配置为1)CONFIG_SOC_FORCE_CALIB_RCL_CLK=y
:在系统初始化阶段强制校准内部 32K RCL Clock 时钟(若当前使用的芯片为校准后的芯片,则此开关也可以不开以节约系统启动时间)CONFIG_SOC_DCDC_PAN1080=y
:使能芯片的 DCDC 供电模式,以降低芯片动态功耗
5.2 App DeviceTree 配置¶
本例程的 App DeviceTree(对应 app.overlay 文件)配置如下:
&uart1 {
status = "disabled";
};
&clk_xtl {
/* Frequency of XTL clock, DO NOT CHANGE THIS ITEM */
clock-frequency = <32768>;
/* Enable/Disable the external 32768 Hz low speed crystal oscillator */
status = "disabled";
};
&clk_rcl {
/* Frequency of RCL clock, DO NOT CHANGE THIS ITEM */
clock-frequency = <32000>;
/* Enable/Disable the internal 32 KHz Low Speed RC */
status = "okay";
};
&dpll {
/* f(dpll_output) = f(dpll_input) / clock_div * clock_mult */
clocks = <&clk_xth>;
clock-div = <2>; /* Fixed to 2 */
clock-mult = <4>; /* Can be 3 (48MHz) or 4 (64MHz) */
status = "okay";
};
&rcc {
clock-names = "clk_system", "clk_slow";
clocks = <&dpll &clk_rcl>;
clock-frequency-system = <DT_FREQ_M(64)>;
clock-frequency-slow = <32000>;
ahb-prescaler = <1>;
apb1-prescaler = <1>;
apb2-prescaler = <1>;
};
其中:
将 uart1 的
status
属性配置为disabled
以禁止系统上电后初始化 uart1 设备,确保不会因为 uart1 的引脚配置产生 IO 漏电将 clk_xtl 的
status
属性配置为disabled
以禁用 32768Hz XTL 低速晶振(此为 SDK 默认配置)将 clk_rcl 的
status
属性配置为okay
以打开 32kHz RCL 内部低速时钟(此为 SDK 默认配置)将 dpll 的
clock-mult
属性配置为4
以将 DPLL 时钟输出配置为 64MHz(此为 SDK 默认配置)将 rcc 的:
clocks
属性的第 1 项(clk_system)配置为dpll
以将系统高速时钟(AHB 总线时钟)源选择为 DPLL 时钟(此为 SDK 默认配置)clocks
属性的第 2 项(clk_slow)配置为clk_rcl
以使系统使用 32kHz RCL 内部低速时钟(此为 SDK 默认配置)clock-frequency-slow
属性配置为32000
以告诉系统当前使用的低速时钟频率为 32000 Hz(此为 SDK 默认配置)
5.3 程序代码¶
5.3.1 主程序¶
主程序 main() 函数内容如下:
void main(void)
{
int16_t temp_cnt, curr_cnt, last_cnt, diff;
/* Initialize wakeup semaphore */
k_sem_init(&wakeup_sem, 0, 1);
/* Get pinmux device structure */
pinmux = device_get_binding("PINMUX");
if (pinmux == NULL) {
printk("get device PINMUX error!\n");
return;
}
/* Init hardware QDEC module and enable wakeup */
qdec_module_init();
while (1) {
/* Record qdec step counter before entering deepsleep mode */
last_cnt = QDEC_GetEventCnt(QDEC_CNT_IDX_Z);
printk("Try to enter deepsleep mode..\n\n");
k_sem_take(&wakeup_sem, K_FOREVER);
printk("Waked up from deepsleep mode.\n");
/* Wait until input waveform end for 100ms */
temp_cnt = QDEC_GetEventCnt(QDEC_CNT_IDX_Z);
for (size_t i = 0; i < 100; i++) {
curr_cnt = QDEC_GetEventCnt(QDEC_CNT_IDX_Z);
if (curr_cnt != temp_cnt) {
temp_cnt = curr_cnt; /* Update temp_cnt */
i = 0; /* Reset time count value */
}
k_busy_wait(1000); /* Busy wait 1ms */
}
diff = curr_cnt - last_cnt;
if (is_qdec_overflow && (diff < 0)) {
is_qdec_overflow = false;
diff += 32767;
}
if (is_qdec_underflow && (diff > 0)) {
is_qdec_underflow = false;
diff -= 32768;
}
if (diff > 0) {
printk("Quardrature waveform go FORWARD %d steps.\n", diff);
} else {
printk("Quardrature waveform go BACKWARD %d steps.\n", -diff);
}
printk("Total step count: %d\n\n", curr_cnt + qdec_cntr_wrap_cnt);
}
}
初始化一个名为 wakeup_sem 的信号量
在 QDEC 唤醒中断触发时通过 give 此信号量可以激活 main 线程的调度
获取 PINMUX 的设备指针,用于后面修改 pinmux 引脚配置的时候使用
在 qdec_module_init() 函数中初始化 QDEC 硬件模块
使用 QDEC_GetEventCnt() 接口获取当前 QDEC 检测到的输入信号变化个数,此接口默认检测个数的范围为 -32768 ~ 32767
分别使用两个名为
is_qdec_overflow
和is_qdec_underflow
的变量记录输入计数是否发生上溢或下溢情况
5.3.2 QDEC 初始化程序¶
QDEC 初始化程序 qdec_module_init() 函数内容如下:
static void qdec_module_init(void)
{
printk("Initialize hardware QDEC module..\n");
/* Set pinmux of P20/P21 to QDEC Z0/Z1 */
SYS_SET_MFP(P2, 0, QDEC_Z0);
SYS_SET_MFP(P2, 1, QDEC_Z1);
/* Enable digital input path of P20/P21 */
GPIO_EnableDigitalPath(P2, BIT0 | BIT1);
/* Enable clock of HW QDEC module */
CLK_APB2PeriphClockCmd(CLK_APB2Periph_QDEC, ENABLE);
/* Set QDEC clock source to the low speed 32K clock to make sure it can be wakeup from deepsleep mode */
CLK_SetQdecClkSrc(CLKTRIM_QDEC_CLK_SEL_32K);
/* Do not divide QDEC clock source (default) */
CLK_SetQdecDiv(0);
/* Set QDEC counter resolution to 4X */
QDEC_SetCntResolution(QDEC_CNT_RESOLUTION_4X);
/* Set QDEC input filter to 3 clock count (default) */
QDEC_SetFilterThreshold(QDEC_FILTER_PERIOD_3);
/* Enable Qdec counter overflow and underflow interrupt */
QDEC_ClearIntMsk(QDEC_INT_CNT_OVERFLOW_Msk | QDEC_INT_CNT_UNDERFLOW_Msk, ENABLE);
IRQ_DIRECT_CONNECT(QDEC_IRQn, 3, qdec_isr, 0);
irq_enable(QDEC_IRQn);
/* Enable QDEC wakeup feature */
QDEC_Enable(ENABLE, QDEC_WAKEUP_EN_Msk);
/* Enable QDEC module, filter feature, and channel Z */
QDEC_Enable(ENABLE, QDEC_EN_Msk | QDEC_FILTER_EN_Msk | QDEC_CHANNEL_Z_EN_Msk);
}
此函数使用 Panchip HAL QDEC Driver 进行配置
配置 P20/P21 引脚的 PINMUX 为 QDEC 功能
使能两个引脚的数字输入功能
开启 QDEC 硬件模块的时钟
设置 QDEC 计数时钟为 32K 低速时钟
设置 QDEC 的时钟分频为不分频(硬件默认配置)
设置 QDEC 的计数分辨率为 4X,即输入信号的相位发生 90° 变化,计数值就会变化1
设置 QDEC 的中断功能,并使能上溢和下溢中断
使能 QDEC 的 Channel Z,并使能唤醒功能
5.3.3 QDEC 中断服务程序¶
QDEC 中断服务程序如下:
__ramfunc static void qdec_isr(void)
{
if (QDEC_IsIntOccured(QDEC_INT_CNT_OVERFLOW_Msk)) {
QDEC_ClearIntFlag(QDEC_INT_CNT_OVERFLOW_Msk);
qdec_cntr_wrap_cnt += 32767;
is_qdec_overflow = true;
printk("QDEC overflow evt, curr cnt: %d\n", (int16_t)QDEC_GetEventCnt(QDEC_CNT_IDX_Z));
}
if (QDEC_IsIntOccured(QDEC_INT_CNT_UNDERFLOW_Msk)) {
QDEC_ClearIntFlag(QDEC_INT_CNT_UNDERFLOW_Msk);
qdec_cntr_wrap_cnt -= 32768;
is_qdec_underflow = true;
printk("QDEC underflow evt, curr cnt: %d\n", (int16_t)QDEC_GetEventCnt(QDEC_CNT_IDX_Z));
}
if (QDEC_IsIntOccured(QDEC_INT_WAKEUP_Msk)) {
QDEC_ClearIntFlag(QDEC_INT_WAKEUP_Msk);
printk("QDEC wakup evt, curr cnt: %d\n", (int16_t)QDEC_GetEventCnt(QDEC_CNT_IDX_Z));
/* Trigger main thread reschedule */
k_sem_give(&wakeup_sem);
}
/* To indicate zephyr to schedule new ready thread if there is */
z_arm_int_exit();
}
检查是否发生 QDEC 计数上溢中断,如果是,则:
清除中断标志位
更新全局的 QDEC 总计数
qdec_cntr_wrap_cnt
,并置位is_qdec_overflow
标志
检查是否发生 QDEC 计数下溢中断,如果是,则:
清除中断标志位
更新全局的 QDEC 总计数
qdec_cntr_wrap_cnt
,并置位is_qdec_underflow
标志
检查是否发生 QDEC 唤醒中断,如果是,则:
清除中断标志位
give 信号量,当中断服务程序返回后将会触发线程调度,而对于此例程来说则是重新调度至 main 线程的 main() 函数的
k_sem_take(wakeup_sem)
处继续执行
执行 z_arm_int_exit() 以通知 OS 当前中断执行完毕,允许 OS 在中断退出后立刻启动线程调度
5.3.4 与低功耗相关的 Hook 函数¶
本例程实现了 2 个与低功耗相关的 Hook 函数:
__ramfunc void z_power_hw_deep_sleep_enter_hook(void)
{
#ifdef CONFIG_SERIAL
/* 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.
*/
pinmux_pin_set(pinmux, PAN_PIN_P00, PAN1080_PIN_FUNC_P00_GPIO);
pinmux_pin_set(pinmux, PAN_PIN_P01, PAN1080_PIN_FUNC_P01_GPIO);
pinmux_pin_input_enable(pinmux, PAN_PIN_P01, PAN_PIN_DIG_INPUT_PATH_DISABLE);
#endif
}
__ramfunc void z_power_hw_deep_sleep_exit_hook(void)
{
#ifdef CONFIG_SERIAL
/* Resume UART PIN Configurations to reenable UART function */
pinmux_pin_set(pinmux, PAN_PIN_P00, PAN1080_PIN_FUNC_P00_UART0_TX);
pinmux_pin_set(pinmux, PAN_PIN_P01, PAN1080_PIN_FUNC_P01_UART0_RX);
pinmux_pin_input_enable(pinmux, PAN_PIN_P01, PAN_PIN_DIG_INPUT_PATH_ENABLE);
#endif
}
本例程在
z_power_hw_deep_sleep_enter_hook()
函数中,为防止 UART IO 漏电,编写了相关代码以确保在进入 DeepSleep 模式前:串口 Log 数据都打印完毕(即 UART0 Tx FIFO 应为空)
P00 引脚 Pinmux 功能由 UART0 Tx 切换回 GPIO
P01 引脚 Pinmux 功能由 UART0 Rx 切换回 GPIO,并将其数字输入功能关闭
本例程在
z_power_hw_deep_sleep_exit_hook()
函数中,编写了相关代码以恢复串口 Log 打印功能:P00 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Tx
P01 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Rx,并将其数字输入功能重新打开
6 RAM/Flash资源使用情况¶
Memory region Used Size Region Size %age Used
FLASH: 19920 B 384 KB 5.07%
SRAM: 7128 B 64 KB 10.88%