【学习】蓝桥杯嵌入式--按键输入

本文详细介绍了使用STM32开发板实现按键输入与LCD显示的蓝桥杯竞赛项目。通过软件消抖处理按键信号,配合定时器扫描,实现了稳定的输入控制。项目包括开机界面、模式选择及LED模式切换,展示了从硬件连接到软件编程的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

蓝桥杯必考的四个东西(至少我认为的是这样的)LED灯 LCD, 按键,EEPROM。在上一篇文章介绍的是流水灯,这一篇就是按键输入。

实现的任务就是用LCD做一个菜单,用按键控制不同的LED流转模式。

第一步--熟悉硬件

这个就是普通的按键连接方式,没有硬件上消抖所以,就得软件处理了。经过查阅原理图发现,按键连接的引脚是PA0,PA8,PB1,PB2。其中PA0有唤醒的功能,不知道会不会考。

第二步--软件设计

LED,LCD相关的东西我在上一篇以及做了,在这里就不说了。

那么就是介绍按键输入相关。

首先就是对按键的初始化

void Key_GPIO_Config(void){  GPIO_InitTypeDef  GPIO_Strue;  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  GPIO_Strue.GPIO_Pin = GPIO_Pin_0| GPIO_Pin_8;  GPIO_Strue.GPIO_Mode = GPIO_Mode_IPU;  GPIO_Strue.GPIO_Speed =GPIO_Speed_50MHz;  GPIO_Init(GPIOA, &GPIO_Strue);    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  GPIO_Strue.GPIO_Pin = GPIO_Pin_1| GPIO_Pin_2;  GPIO_Init(GPIOB, &GPIO_Strue);}

之后就是对按键处理。

因为有抖动所以就要消抖。有的教程是延时消抖,但是延时的过程中CPU就是空闲下来了,无法对外界消息做出回应,在对时间要求较高的场和中就不太适用。既然我是要学习的,就要和实际看齐,就要想一种不太占用CPU的方法。这是定时器就出来了,我们将一大段的延时拆成一小段一小段的,这样不就减轻CPU的压力了?

在处理抖动的问题上,我们可以采用一种滤波的方式,每隔一段时间采一下值,然后判断这些值,如果这些值稳定在一个值,那么就说明按键处于按下或者弹起的状态。下面的程序就是实现这种消抖的方式

void KEY_Scan(void){  uint8_t i;  uint8_t key_buff[] ={0xff,0xff,0xff,0xff};    key_buff[0]= (key_buff[0] << 1) | KEY1;  key_buff[1]= (key_buff[1] << 1) | KEY2;  key_buff[2]= (key_buff[2] << 1) | KEY3;  key_buff[3]= (key_buff[3] << 1) | KEY4;  for(i = 0; i < 4; i ++)  {    if((key_buff[i] & 0x0f) == 0x0f)    {      keySta[i] = 1;    }    else if((key_buff[i] & 0x0f) == 0x00)    {      keySta[i] = 0;    }    else{}  }}

要实现每隔一段时间就进行一次扫描就需要用定时器扫描,将定时器配置成1ms进一次中断,然后在中断里进行一次扫描。

那么接下来就是配置定时器,这里选择定时器4。

void TIM4_Config(){  TIM_TimeBaseInitTypeDef TIM_structure;  NVIC_InitTypeDef NVIC_structure;    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);  TIM_structure.TIM_ClockDivision = TIM_CKD_DIV1;  TIM_structure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式 向上计数  TIM_structure.TIM_RepetitionCounter = 0;//设置重复计数值  //设置1ms进入一次中断,,TIM4在APB1时钟线上36MHz,那就36预分频  //但是根据说明,如果APB1的预分频系数=1;则频率不变,否则x2  //例程的预分频x2需要按照72M配置  TIM_structure.TIM_Prescaler = 72 -1;  //就是1m 它的倒数就是时间  TIM_structure.TIM_Period = 1000-1; //这就是1ms  TIM_TimeBaseInit(TIM4, &TIM_structure);  TIM_ClearFlag(TIM4, TIM_FLAG_Update);  TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);  TIM_Cmd(TIM4, ENABLE);    //中断配置;  NVIC_structure.NVIC_IRQChannel = TIM4_IRQn;  NVIC_structure.NVIC_IRQChannelPreemptionPriority = 0;  NVIC_structure.NVIC_IRQChannelSubPriority = 2;  NVIC_structure.NVIC_IRQChannelCmd = ENABLE;  NVIC_Init(&NVIC_structure);}

再写一下中断程序就差不多了

void TIM4_IRQHandler(){  if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)  {    KEY_Scan();    TIM_ClearITPendingBit(TIM4, TIM_IT_Update);  }}

之后就是主程序

先来一个开机选择界面

  LCD_Clear(Black);  LCD_SetBackColor(Blue2);  LCD_SetTextColor(Black);  LCD_DisplayStringLine(Line0, (uint8_t *)"     LED Control    ");  LCD_SetBackColor(Black);  LCD_SetTextColor(White);  LCD_DisplayStringLine(Line1, (uint8_t *)"        .__        ");  LCD_DisplayStringLine(Line2, (uint8_t *)"   _____|__|__  ___");  LCD_DisplayStringLine(Line3, (uint8_t *)"  /  ___/  \\  \\/  /");  LCD_DisplayStringLine(Line4, (uint8_t *)"  \\___ \\|  |>    < ");  LCD_DisplayStringLine(Line5, (uint8_t *)" /____  >__/__/\\_ \\");  LCD_DisplayStringLine(Line6, (uint8_t *)"      \\/         \\/");  LCD_DisplayStringLine(Line7, (uint8_t *)"________________________");  LCD_DisplayStringLine(Line8, (uint8_t *)"Press B1 to start...");

然后就是控制界面

      LCD_Clear(Black);      LCD_SetBackColor(Blue);      LCD_SetTextColor(Black);      LCD_DisplayStringLine(Line0, (uint8_t *)"   Mode Selection    ");      LCD_SetBackColor(Black);      LCD_SetTextColor(White);      LCD_DisplayStringLine(Line2, (uint8_t *)"       Mode1");      LCD_DisplayStringLine(Line3, (uint8_t *)"       Mode2");      LCD_DisplayStringLine(Line4, (uint8_t *)"       Mode3");      LCD_DisplayStringLine(Line5, (uint8_t *)"       Mode4");      LCD_DisplayStringLine(Line7, (uint8_t *)"_____________________");      LCD_DisplayStringLine(Line8, (uint8_t *)"Runing:");      LCD_DisplayStringLine(Line9, (uint8_t *)"Nothing.");

最后就是一堆逻辑,感觉写的复杂了

 

int main(void){  uint8_t keyval;  uint8_t mode;  SysTick_Config(SystemCoreClock/1000);  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//抢占和响应优先级的范围0~3  STM3210B_LCD_Init();  LED_Enable_gpio_cofig();  Key_GPIO_Config();  TIM4_Config();  while(1)  {    if(start_flag)    {      LCD_Clear(Black);       while(start_flag)      {        count_flag = 0;        led_count = 0;        LED_ENABLE();        GPIO_Write(GPIOC, 0xff00);        LED_DISENABLE();        keyval = Get_KeyVal();        if(keyval == 0xa1)        {          start_flag = 0;        }      }    }    else    {      mode = 1;      while(start_flag == 0)      {        keyval = Get_KeyVal();        if(keyval == 0xa3)        {          mode = mode + 1;          if(mode > 4)          {            mode = 4;          }        }        if(keyval == 0xa2)        {          mode = mode - 1;          if(mode <= 1)          {            mode = 1;          }                  }        switch(mode)        {          case 1:  LCD_DisplayChar(Line2, 16* 14, '>');               LCD_DisplayChar(Line3, 16* 14, ' ');               LCD_DisplayChar(Line4, 16* 14, ' ');               LCD_DisplayChar(Line5, 16* 14, ' ');               break;          case 2:  LCD_DisplayChar(Line2, 16* 14, ' ');               LCD_DisplayChar(Line3, 16* 14, '>');               LCD_DisplayChar(Line4, 16* 14, ' ');               LCD_DisplayChar(Line5, 16* 14, ' ');               break;          case 3:  LCD_DisplayChar(Line2, 16* 14, ' ');               LCD_DisplayChar(Line3, 16* 14, ' ');               LCD_DisplayChar(Line4, 16* 14, '>');               LCD_DisplayChar(Line5, 16* 14, ' ');               break;          case 4:  LCD_DisplayChar(Line2, 16* 14, ' ');               LCD_DisplayChar(Line3, 16* 14, ' ');               LCD_DisplayChar(Line4, 16* 14, ' ');               LCD_DisplayChar(Line5, 16* 14, '>');               break;                       }        if(keyval == 0xa4)        {          count_flag = 1;            LCD_ClearLine(Line9);          switch(mode)          {            case 1:  LCD_DisplayStringLine(Line9, (uint8_t *)"Mode1");break;            case 2:  LCD_DisplayStringLine(Line9, (uint8_t *)"Mode2");break;            case 3:  LCD_DisplayStringLine(Line9, (uint8_t *)"Mode3");break;            case 4:  LCD_DisplayStringLine(Line9, (uint8_t *)"Mode4");break;                 }        }        if(keyval == 0xa1)        {          start_flag = 1;        }        while(count_flag)        {          LED_ENABLE();          GPIO_Write(GPIOC, led_buff[mode-1][led_count]);          keyval = Get_KeyVal();          if(keyval == 0xa4)          {            count_flag = 0;            GPIO_Write(GPIOC, 0xff00);            LED_DISENABLE();              LCD_ClearLine(Line9);            LCD_DisplayStringLine(Line9, (uint8_t *)"Nothing.");          }          if(keyval == 0xa1)          {            start_flag = 1;            count_flag = 0;            GPIO_Write(GPIOC, 0xff00);            LED_DISENABLE();            }                  }       }    }  }}

视频

蓝桥杯开发板输入演示

详细的代码文末下载

链接:https://pan.baidu.com/s/1Fjv7xB3PHD6OLM0HkMm-ew 

提取码:xxjp

欢迎关注公众号

 

### 关于蓝桥杯嵌入式竞赛中的长按和短按按键操作学习资料 在准备蓝桥杯嵌入式比赛的过程中,掌握按键的操作尤其是长短按的实现方法是非常重要的[^1]。为了更好地理解和实践这些技能,下面是一些推荐的学习资源。 #### 推荐学习材料 - **官方文档与教程** 官方提供的技术手册通常会详细介绍硬件特性以及如何编写驱动程序来检测按键状态变化。这类文档有助于理解底层工作原理并构建稳固的知识基础[^2]。 - **在线课程与视频讲座** 许多平台提供针对特定微控制器系列(如STM32)的教学视频,在其中可以找到有关配置GPIO端口作为输入设备的具体指导,并通过实例展示怎样区分不同的按下模式[^3]。 - **开源项目案例分析** 查看其他参赛者分享的成功作品能够获得宝贵的实战经验。特别是那些解决了类似问题的解决方案,比如优化后的计时逻辑使得长时间保持按钮不会过快触发增量动作[^4]。 #### 实践建议 对于想要深入研究此主题的人来说,尝试动手制作一个小项目可能是最有效的方式之一。可以从简单的实验开始,例如创建一个仅包含几个LED灯的小电路板;当用户点击某个开关时点亮相应的灯光组合表示不同类型的触摸行为——单次轻触对应快速闪烁而持续按压则维持稳定亮起直到释放为止。 此外,还可以探索更复杂的场景应用,像菜单导航界面或是多媒体播放控制面板等场合下的交互设计原则。这不仅限于理论上的认知积累,更重要的是培养解决问题的能力和技术迁移水平。 ```c // 示例C代码片段用于说明基本概念 #include "stm32f1xx_hal.h" #define BUTTON_PIN GPIO_PIN_0 #define BUTTON_PORT GPIOA void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void){ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); uint8_t buttonState = 0; while (1) { if(HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN)==GPIO_PIN_RESET){ // 检测到低电平即认为是被按下 delay_ms(20); // 去抖动延时 if(buttonState==0 && HAL_GPIO_ReadPin(BUTTON_PORT,BUTTON_PIN)==GPIO_PIN_RESET){ shortPressHandler(); // 处理短按事件 while(HAL_GPIO_ReadPin(BUTTON_PORT,BUTTON_PIN)==GPIO_PIN_RESET){ longPressTimer++; // 开始计数 if(longPressTimer>=LONG_PRESS_THRESHOLD){ longPressHandler(); // 达到阈值后处理长按事件 break; } delay_ms(10); } buttonState=HAL_GPIO_ReadPin(BUTTON_PORT,BUTTON_PIN)?0:buttonState+1; }else{ multiTapCounter++; if(multiTapCounter>DOUBLE_TAP_LIMIT){ doubleTapHandler(); // 如果两次间隔小于设定时间,则视为双击 multiTapCounter=0; } } buttonState=(multiTapCounter<=DOUBLE_TAP_LIMIT)?1-buttonState:0; } delay_ms(50); // 循环等待期间适当降低CPU占用率 } } void shortPressHandler(){ /* 执行短按响应 */ } void longPressHandler(){ /* 执行长按响应 */ } void doubleTapHandler(){ /* 执行双击响应 */ } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值