Manual Scan Mode
该示例使用 Keyscan 的手动扫描模式,实现矩阵键盘的扫描功能。
示例中使用软件定时器实现按键按下或释放的去抖功能。
当单次扫描结束时,进入 Keyscan 单次扫描完成中断,在中断处理函数内读取按键信息。
环境需求
该示例支持以下开发套件:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
更多信息请参考 快速入门。
硬件连线
EVB 外接矩阵键盘,连接 P2_3 和 ROW0,P2_4 和 ROW1,P4_0 和 COLUMN0,P4_1 和 COLUMN1。
外接矩阵键盘的原理图如下图所示:

外接矩阵键盘
编译和下载
该示例的工程路径如下:
Project file: board\evb\io_sample\KEYSCAN\Keyscan_manual\mdk
Project file: board\evb\io_sample\KEYSCAN\Keyscan_manual\gcc
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
初始化软件定时器,打印 log。
[io_keyscan] timer_keyscan_init: keyscan timer init
当检测到按键按下后,打印按键信息。
若检测到单个按键按下,打印如下 log。
[io_keyscan] io_keyscan_handle_keys: Single key press. key: (x, x)
若检测到两个按键按下,打印如下 log。
[io_keyscan] io_keyscan_handle_keys: Two key press. key0: (x, x), key1: (x, x)
当按键全部松开后,打印如下 log。
[io_keyscan] io_keyscan_handle_keys: All keys release.
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
sdk\board\evb\io_sample\KEYSCAN\Keyscan_manual
源码路径:
sdk\src\sample\io_sample\KEYSCAN\Keyscan_manual
该工程的工程文件代码结构如下:
└── Project: keyscan_manual
└── secure_only_app
└── include
├── app_define.h
└── rom_uuid.h
├── cmsis includes CMSIS header files and startup files
├── overlay_mgr.c
├── system_rtl876x.c
└── startup_rtl876x.s
├── lib includes all binary symbol files that user application is built on
├── rtl8752h_sdk.lib
├── gap_utils.lib
└── ROM.lib
├── peripheral includes all peripheral drivers and module code used by the application
├── rtl876x_rcc.c
├── rtl876x_pinmux.c
├── rtl876x_nvic.c
├── rtl876x_gdma.c
└── rtl876x_keyscan.c
├── profile
└── app includes the ble_peripheral user application implementation
├── main.c
├── ancs.c
├── app.c
├── app_task.c
├── app_timer.c
└── io_keyscan.c
初始化
当 EVB 复位启动时,调用 main()
函数,将执行以下流程:
int main(void)
{
extern uint32_t random_seed_value;
srand(random_seed_value);
global_data_init();
board_init();
le_gap_init(APP_MAX_LINKS);
gap_lib_init();
app_le_gap_init();
app_le_profile_init();
pwr_mgr_init();
sw_timer_init();
task_init();
os_sched_start();
return 0;
}
备注
le_gap_init()
,gap_lib_init()
,app_le_gap_init
,app_le_profile_init
等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。
与外设相关的初始化流程具体如下:
在
global_data_init
中,执行global_data_keyscan_init
,初始化全局变量Current_Key_Data
。void global_data_keyscan_init(void) { /* Data struct init */ memset(&Current_Key_Data, 0, sizeof(KeyScan_Data_TypeDef)); }
在
board_init
中,执行board_keyboard_init
,该函数为 PAD/PINMUX 设置,包含如下流程:配置 PAD:设置引脚、PINMUX 模式、PowerOn。其中 ROW 相关引脚设置内部上拉,输出失能,COL 相关引脚无内部上拉,输出低。
配置 PINMUX:分配引脚分别为 KEY_ROW_0、KEY_ROW_1、KEY_COL_0、KEY_COL_1 功能。
void board_keyboard_init(void) { /* Keypad pad config */ Pad_Config(KEYBOARD_ROW_0, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW); Pad_Config(KEYBOARD_ROW_1, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW); Pad_Config(KEYBOARD_COLUMN_0, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_LOW); Pad_Config(KEYBOARD_COLUMN_1, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_LOW); /* keypad pinmux config */ Pinmux_Config(KEYBOARD_ROW_0, KEY_ROW_0); Pinmux_Config(KEYBOARD_ROW_1, KEY_ROW_1); Pinmux_Config(KEYBOARD_COLUMN_0, KEY_COL_0); Pinmux_Config(KEYBOARD_COLUMN_1, KEY_COL_1); }
在执行
os_sched_start()
开启任务调度后,在app_main_task
主任务内,执行driver_init
对外设驱动进行初始化配置。在
driver_init
中执行driver_keyboard_init
,该函数为 Keyscan 外设的初始化,包含如下流程:使能 RCC 时钟。
配置 Keyscan 的 rowSize 和 colSize 为(2*2)。
配置扫描模式为 Manual Mode。
使能 Keyscan 的硬件去抖功能。
配置 Keyscan 单次扫描结束中断
KEYSCAN_INT_SCAN_END
。使能 Keyscan 外设。
void driver_keyboard_init(uint32_t vDebounce_En) { /* Turn on keyscan clock */ RCC_PeriphClockCmd(APBPeriph_KEYSCAN, APBPeriph_KEYSCAN_CLOCK, ENABLE); KEYSCAN_InitTypeDef KEYSCAN_InitStruct; KeyScan_StructInit(&KEYSCAN_InitStruct); KEYSCAN_InitStruct.rowSize = KEYBOARD_ROW_SIZE; KEYSCAN_InitStruct.colSize = KEYBOARD_COLUMN_SIZE; KEYSCAN_InitStruct.scanmode = KeyScan_Manual_Scan_Mode; KEYSCAN_InitStruct.debounceEn = vDebounce_En; KeyScan_Init(KEYSCAN, &KEYSCAN_InitStruct); KeyScan_INTConfig(KEYSCAN, KEYSCAN_INT_SCAN_END, ENABLE); KeyScan_ClearINTPendingBit(KEYSCAN, KEYSCAN_INT_SCAN_END); KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, DISABLE); /* Unmask keyscan interrupt */ KeyScan_Cmd(KEYSCAN, ENABLE); /* Keyscan IRQ */ NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = KeyScan_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = (FunctionalState)ENABLE; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_Init(&NVIC_InitStruct); }
在
sw_timer_init
中执行timer_keyscan_init
,该函数为软件定时器的初始化,包含如下流程:执行
os_timer_create()
函数,创建软件定时器,定时时间为 200ms,定时结束后执行timer_keyscan_callback
回调函数,用于模拟 Keyscan 按键按下的 Debounce。
void timer_keyscan_init(void) { APP_PRINT_INFO0("[io_keyscan] timer_keyscan_init: keyscan timer init"); if (false == os_timer_create(&KeyScan_Timer_Handle, "keyscan_timer", 1, \ KEYSCAN_SW_INTERVAL, false, timer_keyscan_callback)) { APP_PRINT_ERROR0("[io_keyscan] timer_keyscan_init: timer creat failed!"); } }
功能实现
程序开始会启动一个 200ms 的软件定时器并使能 Keyscan 外设进行一次手动扫描。扫描结束后会进入单次扫描结束中断。
如果检测到有按键按下,则将 Key_Pressed_Flag
置 1,重启 200ms 定时器,重新使能 Keyscan 开始下一次扫描。
重复上述过程,当 200ms 定时器到期后,重新启动 10ms 的软件定时器用于模拟按键松开去抖。
10ms 定时器到期后判断 Key_Pressed_Flag
,如果为 0 代表扫描到所有按键松开,反之则仍有按键按下。
初始化完毕之后,执行
os_sched_start()
函数,开始任务调度。当有按键按下时,开启手动扫描模式。当单次扫描完成后,进入 Keyscan 中断函数。
屏蔽 Keyscan 单次扫描结束中断。
读取 Keyscan FIFO 中的数据长度和数据内容。
将
Key_Pressed_Flag
置为true
,表明当前有按键按下。执行
os_timer_restart()
重启软件定时器,定时时间为 200ms,继续检测按键按下信息,模拟按下 Debounce 功能。定义消息类型
IO_MSG_TYPE_KEYSCAN
,定义消息子类型为IO_MSG_KEYSCAN_RX_PKT
,发送 msg 给 task。主 task 检测到消息类型时,执行io_keyscan_handle_keys
打印对应的按键信息。
void Keyscan_Handler(void) { uint32_t fifo_length; T_IO_MSG int_keyscan_msg; if (KeyScan_GetFlagState(KEYSCAN, KEYSCAN_INT_FLAG_SCAN_END) == SET) { /* Read current keyscan interrupt status and mask interrupt */ KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, ENABLE); memset(&Current_Key_Data, 0, sizeof(KeyScan_Data_TypeDef)); /* KeyScan fifo not empty */ if (KeyScan_GetFlagState(KEYSCAN, KEYSCAN_FLAG_EMPTY) != SET) { fifo_length = (uint32_t)KeyScan_GetFifoDataNum(KEYSCAN); KeyScan_Read(KEYSCAN, (uint16_t *)&Current_Key_Data.key[0], fifo_length); Current_Key_Data.length = fifo_length; Key_Pressed_Flag = true; /* Start sw timer to check press status */ if (!os_timer_restart(&KeyScan_Timer_Handle, KEYSCAN_SW_INTERVAL)) { APP_PRINT_ERROR0("[io_keyscan] Keyscan_Handler: Restart keyscan_timer failed!"); /* Set flag to default status and reinit keyscan module with debounce enabled */ global_data_keyscan_init(); driver_keyboard_init(KeyScan_Debounce_Enable); return; } /* Send event to app task */ int_keyscan_msg.type = IO_MSG_TYPE_KEYSCAN; int_keyscan_msg.subtype = IO_MSG_KEYSCAN_RX_PKT; int_keyscan_msg.u.buf = (void *)&Current_Key_Data; if (false == app_send_msg_to_apptask(&int_keyscan_msg)) { APP_PRINT_ERROR0("[io_keyscan] Keyscan_Handler: Send IO_MSG_KEYSCAN_RX_PKT failed!"); //Add user code here! return; } } KeyScan_ClearINTPendingBit(KEYSCAN, KEYSCAN_INT_SCAN_END); KeyScan_INTMask(KEYSCAN, KEYSCAN_INT_SCAN_END, DISABLE); } } static void io_keyscan_handle_keys(T_IO_MSG *io_keyscan_msg) { uint16_t subtype = io_keyscan_msg->subtype; if (subtype == IO_MSG_KEYSCAN_RX_PKT) { KeyScan_Data_TypeDef *p_key_data = (KeyScan_Data_TypeDef *)io_keyscan_msg->u.buf; /* Single key press */ if (p_key_data->length == 1) { APP_PRINT_INFO2("[io_keyscan] io_keyscan_handle_keys: Single key press. key: (%d, %d)", p_key_data->key[0].row, p_key_data->key[0].column); } /* two keys press */ if (p_key_data->length == 2) { APP_PRINT_INFO4("[io_keyscan] io_keyscan_handle_keys: Two key press. key0: (%d, %d), key1: (%d, %d)", p_key_data->key[0].row, p_key_data->key[0].column, p_key_data->key[1].row, p_key_data->key[1].column); } } else if (subtype == IO_MSG_KEYSCAN_ALLKEYRELEASE) { APP_PRINT_INFO0("[io_keyscan] io_keyscan_handle_keys: All keys release."); } else { APP_PRINT_INFO0("[io_keyscan] io_keyscan_handle_keys: Wrong key event!"); } }
当 200ms 定时结束时,进入回调函数
timer_keyscan_callback
,检测 flag 标志位Key_Pressed_Flag
。如果 flag 位为 true,表明当前有按键按下
将 flag 位置为 false。
执行
driver_keyboard_init
,重新初始化 Keyscan,使能 Keyscan 功能,重新启动键盘扫描。执行
os_timer_restart()
,重启软件定时器,修改定时时间为 10ms,模拟按键松开的 debounce。
如果 flag 位为 false,表明当前没有按键按下
定义消息子类型为
IO_MSG_KEYSCAN_ALLKEYRELEASE
,在消息处理函数内打印按键全部松开信息。执行
global_data_keyscan_init
,重置按键信息。执行
driver_keyboard_init
,重新初始化 Keyscan。
void timer_keyscan_callback(void *p_xTimer) { if (true == Key_Pressed_Flag) { // APP_PRINT_INFO0("[io_keyscan] timer_keyscan_callback: start release timer"); Key_Pressed_Flag = false; driver_keyboard_init(KeyScan_Debounce_Disable); /* Start timer to check key status */ os_timer_restart(&p_xTimer, KEYSCAN_SW_RELEASE_TIMEOUT); } else { /* Keyscan release event detected */ // APP_PRINT_INFO0("[io_keyscan] timer_keyscan_callback: keyscan release event detected "); T_IO_MSG int_keyscan_msg; int_keyscan_msg.type = IO_MSG_TYPE_KEYSCAN; int_keyscan_msg.subtype = IO_MSG_KEYSCAN_ALLKEYRELEASE; if (false == app_send_msg_to_apptask(&int_keyscan_msg)) { APP_PRINT_ERROR0("[io_keyscan] timer_keyscan_callback: Send IO_MSG_KEYSCAN_ALLKEYRELEASE failed!"); } global_data_keyscan_init(); driver_keyboard_init(KeyScan_Debounce_Enable); } }