速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断

在第一讲中曾经提到,GPIO有输入输出两种模式。在点亮LED时,我们已经使用了GPIO输出模式,在按键识别中,我们将要使用GPIO输入模式。首先来看看按键的电路原理图(下图在选手资源数据包——CT117E-M4产品手册中):

速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第1张图片

其中,B1~B4为4个不同的按键,它们通过PB0、PB1、PB2、PA0四个端口以上拉电阻的方式连接到单片机中。当按键松开时,PB0等端口处于高电平状态;当按键按下后,端口处于低电平状态。因此,我们可以把这些端口设置为GPIO输入+上拉电阻(pull-up)模式,通过读取其电平的高低状态来判断按键是否被按下。(所谓上下拉电阻,其实决定的就是GPIO输入端口断路时的初始电平状态,有关介绍可以自行搜索)

例如,需要判断B1是否被按下时,我们只需要判断PB0的电平状态:

if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)//读取PB0的电平状态,判断是否为低电平
{
    /* 执行任务 */
}

在主循环中,利用按键扫描,我们就可以通过不同的按键操作来执行不一样的任务,例如:

while (1)
{
    //B1按下,点亮LD1和LD2
    if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
    {
        LED_On(LD1|LD2);
    }


    //B2按下,点亮LD3和LD4
    if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
    {
        LED_On(LD3|LD4);
    }


    //B3按下,点亮LD5和LD6
    if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET)
    {
        LED_On(LD5|LD6);
    }


    //B4按下,点亮LD7和LD8
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
    {
        LED_On(LD7|LD8);
    }
}

不仅是按键,GPIO输入模式与输出模式一样,均广泛应用于各种需要读取外部电路电平的场景。有关GPIO输入的函数如下:

/**
  * @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)

然而,这种方法在实际中并不常用。可以想象,除了判断按键是否按下以外,一个嵌入式系统一定还有其他的许多任务需要执行。倘若在主循环中一直判断按键是否被按下,则会占用CPU,效率低下,同时也可能因为在执行其他任务,响应不及时。因此,我们更常用的是采用中断的方式来判断按键是否被按下。

先来普及一下什么是中断。我们知道,CPU是按照顺序依次执行主函数中的指令的。中断是指打断CPU当前正在执行的指令,跳到中断程序中执行,随后跳会主函数原来的位置继续执行原指令。例如在生活中,我们的主程序是写代码,这时电话铃响,我们不得不停下手中的工作,进入中断——接电话,接完电话后又继续回到写代码的工作中。正如打断我们的有可能是电话铃声,有可能是门铃声,也有可能是短信铃声等等,打断CPU的中断方式也是多种多样的,如GPIO外部中断、定时器中断、定时器捕获中断等等……

考虑到比赛中对按键的判断涉及到长短按,我们在考虑程序执行效率而采用中断的同时,要考虑判断长短按的方法,这就涉及到按键按下的时间问题。在单片机中,与时间有关的问题,都是通过定时器来实现的。因此,下面我们来介绍定时器中断

先来介绍一下与定时器有关的概念。在单片机中,有一个晶振(石英晶体振荡器),它通常决定了单片机的时钟频率。通过对时钟的分频,可以得到许许多多的时钟源。不同的硬件通过采用不同的时钟源,再对其进行分频,就得到了独属于这个硬件自己的时钟频率,定时器亦是如此。

参照官方例程(LCD的例程),我们按如下步骤配置时钟树:

(1)开启外部高速时钟

速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第2张图片

(2)勾选HSE,将时钟频率设置为80MHz后按回车

速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第3张图片

(3)所得到的定时器频率即可以在上图右侧圆圈处查看

这样我们就得到了时钟频率为80MHz的定时器。

下面我们来开启定时器中断。我们设置TIM4如下:

速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第4张图片

其中,定时器频率按照如下公式计算:(具体原理请自行搜索)

eq?f%3D%5Cfrac%7Bf_%7B0%7D%7D%7B%5Cleft%20%28Prescaler+1%20%5Cright%20%29%5Cleft%20%28%20Counter%20Period+1%20%5Cright%20%29%7D%3D%5Cfrac%7B80%2C000%2C000%7D%7B80%5Ctimes%2010000%7D%3D100Hz

f0为时钟频率80MHz,Prescaler为预分频系数,Counter Period为计数周期。这样我们就把TIM4定时器的频率设置为了100Hz,即周期为0.01s。最后,只需要打开中断开关,就完成了定时器中断的配置。

535b9bad5fed426eab77001ec3f2b3a9.png

在Cube中设置好后,想要使用定时器中断,还要在主函数初始化时开启定时器中断(在此处是开启TIM4的定时器中断)

HAL_TIM_Base_Start_IT(&htim4);    //开启TIM4的基本(Base)功能(定时)中断(IT(InTerrupt))

然后编写定时器中断函数(注意:函数名和形参均是固定的,不能修改!!!可参照下图寻找)

速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第5张图片

 速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第6张图片

速通蓝桥杯嵌入式省一教程:(三)按键扫描与定时器中断_第7张图片

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时(Period)中断(Elapsed)回调(Callback)函数,回调即从主程序中调到中断程序中
{
    if (htim->Instance == TIM4)    //如果是TIM4定时器触发的中断
    {
        //B1按下,点亮LD1和LD2
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
        {
            LED_On(LD1|LD2);
        }


        //B2按下,点亮LD3和LD4
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
        {
            LED_On(LD3|LD4);
        }


        //B3按下,点亮LD5和LD6
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET)
        {
            LED_On(LD5|LD6);
        }


        //B4按下,点亮LD7和LD8
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
        {
            LED_On(LD7|LD8);
        }
    }
}

这样,每当时间过去了0.01s,CPU就会进入定时中断回调函数中,运行我们预先写好的中断程序(在此处是读取按键端口的电平,随后执行相应任务),即定时按键扫描,而不是一直循环扫描按键是否按下,这样就为CPU节省下了大量的时间,大大提高了程序的运行效率。

你可能感兴趣的:(蓝桥杯嵌入式,蓝桥杯,嵌入式硬件,单片机,stm32)