FreeRTOS学习笔记(九)

📅 2026/7/21 23:09:17 👁️ 阅读次数 📝 编程学习
FreeRTOS学习笔记(九)

FreeRTOS学习笔记(九)

在STM32F407ZGT6上用事件标志组进行任务同步

前言

在前面的笔记中我们提到了使用队列和信号量其中一个主要的作用就是同步任务,但使用信号量只能同步单个任务或事件。然而,在实际开发中,某任务可能需要与多个任务或事件同步,信号量明显不能满足这一要求。

为了满足上述的需求,FreeRTOS提供了事件标志组,用于实现多个任务的同步。这篇围绕事件标志组的概念和使用进行展开。

  • 什么是事件?在STM32中,FreeRTOS事件标志组最多能存储多少个事件?

    事件(Event) 指的是系统运行过程中发生的、值得注意的特定动作或状态变化。事件本身不包含数据,它仅仅是一个“通知”或“标志”,表明“某件事发生了”。

    当 configUSE_16_BIT_TICKS 为 1 的时候事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24个事件位。因此最多支持 24 个事件

  • 事件标志组和队列、信号量之间的异同,他的应用场景是什么?

    在 FreeRTOS 中,事件标志组、队列和信号量是三种核心的任务间通信与同步机制,它们的设计目标都是解决多任务环境下的资源竞争、协作与状态同步问题,但各自侧重点和适用场景不同。

    具体异同可以参考下表所示:

    组件核心作用应用场景
    队列侧重“消息传递”,可携带任意类型的数据(如结构体、指针、整数),支持阻塞等待传感器任务向显示任务发送温度数据;中断服务程序向任务发送按键事件等。
    信号量侧重“任务同步”与“资源计数”,不传递数据,仅表示“资源可用”或“事件发生”状态。任务A等待ADC中断完成;管理3个串口缓冲区;保护全局数组等共享资源。
    事件标志组侧重“多事件组合等待”,用位域表示多个独立事件状态,支持“与”或“或”逻辑触发。等待“温度稳定 + 风扇就绪”同时满足才启动加热器;设备启动自检需多个模块初始化完成。
  • 如何使用事件标志组?FreeRTOS要进行怎样的配置?

    FreeRTOS 提供了事件标志组的一些相关操作函数,如下表所示:

    函数描述
    xEventGroupCreate()使用动态方式创建事件标志组
    xEventGroupCreateStstic()使用静态方式创建事件标志组
    vEventGroupDelete()删除事件标志组
    xEventGroupWaitBits()等待事件标志位
    xEventGroupSetBits()设置事件标志位
    xEventGroupSetBitsFromISR()在中断中设置事件标志位
    xEventGroupClearBits()清零事件标志位
    xEventGroupClearBitsFromISR()在中断中清零事件标志位
    xEventGroupGetBits()获取事件组中各事件标志位的值
    xEventGroupGetBitsFromISR()在中断中获取事件组中各事件标志位的值
    xEventGroupSync()设置事件标志位,并等待事件标志位

    下面简要介绍一下这些函数的原型,中断中使用的函数、静态创建等函数原型可以去源码中查询,这里由于篇幅的原因不多做阐述。

    创建事件标志组

    xEventGroupCreate()函数用于创建事件标志组,该函数的原型如下:

    EventGroupHandle_txEventGroupCreate(void);

    该函数没有形参,创建成功则返回事件标志组句柄,创建失败返回NULL。

    设置事件标志位

    设置事件标志位涉及到两种操作:置1和清0。

    xEventGroupSetBits()是事件标志位置1函数。该函数原型如下:

    EventBits_txEventGroupSetBits(EventGroupHandle_t xEventGroup,constEventBits_t uxBitsToSet)

    形参xEventGroup和uxBitsToSet分别为待操作的事件标志组和待设置的事件标志位,返回值为一个整数。

    xEventGroupClearBits()是事件标志位清零函数,该函数原型如下所示:

    EventBits_txEventGroupClearBits(EventGroupHandle_t xEventGroup,constEventBits_t uxBitsToClear)

    形参xEventGroup和uxBitsToClear分别为待操作的事件标志组和待清零的事件标志位,返回值为一个整数。

    获取事件标志组值

    xEventGroupGetBits()函数是获取事件标志组当前值的函数,该函数的原型如下:

    EventBits_txEventGroupGetBits(xEventGroup);

    等待指定的事件标志位

    某个任务可能需要与多个事件进行同步,可使用xEventGroupWaitBits()函数来判断多个事件标志位。该函数的原型如下:

    EventBits_txEventGroupWaitBits(EventGroupHandle_t xEventGroup,constEventBits_t uxBitsToWaitFor,constBaseType_t xClearOnExit,constBaseType_t xWaitForAllBits,TickType_t xTicksToWait)

    参数说明如下:

    形参说明
    xEvenrGroup等待的事件标志组
    uxBitsToWaitFor等待的事件标志位,可以用逻辑或等待多个事件标志位
    xClearOnExit成功等待到事件标志位后,清除事件组中对应的事件标志位
    xWaitForAllBits等待 uxBitsToWaitFor 中的所有事件标志位(逻辑与)
    xTicksToWait获取等待的阻塞时间

    返回值:返回要等待的事件标志位置1之后的事件标志组值。若因阻塞超时返回,则返回值将没有任何意义。

接下来给一个实例帮助加深对于事件标志组的理解。例子通过操作事件标志组,实现任务与多个事件之间的同步,通过appStartTAsk()函数创建4个FreeRTOS任务。

任务1的任务函数为Led0Task(),优先级为3,其功能是让LED0闪烁,以指示程序正在运行。

任务2的任务函数为Led1Task(),优先级为3,其功能是检测事件标志位,bit2、bit1和bit0同时置1时点亮LED1,并输出信息到串口。

任务3是串口守护任务,优先级为3,任务函数为printTask(),其功能是将通过队列传送过来的字符信息在串口上输出,任何时候只有该守护任务能访问串口。

任务4是按键扫描任务,优先级为4,任务函数为keyTask(),其功能是按键扫描,并根据返回的键值执行事件标志位置1、启动定时器等操作,模拟事件发生。

实例中用到了三个按键,KEY0按键用于将事件标志组bit0置1,KEY1按键用于将事件标志组bit1置1,WAKEUP按键用于启动TIM2,使用TIM2的更新中断将事件标志组bit2置1后停止TIM2。

函数代码

配置宏FreeRTOSConfig.h

#defineINCLUDE_xTimerPendFunctionCall1#defineconfigUSE_TRACE_FACILITY1#defineconfigUSE_TIMERS1

main.c

voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){/* USER CODE BEGIN Callback 0 *//* USER CODE END Callback 0 */if(htim->Instance==TIM6){HAL_IncTick();}/* USER CODE BEGIN Callback 1 */externEventGroupHandle_t evnGroupHandler;/*事件标志组句柄*/BaseType_t xHighPriorityTaskWoken=pdFALSE;/*推出中断后是否进行任务切换*/if(htim->Instance==TIM2){/*中断发生后,事件标志组bit2置1*/xEventGroupSetBitsFromISR(evnGroupHandler,0x04,&xHighPriorityTaskWoken);/*如果有更高优先级的任务,退出中断后执行任务切换*/portYIELD_FROM_ISR(xHighPriorityTaskWoken);HAL_TIM_Base_Stop(&htim2);}/* USER CODE END Callback 1 */}

函数创建

/*FreeRTOS配置*//* Led0Task 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidLed0Task(void*pvParameters);/* 任务函数 */staticTaskHandle_t Led0TaskHandle=NULL;/* 任务句柄 */#defineLed0Task_PRIO3/* 任务优先级 */#defineLed0Task_STK_SIZE128/* 任务堆栈大小 *//* Led1Task 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidLed1Task(void*pvParameters);/* 任务函数 */staticTaskHandle_t Led1TaskHandle=NULL;/* 任务句柄 */#defineLed1Task_PRIO3/* 任务优先级 */#defineLed1Task_STK_SIZE128/* 任务堆栈大小 *//* printTASK 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidprintTASK(void*pvParameters);/* 任务函数 */staticTaskHandle_t printTask_Handler=NULL;/* 任务句柄 */#defineprintTask_PRIO3/* 任务优先级 */#defineprintTask_STK_SIZE128/* 任务堆栈大小 *//* keyTASK 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */staticvoidkeyTASK(void*pvParameters);/* 任务函数 */staticTaskHandle_t keyTask_Handler=NULL;/* 任务句柄 */#definekeyTask_PRIO4/* 任务优先级 */#definekeyTask_STK_SIZE128/* 任务堆栈大小 */staticcharpcToPrint[80];/*串口打印内容缓冲区*/xQueueHandle xQueuePrint;/*用于创建队列及其他函数*/EventGroupHandle_t evnGroupHandler;/*事件标志组句柄*//******************************************************************************************************//** * @brief appStartTask FreeRTOS入口函数 * @param 无 * @retval 无 */voidappStartTask(void){/*创建一个长度为2,队列项大小足够容纳待输出字符的队列*/xQueuePrint=xQueueCreate(2,sizeof(pcToPrint));/*创建一个事件标志组,用于实现任务和多个事件之间的同步*/evnGroupHandler=xEventGroupCreate();if(xQueuePrint&&evnGroupHandler){taskENTER_CRITICAL();/*进入临界段,关中断*/xTaskCreate((TaskFunction_t)Led0Task,/* 任务函数 */(constchar*)"Led0Task",/* 任务名称 */(uint16_t)Led0Task_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)Led0Task_PRIO,/* 任务优先级 */(TaskHandle_t*)&Led0TaskHandle);/* 任务句柄 */xTaskCreate((TaskFunction_t)Led1Task,/* 任务函数 */(constchar*)"Led1Task",/* 任务名称 */(uint16_t)Led1Task_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)Led1Task_PRIO,/* 任务优先级 */(TaskHandle_t*)&Led1TaskHandle);/* 任务句柄 */xTaskCreate((TaskFunction_t)printTASK,/* 任务函数 */(constchar*)"PrintTask",/* 任务名称 */(uint16_t)printTask_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)printTask_PRIO,/* 任务优先级 */(TaskHandle_t*)&printTask_Handler);/* 任务句柄 */xTaskCreate((TaskFunction_t)keyTASK,/* 任务函数 */(constchar*)"keyTask",/* 任务名称 */(uint16_t)keyTask_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传入给任务函数的参数 */(UBaseType_t)keyTask_PRIO,/* 任务优先级 */(TaskHandle_t*)&keyTask_Handler);/* 任务句柄 */taskEXIT_CRITICAL();/*退出临界段,开中断*/vTaskStartScheduler();/*开启任务调度器*/}}

任务函数

***@brief Led0Task*@param pvParameters:传入参数*@retval 无*/staticvoidLed0Task(void*pvParameters){while(1){LED0_TOGGLE();/*LED0闪烁*/vTaskDelay(pdMS_TO_TICKS(500));/*每秒闪烁1次*/}}/** * @brief Led1Task * @param pvParameters : 传入参数 * @retval 无 */staticvoidLed1Task(void*pvParameters){while(1){LED1(0);/*LED1熄灭*//*检测事件标志组bit2、bit1和bit0,均置位时点亮LED1并发送相应信息到串口*//*3个事件标志位同时置位函数才返回,不满足则无效阻塞任务,退出函数时清除3个事件标志组*/xEventGroupWaitBits(evnGroupHandler,0x07,pdTRUE,pdTRUE,portMAX_DELAY);sprintf(pcToPrint,"KEY0事件、KEY1事件、TIM2中断事件均发生\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);LED1(1);/*LED1点亮*/vTaskDelay(pdMS_TO_TICKS(1000));/*点亮1s*/}}/** * @brief printTASK * @param pvParameters : 传入参数 * @retval 无 */staticvoidprintTASK(void*pvParameters){charpcToWrite[80];while(1){xQueueReceive(xQueuePrint,pcToWrite,portMAX_DELAY);printf("%s",pcToWrite);}}/** * @brief keyTASK * @param pvParameters : 传入参数 * @retval 无 */staticvoidkeyTASK(void*pvParameters){uint8_tkeyValue;//键值externTIM_HandleTypeDef htim2;while(1){keyValue=key_scan();//获取键值if(keyValue==KEY0_PRES){sprintf(pcToPrint,"KEY0按键按下,置位bit0.....\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);/*设置事件标志组bit0*/xEventGroupSetBits(evnGroupHandler,0x01);}elseif(keyValue==KEY1_PRES){sprintf(pcToPrint,"KEY1按键按下,置位bit1.....\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);/*设置事件标志组bit0*/xEventGroupSetBits(evnGroupHandler,0x02);}elseif(keyValue==WKUP_PRES){/*wakup按键,启动TIM2定时器1次,在定时器TIM2中断里,设置事件标志组bit为1*/HAL_TIM_Base_Start_IT(&htim2);sprintf(pcToPrint,"Wakeup按键按下,启动TIM2.....\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);}vTaskDelay(pdMS_TO_TICKS(100));}}

测试结果

通过截图可知,分别按下三个按键,模拟三个事件发生,串口给出提示信息。