news 2026/7/13 5:07:59

FreeRTOS学习笔记(六)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS学习笔记(六)

FreeRTOS学习笔记(六)

在STM32F407ZGT6上用二值信号量实现任务同步

前言

在前一篇中,我们使用了消息队列来实现任务间的消息传递。但在实际的开发项目中,由于消息队列使用的内存过大,或者有时不同任务间,任务与中断间只需要简单的传递给状态,不涉及到具体的数据值,freeRTOS系统引入了信号量这一同步机制。

队列的核心是“传递消息”,而信号量的核心是“控制资源访问”与“任务同步”

  • 什么是信号量?

    信号量(Semaphore),是在多任务环境下使用的一种机制,用在多任务访问同一资源时的资源管理,例如可以用来保证两个或多个关键代码段不被并发调用。

  • FreeRTOS中如何使用信号量?信号量的类型有哪些?

    由于信号量可以是二进制的,也可以是计数型的,所以信号量可以分为二值信号量和计数信号量。互斥信号量看成一种特殊的二值信号量。和二值信号量不同的是,互斥信号量具有优先级继承机制,而二值信号量没有。互斥信号量不能在中断服务函数中使用,而二值信号量可以。

    递归互斥信号量可以看作是特殊的互斥信号量,与互斥信号量不同的是,递归互斥信号量 在被获取后,可以被其持有者重复获取,当然递归互斥信号量的持有者需要释放递归互斥信号 量与之获取递归互斥信号量相同的次数,递归互斥信号量才算被释放。

  • 二值信号量具体如何使用?

    任务一般通过xSemaphoreTake()函数试图获取信号量,如果二值信号量无效,任务进入阻塞态。在任务阻塞的过程中,有中断发生,在该中断的中断服务函数中用xSemaphoreGiveFromISR() 函数释放信号量,二值信号量由无效变为有效。

    这时,任务获取信号量成功,任务解除阻塞,开始执行任务处理程序。由于任务函数通常是一个大循环,在任务处理完相关事务后,又会再次调用xSemaphoreTake()函数试图获取信号量,但由于这时二值信号量无效已经从有效变为无效,任务再次进入阻塞态。

    下面简单的介绍一下创建二值信号量的相关函数,后面会再创建一个示例,完成信号量在 FreeRTOS 中的标准使用流程。

    二值信号量相关 API 函数

函数描述
xSemaphoreCreateBinary()使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic()使用静态方式创建二值信号量
xSemaphoreGive()释放信号量
xSemaphoreGiveFromISR()在中断中释放信号量
xSemaphoreTake()获取信号量
xSemaphoreTakeFromISR()在中断中获取信号量
vSemaphoreDelete()删除信号量
  1. 创建二值信号量

    创建二值信号量可以使用两个宏:xSemaphoreCreateBinary()和xSemaphoreCreateBinaryStatic()。

    创建二值信号量所需的内存,由 FreeRTOS 从 FreeRTOS 管理的堆中进行分配。

    这两个宏最终被队列创建函数xQueueGenericCreate()替换,这在 semphr.h 文件中有定义,如下所示:

    #if(configSUPPORT_DYNAMIC_ALLOCATION==1)#definexSemaphoreCreateBinary()xQueueGenericCreate((UBaseType_t)1,semSEMAPHORE_QUEUE_ITEM_LENGTH,queueQUEUE_TYPE_BINARY_SEMAPHORE)#endif#if(configSUPPORT_STATIC_ALLOCATION==1)#definexSemaphoreCreateBinaryStatic(pxStaticSemaphore)xQueueGenericCreateStatic((UBaseType_t)1,semSEMAPHORE_QUEUE_ITEM_LENGTH,NULL,pxStaticSemaphore,queueQUEUE_TYPE_BINARY_SEMAPHORE)#endif

    根据上面代码所示,要使用上面两个宏创建二值信号量,需要将宏configSUPPORT_STATIC_ALLOCATION设置为1。

  2. 释放二值信号量

    xSemaphoreGive() 也是一个宏,用于释放二值信号量,具体的实现功能的是调用队列发送函数xQueueGenericSend()。

    *@endcode*\defgroup xSemaphoreGive xSemaphoreGive*\ingroup Semaphores*/#definexSemaphoreGive(xSemaphore)xQueueGenericSend((QueueHandle_t)(xSemaphore),NULL,semGIVE_BLOCK_TIME,queueSEND_TO_BACK)

    xSemaphoreGiveFromISR() 和xSemaphoreGive() 类似,也是一个宏,要是感兴趣可以去 semphr.h 中查找,这里不多做阐述。

  3. 获取二值信号量

    xSemaphoreTakeFromISR()是用来获取信号量的中断版本,在中断服务函数中使用。他也是一个宏,用于获取二值信号量,该宏可以用于获取计数信号量和二值信号量,但不能用于互斥信号量。

    该宏的定义如下:

    #definexSemaphoreTakeFromISR(xSemaphore,pxHigherPriorityTaskWoken)xQueueReceiveFromISR((QueueHandle_t)(xSemaphore),NULL,(pxHigherPriorityTaskWoken))

    xSemaphoreTake()和xSemaphoreTakeFromISR()类似,也是一个宏,用于获取二值信号量,该宏还可以用于获取计数信号量和互斥信号量。要是感兴趣可以去 semphr.h 中查找,这里不多做阐述。

实例:用二值信号量进行任务同步

​ 本实例是通过对二值信号量的操作,实现任务与任务之间以及任务与中断间的同步。使用appStartTask()函数创建4个任务:

​ 任务1的功能是使LED0闪烁,优先级为3,通过二值信号量实现任务与任务之间的同步,输出信息到串口。

​ 任务2的功能是使LED1闪烁,优先级为3,通过二值信号量实现任务与中断之间的同步,输出信息到串口。

​ 任务3是串口守护函数,优先级为3,其功能是将通过队列传输过来的字符信息在串口上输出,任何情况下只有串口守护函数能访问串口。

​ 任务4是按键扫描函数,优先级为4,其功能是按键扫描,并根据返回的键值执行释放二值信号量、启动定时器等操作。WAKEUP按键用于释放任务与任务间的同步的二值信号量,KEY0用于启动TIM2,利用TIM2的更新中断释放任务和中断之间同步的二值信号量,KEY1用于停止TIM2。

​ 具体代码如下:

appTask.c

#include"freertos_demo.h"#include"led.h"#include"key.h"#include"usart.h"#include<stdio.h>/*********************************************FreeRTOS*************************************************/#include"FreeRTOS.h"#include"task.h"#include"queue.h"#include"semphr.h"/*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;/*用于创建队列及其他函数*/volatileuint8_tuIRQCounter;/*用于统计中断次数*/SemaphoreHandle_t binKeySemaphore;/*任务与任务之间同步二值信号量句柄*/SemaphoreHandle_t binIRQSemaphore;/*任务与中断之间同步二值信号量句柄*//******************************************************************************************************//** * @brief appStartTask FreeRTOS入口函数 * @param 无 * @retval 无 */voidappStartTask(void){xQueuePrint=xQueueCreate(2,sizeof(pcToPrint));binIRQSemaphore=xSemaphoreCreateBinary();binKeySemaphore=xSemaphoreCreateBinary();if(xQueuePrint!=NULL){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));/*延时500ms*//*若获取二值信号量成功,则执行一些操作,本例为发送相应信息到串口*/if(xSemaphoreTake(binKeySemaphore,10)==pdTRUE){/*生成待打印输出信息*/sprintf(pcToPrint,"按键任务通过二值信号量同步任务1\r\n");/*打印信息,不直接输出,通过队列发送给串口守护函数*/xQueueSendToBack(xQueuePrint,pcToPrint,0);}}}/** * @brief Led1Task * @param pvParameters : 传入参数 * @retval 无 */staticvoidLed1Task(void*pvParameters){while(1){LED1_TOGGLE();/*LED1闪烁*/vTaskDelay(pdMS_TO_TICKS(500));/*延时500ms*//*若获取二值信号量成功,则执行一些操作,本例为发送相应信息到串口*/if(xSemaphoreTake(binIRQSemaphore,10)==pdTRUE){/*生成待打印输出信息*/sprintf(pcToPrint,"TIM2中断同步任务2,中断%3d次\r\n",uIRQCounter);/*打印信息,不直接输出,通过队列发送给串口守护函数*/xQueueSendToBack(xQueuePrint,pcToPrint,0);}}}/** * @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==WKUP_PRES){sprintf(pcToPrint,"WAKEUP按键按下,发送同步信号.....\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);xSemaphoreGive(binKeySemaphore);}elseif(keyValue==KEY0_PRES){HAL_TIM_Base_Start_IT(&htim2);sprintf(pcToPrint,"KEY0按键按下,启动TIM2更新中断.....\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);}elseif(keyValue==KEY1_PRES){HAL_TIM_Base_Stop_IT(&htim2);xSemaphoreTake(binIRQSemaphore,10);sprintf(pcToPrint,"KEY1按键按下,停止TIM2.....\r\n");xQueueSendToBack(xQueuePrint,pcToPrint,0);}vTaskDelay(pdMS_TO_TICKS(100));}}

main.c

/** * @brief Period elapsed callback in non blocking mode * @note This function is called when TIM6 interrupt took place, inside * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment * a global variable "uwTick" used as application time base. * @param htim : TIM handle * @retval None */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 */externSemaphoreHandle_t binIRQSemaphore;/*任务与中断之间同步二值信号量句柄*/externvolatileuint8_tuIRQCounter;/*用于统计中断次数*/BaseType_t xHighPriorityTaskWoken=pdFALSE;/*推出中断后是否进行任务切换*/if(htim->Instance==TIM2){uIRQCounter++;/*通过发送二值信号量给任务2进行同步,使用freeRTOS的API函数*/xSemaphoreGiveFromISR(binIRQSemaphore,&xHighPriorityTaskWoken);/*如果有更高优先级的任务,退出中断后执行任务切换*/portYIELD_FROM_ISR(xHighPriorityTaskWoken);}/* USER CODE END Callback 1 */}

​ 实例的结果如图所示:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/13 5:06:18

3分钟掌握暗黑破坏神2存档编辑器的终极指南

3分钟掌握暗黑破坏神2存档编辑器的终极指南 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor 还在为刷不到心仪的装备而烦恼吗&#xff1f;或者想快速测试不同角色build却不想重新练级&#xff1f;d2s-editor这款免费开源的暗黑破…

作者头像 李华
网站建设 2026/7/13 5:05:09

UEcpp创建Actor并添加组件

本文用于记录UE学习。UE C中&#xff0c;一个常见的Actor的创建过程可以分成三部分&#xff1a;在.h中声明组件变量在构造函数中使用CreateDefaultSubobject创建组件。在BeginPlay&#xff08;&#xff09;中执行游戏运行时的初始化组件是 Actor 持有的子对象&#xff0c;可以负…

作者头像 李华
网站建设 2026/7/13 5:05:02

# [特殊字符] 太空模拟器 — 鸿蒙ArkTS完整技术解析

应用定位&#xff1a;太空探索模拟类小应用 技术栈&#xff1a;HarmonyOS NEXT ArkTS Stage模型 源码行数&#xff1a;165行 核心亮点&#xff1a;目标驱动飞行系统、多星球目标选择、燃料精确管理、星际距离系统、星星采集一、引言 太空模拟器是系列中第一个引入目标驱动系统…

作者头像 李华
网站建设 2026/7/13 5:03:59

城市对比分析实战:构建个人尺度的约束满足型决策模型

1. 项目概述&#xff1a;一场用数据说话的城市选择实验 “Mumbai vs. Delhi, What is Your Choice? Let the Data Decide!”——这个标题乍看像一篇旅游攻略或社交媒体投票帖&#xff0c;但真正懂行的人一眼就能看出&#xff0c;它背后藏着一套完整、可复现、有现实决策价值的…

作者头像 李华
网站建设 2026/7/13 5:02:32

python 学习 拷贝

1. 浅拷贝&#xff08;Shallow Copy&#xff09; 浅拷贝创建一个新的字典对象&#xff0c;但只复制原字典中元素的引用&#xff0c;不复制元素本身。实现方式&#xff1a; python 复制 下载 import copyoriginal {a: [1, 2, 3], b: {x: 10, y: 20}}# 方法1&#xff1a;使用 di…

作者头像 李华