矩阵键盘
普通的按键,都是一个IO口控制一个按键,但是当按键数量变多时,单片机可能没有那么多的IO口来供给按键使用,这时候就需要用到矩阵键盘,比如45的矩阵键盘,用到了4+5共9根IO口线,可以控制45=20个按键。相应的,程序也要复杂些。
cubemx配置
X0到X3这4个IO口配置为低电平输出,无上拉,无下拉,即推挽方式,速度为中
Y0到Y4这5个IO口配置为EXTI外部中断模式,上拉,下降沿触发。
编程思路
1 X0~3配置为输出低电平
2 Y0~4配置为下降沿中断,但是芯片内部配置了上拉,所以无按键触发时不会进中断。
3 当有按键按下,比如X2Y4按下时,X2路的低电平会进入到Y4路,触发下降沿中断。
4 在Y4中断内,将Y4配置为低电平输出,而X0~3配置为上拉输入,记录下Y值为4
5 逐一检测X0~3的电平,哪一路为低,即对应的为X键值,本例X值为2
6 综合x=2, y=4, 即可知道是哪一个按键按下了。
代码编写
定义一个结构体,有按键触发时,active置1,x,y分别记录行列值,num是最后的键值。
typedef struct KEYBOARD_TYPEDEF
{
u8 active;
u8 x;
u8 y;
u8 num;
}KEYBOARD_Typedef;
KEYBOARD_Typedef keyboard;
以Y4为例,列出一路中断如下:
void EXTI15_10_IRQHandler(void)
{
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
if(__HAL_GPIO_EXTI_GET_FLAG(KEY_Y4_Pin))
{
delay_us(10);
if(KEY_Y4_READ() != 0)
{
HAL_GPIO_EXTI_IRQHandler(KEY_Y4_Pin);
return;
}
delay_us(10);
if(KEY_Y4_READ() != 0)
{
HAL_GPIO_EXTI_IRQHandler(KEY_Y4_Pin);
return;
}
delay_us(10);
if(KEY_Y4_READ() != 0)
{
HAL_GPIO_EXTI_IRQHandler(KEY_Y4_Pin);
return;
}
keyboard.active = 1;
keyboard.y = 4;
}
keyboard_scan();
/* USER CODE END EXTI15_10_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(KEY_Y4_Pin);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}
增加了几次延时,作为键盘消抖的判断
确认不是误触发后,active置1,并相应的y值。
最后调用键盘扫描函数,来确认X的键值。
.
.
引脚功能模式切换
//切换GPIO引脚的方向
//port 端口号
//pin号
//dir 方向,0为输入,1为输出, 2为EXTI
void pin_io_switch(GPIO_TypeDef *port, u32 pin, u8 mode)
{
switch(mode)
{
case GPIO_IN://输入
GPIO_InitStruct.Pin = pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(port, &GPIO_InitStruct);
break;
case GPIO_OUT://输出
GPIO_InitStruct.Pin = pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(port, &GPIO_InitStruct);
break;
case GPIO_EXTI:
GPIO_InitStruct.Pin = pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(port, &GPIO_InitStruct);
break;
}
}
.
.
本函数用来确认X轴的键值。
void keyboard_scan(void)
{
u16 i,j;
u8 x0, x1, x2, x3;
u8 temp, current;
if(keyboard.active == 0)
return;
//关中断
HAL_NVIC_DisableIRQ(EXTI3_IRQn);
HAL_NVIC_DisableIRQ(EXTI4_IRQn);
HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);
//X0~3切换为输入状态
pin_io_switch(KEY_X0_PORT, KEY_X0_Pin, GPIO_IN); //X0 PC13
pin_io_switch(KEY_X1_PORT, KEY_X1_Pin, GPIO_IN); //X1 PC14
pin_io_switch(KEY_X2_PORT, KEY_X2_Pin, GPIO_IN); //X2 PC15
pin_io_switch(KEY_X3_PORT, KEY_X3_Pin, GPIO_IN); //X3 PB7
//只将相应的一路Y口切换为输出状态,Y的数值,在中断函数中设置过
switch(keyboard.y)
{
case 0:
pin_io_switch(KEY_Y0_PORT, KEY_Y0_Pin, GPIO_OUT);
KEY_Y0_SET(0); //Y0 PB6
break;
case 1:
pin_io_switch(KEY_Y1_PORT, KEY_Y1_Pin, GPIO_OUT);
KEY_Y1_SET(0); //Y0 PB5
break;
case 2:
pin_io_switch(KEY_Y2_PORT, KEY_Y2_Pin, GPIO_OUT);
KEY_Y2_SET(0); //Y0 PB4
break;
case 3:
pin_io_switch(KEY_Y3_PORT, KEY_Y3_Pin, GPIO_OUT);
KEY_Y3_SET(0); //Y0 PB3
break;
case 4:
pin_io_switch(KEY_Y4_PORT, KEY_Y4_Pin, GPIO_OUT);
KEY_Y4_SET(0); //Y0 PA15
break;
}
//读取X轴的状态
x0 = KEY_X0_READ(); //X0 PC13
x1 = KEY_X1_READ(); //X1 PC14
x2 = KEY_X2_READ(); //X2 PC15
x3 = KEY_X3_READ(); //X3 PB7
if(x0 == 0)
keyboard.x = 3; //X方向从左开始为x3, x2, x1, x0
else if(x1 == 0)
keyboard.x = 2; //与电路图上相反,下一版电路要把方向改过来,改为x0,x1,x2,x3
else if(x2 == 0)
keyboard.x = 1;
else
keyboard.x = 0;
keyboard.num = keyboard.y*4 + keyboard.x; //计算键值,0~19
//将Y口配置为EXTI,上拉
pin_io_switch(KEY_Y0_PORT, KEY_Y0_Pin, GPIO_EXTI);
pin_io_switch(KEY_Y1_PORT, KEY_Y1_Pin, GPIO_EXTI);
pin_io_switch(KEY_Y2_PORT, KEY_Y2_Pin, GPIO_EXTI);
pin_io_switch(KEY_Y3_PORT, KEY_Y3_Pin, GPIO_EXTI);
pin_io_switch(KEY_Y4_PORT, KEY_Y4_Pin, GPIO_EXTI);
//将X口配置为输出,低电平
pin_io_switch(KEY_X0_PORT, KEY_X0_Pin, GPIO_OUT);
pin_io_switch(KEY_X1_PORT, KEY_X1_Pin, GPIO_OUT);
pin_io_switch(KEY_X2_PORT, KEY_X2_Pin, GPIO_OUT);
pin_io_switch(KEY_X3_PORT, KEY_X3_Pin, GPIO_OUT);
KEY_X0_SET(0);
KEY_X1_SET(0);
KEY_X2_SET(0);
KEY_X3_SET(0);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
HAL_NVIC_EnableIRQ(EXTI4_IRQn);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
最后在主程序中检查keyboard.active,若不为0,则表示有按键按下,此时需要做相应处理,并且将keyboard中的各成员清零。