蓝桥杯嵌入式第三课--LED与按键检测
前言
纵观多年考题,LED和按键检测作为必考的一个部分同时也作为GPIO的重点考察内容一直都是我们必须要掌握的部分。本节课带着大家,从底层硬件开始,把GPIO的这两个考点学的清清楚楚!
点亮LED!
一、底层硬件
![](https://img-blog.csdnimg.cn/img_convert/df2c4977ecf08f4b32609dfdda7a87de.png)
板载的LED如上图所示,一共有八个LED灯,并且由一块锁存器芯片控制单片机引脚和LED的开关,这么做的目的是,LED的部分引脚和LCD的引脚产生了冲突,因此LED的(PC8\PC10\PC12\PC14)和LCD不能同时使用!,另外锁存器芯片的LE脚为高电平时,Q和D连通,LE脚为低电平时,Q端保持之前的状态,见真值表如下:
![](https://img-blog.csdnimg.cn/img_convert/35313a8b467f0a3af3f3042b3d5e9aaf.png)
具体来说就是,PC8-PC16控制八个LED灯,低电平有效;PD2控制锁存器的开关,高电平时连通,低电平时断开。
二、程序设计思路
LED的程序比较固定,市面上流传的都是一次性随意点亮八个灯的版本,因为考试的时候我们需要盲打出来,所以这里我先和大家讨论一下程序设计的思路:
![](https://img-blog.csdnimg.cn/img_convert/d9a8dbcf827b9795e9e00fd17c58642c.png)
模块大体就两个步骤:先完成LED初始化,保证LED灯都在熄灭的状态,然后在点亮需要的LED灯。可能有人会疑惑:为啥每次LED操作之后都要把PD2从高拉到低呢?
这是因为PD2拉高是为了让Q端的数据和D端的同步,PD2拉低是为了让D端的数据不影响LED端,这是功能需要,也是使用的一种规范。就像上下车要打开和关闭车门一样。
三、实例代码
//函数的输入是八位的16进制数,每一位分别代表一个LED,如0x05=0000_0101,相当于第1个和3个亮
void led_disp(uint8_t led){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,led<<8,GPIO_PIN_RESET);//左移八位的原因是我们控制的引脚是从8-16的高八位
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
按键输入检测
一、底层硬件
![](https://img-blog.csdnimg.cn/img_convert/1f574c153def6c4380f239495d3452b7.png)
从上图看出,板载的按键一共有四个,它们未按下时为高电平,按下后为低电平。
具体地来说,Key1-4分别为 PB0、PB1、PB2、PA0。
二、程序设计思路
按键这事儿吧,其实没有什么固定的套路,核心就是要带消抖功能,老夫遍寻网络世界,发现实现按键检测的方式千千万万,从最基本的轮询式到高级一点的中断,可谓繁多复杂,所以必须要因题制宜,找到最合适的方法。下面我介绍常用的两种方式。
基础款(带消抖,不精确)
相信很多人都学过51单片机,里头按键的检测最基本的就是采用if嵌套的方式,使用延时函数进行消抖处理:
![](https://img-blog.csdnimg.cn/img_convert/eecb1106714592d89828b4f3a23d7e01.png)
优点是简单,缺点是复杂场合下使用Bug较多,编程负责,按键按下次数不好判定,长按短按的实现只能通过调整延时时间,容易误判。
b.升级款(使用GPIO外部中断进行按键检测)
![](https://img-blog.csdnimg.cn/img_convert/10cf4e1b236d230a0f4d25787b050b60.png)
这个思路很好理解,按键按下时会产生下降沿,一按按钮就触发一次,长按也没啥影响,毕竟只对边沿敏感,所以这个的重点就在于配置上,下图是配置外部中断的步骤:
![](https://img-blog.csdnimg.cn/img_convert/786452bd70d83609784e66471edad56d.png)
三、具体实现
a.基础款
uint8_t keyscan(void) //该函数返回按下的按键号
{
if(~HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)|~HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)|~HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)|~HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0))
{
HAL_Delay(100); //消抖时间设置
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
{
return 1;
}
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
{
return 2;
}
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==0)
{
return 3;
}
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==0)
{
return 4;
}
}
return 0;
}
b.中断款
一、找到对应引脚,设置为GPIO_EXTI模式,并在GPIO栏里设置中断模式以及是否上下拉
![](https://img-blog.csdnimg.cn/img_convert/7723c4bd07c1dbcb1525d3b7da2fe540.png)
二、在NVIC中打开外部中断
![](https://img-blog.csdnimg.cn/img_convert/789f79e118699d6662cc055bcbdf5468.png)
三、生成代码,我们在stm32g4xx_it.c中找到中断的句柄函数
![](https://img-blog.csdnimg.cn/img_convert/4c796944448bb37b2af8fe18f6b25d7d.png)
点击HAL_GPIO_EXIT_IRQHandler();函数,找到原型实现:
![](https://img-blog.csdnimg.cn/img_convert/e55727db2f360f173485db62d1355c3a.png)
我们可以看到中断发生后,先执行的操作是清除中断标志位,然后进入回调函数,因此我们需要自己编写我们的回调函数:
![](https://img-blog.csdnimg.cn/img_convert/1debc1c82ffa30b321ea002eb5743fa8.png)
我们可以看到,回调函数是一个弱函数,因此我们只需要找一个地方重写它即可,不需要对其进行改动。我将其写在main.c中如下:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==GPIO_PIN_0) //判断是哪个按键按下,因为GPIO的外部中断都会进入这个函数
{
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)//再次确认消抖
{
按下后执行的语句;
}
//__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);//清楚中断标志,可省略,因为前一级中有代码执行该操作
}
}
这样,每按下一次按键,都会执行一遍回调函数,也就实现了精确测量按键按下个数的目的。
HAL库GPIO函数
一、HAL_GPIO_ReadPin
/**
* @brief Read the specified input port pin.
* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family
* @param GPIO_Pin specifies the port bit to read.
* This parameter can be any combination of GPIO_PIN_x where x can be (0..15).
* @retval The input port pin value.
*/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
该函数主要功能是用来读取GPIO引脚的电平状态;输入的参数,第一个为GPIO的类型,例如GPIOA、GPIOB等等;输入的第二个参数为GPIO引脚,例如GPIO_PIN_0、GPIO_PIN_1等。
二、HAL_GPIO_WritePin
/**
* @brief Set or clear the selected data port bit.
*
* @note This function uses GPIOx_BSRR and GPIOx_BRR registers to allow atomic read/modify
* accesses. In this way, there is no risk of an IRQ occurring between
* the read and the modify access.
*
* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family
* @param GPIO_Pin specifies the port bit to be written.
* This parameter can be any combination of GPIO_PIN_x where x can be (0..15).
* @param PinState specifies the value to be written to the selected bit.
* This parameter can be one of the GPIO_PinState enum values:
* @arg GPIO_PIN_RESET: to clear the port pin
* @arg GPIO_PIN_SET: to set the port pin
* @retval None
*/
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
该函数的用于将GPIO引脚设置为高电平(SET)或者是低电平(RESET),输入的参数为:
GPIOx(GPIOA、GPIOB、等等)
GPIO_PIN_x(GPIO_PIN_0、GPIO_PIN_1、等等)
GPIO_PIN_SET / GPIO_PIN_RESET (置位或者复位)
三、HAL_GPIO_TogglePin
/**
* @brief Toggle the specified GPIO pin.
* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family
* @param GPIO_Pin specifies the pin to be toggled.
* This parameter can be any combination of GPIO_PIN_x where x can be (0..15).
* @retval None
*/
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
该函数的功能是将GPIO输出的电平状态反转(0-1、1-0),参数为:
GPIOx(GPIOA、GPIOB、等等)
GPIO_PIN_x(GPIO_PIN_0、GPIO_PIN_1、等等)
四、HAL_GPIO_EXTI_IRQHandler
/**
* @brief Handle EXTI interrupt request.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
外部中断服务函数,这个我们在前面的介绍中看到过,其分为两部分:
中断标志位清楚
回调函数
![](https://img-blog.csdnimg.cn/img_convert/153c403459de146dc43ecbddfdc7faed.png)
不需要我们编写和调用。
五、HAL_GPIO_EXTI_Callback
/**
* @brief EXTI line detection callback.
* @param GPIO_Pin: Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
中断回调函数,老朋友了;中断发生后,在中断服务函数里会调用该函数,执行自定义代码。
六、HAL_GPIO_LockPin
/**
* @brief Lock GPIO Pins configuration registers.
* @note The locked registers are GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR,
* GPIOx_PUPDR, GPIOx_AFRL and GPIOx_AFRH.
* @note The configuration of the locked GPIO pins can no longer be modified
* until the next reset.
* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family
* @param GPIO_Pin specifies the port bits to be locked.
* This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
* @retval None
*/
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
该函数的作用是锁住引脚所对应的寄存器某些位,一旦锁住就不能再进行修改,只有复位后才可以重新配置。极少使用。
总结
GPIO虽然简单,但是如果认真说道的话内容其实也很繁杂,更高级的用法是在寄存器的级别,通过操控ODR寄存器等对GPIO进行操作,这个层面会比HAL库更加高效。下一节我们将重点学习定时计数器的相关内容。
相关文章
- 《安富莱嵌入式周报》第301期:ThreadX老大离开微软推出PX5 RTOS第5代系统,支持回流焊的自焊接PCB板设计,单色屏实现多级灰度播放视频效果
- 《安富莱嵌入式周报》第292期:树莓派单片机100M双通道示波器开源,MDK5.38发布,万用表单芯片解决方案,8通道±25V模拟前端芯片,开源贴片拾取电机板
- 《安富莱嵌入式周报》第280期:支持在线仿真编程的网页版电子开发,CAN总线防攻击实现,BigFAT 规范打破了 FAT 每个文件 4GB 的限制
- 《安富莱嵌入式周报》第270期:2022.06.13--2022.06.19
- 《安富莱嵌入式周报》第268期:2022.05.30--2022.06.05
- 《安富莱嵌入式周报》第267期:2022.05.23--2022.05.29
- 《安富莱嵌入式周报》第237期:2021.10.25--2021.10.31
- 《安富莱嵌入式周报》第227期:2021.08.23--2021.08.29
- 嵌入式新闻早班车-第19期
- 《安富莱嵌入式周报》第224期:2021.08.02--2021.08.08
- 嵌入式新闻早班车-第9期
- 《安富莱嵌入式周报》第210期:2021.04.26--2021.05.02
- 手把手带你基于嵌入式Linux移植samba服务
- 嵌入式linux开发,Ubuntu18.04 下搭建 NFS 服务器
- RK3399平台开发系列讲解(项目实践篇)1.8、嵌入式开发都可以理解的卷积网络(简单明了无公式)
- 研发管理02----嵌入式硬件设计流程之完善
- 蓝桥杯嵌入式--LCD卡死问题
- 蓝桥杯嵌入式--实战模拟题
- 蓝桥杯嵌入式--LCD屏幕使用提升
- 蓝桥杯嵌入式--串口收发数据异常的解决
- 蓝桥杯嵌入式第八课--EEPROM读写
- 蓝桥杯嵌入式第七课--ADC的配置与使用