快速学习蓝桥杯STM32 G431开发中的GPIO输入和按键处理技巧

适用于学习了TIM定时器跟GPIO输入(按键)的新手作为练习的综合项目!

一、按键长短按

功能:长短按四个按键分别点亮八个灯

一.整体工作流程

这段代码用于检测和处理四个不同按键的嵌入式系统程序,通常用于例如微控制器等硬件上。代码的主要功能是检测哪个按键被按下以及按键被按下的持续时间,并根据这些信息执行不同的动作。

1.1 定义部分

定义按键标志:

#define key1_flag 1 等定义是为了方便识别哪个按键被按下。这些标志用于在代码中表示不同的按键。

定义按键读取的宏:

#define key1 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) 等定义是用于读取特定硬件引脚的状态。在这里,它们用于检测不同按键是否被按下。HAL_GPIO_ReadPin 是一个硬件抽象层(HAL)函数,用于读取指定GPIO引脚的状态。

按键标志变量:

uint8_t key_flag; 这个变量用于存储当前被按下的按键。

1.2 Key_Scan 函数

这个函数用于检测哪个按键被按下以及按下的持续时间。

静态标志变量:

static uint8_t flag=1; 这个静态变量用于标记是否有按键正在被按下。

检测按键:

函数中的 if (key1 == 0) 等条件判断用于检测每个按键是否被按下。

计算按键持续时间:

如果检测到按键被按下,函数会进入一个 while 循环,该循环计数直到按键释放或达到设定的最大值(在这里是85)。循环中使用 HAL_Delay(10); 来实现10毫秒的延时。

设置按键标志并返回时间:

每个按键的检测部分都会在检测到按键按下时设置 key_flag,并返回按键被按下的时间长度。

1.3 Key_mode 函数

这个函数根据按键标志和按键持续时间来执行相应的动作。

读取按键状态:

使用 key_t = Key_Scan(); 来获取当前按键的状态。

基于按键标志执行操作:

switch(key_flag) 用于根据 key_flag 变量的值决定执行哪个按键的操作。

不同的按键执行不同的动作:

在每个 case 语句中,根据按键被按下的时间长度(key_t 的值),选择执行不同的操作。例如,if (key_t > 1 && key_t < 80) 表示如果某个按键被按下的时间在1到80之间,则执行一种操作;如果时间超过80,则执行另一种操作。

总结:通过硬件抽象层函数(HAL函数)与硬件交互,用于检测和响应物理按键的操作。它可以用于各种需要按键输入的嵌入式系统应用中,如控制面板、用户界面等。代码中的动作(例如点亮LED灯),需要根据实际的应用场景进行相应的实现和启用。

二.代码实现

1.在/* USER CODE BEGIN 0 /与/ USER CODE END 0 */之间增加如下代码

#define key1_flag 1
#define key2_flag 2
#define key3_flag 3
#define key4_flag 4
#define key1 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)
#define key2 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)
#define key3 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2)
#define key4 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)
uint8_t key_flag;

uint8_t Key_Scan(void)
{
    static uint8_t flag = 1;  // 静态变量,用于标记按键是否已经被按下,初始化只进行一次!
    uint8_t temp;             // 用于计算按键按下的持续时间

    if (flag == 1)            // 如果没有按键被按下
    {
        if (key1 == 0)        // 检查KEY1是否被按下
        {
            flag = 0;         // 标记有按键被按下
            temp = 0;
            while (key1 == 0 && temp < 85)  // 按键持续按下时增加temp的值,直到达到85或按键释放
            {
                temp++;
                HAL_Delay(10);  // 等待10ms
            }
            key_flag = key1_flag;  // 设置按键标记为KEY1
            return temp;           // 返回按键持续时间
        }
        // 以下代码与上面类似,但对应KEY2、KEY3和KEY4
        if (key2 == 0)
        {
            flag = 0;
            temp = 0;
            while (key2 == 0 && temp < 85)
            {
                temp++;
                HAL_Delay(10);
            }
            key_flag = key2_flag;
            return temp;
        }
        if (key3 == 0)
        {
            flag = 0;
            temp = 0;
            while (key3 == 0 && temp < 85)
            {
                temp++;
                HAL_Delay(10);
            }
            key_flag = key3_flag;
            return temp;
        }
        if (key4 == 0)
        {
            flag = 0;
            temp = 0;
            while (key4 == 0 && temp < 85)
            {
                temp++;
                HAL_Delay(10);
            }
            key_flag = key4_flag;
            return temp;
        }
    }
    else if (key1 && key2 && key3 && key4)  // 如果所有按键都没有被按下
    {
        flag = 1;   // 重置flag,准备下一次按键检测
        key_flag = 0;  // 清除按键标记
    }
    return 0;  // 如果没有按键被按下,返回0
}

void Key_mode(void)
{                   
    uint8_t key_t;
    key_t = Key_Scan();  // 获取按键状态
    switch (key_flag)  // 根据按键标记执行不同的操作
    {
        // 对于每个案例,根据按键持续时间决定执行的动作
        case key1_flag: 
            if (key_t > 1 && key_t < 80)
            {
                // 如果KEY1被短时间按下
                //led(1,1);
            }
            else if (key_t > 80)
            {
                // 如果KEY1被长时间按下
                //led(2,1);
            }
            break;
        case key2_flag: 
            if (key_t > 1 && key_t < 80)
            {
                // 如果KEY2被短时间按下
                //led(3,1);
            }
            else if (key_t > 80)
            {
                // 如果KEY2被长时间按下
                //led(4,1);                              
            }
            break;
        case key3_flag: 
            if (key_t > 1 && key_t < 80)
            {
                // 如果KEY3被短时间按下
                //led(5,1);
            }
            else if (key_t > 80)
            {
                // 如果KEY3被长时间按下
                //led(6,1);
            }
            break;
        case  key4_flag: 	
        	if(key_t>1&&key_t<80)
			{
				//led(7,1);
			}
			else if(key_t>80)
			{
				//led(8,1);								
			}break;	
	}
}

2.在while(1)中添加如下代码

	Key_mode();

二、按键单击双击(利用定时器3,设置250ms)

主题思路:检测到第一次按键按下时,开启定时器中断250ms,检测在250ms内按键按下去的次数times,实现单双击!

功能:单击和双击实现不同的效果

一.配置STM32cubeMX

选择TIM3的时钟源为内部时钟源,PSC为800-1,ARR为25000-1。

设置PSC为800-1,ARR为25000-1

即:
溢出频率=80M/800/25000=4
溢出时间=1/4=0.25s=250ms。

二.整体工作流程

用于实现基于按键次数(如单击或双击)的不同响应的嵌入式系统程序。它利用了一个定时器(在这个例子中是TIM3)来判断按键在特定时间内被按下的次数。

1.全局变量:

int begin_flag:用来标记是否是第一次按键按下。如果是第一次按下,需要启动定时器计时。

int times:用来记录在定时器时间内按键被按下的次数。

int times_ok:用来存储确认的按键次数(即在定时器时间结束后的按键次数)。

int t:用于防止在按键持续被按下时 times 不断增加的情况。

2.key_scan 函数:

按键检测:通过 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) 检测按键是否被按下(GPIO_PIN_RESET 表示按下)。

定时器启动逻辑:
当第一次检测到按键按下时(begin_flag == 0),启动定时器 htim3 并将 begin_flag 设置为1。在定时器运行期间(begin_flag == 1),如果再次检测到按键被按下,times 变量增加,记录按键按下的次数。t 变量确保在按键释放前不会重复计数。

3.定时器中断回调函数 HAL_TIM_PeriodElapsedCallback:

定时器判断:判断中断是否由 TIM3 定时器产生。

按键次数存储:在定时器时间到达时(250ms结束),将 times 的值存储到 times_ok 中,并重置 times 和 begin_flag。

4.while(1) 循环中的调用:

在主循环(while(1))中不断调用 key_scan() 函数来检测按键状态。
根据 times_ok 的值判断按键是单击还是双击,并执行相应的动作。
例如:
如果 times_ok == 1,执行单击时的动作(如点亮LED灯)。
如果 times_ok == 2,执行双击时的动作。

总结:
这段代码的主要功能是通过定时器检测在250ms内按键被按下的次数,从而区分单击和双击动作。这种功能在用户界面的交互中非常常见,如双击打开文件、单击选择项目等。在嵌入式系统中,这种机制可以用于各种场景,如控制设备的不同模式、执行特定命令等。

三.代码实现

1.在/* USER CODE BEGIN 0 /与/ USER CODE END 0 */之间增加如下代码

int begin_flag=0;//0:第一次按键按下需要开启定时器计时
int times=0;//计算按键次数
int times_ok=0;//存放按键次数
int t=0;//防止出现长按时times一直增加情况,即检测到按下时候times加1,直到检测到松开,才可以下一次按键
//主题思路:检测到第一次按键按下时,开启定时器中断250ms,检测在250ms内按键按下去的次数times,实现单双击
void key_scan()
{

	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET&&t==1)
	{
		HAL_Delay(5);
		if(begin_flag==0)
		{
			HAL_TIM_Base_Start_IT(&htim3);
			__HAL_TIM_SetCounter(&htim3,0);
		  begin_flag=1;
		}
	  if(begin_flag==1)
		{
				if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET)
						times++;
		}	
	t=0;
	}
	else	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_SET)
	{
		t=1;
	}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  //定时器中断回调函数
{  	
    if(htim->Instance==TIM3)//判断是来自定时器1的
    {

       begin_flag=0; 
			 times_ok=times;
			 times=0;
    }
   HAL_TIM_Base_Stop_IT(&htim3);//定时器中断默认是常开的
}

2.在main.c中的int main(void)函数添加如下代码

//解决:STM32定时器有时一开启就进中断的情况  答:在初始化定时器开启前调用
	 __HAL_TIM_CLEAR_FLAG(&htim3, TIM_IT_UPDATE);
   HAL_TIM_Base_Stop_IT(&htim3);//定时器中断默认是常开的

3.在while(1)中添加如下代码

	  	key_scan();
		if(times_ok==1)//250ms内按一下:单击
		{led(1,1);}
		if(times_ok==2)//250ms内按两下:双击
		{led(2,1);}		

注:led(int a,in b)的代码实现在我的第一篇文章中。

四. 代码问题

将void key_scan()这个函数中的代码修改成下述,为什么功能实现不了呢?但按照逻辑是应该可以实现的!

void key_scan()
{

	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET&&t==1)
	{
		HAL_Delay(5);
		if(begin_flag==0)
		{
			HAL_TIM_Base_Start_IT(&htim3);
			__HAL_TIM_SetCounter(&htim3,0);
		  begin_flag=1;
		}
	  if(begin_flag==1)
		{
				if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET)
						times++;
				while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET);
		}	
	}

}

这个循环会阻塞程序执行,直到按键被释放。这意味着在按键按下的整个期间,程序不会执行任何其他操作,包括 处理 定时器中断 (是处理定时器中断,在回调函数里面处理的)! 如果按键保持按下的时间超过了定时器中断的周期,定时器中断可能不会正确处理。

五. 注意事项

1.如何解决STM32定时器有时一开启就进中断的情况 ?

在开启定时器函数前,初始化TIM函数之后 调用这个函数!

 __HAL_TIM_CLEAR_FLAG(&htim3, TIM_IT_UPDATE);

原因:HAL库中定时器初始化后没有更新中断标志位,在调用__HAL_TIM_CLEAR_FLAG(&htim3, TIM_IT_UPDATE)函数后清除了更新中断标志位,此时TIM_IT_UPDATE为0,中断产生后TIM_IT_UPDATE为1。

2.TIM_FLAG_UPDATE vs TIM_SR_UIF

在有些地方是调用的这个函数

__HAL_TIM_CLEAR_FLAG(&htim3, TIM_SR_UIF)

其实将TIM_FLAG_UPDATE换成TIM_SR_UIF也是可以的!

TIM_FLAG_UPDATE vs TIM_SR_UIF的区别如下:

TIM_FLAG_UPDATE 是在HAL库中使用的标志名称,用于指示定时器的更新事件(通常是计数器溢出或达到预设值时发生)。
TIM_SR_UIF 通常是在较早版本的库或不同的库(如标准外设库)中使用的,它同样表示定时器的更新中断标志。UIF代表“Update Interrupt Flag”。

库版本差异:

如果代码是基于HAL库(硬件抽象层),可能会看到的是 TIM_FLAG_UPDATE。

如果是基于STM32的标准外设库,可能会看到 TIM_SR_UIF。

功能上的区别:

从功能角度来看,两者实际上完成的是相同的任务——清除定时器的更新事件标志。这是必要的步骤,以防止定时器产生重复的中断。

使用哪一个:

应根据您使用的STM32库(HAL库还是标准外设库)以及库的具体版本来决定使用哪个标志。在HAL库中,推荐使用 TIM_FLAG_UPDATE。

总的来说,尽管看起来有区别,但这两个表达式实际上完成的是相同的功能。应该根据不同的开发环境和所用库的文档选择适合的表达方式。

物联沃分享整理
物联沃-IOTWORD物联网 » 快速学习蓝桥杯STM32 G431开发中的GPIO输入和按键处理技巧

发表评论