快速学习蓝桥杯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。
总的来说,尽管看起来有区别,但这两个表达式实际上完成的是相同的功能。应该根据不同的开发环境和所用库的文档选择适合的表达方式。