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

17届蓝桥杯嵌入式赛道开发板外设使用教程——按键、蜂鸣器、LCD屏幕

17届蓝桥杯嵌入式赛道开发板外设使用教程——按键、蜂鸣器、LCD屏幕,实现按键复杂状态检测

引言

上期视频我们介绍了开发板上最基础的外设——LED,接下来我会继续介绍单片机上的按键以及蜂鸣器外设,同时我会编写一个捕获按键状态的项目并通过LCD屏幕输出结果信息

硬件连接介绍

按键

通过观察原理图我们发现单片机上的四个按键与引脚之间的对应关系如下

按键编号对应引脚
B1PB0
B2PB1
B3PB2
B4PA0

通过硬件连接图给出的信息我们总结出当按键按下时对应的引脚电平会变为低电平,当按键抬起的时候对应的引脚电平为高电平

蜂鸣器

由图可知蜂鸣器连接着单片机上的PB3引脚,且当对应引脚为低电平时蜂鸣器才会开始工作

LCD屏幕

由图可知LCD屏幕一共有30个引脚,我们观察之后可得引脚对应关系如下

LCD编号对应引脚
LCD_D0PC0
LCD_D1PC1
LCD_D2PC2
LCD_D3PC3
LCD_D4PC4
LCD_D5PC5
LCD_D6PC6
LCD_D7PC7
LCD_D8PC8
LCD_D9PC9
LCD_D10PC10
LCD_D11PC11
LCD_D12PC12
LCD_D13PC13
LCD_D14PC14
LCD_D15PC15
LCD_CSPB9
LCD_RSPB8
LCD_WR/SCLPB5
LCD_RDPA8
LCD_RESETRST

乍一看这么多引脚是不是心里都在想:这么多引脚在CubeMX要配置多长时间才能使用?这你就多虑了,在实际的比赛过程中官方会给我们提供对应的LCD驱动项目的完整文件,我们只需要直接将其中的LCD驱动文件添加到我们对应的项目中即可,根本不要花时间和精力去进行LCD的配置操作

项目配置

基本配置

了解完底层的硬件连接之后我们就可以尝试去进行项目的配置操作了,由于开发板上搭载的按键以及蜂鸣器都较为常规,因此配置过程也较为简单

我们将蜂鸣器对应引脚设置为普通的推挽输出即可,由于在当前项目中需要利用状态机来检测按键的状态,因此我们需要将按键对应的引脚设置为下降沿中断触发模式

由于在本项目中我们需要定期的扫描按键的状态,因此为了减少对主逻辑的影响我们选择在定时器的中断回调函数中进行按键扫描操作,定时器配置如下

由于单片的主频为170Mhz,因此预分频器的值设置为170-1,这样定时器的计数频率就是一秒钟1,000,000次,随后我们选择将自动重装载器的值设置为10000-1,这样定时器就会每10ms产生一次中断

最后不要忘了检查一下按键中断是否成功开启,确认无误后就可以点击右上角的生成代码选项生成项目

LCD驱动导入

由于我们使用的是官方提供的驱动文件,因此我们只需要直接导入对应驱动文件到项目中即可导入流程如下

  • HardWare文件夹下创建LCD文件夹,并将对应驱动文件直接拷贝到该文件夹下

  • Keil软件中创建对应文件夹

  • 添加对应的头文件收索路径

至此我们就成功的将LCD的驱动文件添加到我们项目中去了,在后续的代码编写过程中我们如果需要使用LCD显示屏,那么只需要直接引用相关头文件,然后直接调用相关的驱动函数即可

代码编写

项目配置完成后我们就可以尝试编写对应的外设驱动文件了

蜂鸣器

由于当前单片机上搭载的是有源蜂鸣器,因此只能操作其发声和关闭,对应的驱动文件编写也是最简单的

头文件
#ifndef__BUZZ_H__#define__BUZZ_H__#include"main.h"// 开启蜂鸣器voidOpen_buzz(void);// 关闭蜂鸣器voidClose_buzz(void);#endif
源文件
#include"buzz.h"// 开启蜂鸣器voidOpen_buzz(void){//写入低电平即可开启蜂鸣器HAL_GPIO_WritePin(Buzz_GPIO_Port,Buzz_Pin,GPIO_PIN_RESET);}// 关闭蜂鸣器voidClose_buzz(void){//写入高电平关闭蜂鸣器HAL_GPIO_WritePin(Buzz_GPIO_Port,Buzz_Pin,GPIO_PIN_SET);}

按键

在当前项目中我们需要判断案件的三种不同状态:短按、长按和双击,因此我们需要用到状态机来实现状态转换以及状态检测。在之前的项目中我们就做过这个功能,这里再做一次只是为了演示一下开发板上按键和LCD屏幕的用法,大家也可以去那个文章中了解一下更多的细节和具体的实现思路
文章链接

头文件

首先我们需要存储按键的状态,根据前面的介绍我们了解到在当前项目中按键一共会有四种状态:空闲态、确认状态(用于按键消抖)、按下状态以及等待抬起状态(这里不理解的话建议看看上面推荐的文章)

typedefenum{KEY_STATE_IDLE=0,//空闲状态KEY_STATE_CONFIRM,//确认状态KEY_STATE_PRESSED,//按下状态KEY_STATE_WAIT_RELEASE,//等待抬起状态}KeyState;

同时我们还需要存储对应的事件,以便当不同事件发生时我们去执行不同的操作,本项目中的事件也有四种:未发生事件、按键短按事件、按键长按事件以及按键双击事件,我们会根据状态的发生来更改按键的状态

typedefenum{KEY_EVENT_NONE=0,//未发生事件KEY_EVENT_SHORT,//短按事件KEY_EVENT_LONG,//长按事件KEY_EVENT_DOUBLE,//双击事件}KeyEvent;

最后为了方便统一管理所有与按键有关的状态和事件,我们顶一个结构体来存储所有有关按键的变量,其中包含了按键状态枚举类型变量(state)、按键事件枚举类型变量(event)、记录按下持续时间变量(press_times)、记录两次按键按下间隔时间变量(gap_times)以及存储在一轮判断中按键按下的次数变量(click_count

typedefstruct{KeyState state;uint16_tpress_times;//记录当前这次按下持续了多少个周期uint16_tgap_times;//第一次松开按键到第二次按键按键中间经历了多少个周期uint8_tclick_count;//记录在一轮操作中按下的次数KeyEvent event;}KeyEx;

随后就是一些循环参数以及外部函数的声明

//扫描周期#defineKEY_SCAN_PERIOD_MS10//长按时间判定,单按键按下时间超过1秒钟时说明按键处于长按状态#defineKEY_LONG_MS1000//双击时间间隔,当两次按键按下的时间间隔在300ms以内证明按键被双击了#defineKEY_DOUBLE_MS300//将时间转换为扫描的次数#defineKEY_LONG_TIMES(KEY_LONG_MS/KEY_SCAN_PERIOD_MS)#defineKEY_DOUBLE_TIMES(KEY_DOUBLE_MS/KEY_SCAN_PERIOD_MS)// 按键事件标志位,在 Key.c 中定义,这里只做声明externvolatileuint8_tkey1_flag;externvolatileuint8_tkey2_flag;externvolatileuint8_tkey3_flag;// 三个按键的扩展状态(在 Key.c 中定义),用于在 Bsp_loop 中读取事件externKeyEx key1;externKeyEx key2;externKeyEx key3;// 单个按键扫描函数voidKey_scan_states_one(GPIO_TypeDef*port,uint16_tpin,KeyEx*k);//全部按键扫描函数voidKey_scan_states_aLL(void);
源文件

在头文件中声明了对应的驱动函数之后就需要在源文件中实现对应的函数功能

首先初始化全局变量,设置好按键的初始状态

// 初始化按键为空闲状态,全局标志位只在本文件定义一次volatileuint8_tkey1_flag=0;volatileuint8_tkey2_flag=0;volatileuint8_tkey3_flag=0;// 三个按键的扩展状态(供其它模块读取事件)KeyEx key1={KEY_STATE_IDLE};KeyEx key2={KEY_STATE_IDLE};KeyEx key3={KEY_STATE_IDLE};

随后实现单个按键的扫描函数

/** * @brief 单个按键扫描函数 * * @param port 按键对应的引脚组 * @param pin 按键对应的引脚编号 * @param k 按键的状态 */voidKey_scan_states_one(GPIO_TypeDef*port,uint16_tpin,KeyEx*k){//获取按键电平状态GPIO_PinState level=HAL_GPIO_ReadPin(port,pin);//根据按键的不同状态做出不同响应switch(k->state){//空闲状态caseKEY_STATE_IDLE://重置变量状态k->event=KEY_EVENT_NONE;k->press_times=0;k->gap_times=0;k->click_count=0;//电平发生变化,说明此时按键疑似按下,接下来需要进步验证if(level==GPIO_PIN_RESET){k->state=KEY_STATE_CONFIRM;}break;//接下来进入验证状态,在验证状态中需要检测按键是否真的已经按下caseKEY_STATE_CONFIRM://再次读取信息以确定按键状态if(level==GPIO_PIN_RESET){k->state=KEY_STATE_PRESSED;k->press_times=0;}else{//此时说明前面的按下是抖动误触,此时将状态返回到空闲态等待下一次电平变化k->state=KEY_STATE_IDLE;}break;//当确定按键第一次按下后需要记录按下的持续时间以进行下一步操作caseKEY_STATE_PRESSED://记录按下状态的持续时间k->press_times++;//长按判定,达到指定时间则更改状态为长按状态if(k->press_times==KEY_LONG_TIMES){k->event=KEY_EVENT_LONG;}//状态变化说明按键抬起,表明此时不是长按状态if(level==GPIO_PIN_SET){//此时进入短按一次状态if(k->press_times<KEY_LONG_TIMES){k->click_count++;//如果这是第一次短按则进入等待,尝试等待第二次按键按下if(k->click_count==1){//状态更新为等待按键松开k->state=KEY_STATE_WAIT_RELEASE;//重置间隔时间k->gap_times=0;}else{//此时说明第二次短按也完成,则判定进入了双击状态k->event=KEY_EVENT_DOUBLE;//一轮判断完成,将状态恢复为空闲态k->state=KEY_STATE_IDLE;}}else{//此时已经触发过了长按,当不再短按时直接回到空闲态k->state=KEY_STATE_IDLE;}}break;caseKEY_STATE_WAIT_RELEASE://记录两次按键按下的时间间隔k->gap_times++;//按键按下时间超过了双击的间隔时间则进一步判断是否为长按状态if(k->gap_times>KEY_DOUBLE_TIMES){if(k->click_count==1&&k->event!=KEY_EVENT_LONG){k->event=KEY_EVENT_SHORT;}k->state=KEY_STATE_IDLE;}else{if(level==GPIO_PIN_RESET){k->state=KEY_STATE_CONFIRM;}}break;default:k->state=KEY_STATE_IDLE;break;}}

实现了单个按键的扫描函数之后我们只需要调用三次按键扫描函数分别扫描三个按键的状态即可

/** * @brief 扫描所有按键状态函数 * */voidKey_scan_states_aLL(void){Key_scan_states_one(KEY1_GPIO_Port,KEY1_Pin,&key1);Key_scan_states_one(KEY2_GPIO_Port,KEY2_Pin,&key2);Key_scan_states_one(KEY3_GPIO_Port,KEY3_Pin,&key3);}

最后我们需要重写TIM6的中断回调函数,并在中断回调函数中调用全键扫描函数即可

voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){// 判断中断信号源if(htim->Instance==TIM6){Key_scan_states_aLL();}}

LCD

由于LCD的驱动文件我们是直接使用官方提供的,因此在这里我们只需要了解一下官方给我们编写的驱动函数的用法即可,接下来只介绍常用的几个函数的用法及作用

  • 初始化函数:用于初始化LCD屏幕,每次使用LCD屏幕都必须调用当前函数

    voidLCD_Init(void);
  • 设置文本颜色函数:传参时传入对应的宏定义即可

    voidLCD_SetTextColor(vu16 Color)// 颜色宏定义#defineWhite0xFFFF#defineBlack0x0000#defineGrey0xF7DE#defineBlue0x001F#defineBlue20x051F#defineRed0xF800#defineMagenta0xF81F#defineGreen0x07E0#defineCyan0x7FFF#defineYellow0xFFE0
  • 设置背景颜色:传参时同样传入对应的颜色宏定义即可

    voidLCD_SetBackColor(vu16 Color)
  • 清除指定行内容:传入指定行数,随后便会将指定行数内容清空,传参时同样传入对应的行数宏定义即可

    voidLCD_ClearLine(u8 Line)//行数宏定义#defineLine00#defineLine124#defineLine248#defineLine372#defineLine496#defineLine5120#defineLine6144#defineLine7168#defineLine8192#defineLine9216
  • 清除整个LCD屏幕中的内容

    voidLCD_Clear(u16 Color)
  • 设置光标位置:传入目标位置的x和y坐标即可

    voidLCD_SetCursor(u8 Xpos,u16 Ypos)
  • LCD屏幕上绘制字符:传入绘制字符的位置信息以及字符内容

    voidLCD_DrawChar(u8 Xpos,u16 Ypos,uc16*c)
  • LCD上显示字符:传入行列信息即可,当前函数通过调用LCD_DrawChar函数实现

    voidLCD_DisplayChar(u8 Line,u16 Column,u8 Ascii)
  • LCD上显示字符串:直接传入对应的字符串即可在LCD上显示对应信息,该函数底层通过调用LCD_DisplayChar函数实现

    voidLCD_DisplayStringLine(u8 Line,u8*ptr)

至此有关LCD的驱动函数我们就介绍完毕了,接下来我们如果需要使用LCD屏幕,直接调用对应的驱动函数即可

函数调用

前面完成了所有驱动文件的编写以及介绍,接下来我就展示一下如何调用上述驱动函数实现按键检测的完整代码

voidBsp_loop(void){LED_TurnOffALL();// 处理按键1的事件(控制LED1)KeyEvent e1=key1.event;key1.event=KEY_EVENT_NONE;switch(e1){caseKEY_EVENT_SHORT:// 单击:切换LED1的亮灭LED_Toggle(LED1);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key1 Pressed!");break;caseKEY_EVENT_LONG:// 长按:熄灭LED1LED_TurnOff(LED1);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key1 Long Pressed!");break;caseKEY_EVENT_DOUBLE:// 双击:点亮LED1LED_TurnOn(LED1);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key1 Double Pressed!");break;default:break;}// 处理按键2的事件(控制LED2)KeyEvent e2=key2.event;key2.event=KEY_EVENT_NONE;switch(e2){caseKEY_EVENT_SHORT:// 单击:切换LED2的亮灭LED_Toggle(LED2);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key2 Pressed!");break;caseKEY_EVENT_LONG:// 长按:熄灭LED2LED_TurnOff(LED2);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key2 Long Pressed!");break;caseKEY_EVENT_DOUBLE:// 双击:点亮LED2LED_TurnOn(LED2);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key2 Double Pressed!");break;default:break;}// 处理按键3的事件(控制LED3)KeyEvent e3=key3.event;key3.event=KEY_EVENT_NONE;switch(e3){caseKEY_EVENT_SHORT:// 单击:切换LED3的亮灭LED_Toggle(LED3);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key3 Pressed!");break;caseKEY_EVENT_LONG:// 长按:熄灭LED3LED_TurnOff(LED3);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key3 Long Pressed!");break;caseKEY_EVENT_DOUBLE:// 双击:点亮LED3LED_TurnOn(LED3);LCD_Clear(White);// 清屏,背景设为白色LCD_DisplayStringLine(Line4,(u8*)" Key3 Double Pressed!");break;default:break;}}

将上述函数直接在主循环中调用就可以实现对应的按键状态判断功能

结果展示

代码烧录后我们按下按键,LCD显示屏上就会以文字的形式显示按键的状态

总结

通过当前项目你应该就对开发板上对应的按键、蜂鸣器以及LCD屏幕的使用有了大体的了解,但是项目中的按键检测逻辑较为繁琐还是建议大家去看一下另一篇介绍思路的文章
文章链接

http://www.jsqmd.com/news/466405/

相关文章:

  • 机关智慧食堂后勤管理系统__Python django flask
  • 隧道能见度检测器:守护隧道安全的“火眼金睛”
  • 那就随便说说
  • Carsim联合仿真模型验证:十四自由度车辆动力学模型的应用
  • 2026 第八批 “小巨人” 申报收官在即 评审核心导向升级
  • 互联网大厂Java求职者面试实战:严肃面试官与搞笑程序员谢飞机的故事
  • 逆向新手之攻防世界--key
  • **Gemini2.5Pro去AI味2025指南,打造自然流畅的文本生成体验**
  • CUDA graph 简析
  • 基于微信小程序的课程作业管理系统[小程序]-计算机毕业设计源码+LW文档
  • 别死记硬背!Java的CountDownLatch 核心原理:AQS state 才是关键
  • 知识体系——MCP(四)demo(2)开发mcp client
  • OWASP Top10 2021 完整版:与 SAST 适配的深度解析
  • Rocky Linux 10 上搭建 社区版 GitLab CE
  • 2026年 智能制造实训设备厂家推荐排行榜:高校教学、模拟药厂、生产线实训平台与系统装置一站式解决方案 - 品牌企业推荐师(官方)
  • g更改linux root密码
  • LeetCode 76. 最小覆盖子串(详细技术解析)
  • 虚拟同步发电机(VSG)孤岛与并网的Simulink(2019a)仿真模型搭建与探索
  • 对于【LSTM与GRU在水文预测中的对比分析】的未来改进和建议
  • 工业清洁设备优质品牌推荐榜:驾驶式洗地机/1000公斤高压清洗机/商用洗地机/工业吸尘器/工业洗地机/工业清洗机厂家/选择指南 - 优质品牌商家
  • 2026年比较好的西安租赁洗地机工厂推荐:西安洗地机租赁稳定供应商推荐 - 行业平台推荐
  • visual studio编译wxWidgets
  • 防疫站疫苗预约管理系统_Python django flask
  • 2026宁波好用的芯轴品牌生产厂盘点,如何选择靠谱厂家 - 工业推荐榜
  • 2026河北新河优质MC浇筑尼龙加工件推荐榜:pa66尼龙棒/pp尼龙棒/尼龙加工件源头厂家/浇筑尼龙棒/玻纤mc尼龙浇铸棒/选择指南 - 优质品牌商家
  • Django + Vue3 + YOLO 实现车辆检测、测速预警与违章分析平台
  • 互联网大厂Java面试:谢飞机与严肃面试官的搞笑对决
  • 救命神器! 降AIGC工具 千笔·降AIGC助手 VS WPS AI 专科生专属
  • Gemini认证工具创意开发(技术深度解析)
  • 探讨江苏地区井口装置涂装厂家排名,江苏万和涂装排第几? - myqiye