2021-07-08

设备

  • stm32103c8t6
  • 4*4矩阵键盘
  • 杜邦线8根

原理

  1. 利用外部中断,识别按键的按下动作
  2. 在识别按下按键后, 再去快速分析具体的按键
  3. 具体的按键识别后, 就可以再进行时长的检测(未做)

(画了一个超级简图)
在这里插入图片描述

  1. 绿色接到单片机的输出引脚, 配置为推挽模式, 电平配置为高电平。
  2. 黑色线对接到单片机的输入引脚,每个引脚配置为下拉输入, 引脚的中断设置为上升引起中断。
  3. 当按键按下的时候, 会导致输入引脚出现上升电平, 引起中断。由于配置了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;
}

效果

效果是通过串口将记录的数组打印出来, 根据行和列的组合即可知道按下的按键, 同时支持多个按键识别。需要再添加一个对得到的数据分析的函数即可。
在这里插入图片描述

按键时长测量方法猜想

采用之前叙述的方法,能够快速识别出按下的具体按键, 然后去读取定时器的输入捕获数据, 进而就可以判断是长按或者短按。

后记

作为一个菜鸟, 仅作为记录自己的轨迹,感谢观看!欢迎提出建议!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值