【蓝桥杯嵌入式备赛】其三:按键

本文详细介绍了使用STM32G431RBT6开发板,通过DAPLINK软件和STM32CubeMX/KeilMDK5进行编程,实现单击和长按按键的处理,包括配置GPIO、定时器中断、按键消抖、时间戳应用和中断回调函数的编写,以及涉及到的数值类型、清屏函数和时间戳相关知识。
摘要由CSDN通过智能技术生成

主控板:STM32G431RBT6
板载下载器:DAPLINK
软件:STM32CubeMX、Keil MDK5

1、初始配置

  • 继续在之前的工程中进行配置(注意备份)。首先根据数据手册配置按键,可以看到连接按键的引脚为PA0、PB0、PB1、PB2,将相应引脚设置为GPIO_Input以及上拉输入Pull-up
    在这里插入图片描述

在这里插入图片描述

  • 再配置定时器,选择其中一个如TIM3进行配置并使能中断:
    在这里插入图片描述
    在这里插入图片描述
  • 最后生成工程即可。
  • 在keil中打开工程,在bsp文件夹中添加interrupt.cinterrupt.h两个文件。

2、代码编写

(1)单击按键

  • interrupt.c

    #include "interrupt.h"
    struct keys key[4]={0,0,0};
    
    //触发中断进入中断回调函数
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    //判断是否为TIM3
       if(htim->Instance==TIM3){
       //按键电平位
       	key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
       	key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
       	key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
        key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
       
       //轮询按键消抖
       for(int i=0;i<4;i++){				
       	switch(key[i].judge_sta){
       		case 0:
       		{
       			if(key[i].key_sta==0)
       				key[i].judge_sta=1;
       		}break;
       		//10ms后按键仍按下
       		case 1:
       		{
       			if(key[i].key_sta==0){
       				key[i].judge_sta=2;
       				key[i].single_flag=1;
       			}
       			else
       				key[i].judge_sta=0;
       		}break;
       		//读取到松手
       		case 2:
       		{
       			if(key[i].key_sta==1){
       				key[i].judge_sta=0;
       			}
       		}break;	
       	}
       }	
     }
    }
    
  • interrupt.h

     #ifndef _INTERRUPT_H_
    #define _INTERRUPT_H_
    
    #include "main.h"
    
    struct keys{
      unsigned char judge_sta;//判断进程
      int key_sta;//标志位,判断按键是否按下
      int single_flag;//确认按键按下
    
    };
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
    #endif
    
  • main.c增加的部分

    #include "interrupt.h"
    
    extern struct keys key[];
    
    //初始化
    HAL_TIM_Base_Start_IT(&htim3);
    
    while (1){
        if(key[0].single_flag ==1){
      	  LCD_DisplayStringLine(Line3, (uint8_t *)"    key0 down");	
        }
        if(key[1].single_flag ==1){
      	  LCD_DisplayStringLine(Line5, (uint8_t *)"    key1 down");	
        }
    }
    

(2)长按按键

  • interrupt.h的结构体中增加变量
      unsigned int key_time;//时间记录
      bool long_flag;
    
  • interrupt.c
    	//轮询按键消抖
     	for(int i=0;i<4;i++){				
     		switch(key[i].judge_sta==0){
     			case 0:
     			{
     				if(key[i].key_sta==0){
     					key[i].judge_sta=1;
     					key[i].key_time=0;
     				}
     			}break;
     			//10ms后按键仍按下
     			case 1:
     			{
     				if(key[i].key_sta==0){
     					key[i].judge_sta=2;
     					
     				}
     				else
     					key[i].judge_sta=0;
     			}break;
     			//读取到松手
     			case 2:
     			{
     				if(key[i].key_sta==1){
     					key[i].judge_sta=0;						
     					
     					if(key[i].key_time<70){
     						key[i].single_flag=1;
     						
     					}
     				}
     				else{
     					key[i].key_time++;
     					if(key[i].key_time>70)
     						key[i].long_flag=1;
     				}
     				
     			}break;	
     		}
     	}
    
  • main.c
    if(key[0].single_flag ==1){
      	  LCD_DisplayStringLine(Line3, (uint8_t *)"    key0 down");	
        }
        
    if(key[1].long_flag ==1){
      	  LCD_DisplayStringLine(Line5, (uint8_t *)"    key1 down");	
        }
    

(3)通过时间戳进行消抖和判断按键长短的方法

  • 除了延时消抖和定时轮询之外还可以考虑使用全局时间戳对按键是否按下以及按键按下时间长短进行判断。

  • 代码编写思路为:

    1. 结构体中有一个用于识别的状态位private_state,默认处于Release。一旦按键被按下,中断触发。
    2. 首先确认定时器,再检查状态为是否在Relase状态,如果是就检查按键电平是否被拉低,如果是进入May_Press状态,并记录此时的时间戳last_time
    3. 按键被释放后再次触发中断,会再次进行处理,此时计算和上一次记录的时间戳last_time的差值。
    4. 如果差值小于10ms就认为是抖动,不修改按键状态而是直接将按键状态置回Relase状态;差值在10ms和1500ms之间就是短暂按键Short_Press,差值超出1500ms就认为是长按按键Long_Press
  • interrupt.h:首先要定义一个结构体变量,定义按键的几个关键标志位,用于管理按键状态。

    //定义一个
    #define Is_ShortPress_Threshold   1500
    typedef struct _Key_t
    {
      uint32_t last_time;
      uint8_t key_sta;
      enum
      {
          Release,//按键状态默认处于Release,也就是释放的状态
          May_Press,
      }private_state;
      enum
      {
          No_Press,
          Short_Press,
          Long_Press,
      }state;
    }Key_t;
    
  • interrupt.c:将事件添加到中断处理函数中。

    //触发中断进入中断回调函数
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
      //判断是否为TIM3
      if(htim->Instance==TIM3){
      	key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
      	key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
     	    key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
          key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
      	
      	for(int i=0;i<4;i++){		
      		if(key[i].private_state==Release){
      			if(key[i].key_sta == 0){
      				key[i].private_state=May_Press;
      				key[i].last_time=HAL_GetTick();
      			}
      		}
      		
      		else if(key[i].private_state==May_Press){				
      			if(key[i].key_sta == 1){
      				if(((HAL_GetTick() - key[i].last_time)>10)&&((HAL_GetTick() - key[i].last_time)<Is_ShortPress_Threshold)){
      					key[i].state=Short_Press;
      					key[i].private_state=Release;
      				}
      			
      				else if((HAL_GetTick() - key[i].last_time)>Is_ShortPress_Threshold){
      					key[i].state=Long_Press;
      					key[i].private_state=Release;
      				}
      				else
      					key[i].private_state=Release;
      			}
      		}
      	}  
      }
    }
    
  • main.c:在主函数中的循环中添加显示函数,将按键与切换界面的事件对应起来:

    void disp_proc(){
    //for(int i=0;i++;i<4){
      	if(key[0].state==Short_Press){
      		LCD_Clear(0x000000);
      		LCD_DisplayStringLine(Line1, (uint8_t *)"     LCD Dispaly");
      		LCD_DisplayStringLine(Line3, (uint8_t *)"     shortdown");
      		
      	}
      	else if (key[0].state==Long_Press){
      		LCD_Clear(0x000000);
      		LCD_DisplayStringLine(Line1, (uint8_t *)"     LCD Dispaly");
      		LCD_DisplayStringLine(Line3, (uint8_t *)"     longdown");
      	}
      }
    //}
    

3、硬件显示结果

  • 按键单击可以分别显示;长按键可以显示对应结果。
    在这里插入图片描述
  • 第二种方法的显示结果:
    在这里插入图片描述

4、问题和总结

(1)按键

  根据原理图,按键按下,引脚电平为逻辑0,否则为逻辑1。

(2)数值类型

  使用bool类型时要包含头文件 #include <stdbool.h>。

(3)清屏函数

  HAL库中已经定义了清屏函数:

void LCD_Clear(uint32_t Color);
//Color表示清平颜色,如黑色:0x000000,白色:0xFFFFFF

(4)时间戳

   该函数返回自启动以来经过的毫秒数:

viod HAL_GetTick();

(5)枚举

   使用enum关键字定义一个枚举类型,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

  在枚举中,变量初值默认为第一个枚举值,因此在排列枚举值中需要注意将希望的初值放在第一位。

(6)中断回调函数

//使能定时器3,开启TIM3的更新中断
HAL_TIM_Base_Start_IT(&htim3);
//定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用
//则更新中断(溢出)发生时执行下列函数体内的内容
HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

参考资料
(1)B站学习视频:【备战2024蓝桥杯 嵌入式组】CT117E-M4 新款开发板 3小时省赛模块 速成总结
(2)keil / C99中使用bool类型
(3)【STM32学习】——TIM定时中断
(4)一种相对高效的按键消抖方法
(5)C enum(枚举)
(6)stm32 - HAL_GetTick() 是否返回刻度或毫秒? (以及如何以微秒为单位进行测量)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值