11 硬件键盘扫描控制器(KEYSCAN)
11.1 功能概述
特性:
- 可配置矩阵,最大支持18行*18列的键盘矩阵。
- 每个单独的行或者列可以设置启用或者禁用。
- 可配置时钟。
- 支持输入硬件去抖动,去抖时间可配置。
- 支持配置扫描间隔和释放时间,支持多按键同时按下。
- 支持中断和DMA。
11.2 使用说明
以4行*4列的键盘矩阵为例:
11.2.1 键盘矩阵的软件描述
typedef struct {
;
KEYSCAN_InColIndex_t in_col;
GIO_Index_t gpio} KEYSCAN_InColList;
typedef struct {
;
KEYSCAN_OutRowIndex_t out_row;
GIO_Index_t gpio} KEYSCAN_OutRowList;
[] = {
KEYSCAN_OutRowList key_out_row{KEY_OUT_ROW_6, GIO_GPIO_32}, // 第1行
{KEY_OUT_ROW_7, GIO_GPIO_33}, // 第2行
{KEY_OUT_ROW_5, GIO_GPIO_31}, // 第3行
{KEY_OUT_ROW_0, GIO_GPIO_23}, // 第4行
};
#define key_out_row_num (sizeof(key_out_row) / sizeof(key_out_row[0]))
[] = {
KEYSCAN_InColList key_in_col{KEY_IN_COL_11, GIO_GPIO_11}, // 第1列
{KEY_IN_COL_12, GIO_GPIO_12}, // 第2列
{KEY_IN_COL_13, GIO_GPIO_13}, // 第3列
{KEY_IN_COL_14, GIO_GPIO_14}, // 第4列
};
#define key_in_col_num (sizeof(key_in_col) / sizeof(key_in_col[0]))
第1行按键接到了GPIO32,映射到KEYSCAN模块的ROW6。第1列按键接到了GPIO11,映射到KEYSCAN模块的COL11。以此类推4行4列的键盘阵列。
注意:KEYSCAN的ROW和COL不是随意映射到GPIO,映射关系参考管脚管理(PINCTRL)说明文档。
11.2.2 KEYSCAN模块初始化
typedef struct {
*col;
KEYSCAN_InColList int col_num;
*row;
KEYSCAN_OutRowList int row_num;
uint8_t fifo_num_trig_int;
uint8_t dma_num_trig_int;
uint8_t dma_en;
uint8_t int_trig_en;
uint16_t release_time;
uint16_t scan_interval;
uint8_t debounce_counter;
} KEYSCAN_SetStateStruct;
/**
* @brief Initialize keyscan module
*
* @param[in] keyscan_set Initial parameter struct
* @return 0 if success else non-0
*/
int KEYSCAN_Initialize(const KEYSCAN_SetStateStruct* keyscan_set);
/**
* @brief Initialize mapping table of keyboard array row and col
*
* @param[in] keyscan_set Initial parameter struct
* @param[out] ctx keyboard array mapping table
*/
void KEYSCAN_InitKeyScanToIdx(const KEYSCAN_SetStateStruct* keyscan_set,
*ctx); KEYSCAN_Ctx
11.2.3 获取扫描到的按键
KEYSCAN模块使能扫描后会按照行和列的配置开始扫描。模块有FIFO缓存扫描数据。每次扫描循环结束,FIFO中压入1个0x400标志完成一次扫描。
可以配置FIFO中数据个数触发中断或者DMA触发中断:
void KEYSCAN_SetFifoNumTrigInt(uint32_t trig_num);
void KEYSCAN_SetDmaNumTrigInt(uint32_t trig_num);
获取FIFO是否为空的状态和数据:
/**
* @brief Check keyscan FIFO empty or not
*
* @return 0: FIFO have data; 1: empty
*/
uint8_t KEYSCAN_GetIntStateFifoEmptyRaw(void);
/**
* @brief GET keyscan FIFO data
*
* @return 0~4 bits: col; 5~9 bits: row; 10 bit: scan cycle end flag
*/
uint16_t KEYSCAN_GetKeyData(void);
按键FIFO原始数据的0~4位是按下按键所在的KEYSCAN模块中的col,5~9位是row,注意这个值并不是键盘矩阵中的行和列,可以用下面接口将原始数据解析为键盘矩阵中的行和列:
/**
* @brief Transfer keyscan FIFO raw data to keyboard array row and col
*
* To use this helper function, `ctx` must be initialized with `KEYSCAN_InitKeyScanToIdx`.
*
* @param[in] ctx keyboard array mapping table
* @param[in] key_data keyscan FIFO raw data
* @param[out] row pressed key's 0-based row index in keyboard array
* @param[out] col pressed key's 0-based col index in keyboard array
* @return 0: scan cycle end data;
* 1: find key pressed, *row and *col are key positions in keyboard array
*/
uint8_t KEYSCAN_KeyDataToRowColIdx(const KEYSCAN_Ctx *ctx, uint32_t key_data,
uint8_t *row, uint8_t *col);
11.3 应用举例
11.3.1 初始化KEYSCAN模块
[] = {
KEYSCAN_OutRowList key_out_row{KEY_OUT_ROW_6, GIO_GPIO_32}, // 第1行
{KEY_OUT_ROW_7, GIO_GPIO_33}, // 第2行
{KEY_OUT_ROW_5, GIO_GPIO_31}, // 第3行
{KEY_OUT_ROW_0, GIO_GPIO_23}, // 第4行
};
#define key_out_row_num (sizeof(key_out_row) / sizeof(key_out_row[0]))
[] = {
KEYSCAN_InColList key_in_col{KEY_IN_COL_11, GIO_GPIO_11}, // 第1列
{KEY_IN_COL_12, GIO_GPIO_12}, // 第2列
{KEY_IN_COL_13, GIO_GPIO_13}, // 第3列
{KEY_IN_COL_14, GIO_GPIO_14}, // 第4列
};
#define key_in_col_num (sizeof(key_in_col) / sizeof(key_in_col[0]))
static KEYSCAN_Ctx key_ctx = {0};
static KEYSCAN_SetStateStruct keyscan_set = {
.col = key_in_col,
.col_num = key_in_col_num,
.row = key_out_row,
.row_num = key_out_row_num,
.fifo_num_trig_int = 1,
.release_time = 110,
.scan_interval = 0xFFFF,
.debounce_counter = 50,
.dma_num_trig_int = 0x10,
.dma_en = 0,
.int_trig_en = 1,
};
static uint32_t keyscan_cb_isr(void *user_data);
static void setup_peripherals_keyscan(void)
{
(1 << SYSCTRL_ITEM_APB_KeyScan);
SYSCTRL_ClearClkGateMulti(1 << SYSCTRL_ITEM_APB_PinCtrl);
SYSCTRL_ClearClkGateMulti
(&keyscan_set);
KEYSCAN_Initialize(&keyscan_set, &key_ctx);
KEYSCAN_InitKeyScanToIdx
(PLATFORM_CB_IRQ_KEYSCAN, keyscan_cb_isr, 0);
platform_set_irq_callback
return;
}
11.3.2 中断数据处理
static uint8_t key_state_buf[2][key_out_row_num][key_in_col_num] = {0};
static uint8_t key_state_last_index = 0;
static uint8_t key_state_now_index = 1;
static void printf_key_state(void)
{
int row, col;
for (row = 0; row < key_out_row_num; row++) {
for (col = 0; col < key_in_col_num; col++) {
if (key_state_buf[key_state_now_index][row][col] !=
[key_state_last_index][row][col]) {
key_state_buf("row%u col%u %s\r\n",
printf+ 1, col + 1,
row [key_state_now_index][row][col] == 0 ?
key_state_buf"release" : "press");
}
}
}
if (key_state_now_index == 0) {
= 1;
key_state_now_index = 0;
key_state_last_index } else {
= 0;
key_state_now_index = 1;
key_state_last_index }
for (row = 0; row < key_out_row_num; row++) {
for (col = 0; col < key_in_col_num; col++) {
[key_state_now_index][row][col] = 0;
key_state_buf}
}
return;
}
static void key_state_clear(void)
{
int row, col;
for (row = 0; row < key_out_row_num; row++) {
for (col = 0; col < key_in_col_num; col++) {
[0][row][col] = 0;
key_state_buf[1][row][col] = 0;
key_state_buf}
}
}
static uint32_t keyscan_cb_isr(void *user_data)
{
uint32_t key_data;
uint8_t key_scan_row;
uint8_t key_scan_col;
uint8_t row = 0;
uint8_t col = 0;
static uint8_t have_key_pressed = 0;
static uint8_t no_key_pressed_cnt = 0;
while (KEYSCAN_GetIntStateFifoEmptyRaw() == 0) {
= KEYSCAN_GetKeyData();
key_data if (KEYSCAN_KeyDataToRowColIdx(&key_ctx, key_data, &row, &col)) {
// 扫描到有按键按下 按键位置为 row col
[key_state_now_index][row][col] = 1;
key_state_buf= 1;
have_key_pressed } else {
// 完成一次扫描 根据have_key_pressed判断该轮扫描中是否有按键按下
if (have_key_pressed == 1) {
= 0;
have_key_pressed = 0;
no_key_pressed_cnt } else {
}
switch (no_key_pressed_cnt) {
case 0: // 该轮扫描中有按键按下
++;
no_key_pressed_cnt();
printf_key_statebreak;
case 1: // 该轮扫描中没有按键按下 上一轮扫描中有按键按下 说明按键释放
++;
no_key_pressed_cnt();
printf_key_state();
key_state_clearbreak;
case 2: // 连续两轮或以上都没有按键按下
break;
default:
break;
}
}
}
return 0;
}