当前位置: 首页 > news >正文

细说STM32单片机FreeRTOS互斥量及其编程实例

目录

一、互斥量的工作原理

1、优先级继承

2、互斥量相关函数详解

(1)创建互斥量

(2)获取和释放互斥量

二、互斥量使用示例

1、示例功能和CubeMX项目设置

(1)RCC、SYS、Code Generator、USART3、TIM6、NVIC

(2)RTOS

2、程序功能实现

(1)主程序

(2)FreeRTOS对象初始化

(3)3个任务的功能实现

3、运行与测试


        使用信号量进行互斥型资源访问控制时,容易出现优先级翻转(priority inversion)现象。其现象展示可以参考本文作者的其他文章:

        参考文章:细说STM32单片机FreeRTOS优先级翻转及其展示实例-CSDN博客  https://wenchm.blog.csdn.net/article/details/147580660?spm=1011.2415.3001.5331

        互斥量是对信号量的一种改进,增加了优先级继承机制,虽不能完全消除优先级翻转问题,但是可以缓减该问题

一、互斥量的工作原理

1、优先级继承

        事实上,并不希望在TaskHP等待TaskLP释放信号量的过程中,被一个比TaskHP优先级低的任务抢占了CPU的使用权。也就是说,不希望在参考文章图中t4时就出现TaskMP抢占CPU使用权的情况。 

        为此,FreeRTOS在二值信号量的功能基础上引入了优先级继承(priority inheritance)机制,这就是互斥量。使用了互斥量后,3个任务运行过程变为上图所示的时序图。

  • 在t1时刻,低优先级任务TaskLP处于运行状态,并且获取了一个互斥量mutex。
  • 在t2时刻,高优先级任务TaskHP进入运行状态,它申请互斥量mutex,但是互斥量被任务TaskLP占用,所以,TaskHP在t3时刻进入阻塞等待状态,TaskLP进入运行状态。但是在t3时刻,FreeRTOS将TaskLP的优先级临时提高到与TaskHP相同的级别,这就是优先级继承。
  • 在t4时刻,中等优先级任务TaskMP进入就绪状态,发生任务调度,但是因为TaskLP的临时优先级高于TaskMP,所以TaskMP无法获得CPU的使用权,只能继续处于就绪状态。
  • 在t5时刻,任务TaskLP释放互斥量,任务TaskHP立刻抢占CPU的使用权,并恢复TaskLP原来的优先级。
  • 在t6时刻,TaskHP进入阻塞状态后,TaskMP才进入运行状态。

        互斥量引入了优先级继承机制,临时提升了占用互斥量的长优先级任务TaskLP的优先级,与申请互斥量的高优先级任务TaskHP的优先级相同,这样就避免了被中间优先级的任务TaskMP抢占CPU的使用权,保证了高优先级任务运行的实时性。斥量特别适用于互斥型资源访问控制

        使用互斥量可以减缓优先级翻转的影响,但是不能完全消除优先级翻转的问题。例如,在上图中,若TaskMP在t2时刻之前抢占了CPU,在TaskMP运行期间TaskHP可以抢占CPU,但是因为要等待TaskLP释放占用的互斥量,还是要进入阻塞状态等待,还是会让TaskMP占用CPU运行。

2、互斥量相关函数详解

(1)创建互斥量

        函数xSemaphoreCreateMutex()以动态分配内存方式创建互斥量,xSemaphoreCreateMutexStatic()以静态分配内存方式创建互斥量。其中,函数xSemaphoreCreateMutex()的原型定义如下:

* \defgroup xSemaphoreCreateMutex xSemaphoreCreateMutex
* \ingroup Semaphores
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif

        它调用了文件queue.c中的函数xQueueCreateMutex(),这个函数的原型定义如下:

#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{QueueHandle_t xNewQueue;const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );prvInitialiseMutex( ( Queue_t * ) xNewQueue );return xNewQueue;
}#endif /* configUSE_MUTEXES */

        其中,参数ucQueueType表示要创建的对象类型,常量queueQUEUE_TYPE_MUTEX用于创建互斥量,常量queueQUEUE_TYPE_RECURSIVE_MUTEX用于创建递归互斥量。

        函数xSemaphoreCreateMutex(的返回数据类型是QueueHandle_t,是所创建互斥量的句柄。

(2)获取和释放互斥量

        获取互斥量使用函数xSemaphoreTake(),释放信号量使用函数xSemaphoreGive(),这两函数的用法与获取和释放二值信号量一样。

        互斥量不能在ISR中使用,因为互斥量具有针对任务的优先级继承机制,而ISR不是任务。所以,函数xSemaphoreGiveFromISR()和xSemaphoreTakeFromISR()不能应用于互斥量。

二、互斥量使用示例

1、示例功能和CubeMX项目设置

        设计一个示例演示使用互斥量时避免出现优先级翻转现象。本示例的主要功能与参考文章的示例相同,只是将其中的二值信号量换成了互斥量。

        继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。

(1)RCC、SYS、Code Generator、USART3、TIM6、NVIC

        与参考文章相同。

(2)RTOS

         删除FreeRTOS中原来的二值信号量token,创建一个互斥量token。其他内容的设置与参考文章相同。

        在FreeRTOS参数配置的Mutex页面,创建一个名称为token的互斥量:

2程序功能实现

(1)主程序

        在CubeMX里,生成代码,在CubeIDE里打开项目。自动生成绝大多数的代码。

        私有代码是需要手动添加的。手动添加include:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

        手动添加PFP原型:

/* USER CODE BEGIN PFP */
uint16_t HexToASCII(uint8_t data_hex);
/* USER CODE END PFP */

        手动添加启动菜单私有代码对:

/* USER CODE BEGIN 2 */uint8_t startstr[] = "Demo6_2:Using Mutex To avoid priority inversion.\r\n";HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);// start menu// transmit 32bit BaudRate,process low 16b only.uint8_t startstr2[] = "Baud Rate = ";HAL_UART_Transmit(&huart3,startstr2,sizeof(startstr2),0xFFFF);uint16_t high8Bits = HexToASCII((huart3.Init.BaudRate>>8) & 0xFF); 		//high 8bituint16_t low8Bits = HexToASCII(huart3.Init.BaudRate & 0xFF); 				//low 8bitHAL_UART_Transmit(&huart3,&high8Bits,2,100);								//transmit high 8 bitHAL_UART_Transmit(&huart3,&low8Bits,2,100);								//transmit low 8 bituint8_t startstr3[] = "\r\n";HAL_UART_Transmit(&huart3,startstr3,sizeof(startstr3),0xFFFF);HAL_Delay(10);//orprintf("Baud Rate = %ld.\r\n\r\n",huart3.Init.BaudRate);uint8_t startstr4[] = "1. Connect to PC via USART3.\r\n";HAL_UART_Transmit(&huart3,startstr4,sizeof(startstr4),0xFFFF);uint8_t startstr5[] = "2. View result on PC via ComAssistant.\r\n\r\n";HAL_UART_Transmit(&huart3,startstr5,sizeof(startstr5),0xFFFF);/* USER CODE END 2 */

         手动添加用户函数私有代码对:

/* USER CODE BEGIN 4 */
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);return ch;
}//将单个字节转化为两个ASCII字符进行显示,即一个字节转化为两个字节。
//如0x27,转化为'2' '7'两个字符。
uint16_t HexToASCII(uint8_t data_hex)
{uint8_t data_ASCII_H;uint8_t data_ASCII_L;uint16_t data_ASCII;data_ASCII_H = ((data_hex >> 4) & 0x0F);data_ASCII_L = data_hex & 0x0F;if(data_ASCII_H <= 9){data_ASCII_H += 0x30;}else if((data_ASCII_H >= 10) && (data_ASCII_H <= 15)){data_ASCII_H += 0x37;}if(data_ASCII_L <= 9){data_ASCII_L += 0x30;}else if((data_ASCII_L >= 10) && (data_ASCII_L <= 15)){data_ASCII_L += 0x37;}//HAL_UART_Transmit打印输出顺序是先低8b后高8bdata_ASCII = (((data_ASCII_L & 0x00FF) << 8) | data_ASCII_H);return data_ASCII;
}/* USER CODE END 4 */

        用户函数中,最特别的是串口发送32位波特率的函数处理,波特率是32位字节数值,串口接手并显示的是ASCII,串口发送函数不能直接发送uint32_t数值,需要做转换。 

(2)FreeRTOS对象初始化

        函数MX_FREERTOS_Init()用于创建在CubeMX中设计的互斥量token和3个任务。

        自动生成includes,并手动添加私有includes:

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
#include "semphr.h"
/* USER CODE END Includes */

        自动生成3个任务函数和1个互斥量句柄的定义:

/* Definitions for Task_High */
osThreadId_t Task_HighHandle;
const osThreadAttr_t Task_High_attributes = {.name = "Task_High",.stack_size = 128 * 4,.priority = (osPriority_t) osPriorityHigh,
};
/* Definitions for Task_Middle */
osThreadId_t Task_MiddleHandle;
const osThreadAttr_t Task_Middle_attributes = {.name = "Task_Middle",.stack_size = 128 * 4,.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for Task_Low */
osThreadId_t Task_LowHandle;
const osThreadAttr_t Task_Low_attributes = {.name = "Task_Low",.stack_size = 128 * 4,.priority = (osPriority_t) osPriorityLow,
};
/* Definitions for token */
osMutexId_t tokenHandle;
const osMutexAttr_t token_attributes = {.name = "token"
};

        其中,osMutexId_t是在文件cmsis_os2.h中定义的类型,就是一个void指针类型。

        osMutexAttr_t是在文件cmsis_os2.h中定义的结构体类型,用于描述互斥量的属性,其定义如下:

// Mutex attributes (attr_bits in \ref osMutexAttr_t).
#define osMutexRecursive      0x00000001U ///< Recursive mutex.
#define osMutexPrioInherit    0x00000002U ///< Priority inherit protocol.
#define osMutexRobust         0x00000008U ///< Robust mutex.

        在使用动态分配内存方式创建互斥量时,只需设置互斥量名称即可,控制块由FreeRTOS自动创建和分配内存。 

         自动声明函数原型:

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes *//* USER CODE END FunctionPrototypes */void AppTask_High(void *argument);
void AppTask_Middle(void *argument);
void AppTask_Low(void *argument);void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

        自动创建3个任务和互斥量:

/*** @brief  FreeRTOS initialization* @param  None* @retval None*/
void MX_FREERTOS_Init(void) 
{/* Create the mutex(es) *//* creation of token */tokenHandle = osMutexNew(&token_attributes);/* Create the thread(s) *//* creation of Task_High */Task_HighHandle = osThreadNew(AppTask_High, NULL, &Task_High_attributes);/* creation of Task_Middle */Task_MiddleHandle = osThreadNew(AppTask_Middle, NULL, &Task_Middle_attributes);/* creation of Task_Low */Task_LowHandle = osThreadNew(AppTask_Low, NULL, &Task_Low_attributes);
}

        在函数MX_FREERTOS_Init()中使用函数osMutexNew()创建互斥量,这是在sis_os2.h中定义的CMSIS-RTOS标准接口函数。根据传递的互斥量属性,osMutexNew()自动判别是创建互斥量,还是创建递归互斥量。在创建互斥量时,函数会根据属性设置,自动调用SmaphoreCreateMutex()以动态分配内存方式创建互斥量,或调用xSemaphoreCreateMutexStatic()以静态分配内存方式创建互斥量。

(3)3个任务的功能实现

        自动生成3个任务函数的框架,需要手动添加框架内的用户私有代码:

/* USER CODE BEGIN Header_AppTask_High */
/*** @brief  Function implementing the Task_High thread.* @param  argument: Not used* @retval None*/
/* USER CODE END Header_AppTask_High */
void AppTask_High(void *argument)
{/* USER CODE BEGIN AppTask_High *//* Infinite loop */uint8_t	strHigh[]="Task_High get token,transmit a message,give token and then Block.\n";for(;;){if (xSemaphoreTake(tokenHandle, portMAX_DELAY)==pdTRUE){HAL_UART_Transmit(&huart3,strHigh,sizeof(strHigh),300);HAL_Delay(10);xSemaphoreGive(tokenHandle);}vTaskDelay(500);}/* USER CODE END AppTask_High */
}/* USER CODE BEGIN Header_AppTask_Middle */
/**
* @brief Function implementing the Task_Middle thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_Middle */
void AppTask_Middle(void *argument)
{/* USER CODE BEGIN AppTask_Middle *//* Infinite loop */uint8_t	strMid[]="Task_Middle is running without token. \n";for(;;){HAL_UART_Transmit(&huart3,strMid,sizeof(strMid),300);HAL_Delay(10);vTaskDelay(500);}/* USER CODE END AppTask_Middle */
}/* USER CODE BEGIN Header_AppTask_Low */
/**
* @brief Function implementing the Task_Low thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_Low */
void AppTask_Low(void *argument)
{/* USER CODE BEGIN AppTask_Low *//* Infinite loop */uint8_t	str1[]="Task_Low take token. \n";uint8_t	str2[]="Task_Low give token. \n";for(;;){if (xSemaphoreTake(tokenHandle, pdMS_TO_TICKS(200))==pdTRUE){HAL_UART_Transmit(&huart3,str1,sizeof(str1),300);HAL_Delay(1000);	///<连续延时,但是不释放信号量,期间会被Task_Middle抢占任务HAL_UART_Transmit(&huart3,str2,sizeof(str2),300);HAL_Delay(10);		///<必须延时,避免换行符\n不能被正常输出xSemaphoreGive(tokenHandle);}vTaskDelay(20);}/* USER CODE END AppTask_Low */
}

3、运行与测试

        在这个示例中,由于使用了互斥量,在高优先级任务Task_High试图获取互斥量时,如果互斥量被Task_Low占用着,FreeRTOS会将Task_Low的优先级临时提高到Task_High的优先级。这样,在Task_Low占用互斥量运行期间,Task_Middle就无法抢占CPU运行,在Task_Low释放互斥量后,Task_High就能抢占CPU立刻运行。所以,使用互斥量,就避免了高优先级任务被中等优先级任务插队运行的情况。

 

        互斥量并不能在所有情况下彻底解决优先级翻转问题,但是至少可以减缓优先级翻转问题的出现。另外,因为互斥量使用了优先级继承机制,所以不能在ISR中使用互斥量。

http://www.xdnf.cn/news/219763.html

相关文章:

  • C# 导入EXCEL 报错外部表不是预期的格式错误指南方案
  • C++中的vector和list有什么区别?
  • Launcher3-实现家长管控-儿童模式-老人模式
  • 机器学习第四篇 线性回归-最小二乘法
  • 案例分享|20倍提效!水力设备电磁仿真的云端实战
  • DDoS攻击真的无解吗?
  • DeepClaude开源程序可以实现代码生成、创作诗句以及内容创作等功能
  • 详解大语言模型生态系统概念:lama,llama.cpp,HuggingFace 模型 ,GGUF,MLX,lm-studio,ollama这都是什么?
  • 【LaTex】3.8流程图绘制
  • Transformer数学推导——Q34 推导位置插值(Position Interpolation)在长文本外推中的误差上界
  • (02)Redis 的订阅发布Pub/Sub
  • Ubuntu上搭建python环境并安装第三方库
  • C语言教程(二十四):C 语言中递归的详解
  • cuda学习3: 全局线程id计算
  • 大语言模型能否替代心理治疗师的深度拓展研究:fou
  • 两数之和II-输入有序数组(中等)
  • 洛谷题解 | CF1979C Earning on Bets
  • DNA复制过程3D动画教学工具
  • 稳定性 复杂度
  • 浅析localhost、127.0.0.1 和 0.0.0.0的区别
  • 【RocketMq延迟消息操作流程】
  • 鸟笼效应——AI与思维模型【84】
  • Canvas基础篇:概述
  • DeepSeek 本地化部署与 WebUI 配置的方法
  • Fiddler抓取APP端,HTTPS报错全解析及解决方案(一篇解决常见问题)
  • 在Ubuntu中安装python
  • 02_高并发系统问题及解决方案
  • 大模型高效化三大核心技术:量化、蒸馏与剪枝详解
  • 【AI论文】BitNet v2:针对1位LLM的原生4位激活和哈达玛变换
  • 物流新速度:数字孪生让仓库“聪明”起来