LowPower: DeepSleep KSCAN Wakeup¶
1 功能概述¶
本例程演示如何使 SoC 进入 DeepSleep 状态,然后通过连接 KSCAN 模块的矩阵按键将其唤醒。
2 环境准备¶
硬件设备与线材:
PAN1080 EVB 核心板与底板各一块
PAN1080 EVB 配套 8x8 矩阵按键板(PAN1080X Development Kit Keyboard V2.1)一块
JLink 仿真器(用于烧录例程程序)
USB-TypeC 线一条(用于底板供电和查看串口打印 Log)
杜邦线数根(用于连接各个硬件设备)
硬件接线:
将 EVB 核心板插到 EVB 底板上
将 Keyboard 按键板通过排线接到 EVB 底板上
使用 USB-TypeC 线,将 PC USB 插口与 EVB 底板 USB->UART 插口相连
根据核心板芯片的型号,选择对应的 UART Log 线接法:
确认核心板芯片为LQFP64 封装,然后使用杜邦线将 EVB 底板上的 TX0 引脚与核心板上的 P30 引脚相连, EVB 底板上的 RX0 引脚与核心板上的 P31 引脚相连
由于 kscan 引脚限制,本例程不支持封装为 QFN32 和 QFN48 的核心板
使用杜邦线将 JLink 仿真器的:
SWD_CLK 引脚与 EVB 核心板的 P46 引脚相连
SWD_DAT 引脚与 EVB 核心板的 P47 引脚相连
SWD_GND 引脚与 EVB 核心板的 GND 引脚相连
PC 软件:
终端工具(SecureCRT),波特率 921600(用于接收串口打印 Log)
3 编译和烧录¶
例程位置:zephyr\samples_panchip\low_power\deepsleep_kscan_wakeup
使用 ZAL 工具可以对其进行编译、烧录、打开 VS Code 调试等操作。关于 ZAL 工具的详细介绍请参考:Zephyr APP Launcher 工具介绍。
4 例程演示说明¶
使用 ZAL 工具将 KSCAN Wakeup 例程编译烧录至芯片,程序在初始化完成后立刻进入 DeepSleep 模式:
Try to load HW calibration data.. DONE. - Chip Type : 0x80 - Chip CP Version : None - Chip FT Version : 7 - Chip MAC Address : D0000C06FB74 - Chip Flash UID : 31373237304A6D074330FFFFFFFFFFFF - Chip Flash Size : 1024 KB *** Booting Zephyr OS build zephyr-v2.7.0 *** Initialize hardware KSCAN module.. main(): Try to enter deepsleep mode..
按一下 Keyboard 按键板上的任意一个按键(如 K11),可以看到芯片被成功从 DeepSleep 状态下唤醒,然后在 1s 以后重新进入 DeepSleep 模式:
main(): Waked up from deepsleep mode. K11 pressed.. K11 released.. main(): Try to enter deepsleep mode..
再按住 Keyboard 按键板上的任意一个按键(如 K64),然后在 2s 以后松开,可以观察到芯片被按键按下的事件唤醒,稍后重新进入 DeepSleep 模式,然后又被按键松开的事件重新唤醒:
main(): Waked up from deepsleep mode. K64 pressed.. main(): Try to enter deepsleep mode.. main(): Waked up from deepsleep mode. K64 released.. main(): Try to enter deepsleep mode..
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
# Increase Low power LDO calibrate code (can be 0/1/2/3)
CONFIG_SOC_INCREASE_LLDO_CALIB_CODE=1
# 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_INCREASE_LLDO_CALIB_CODE=1
:强制提高芯片 LLDO(Lowpower LDO)的校准档位(当芯片使用 KSCAN 无法正常唤醒的时候可以尝试配置此功能)CONFIG_SOC_DCDC_PAN1080=y
:使能芯片的 DCDC 供电模式,以降低芯片动态功耗
5.2 App DeviceTree 配置¶
本例程的 App DeviceTree(对应 app.overlay 文件)配置如下:
&gpioctrl {
/* port, pin, pinmux_name, pinmux_sel [, flag1, ... ] */
DT_PAN_PINS(p3, 1, uart0_rx, PAN1080_PIN_FUNC_P31_UART0_RX, input-enable);
};
&uart0 {
current-speed = <921600>;
pinctrl-0 = <&p3_0_uart0_tx &p3_1_uart0_rx>;
status = "okay";
};
&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>;
};
其中:
更新 gpioctrl 中的引脚属性配置,将 P31(用作 UART Rx)引脚的
input-enable
功能打开,以使能此引脚的数字输入功能将 uart0 的
pinctrl-0
属性配置为<&p3_0_uart0_tx &p3_1_uart0_rx>
,以将 uart0 的 tx 重定向到 P30 引脚,rx重定向到 P31 引脚将 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)
{
/* 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 KSCAN module and enable wakeup */
kscan_module_init();
while (1) {
printk("%s(): Try to enter deepsleep mode..\n\n", __func__);
k_sem_take(&wakeup_sem, K_FOREVER);
printk("%s(): Waked up from deepsleep mode.\n", __func__);
/* Busy wait 1s to avoid soc entering deepsleep mode again after wakeup */
k_busy_wait(1000000);
}
}
初始化一个名为 wakeup_sem 的信号量
获取 PINMUX 的设备指针,用于后面修改 pinmux 引脚配置的时候使用
在 kscan_module_init() 函数中初始化 KSCAN 硬件模块
在
while (1)
循环中等待wakeup_sem
信号量,然后 busy wait 1s 后重新通过k_sem_take
的方式进入 DeepSleep 状态
5.3.2 KSCAN 初始化程序¶
KSCAN 初始化程序 kscan_module_init() 函数内容如下:
static void kscan_io_init(void)
{
/* Set pinmux to enable kscan input function */
SYS_SET_MFP(P2, 0, KSCAN_I0);
SYS_SET_MFP(P2, 1, KSCAN_I1);
SYS_SET_MFP(P2, 2, KSCAN_I2);
SYS_SET_MFP(P2, 3, KSCAN_I3);
SYS_SET_MFP(P1, 4, KSCAN_I4);
SYS_SET_MFP(P1, 5, KSCAN_I5);
SYS_SET_MFP(P1, 6, KSCAN_I6);
SYS_SET_MFP(P1, 7, KSCAN_I7);
/* Enable internal pull-down resistor on kscan input pads */
GPIO_EnablePulldownPath(P2, BIT0 | BIT1 | BIT2 | BIT3);
GPIO_EnablePulldownPath(P1, BIT4 | BIT5 | BIT6 | BIT7);
/* Enable digital input path on kscan input pads */
GPIO_EnableDigitalPath(P2, BIT0 | BIT1 | BIT2 | BIT3);
GPIO_EnableDigitalPath(P1, BIT4 | BIT5 | BIT6 | BIT7);
/* Enable kscan input pins */
KS_InputEnable(KSCAN, ENABLE, 0xFF);
/* Set pinmux to enable kscan output function */
SYS_SET_MFP(P5, 0, KSCAN_O0);
SYS_SET_MFP(P5, 1, KSCAN_O1);
SYS_SET_MFP(P4, 0, KSCAN_O2);
SYS_SET_MFP(P5, 3, KSCAN_O3);
SYS_SET_MFP(P5, 5, KSCAN_O4);
SYS_SET_MFP(P5, 7, KSCAN_O5);
SYS_SET_MFP(P0, 0, KSCAN_O6);
SYS_SET_MFP(P0, 1, KSCAN_O7);
/* Enable kscan output pins */
KS_OutputEnable(KSCAN, ENABLE, 0x0000FF);
}
static void kscan_module_init(void)
{
printk("Initialize hardware KSCAN module..\n");
/* Enable clock of hw kscan module */
CLK_APB2PeriphClockCmd(CLK_APB2Periph_KSCAN, ENABLE);
/* Select 32K clock as scan source */
CLK_SetKeyscanClkSrc(CLKTRIM_KSCAN_CLK_SEL_32K);
/* Init IO for kscan */
kscan_io_init();
/* Enable Interrupt */
KS_IntEnable(KSCAN, ENABLE);
IRQ_DIRECT_CONNECT(KSCAN_IRQn, KSCAN_HW_INTERRUPT_PRIORITY, kscan_isr, 0);
irq_enable(KSCAN_IRQn);
/* Set valid scan signal to high level */
KS_SetKeyscanPolarity(KSCAN, ENABLE);
/* Set scan interval between rows, t_clk_cnt = reg_val + 1 */
KS_SetRowInterval(KSCAN, 0x2);
/* Set scan interval between frames, t_clk_cnt = 2 ^ reg_val + 1 */
KS_SetFrameInterval(KSCAN, 0x1);
/* Set debounce time when key pressed, t_clk_cnt = 2 ^ reg_val + 1 */
KS_SetDebounceTime(KSCAN, 0x2);
/*
* Now the scan interval of each frame becomes:
* t_interval = t_row_interval * 24 + t_frame_interval * 24
* = ((row_reg_val + 1) * 24 + (2 ^ frame_reg_val + 1) * 24) / 32000
* = ((2 + 1) * 24 + (2^1 + 1) * 24) / 32000 s
* = 0.0045s = 4.5ms
*/
/* Clear All Keys' Status Regs */
KS_ClearKeyscanStatus(KSCAN, ENABLE);
/* Start Scanning and wakeup feature */
KS_Enable(KSCAN, ENABLE, KS_WAKEUP_AND_PERIPH_ENABLE);
}
此函数使用 Panchip HAL KSCAN Driver 进行配置
开启 KSCAN 硬件模块的时钟
设置 KSCAN 扫描时钟为 32K 低速时钟
KSCAN 用到的 IO 较多,因此将 IO 相关的配置单独写在
kscan_io_init()
函数中设置 KSCAN 的中断功能
设置 KSCAN 为高电平扫描
设置 KSCAN 扫描间隔参数
使能 KSCAN 及唤醒功能
5.3.3 KSCAN 中断服务程序¶
KSCAN 中断服务程序如下:
__ramfunc static void kscan_isr(void)
{
static uint8_t last_key_status[24] = { 0 };
uint8_t *ks_info_base = (uint8_t *)&KSCAN->KS_INFO0;
uint8_t key_detect_flag;
for (size_t row = 0; row < 24; row++) {
key_detect_flag = ks_info_base[row] ^ last_key_status[row];
if (key_detect_flag) {
for (size_t col = 0; col < 8; col++) {
if (key_detect_flag & BIT(col)) {
if (ks_info_base[row] & BIT(col)) {
printk("K%d pressed..\n", row + col * 8 + 1);
} else {
printk("K%d released..\n", row + col * 8 + 1);
}
}
}
}
last_key_status[row] = ks_info_base[row];
}
#ifdef KSCAN_EVENT_DEBUG
printk("\nKey status change detected..\n");
for (size_t i = 0; i < 24; i++) {
if (i % 8 == 0) {
printk("KSCAN ROW INFO %02d~%02d: ", i, i + 7);
}
printk("0x%02x ", ks_info_base[i]);
if ((i + 1) % 8 == 0) {
printk("\n");
}
}
#endif
/* Clear all flags if there is */
KS_ClearFlag(KSCAN, KS_INT_FLAG_Msk | KS_WK_FLAG_Msk | KS_FLAG_Msk);
/* Re-enable kscan */
KS_Enable(KSCAN, ENABLE, KS_PERIPHRAL_ENABLE);
/* To indicate zephyr to schedule new ready thread if there is */
z_arm_int_exit();
}
检查是哪个按键引起的中断,并判断按键是按下还是释放
清除 KSCAN 相关的中断标志位
执行 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_P30, PAN1080_PIN_FUNC_P30_GPIO);
pinmux_pin_set(pinmux, PAN_PIN_P31, PAN1080_PIN_FUNC_P31_GPIO);
pinmux_pin_input_enable(pinmux, PAN_PIN_P31, 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_P30, PAN1080_PIN_FUNC_P30_UART0_TX);
pinmux_pin_set(pinmux, PAN_PIN_P31, PAN1080_PIN_FUNC_P31_UART0_RX);
pinmux_pin_input_enable(pinmux, PAN_PIN_P31, PAN_PIN_DIG_INPUT_PATH_ENABLE);
#endif
/* Trigger main thread reschedule after wakeup from deepsleep mode */
k_sem_give(&wakeup_sem);
}
本例程在
z_power_hw_deep_sleep_enter_hook()
函数中,为防止 UART IO 漏电,编写了相关代码以确保在进入 DeepSleep 模式前:串口 Log 数据都打印完毕(即 UART0 Tx FIFO 应为空)
P30 引脚 Pinmux 功能由 UART0 Tx 切换回 GPIO
P31 引脚 Pinmux 功能由 UART0 Rx 切换回 GPIO,并将其数字输入功能关闭
本例程在
z_power_hw_deep_sleep_exit_hook()
函数中:编写了相关代码以恢复串口 Log 打印功能:
P30 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Tx
P31 引脚 Pinmux 功能由 GPIO 重新切换成 UART0 Rx,并将其数字输入功能重新打开
调用 k_sem_give() 接口,释放
wakeup_sem
信号量,使程序从低功耗状态下唤醒后,能够调度到 main() 函数中
6 RAM/Flash资源使用情况¶
Memory region Used Size Region Size %age Used
FLASH: 19888 B 384 KB 5.06%
SRAM: 7112 B 64 KB 10.85%