软件篇(一)—— 按键扫描
回顾一下硬件篇矩阵键盘部分,为了方便软件扫描、提高扫描效率,我在每一行增加了一个上拉电阻,如图:
首先分析一下原理,经过电阻上拉,每一行的初始状态为高电平,那么要检测到按键事件对应行 K_ROWx 就应该出现低电平或下降沿才能检测。从图中可以看出产生低电平或下降沿由 COLx 控制,对应的只有软件逐列拉低 COLx 即可。这样一来只需要循环拉低 COLx,获取 K_ROWx 状态,扫描一次即可实现键值读取。例如,拉低 COL3,此时 K_ROW4 检测到下降沿,说明坐标 (4,3) 的按键被按下。
为了进一步提高扫描速率,我把 K_ROWx 和 COLx 分别接入一组 IO 口,这样就可以通过寄存器移位实现按键扫描,提升效率。弄清原理之后,写代码就轻松多了。下面看一下代码:
- 相关定义
#define COL_NUM 14
#define ROW_NUM 5
#define KEY_PRESSED 0
#define KEY_UNPRESSED 1
#define KEY_ROW_GPIO_CLK RCC_APB2Periph_GPIOA
#define KEY_ROW_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define KEY_ROW_PORT GPIOA
#define KEY_ROW_PIN 0x001F
#define KEY_COL_GPIO_CLK RCC_APB2Periph_GPIOB
#define KEY_COL_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define KEY_COL_PORT GPIOB
#define KEY_COL_PIN 0x3FFF
#define KEY_ROW_READ GPIOA->IDR & 0x001F // 取低5位
#define KEY_COL_OUT GPIOB->ODR
const u16 COL_Scan_Table[COL_NUM] = {
0x3FFE, 0x3FFD, 0x3FFB, 0x3FF7,0x3FEF, 0x3FDF, 0x3FBF,
0x3F7F, 0x3EFF, 0x3DFF, 0x3BFF, 0x37FF,0x2FFF, 0x1FFF,
};
u8 KeyBoard_KeyStatus_New[5][14] = {KEY_UNPRESSED};
- IO初始化
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
KEY_ROW_GPIO_APBxClkCmd(KEY_ROW_GPIO_CLK, ENABLE);
KEY_COL_GPIO_APBxClkCmd(KEY_COL_GPIO_CLK, ENABLE);
/* 这里用到了JTAG接口的IO,需要重定义 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
/* 行输入,外部上拉,低电平有效 */
GPIO_InitStructure.GPIO_Pin = KEY_ROW_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY_ROW_PORT, &GPIO_InitStructure);
/* 列输出,低电平有效 */
GPIO_InitStructure.GPIO_Pin = KEY_COL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY_COL_PORT, &GPIO_InitStructure);
KEY_COL_OUT = 0x3FFF; // 全部列拉高
}
这里重点说明一下列输出 IO 的配置。首先讨论一下推挽输出的情况:当按下某一行的一个按键,此时该行电平被拉低,能够正常检测。但当这一行同时按下两个及以上按键时,由于列扫描是逐列移位拉低(即同一时刻有且只有一列为低电平),那么其他列如果也按下去的话,该行就会被多次上拉,导致无法完全拉低该行电平,进而导致无法识别同一行的多个按键。
该如何解决这个问题,从上面的分析可以得出同一行多个按键无法识别的根本原因是其他列的高电平导致了电平无法完全拉低。那么如果其他列也保持低电平呢? 显然是不行的,这样就无法检测到这一行到底是哪个按键被按下。因此,可以明确一点,同一时刻有且只能有一列为低电平。
那么除了高、低电平两个状态还有什么? 就是高阻态。将 IO 配置为开漏输出,就能保证同一时刻有且只有一列为低电平,同时其他列为高阻态,不会影响按键行电平。
- 按键扫描
void Key_Scan(void)
{
u8 i,j;
u8 row_data;
for(i=0;i<COL_NUM;i++)
{
KEY_COL_OUT = COL_Scan_Table[i]; // 列输出
Delay_us(10);
row_data = KEY_ROW_READ; // 获取行按键状态
for(j=0;j<ROW_NUM;j++)
{
KeyBoard_KeyStatus_New[j][i] = row_data & 0x01; // 获取按键状态
row_data = row_data >> 1; // 行切换
}
}
}
程序中 i 的值为当前列,j 为当前行。每切换一次列输出读一组行,再将读出的行值按位更新到数组中。