FreeRTROS3——事件组和定时器
一、介绍事件组
跟其他信号量、互斥量的不同,它可以用来等待多个事件之一、多个事件都发生
场景一:
● 任务3:等待(等事情A与事情B)
● 任务1:做好事情A
● 任务2:做好事情B
我们有一个任务3,他可以等待:A或B,A与B
A、B就是一组事件
如果任务3等待A或B,A、B只要有一个,就可以唤醒任务3
如果任务3等待A与B,只有A和B同时都做好了,任务3就被唤醒
场景二:
唤醒多个任务:
● 任务3:等待(等到事情A与事情B)
● 任务4:等待(等到事情A与事情B)
● 任务1:做事情A
● 任务2:做事情B
这时,任务3、任务4都被唤醒!
可能有很多个任务在等待事件,
他们可能等待的事件各不相同,也有可能等待的事件相同
事件发生之后,满足条件的那些任务,都会被唤醒
在之前提到的队列、信号量、互斥量这些场景中,只会唤醒一个任务
2.事件组的主要特性:
● 位操作:每个事件组是一个 32 位整数,每位代表一个独立事件高效同步:任务可以等待单个或多个事件
● 零消耗等待:任务在等待事件时不会消耗 CPU 资源
● 广播机制:一个任务可以同时通知多个等待任务
3.函数接口
osEventFlagsNew() 创建新事件组
osEventFlagsWait() 等待事件位
osEventFlagsSet() 设置事件位
osEventFlagsDelete() 删除事件组
osEventFlagsId_t osEventFlagsNew(const osEventFlagsAttr_t *attr);
功能
创建一个新的事件组对象,用于任务间的事件同步。参数
attr:指向事件组属性的指针,可为 NULL(使用默认属性)
typedef struct {const char *name; // 事件组名称(调试用)void *cb_mem; // 控制块内存位置(通常为NULL)uint32_t cb_size; // 控制块大小
} osEventFlagsAttr_t;
返回值
成功:事件组 ID失败:NULL(内存不足或参数错误)
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags,uint32_t options,uint32_t timeout);
功能
等待事件组中指定的事件位被设置。参数
ef_id:事件组 ID
flags:要等待的事件位掩码(可以看后面的实例来理解,该参数比较难理解)
options:等待选项:osFlagsWaitAny:等待任意指定事件位被设置(默认)osFlagsWaitAll:等待所有指定事件位被设置osFlagsNoClear:等待成功后不清除事件位
timeout:超时时间(tick数):
0:不等待,立即返回
osWaitForever:无限等待//那就是阻塞函数
n:等待 n 个 tick
返回值
成功:触发等待的事件位值
失败:错误代码(最高位为1):
osErrorParameter:无效参数
osErrorResource:超时或事件组不可用
osErrorISR:在中断中调用
uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags);
功能
设置事件组中的指定标志位(置1),并唤醒等待这些事件的任务。
//主要应用以及理解还是需要通过下面的实例
参数
ef_id:事件组 ID
flags:要设置的事件位掩码(例如:0x01 设置的是位0,0x05 设置的是位0和位2)
返回值
成功:事件组设置后的新标志值
失败:错误代码(最高位为1):
osErrorParameter:无效参数
osErrorResource:事件组不可用
osErrorISR:在中断中调用但未启用中断API
osStatus_t osEventFlagsDelete(osEventFlagsId_t ef_id);
功能
删除事件组并释放相关资源。参数
ef_id:事件组 ID
返回值
osOK:删除成功
osErrorParameter:无效参数
osErrorResource:事件组不可用
4.应用:
/* USER CODE BEGIN FunctionPrototypes */
// 事件位定义
#define BUTTON_PRESSED (1UL << 0)
#define SENSOR_READY (1UL << 1)
#define DATA_PROCESSED (1UL << 2)
#define event_group myEvent01Handle
/* USER CODE END FunctionPrototypes *//* USER CODE BEGIN Header_SensorTask */
/**
* @brief Function implementing the SensorTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_SensorTask */
void SensorTask(void *argument)
{/* USER CODE BEGIN SensorTask *//* Infinite loop */for (;;) {// 模拟传感器数据采集osDelay(500);// 设置传感器就绪事件osEventFlagsSet(event_group, SENSOR_READY);}/* USER CODE END SensorTask */
}/* USER CODE BEGIN Header_DataTask */
/**
* @brief Function implementing the DataTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_DataTask */
void DataTask(void *argument)
{/* USER CODE BEGIN DataTask *//* Infinite loop */uint32_t events;for (;;) {// 等待按钮按下或传感器就绪events = osEventFlagsWait(event_group, BUTTON_PRESSED | SENSOR_READY, osFlagsWaitAny, osWaitForever);if (events & BUTTON_PRESSED) {// 处理按钮事件printf("Button pressed!\n");}if (events & SENSOR_READY) {// 处理传感器数据printf("Sensor data received\n");// 数据处理完成后设置标志osEventFlagsSet(event_group, DATA_PROCESSED);}}/* USER CODE END DataTask */
}/* USER CODE BEGIN Header_MonitorTask */
/**
* @brief Function implementing the MonitorTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MonitorTask */
void MonitorTask(void *argument)
{/* USER CODE BEGIN MonitorTask *//* Infinite loop */for (;;) {// 等待数据被处理osEventFlagsWait(event_group, DATA_PROCESSED, osFlagsWaitAll, osWaitForever);printf("Data processing complete\n");}/* USER CODE END MonitorTask */
}/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{osEventFlagsSet(event_group, BUTTON_PRESSED);
}
/* USER CODE END Application */
二、软件定时器
1.概念
软件定时器就是 "闹钟", 可以通过设置闹钟:
1. 设定一个一次性闹钟: 18:00 提醒我去洗衣服
2. 设定一个周期性闹钟: 6:40 提醒我起床
将 " 闹钟 " 转换为 软件定时器来讲:
1. 在未来的某个时间点, 运行某个函数的功能(一次性的闹钟)
2. 周期性的去执行某个函数的功能(周期性)
2.函数接口
1.创建一个新的软件定时器
osTimerId_t osTimerNew(osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr_t *attr)
参数:func: 该函数指针用于指定在定时器到期时执行的回调函数, 当定时器触发,会执行该函数//typedef void (*osTimerFunc_t) (void *argument);type:定时器的类型,可选择 单次 或 循环触发/* typedef enum {/** 一次性定时器 **/osTimerOnce = 0,/** 重复使用的定时器 **/osTimerPeriodic = 1} osTimerType_t;argument:传入定时器的回调函数的指针,当定时器到期,回调函数将接收到此函数,不需要NULLattr:定时器对象指针(见下面2),用于指向特定的定时器属性,比如优先级,名称等,不需要为NULL
2.启用定时器osStatus_t osTimerStart(osTimerId_t timer_id, uint32_t ticks)参数:timer_id:定时器ID,用于指定要启动的定时器,通常需要先使用osTimerNew函数创建定时器对象ticks: 设置的定时值 ms为单位返回值:成功返回0,失败返回错误码(错误码<0)关于错误码的定义:typedef enum {osOK = 0, ///定时器成功启动osError = -1, ///定时器发生错误,未能成功启动osErrorTimeout = -2, ///超时错误osErrorResource = -3, ///资源不可用osErrorParameter = -4, ///参数错误osErrorNoMemory = -5, ///系统内存不足:无法为操作分配或保留内存。} osStatus_t;
osStatus_t osTimerStop (osTimerId_t timer_id);
功能 :停止定时器参数 :timer_id:要停止的定时器ID返回值 :操作状态
osStatus_t osTimerDelete (osTimerId_t timer_id);
功能 :删除定时器并释放资源参数 :timer_id:要删除的定时器ID返回值 :操作状态
uint32_t osTimerIsRunning (osTimerId_t timer_id);
功能 :检查定时器是否正在运行参数 :timer_id:要检查的定时器ID返回值 :0表示未运行,非0表示正在运行
3.注意事项
1) 软件定时器的精度取决于系统节拍频率
2)定时器回调函数应尽可能简短,避免长时间阻塞
3)不要在中断服务例程(ISR)中调用定时器函数(除osTimerStart和osTimerStop外)
4)多个定时器可以共享同一个回调函数,通过参数区分
5)不能无限次创建,最多16个
4.CubeMX配置: