当前位置: 首页 > news >正文

2025年信息科学与工程学院科协单片机编程介绍——按键拓展编程

按键编程拓展与状态机编程

起源
在学习按键的过程中,随着学习的不断深入,我们常常会遇到双击,长按,多次单机的判断,这样就需要更完善的按键代码逻辑,以下介绍两种代码编程逻辑,供大家参考。PS:此处代码逻辑以STM32为基础,并且默认大家已经学会最为基本的按键编写与消抖逻辑。

四行代码与编程判断

uint8_t key_val;
uint8_t key_old;
uint8_t key_up;
uint8_t key_down;

uint8_t key_read(void){
	uint8_t temp=0;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET)
		temp=1;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET)
		temp=2;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET)
		temp=3;
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
		temp=4;
	return temp;
}
void key_proc(){
    key_val=key_read();
    key_down=key_val&(key_old^key_val);
    key_up=~key_val&(key_old^key_val);
    key_old=key_val;

此处判断的逻辑较为简单基本,通过key_read函数读取并返回一个temp值,在key_proc函数中通过定义四个变量进行判断,其中key_val用来储存返回的temp数值,key_down=key_val&(key_val^key_old)可以通过与上次读取的数值进行异或运算,并通过与操作得到下降沿数值,同理key_up即为上升沿数值。
那么,如何才能判断长按与双击呢?

if(key_down==4){
		if(lcd_disp_mode==1){
		switch(data_choose_mode){
	  case 0:
			if(R_value--==1){
			R_value=10;
			}
			break;
		case 1:
			if(K_value--==1){
			K_value=10;
			}
			break;
	}
	}
		if(lcd_disp_mode==2){
			if(unlock_flag==1) {unlock_flag=0;key_tick_2=HAL_GetTick();ucLed[2]=0;}
			else key_tick_2=HAL_GetTick();
		}
}
	if(key_up==4&&lcd_disp_mode==2){
	  uint32_t key_now_time_1;
		key_now_time_1=HAL_GetTick();
		if(key_now_time_1-key_tick_2>=2000){
		ucLed[2]=1;
		unlock_flag=1;
		}
	}

以上是一段示例代码,可以发现在key_down为4时,我们定义了key_tick_2来储存按下时的时间,当检测到上升沿key_up=同样为4时,即上升沿为4,按键4被松开时,判断此时的时间和上次的时间差值是否超过一定值,如果超过了,则满足长按的条件,而双击则是相似的代码逻辑。

状态机判断

typedef struct{
  GPIO_TypeDef *gpiox;
  uint16_t pin;
  uint16_t ticks;
  uint8_t level;
  uint8_t id;
  uint8_t state;
  uint8_t debouce_cnt;
  uint8_t repeat;
} button;

button btns[4];

void key_init(void)
{
  btns[0].gpiox = GPIOB;
  btns[0].pin = GPIO_PIN_0;
  btns[0].level = 1;
  btns[0].id = 0;

  btns[1].gpiox = GPIOB;
  btns[1].pin = GPIO_PIN_1;
  btns[1].level = 1;
  btns[1].id = 1;

  btns[2].gpiox = GPIOB;
  btns[2].pin = GPIO_PIN_2;
  btns[2].level = 1;
  btns[2].id = 2;

  btns[3].gpiox = GPIOA;
  btns[3].pin = GPIO_PIN_0;
  btns[3].level = 1;
  btns[3].id = 3;
}

void key_task(button *btn)
{
  uint8_t gpio_level = HAL_GPIO_ReadPin(btn->gpiox, btn->pin);
  if (btn->state > 0) {
    btn->ticks++;
  }
  if (btn->level != gpio_level) {
    if (++(btn->debouce_cnt) > 3) {
      btn->level = gpio_level;
      btn->debouce_cnt = 0;
    }
  } else {
    btn->debouce_cnt = 0;
  }

  switch (btn->state) {
    case 0:
      if (btn->level == 0) {
        btn->ticks = 0;
        btn->repeat = 1;
        btn->state = 1;
      }
      break;
    case 1:
      if (btn->level != 0) {
        if (btn->ticks >= 30) {  
          btn->repeat = 0;  
          ucLED[0] ^= 1;
          btn->state = 0;
        } else {
          btn->state = 2;  
        }
        btn->ticks = 0;
      }
      break;
    case 2:
			if (btn->ticks >= 10&&btn->repeat==2) {  
        btn->state = 0;  
        ucLED[2] ^= 1;
      } 
      if (btn->ticks >= 10&&btn->repeat==1) {  
        btn->state = 0;  
        ucLED[1] ^= 1;
      } else {
        if (btn->level == 0) {  
          btn->repeat++;
          btn->state = 1;
          btn->ticks = 0; 
        }
      }
      break;
  }
}
void key_state(void){
  for(uint8_t i=0;i<4;i++){
	  key_task(&btns[i]);
	}
}

状态机的基本逻辑:

1.通过state判断按键初始状态

当state=0时表示按键处于初始状态,当state=1时表示按键处于被按下状态,当state=2时表示按键处于被按下后的释放状态

2.当state=1或者state=2时

btn->ticks不断计时,计算处于每个状态的时间,并通过循环检查按键对应所产生的电平,

3.当state=0时

如果检查到btn->level==0,说明按键第一次被按下,btn->repeat=1,btn->ticks=0开始计时,btn->state=1进入按键按下判断状态。

4.当state=1时

不断计时,计时到检测到按键被松开时如果btn->ticks时间过长,则表示为长按,btn->repeat重置为0,btn->state重置为0,如果检测到一次短按,btn->state=2。两种情况btn->ticks都需要重新置0。

5.当state=2时

如果btn->ticks时间过长,则表示为一次短按,在释放状态下btn->ticks时间较短时且检测到按键被按下时,btn->repeat加一,btn->state重新变为1,btn->ticks重新计时,此时表示一次双击。

6.逻辑注意点:

通过上述逻辑的描述,较为难为理解的两个注意点是:
当state=1时,因为state表示按键被按下的状态,因此判断条件应该为btn->level!=0按键被释放的状态
同理,当state=2时,因为state表示按键被释放的状态,因此判断条件应为btn->level=0按键被按下的状态

总结

总的来说,按键的复杂逻辑判断都是通过按键引脚的时序逻辑编写对应的函数进行相应的判断,第一种方式较为简单,便于端平快的项目,第二种项目较为复杂,但是有着更好的实用性与移植性。你,学会了吗?
------------------------------------------------------------- L.M.
-----------------------------------------------------2025-2-20

相关文章:

  • 第6章:基于LangChain如何开发Agents,附带客户支持智能体示例
  • Spring Boot 中多线程工具类的配置与使用:基于 YAML 配置文件
  • 21.回溯算法3
  • 【2025最新版】Chrome谷歌浏览器如何能恢复到之前的旧版本
  • 【信息系统项目管理师-案例真题】2013下半年案例分析答案和详解
  • 对CSS了解哪些?
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_os_specific_init函数
  • 网站改了域名,如何查找?
  • HTTP和HTTPS详解
  • ai json处理提示词
  • AI大模型零基础学习(7):边缘智能与物联网——让AI走出云端
  • XML XML约束 二、DTD
  • 基于STM32的智能工业设备健康监测系统
  • StableDiffusion学习笔记——6、XYZ图表
  • 基于spring boot物流管理系统设计与实现(代码+数据库+LW)
  • 文心一言大模型的“三级跳”:从收费到免费再到开源,一场AI生态的重构实验
  • LLM增强强化学习:开启智能决策的新篇章
  • 【文本】词嵌入经典模型:从one-hot到BERT
  • 最优化方法-牛顿法
  • 专题--Kafka
  • 中国驻英国大使郑泽光:中国需要世界,世界也需要中国
  • 财政部部长:中方主张通过平等对话协商解决贸易和关税争议
  • 牧原股份一季度归母净利润44.91亿元,同比扭亏为盈
  • 限时离境、关闭领空、暂停贸易,巴基斯坦宣布一系列对印反制措施
  • 2025年一季度上海市生产总值
  • 人民日报首推“大地书单”,10本好书上榜!