设备
- stm32103c8t6
- 4*4矩阵键盘
- 杜邦线8根
原理
- 利用外部中断,识别按键的按下动作
- 在识别按下按键后, 再去快速分析具体的按键
- 具体的按键识别后, 就可以再进行时长的检测(未做)
(画了一个超级简图)
- 绿色接到单片机的输出引脚, 配置为推挽模式, 电平配置为高电平。
- 黑色线对接到单片机的输入引脚,每个引脚配置为下拉输入, 引脚的中断设置为上升引起中断。
- 当按键按下的时候, 会导致输入引脚出现上升电平, 引起中断。由于配置了4个中断,所以可以识别是哪一行或者是哪一列,但是具体是哪一个按键,需要在此基础上进行查询
引脚与宏
#define PRESS 1
#define DELAY_TIME 40
#define DELAY_CNT 10
// 输入电平触发中断引脚
#define IN_PIN1 GPIO_Pin_1
#define IN_PIN2 GPIO_Pin_2
#define IN_PIN3 GPIO_Pin_3
#define IN_PIN4 GPIO_Pin_4
// 输出电平的引脚
#define OUT_PIN1 GPIO_Pin_12
#define OUT_PIN2 GPIO_Pin_13
#define OUT_PIN3 GPIO_Pin_14
#define OUT_PIN4 GPIO_Pin_15
u8 matrixR[4] = {0}, key_signal, matrixC[4] = {0};
uint16_t gpio_pin[4] = {GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_14, GPIO_Pin_15};
矩阵键盘初始化
void key_board_matrix_init(void) {
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 输入电平引脚初始化
gpio_in_init(GPIOA, IN_PIN1, GPIO_Mode_IPD);
gpio_in_init(GPIOA, IN_PIN2, GPIO_Mode_IPD);
gpio_in_init(GPIOA, IN_PIN3, GPIO_Mode_IPD);
gpio_in_init(GPIOA, IN_PIN4, GPIO_Mode_IPD);
// 输出电平引脚初始化
gpio_out_init(GPIOB, OUT_PIN1, GPIO_Mode_Out_PP);
gpio_out_init(GPIOB, OUT_PIN2, GPIO_Mode_Out_PP);
gpio_out_init(GPIOB, OUT_PIN3, GPIO_Mode_Out_PP);
gpio_out_init(GPIOB, OUT_PIN4, GPIO_Mode_Out_PP);
// 控制所有的输出电平引脚输出高电平, 以便按按键按下引起中断
_all_point_high();
// 打开处于AFIO时钟, 外部中断的寄存器使用
_key_board_exti_AFIO();
// 外部与引脚连接
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);
// 外部中断设置
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_Line = EXTI_Line1;
EXTI_Init(&EXTI_InitStruct);
EXTI_InitStruct.EXTI_Line = EXTI_Line2;
EXTI_Init(&EXTI_InitStruct);
EXTI_InitStruct.EXTI_Line = EXTI_Line3;
EXTI_Init(&EXTI_InitStruct);
EXTI_InitStruct.EXTI_Line = EXTI_Line4;
EXTI_Init(&EXTI_InitStruct);
// 内核中断设置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 4;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_Init(&NVIC_InitStruct);
}
列数据分析
在确定按键按下后, 留出一段时间,防止同时按下多个按键无法识别
void _cloumn_analsy(void) {
u8 i;
_all_point_low(); // 输入引脚电平全部改为低电平
key_signal = !PRESS; // 改为没有触发装填, 等待在将电平转为高电平后, 触发函数将其更改为PRESS
delay_us(100);
for (i = 0; i < 4; i++) {
__scan(i, gpio_pin[i]);
}
return ;
}
扫描函数
主动把高电平降至低电平, 然后再挨个提高电平, 看是否触发中断,若是触发,则列数就可以确定了。然后再将电平改为低电平防止影响其他的判断。
在这里可以对cnt 的值进行判断,进而得到按键的具体时间。但是按键的时间不能过长, 因为是轮询的方法所以会导致同时按下的其他按键无法识别。
void __scan(u8 i, uint16_t pin) {
// 第一列扫描
u8 cnt = 0;
key_signal = !PRESS;
gpio_out_high(GPIOB, pin); // 第一个输入引脚改为高电平,测试是否产生中断,若产生将matrixC 1 改为 1
while(key_signal != PRESS && cnt < DELAY_CNT) {
cnt++;
delay_us(DELAY_TIME);
}
gpio_out_low(GPIOB, GPIO_Pin_12);
if (cnt < 10) { // 大于20ms 代表超时,未触发中断,不是该列按键
matrixC[i] = 1;
}
}
按键数据接收
数据采用两个数组接收, 可以用一个uint8_t 的类型接收
u8 detect_key_board(void) {
key_signal = !PRESS;
memset(matrixR, 0, sizeof(matrixR));
memset(matrixC, 0, sizeof(matrixC));
while (key_signal != PRESS); // 等待按键按下
delay_ms(100); // 同时按下的最大时间差异, 将同时按下记录到数组中。进行列分析
/*** 此时根据中断函数已经可以确定是哪一行按键按下了, 接下来进行行扫描确定列
在行扫描之前先将所有引脚改为输出低电平。然后再逐个输出高电平,如果中断触发就找到了列
在确定了具体的按键之后就可以继续进行计时,判断长按还是短按
***/
_cloumn_analsy();
_all_point_high();
printf(" Row: %d %d %d %d\r\n", matrixR[0], matrixR[1], matrixR[2], matrixR[3]);
printf("Column: %d %d %d %d\r\n", matrixC[0], matrixC[1], matrixC[2], matrixC[3]);
// 按键去抖
delay_ms(400);
return 0;
}
中断函数
中断函数负责记录触发的行数或者列数, 在第二次中断来临时,确定列数或者行数(第二次的行数确定的是根据自己定义的顺序,就是列数据分析中记录的列, 可以看到第二次可以分辨出行数和列数,但是第一次有通过中断开始按键识别的作用)
void EXTI1_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line1) == SET) {
matrixR[0] = 1;
}
EXTI_ClearITPendingBit(EXTI_Line1);
key_signal = PRESS;
}
void EXTI2_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line2) == SET) {
matrixR[1] = 1;
}
EXTI_ClearITPendingBit(EXTI_Line2);
key_signal = PRESS;
}
void EXTI3_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line3) == SET) {
matrixR[2] = 1;
}
EXTI_ClearITPendingBit(EXTI_Line3);
key_signal = PRESS;
}
void EXTI4_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line4) == SET) {
matrixR[3] = 1;
}
EXTI_ClearITPendingBit(EXTI_Line4);
key_signal = PRESS;
}
效果
效果是通过串口将记录的数组打印出来, 根据行和列的组合即可知道按下的按键, 同时支持多个按键识别。需要再添加一个对得到的数据分析的函数即可。
按键时长测量方法猜想
采用之前叙述的方法,能够快速识别出按下的具体按键, 然后去读取定时器的输入捕获数据, 进而就可以判断是长按或者短按。
后记
作为一个菜鸟, 仅作为记录自己的轨迹,感谢观看!欢迎提出建议!