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 与 QDEC 初始 Log¶
QENC Shell 界面下:输入
help命令可以查看帮助信息;按Tab键可以列出所有实现的 Shell 命令。在 QENC Shell 界面输入命令
qenc forward 100,输出 100 个正向相位变化的正交编码波形,观察 QDEC 唤醒与解码情况:
QENC 波形正向变化 100 个相位¶
由 QDEC 端可看出,在 QENC 波形变化了 4 个相位的时候,SoC 成功被唤醒,并最终成功检测出 QENC 正向变化的 100 个相位。
在 QENC Shell 界面输入命令
qenc backward 150,输出 150 个反向相位变化的正交编码波形,观察 QDEC 唤醒与解码情况:
QENC 波形反向变化 150 个相位¶
由 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:       20060 B       384 KB      5.10%
SRAM:        7136 B        64 KB     10.89%