zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

蓝桥杯嵌入式第三课--LED与按键检测

嵌入式 -- 检测 蓝桥 按键 LED
2023-09-14 09:16:16 时间

前言

纵观多年考题,LED和按键检测作为必考的一个部分同时也作为GPIO的重点考察内容一直都是我们必须要掌握的部分。本节课带着大家,从底层硬件开始,把GPIO的这两个考点学的清清楚楚!

点亮LED!

一、底层硬件

板载的LED如上图所示,一共有八个LED灯,并且由一块锁存器芯片控制单片机引脚和LED的开关,这么做的目的是,LED的部分引脚和LCD的引脚产生了冲突,因此LED的(PC8\PC10\PC12\PC14)和LCD不能同时使用!,另外锁存器芯片的LE脚为高电平时,Q和D连通,LE脚为低电平时,Q端保持之前的状态,见真值表如下:

具体来说就是,PC8-PC16控制八个LED灯,低电平有效;PD2控制锁存器的开关,高电平时连通,低电平时断开。

二、程序设计思路

LED的程序比较固定,市面上流传的都是一次性随意点亮八个灯的版本,因为考试的时候我们需要盲打出来,所以这里我先和大家讨论一下程序设计的思路:

模块大体就两个步骤:先完成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);
}

按键输入检测

一、底层硬件

从上图看出,板载的按键一共有四个,它们未按下时为高电平,按下后为低电平

具体地来说,Key1-4分别为 PB0、PB1、PB2、PA0。

二、程序设计思路

按键这事儿吧,其实没有什么固定的套路,核心就是要带消抖功能,老夫遍寻网络世界,发现实现按键检测的方式千千万万,从最基本的轮询式到高级一点的中断,可谓繁多复杂,所以必须要因题制宜,找到最合适的方法。下面我介绍常用的两种方式。

  1. 基础款(带消抖,不精确)

相信很多人都学过51单片机,里头按键的检测最基本的就是采用if嵌套的方式,使用延时函数进行消抖处理:

优点是简单,缺点是复杂场合下使用Bug较多,编程负责,按键按下次数不好判定,长按短按的实现只能通过调整延时时间,容易误判。

b.升级款(使用GPIO外部中断进行按键检测)

这个思路很好理解,按键按下时会产生下降沿,一按按钮就触发一次,长按也没啥影响,毕竟只对边沿敏感,所以这个的重点就在于配置上,下图是配置外部中断的步骤:

三、具体实现

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栏里设置中断模式以及是否上下拉

二、在NVIC中打开外部中断

三、生成代码,我们在stm32g4xx_it.c中找到中断的句柄函数

点击HAL_GPIO_EXIT_IRQHandler();函数,找到原型实现:

我们可以看到中断发生后,先执行的操作是清除中断标志位,然后进入回调函数,因此我们需要自己编写我们的回调函数:

我们可以看到,回调函数是一个弱函数,因此我们只需要找一个地方重写它即可,不需要对其进行改动。我将其写在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),输入的参数为:

  1. GPIOx(GPIOA、GPIOB、等等)

  1. GPIO_PIN_x(GPIO_PIN_0、GPIO_PIN_1、等等)

  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),参数为:

  1. GPIOx(GPIOA、GPIOB、等等)

  1. 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)

外部中断服务函数,这个我们在前面的介绍中看到过,其分为两部分:

  1. 中断标志位清楚

  1. 回调函数

不需要我们编写和调用。

五、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库更加高效。下一节我们将重点学习定时计数器的相关内容。