FreeRTOS之编写按钮驱动

按钮松开时,读到的值是1;按钮按下时,读到的值是0。
按钮松开和按下时对应的状态
上图,一开始按钮处于松开状态,因此读到引脚的值是1;之后按钮按下,读到的值变为0;再次松开,值有变为1。
因此,要捕捉按钮松开的瞬间,即值从0变为1的瞬间。

编写按钮代码

首先,FreeRTOS需要新建目录,让代码更加易于区分。新建User目录,在User目录下新建Task目录,在Task目录下新建Key_Task.c和Key_Task.h文件。
User目录

Key_Task.c

1234567891011121314151617181920
#include "main.h"#include <string.h>#include "FreeRTOS.h"#include "task.h"void KeyTask(void *pvParameters){GPIO_PinState previous = GPIO_PIN_SET;GPIO_PinState current = GPIO_PIN_SET;//类型是GPIO_PinState,先都赋值成GPIO_PIN_SETfor(;;){current = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);//读KEY1引脚的值if(previous == GPIO_PIN_RESET && current == GPIO_PIN_SET)//按钮被点击,即上一次为0,这一次为1{extern UART_HandleTypeDef huart1;//huart是全局变量,声明在main文件,要在Key_Task文件用,需要externHAL_UART_Transmit(&huart1, (const uint8_t *)"KEY1 Clicked.\n", strlen("KEY1 Clicked.\n"), HAL_MAX_DELAY);//每次按下按钮,串口发送一个消息}previous = current;//把本次的状态给到上一次,用于下次判断使用。vTaskDelay(pdMS_TO_TICKS(10));//延迟10ms}}

Key_Task.h

1234
#ifndef TASK_KEY_TASK_H_#define TASK_KEY_TASK_H_void KeyTask(void *pvParameters);#endif /* TASK_KEY_TASK_H_ */

最后,在主程序中新建一个任务

1
xTaskCreate( KeyTask, "Key", 128, NULL, 3 , NULL );

程序现象是,每次按下KEY1按钮,串口会发送一条KEY1 Clicked.
程序现象
Q1:为什么在KeyTask()方法的for循环中要加vTaskDelay延迟,可以省略吗?
删掉该延迟后,KeyTask()方法无法进入阻塞,按钮任务始终执行,低优先级的任务没有执行的机会,因此不能省略。

Q2:为什么上方的按钮驱动程序没有消抖?
因为每次读取一次按钮状态后,要等10ms,而按钮抖动只有不到1ms,没有必要消抖。

编写通用按钮驱动程序

有时需要使用到多个按钮,为每个按钮单独编写程序很麻烦,而且难以维护。
因此需要编写通用按钮驱动程序。

驱动程序适合存放在Drivers文件夹中,因此在Drivers文件夹中创建key.c和key.h这2个文件。
Drivers目录
key.c

12345678910111213141516171819
#include "main.h"#include <string.h>#include "key.h" void Key_Init(KeyHandle_TypeDef *Handle){	Handle->previous = GPIO_PIN_SET;	Handle->current = GPIO_PIN_SET;//类型是GPIO_PinState,先都赋值成1} void Key_Scan(KeyHandle_TypeDef *Handle){	Handle->current = HAL_GPIO_ReadPin(Handle->GPIO_Port, Handle->GPIO_Pin);//读KEY引脚的值	if(Handle->previous == GPIO_PIN_RESET && Handle->current == GPIO_PIN_SET)//按钮被点击,即上一次为0,这一次为1	{		Handle->ClickedCallback();	}	Handle->previous = Handle->current;//更新值,为下一次做准备}

key.h

123456789101112131415161718192021
#ifndef KEY_H_#define KEY_H_ #include "main.h" typedef struct//定义按键句柄结构体,将一个按键相关的信息封装在一起,方便管理多个按键:{	GPIO_PinState previous;//按钮前一时刻的状态	GPIO_PinState current;//按钮当前时刻的状态 	GPIO_TypeDef *GPIO_Port;//按钮的端口号	uint16_t GPIO_Pin;//按钮的引脚号 	void (*ClickedCallback)(void);//按钮点击后要执行的动作} KeyHandle_TypeDef;  void Key_Init(KeyHandle_TypeDef *Handle);void Key_Scan(KeyHandle_TypeDef *Handle); #endif /* KEY_H_ */

Key_Task.c

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
#include "FreeRTOS.h"#include "task.h"#include "key.h"#include "main.h"#include <string.h> static KeyHandle_TypeDef hkey1;static KeyHandle_TypeDef hkey2; static void KEY1_ClickedCallback(void);static void KEY2_ClickedCallback(void); void Key1Task(void *pvParameters){	hkey1.GPIO_Port = KEY1_GPIO_Port;	hkey1.GPIO_Pin = KEY1_Pin;	hkey1.ClickedCallback = KEY1_ClickedCallback;	Key_Init(&hkey1);	for(;;)	{		Key_Scan(&hkey1);		vTaskDelay(pdMS_TO_TICKS(10));//延迟10ms	}} static void KEY1_ClickedCallback(void){	extern UART_HandleTypeDef huart1;	HAL_UART_Transmit(&huart1, (const uint8_t *)"KEY1 Clicked.\n", strlen("KEY1 Clicked.\n"), HAL_MAX_DELAY);} void Key2Task(void *pvParameters){	hkey2.GPIO_Port = KEY2_GPIO_Port;	hkey2.GPIO_Pin = KEY2_Pin;	hkey2.ClickedCallback = KEY2_ClickedCallback;	Key_Init(&hkey2);	for(;;)	{		Key_Scan(&hkey2);		vTaskDelay(pdMS_TO_TICKS(10));//延迟10ms	}}static void KEY2_ClickedCallback(void){	extern UART_HandleTypeDef huart1;	HAL_UART_Transmit(&huart1, (const uint8_t *)"KEY2 Clicked.\n", strlen("KEY1 Clicked.\n"), HAL_MAX_DELAY);}

Key_Task.h

1234567
#ifndef TASK_KEY_TASK_H_#define TASK_KEY_TASK_H_ void Key1Task(void *pvParameters);void Key2Task(void *pvParameters); #endif /* TASK_KEY_TASK_H_ */

程序现象是,按下KEY1按钮,串口会发送一条KEY1 Clicked.
按下KEY2按钮,串口会发送一条KEY2 Clicked.
程序现象

拆分长代码

按照上面代码的逻辑,如果有多个按钮,可能会导致单个文件的代码过长,不容易维护。
因此需要把长代码拆分为多个文件。对key_task.c文件进行拆分。
例如有6个按钮,因此需要把key_task.c拆分为key1.c、key2.c、key3.c、key4.c、key5.c、key6.c。

在User文件夹下创建keys文件夹,把这些拆分的文件放在里面,专门存放不同按钮的代码。
文件目录

Drivers -> key.c

1234567891011121314151617181920
#include "main.h"#include <string.h>#include "key.h" void Key_Init(KeyHandle_TypeDef *Handle){	Handle->previous = GPIO_PIN_SET;	Handle->current = GPIO_PIN_SET;//类型是GPIO_PinState,先都赋值成1} void Key_Scan(KeyHandle_TypeDef *Handle){	Handle->current = HAL_GPIO_ReadPin(Handle->GPIO_Port, Handle->GPIO_Pin);//读KEY1引脚的值	if(Handle->previous == GPIO_PIN_RESET && Handle->current == GPIO_PIN_SET)//按钮被点击,即上一次为0,这一次为1	{		Handle->ClickedCallback();	}	Handle->previous = Handle->current;} 

User -> Tasks -> Key_Task.c

12345678910111213141516171819202122232425262728293031
#include "FreeRTOS.h"#include "task.h"#include "key.h"#include "main.h"#include <string.h>#include "key1.h"#include "key2.h"#include "key3.h"#include "key4.h"#include "key5.h"#include "key6.h" void KeyTask(void *pvParameters){	Key1_Init();	Key2_Init();	Key3_Init();	Key4_Init();	Key5_Init();	Key6_Init();	for(;;)	{		Key1_Scan();		Key2_Scan();		Key3_Scan();		Key4_Scan();		Key5_Scan();		Key6_Scan();		vTaskDelay(pdMS_TO_TICKS(10));//延迟10ms	}}

User -> Keys ->key1.c

12345678910111213141516171819202122232425
#include "key.h"#include <string.h> static KeyHandle_TypeDef hkey1; static void KEY1_ClickedCallback(void); void Key1_Init(void)//初始化key1{	hkey1.GPIO_Port = KEY1_GPIO_Port;	hkey1.GPIO_Pin = KEY1_Pin;	hkey1.ClickedCallback = KEY1_ClickedCallback;	Key_Init(&hkey1);} void Key1_Scan(void)//key1扫描{	Key_Scan(&hkey1);} static void KEY1_ClickedCallback(void)//key1的回调函数{	extern UART_HandleTypeDef huart1;	HAL_UART_Transmit(&huart1, (const uint8_t *)"KEY1 Clicked.\n", strlen("KEY1 Clicked.\n"), HAL_MAX_DELAY);}

其他按钮的程序与key1类似,不再赘述。
实验现象:
实验现象

🔍 ×