STM32CubeMX学习笔记(31)——FreeRTOS实时操作系统使用(互斥量)

Posted Leung_ManWah

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32CubeMX学习笔记(31)——FreeRTOS实时操作系统使用(互斥量)相关的知识,希望对你有一定的参考价值。

一、FreeRTOS简介

FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。

FreeRTOS 是用 C 和汇编来写的,其中绝大部分都是用 C 语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,FreeRTOS 结构简洁,可读性很强!最主要的是非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。

最新版本 V9.0.0(2016年),尽管现在 FreeRTOS 的版本已经更新到 V10.4.1 了,但是我们还是选择 V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版本之后是亚马逊收购了FreeRTOS之后才出来的版本,主要添加了一些云端组件,一般采用 V9.0.0 版本足以。

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

三、SYS Timebase Source

System Core 中选择 SYS ,对 Timebase Source 进行设置,选择 TIM1 作为HAL库的时基(除了 SysTick 外都可以)。

在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:

  1. HAL的时基,SYS Timebase Source
  2. OS的时基(仅在使用OS的情况下才考虑)

而这些 “时基” 该去如何维护,主要分为两种情况考虑:

  • 裸机运行
    可以通过 SysTick(滴答定时器)或 (TIMx)定时器 的方式来维护 SYS Timebase Source,也就是HAL库中的 uwTick,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler() 中断服务函数中来维护。

  • 带OS运行
    前面提到的 SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。

    在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。

    如果共用SysTick,当我们在CubeMX中选择启用FreeRTOS之后,在生成代码时,CubeMX一定会报如下提示:

强烈建议用户在使用FreeRTOS的时候,不要使用 SysTick(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用,最好是要换一个!!!如果共用,潜在一定风险。

四、FreeRTOS

4.1 参数配置

Middleware 中选择 FREERTOS 设置,并选择 CMSIS_V1 接口版本


CMSIS是一种接口标准,目的是屏蔽软硬件差异以提高软件的兼容性。RTOS v1使得软件能够在不同的实时操作系统下运行(屏蔽不同RTOS提供的API的差别),而RTOS v2则是拓展了RTOS v1,兼容更多的CPU架构和实时操作系统。因此我们在使用时可以根据实际情况选择,如果学习过程中使用STM32F1、F4等单片机时没必要选择RTOS v2,更高的兼容性背后时更加冗余的代码,理解起来比较困难。

Config parameters 进行具体参数配置。

Kernel settings:

  • USE_PREEMPTION: Enabled:RTOS使用抢占式调度器;Disabled:RTOS使用协作式调度器(时间片)。
  • TICK_RATE_HZ: 值设置为1000,即周期就是1ms。RTOS系统节拍中断的频率,单位为HZ。
  • MAX_PRIORITIES: 可使用的最大优先级数量。设置好以后任务就可以使用从0到(MAX_PRIORITIES - 1)的优先级,其中0位最低优先级,(MAX_PRIORITIES - 1)为最高优先级。
  • MINIMAL_STACK_SIZE: 设置空闲任务的最小任务堆栈大小,以字为单位,而不是字节。如该值设置为128 Words,那么真正的堆栈大小就是 128*4 = 512 Byte。
  • MAX_TASK_NAME_LEN: 设置任务名最大长度。
  • IDLE_SHOULD_YIELD: Enabled 空闲任务放弃CPU使用权给其他同优先级的用户任务。
  • USE_MUTEXES: 为1时使用互斥信号量,相关的API函数会被编译。
  • USE_RECURSIVE_MUTEXES: 为1时使用递归互斥信号量,相关的API函数会被编译。
  • USE_COUNTING_SEMAPHORES: 为1时启用计数型信号量, 相关的API函数会被编译。
  • QUEUE_REGISTRY_SIZE: 设置可以注册的队列和信号量的最大数量,在使用内核调试器查看信号量和队列的时候需要设置此宏,而且要先将消息队列和信号量进行注册,只有注册了的队列和信号量才会在内核调试器中看到,如果不使用内核调试器的话次宏设置为0即可。
  • USE_APPLICATION_TASK_TAG: 为1时可以使用vTaskSetApplicationTaskTag函数。
  • ENABLE_BACKWARD_COMPATIBILITY: 为1时可以使V8.0.0之前的FreeRTOS用户代码直接升级到V8.0.0之后,而不需要做任何修改。
  • USE_PORT_OPTIMISED_TASK_SELECTION: FreeRTOS有两种方法来选择下一个要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU自带的硬件指令来实现。STM32有计算前导零指令吗,所以这里强制置1。
  • USE_TICKLESS_IDLE: 置1:使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行。假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用ISP下载办法解决。
  • USE_TASK_NOTIFICATIONS: 为1时使用任务通知功能,相关的API函数会被编译。开启了此功能,每个任务会多消耗8个字节。
  • RECORD_STACK_HIGH_ADDRESS: 为1时栈开始地址会被保存到每个任务的TCB中(假如栈是向下生长的)。

Memory management settings:

  • Memory Allocation: Dynamic/Static 支持动态/静态内存申请
  • TOTAL_HEAP_SIZE: 设置堆大小,如果使用了动态内存管理,FreeRTOS在创建 task, queue, mutex, software timer or semaphore的时候就会使用heap_x.c(x为1~5)中的内存申请函数来申请内存。这些内存就是从堆ucHeap[configTOTAL_HEAP_SIZE]中申请的。
  • Memory Management scheme: 内存管理策略 heap_4

Hook function related definitions:

  • USE_IDLE_HOOK: 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子。
  • USE_TICK_HOOK: 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子。
  • USE_MALLOC_FAILED_HOOK: 使用内存申请失败钩子函数。
  • CHECK_FOR_STACK_OVERFLOW: 大于0时启用堆栈溢出检测功能,如果使用此功能用户必须提供一个栈溢出钩子函数,如果使用的话此值可以为1或者2,因为有两种栈溢出检测方法。

Run time and task stats gathering related definitions:

  • GENERATE_RUN_TIME_STATS: 启用运行时间统计功能。
  • USE_TRACE_FACILITY: 启用可视化跟踪调试。
  • USE_STATS_FORMATTING_FUNCTIONS: 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数prvWriteNameToBuffer()、vTaskList()、vTaskGetRunTimeStats()。

Co-routine related definitions:

  • USE_CO_ROUTINES: 启用协程。
  • MAX_CO_ROUTINE_PRIORITIES: 协程的有效优先级数目。

Software timer definitions:

  • USE_TIMERS: 启用软件定时器。

Interrupt nesting behaviour configuration:

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中断最低优先级。
  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系统可管理的最高中断优先级。

4.2 创建互斥量Mutex

Mutexes 进行配置。

  • Mutex Name: 互斥量名称
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Conrol Block Name: 控制块名称

4.3 创建任务Task

我们创建三个任务,一个高优先级任务,一个中优先级任务,一个低优先级任务。

  • Task Name: 任务名称
  • Priority: 优先级,在 FreeRTOS 中,数值越大优先级越高,0 代表最低优先级
  • Stack Size (Words): 堆栈大小,单位为字,在32位处理器(STM32),一个字等于4字节,如果传入512那么任务大小为512*4字节
  • Entry Function: 入口函数
  • Code Generation Option: 代码生成选项
  • Parameter: 任务入口函数形参,不用的时候配置为0或NULL即可
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Buffer Name: 缓冲区名称
  • Conrol Block Name: 控制块名称

五、UART串口打印

查看 STM32CubeMX学习笔记(6)——USART串口使用

六、生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

七、互斥量

7.1 基本概念

互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,**它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。**任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。

如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于任务与任务、任务与中断的同步,但是互斥量更多的是用于保护资源的互锁。

用于互锁的互斥量可以充当保护资源的令牌,当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问该资源。是不是很熟悉,在我们的二值信号量里面也是一样的,用于保护临界资源,保证多任务的访问井然有序。当任务获取到信号量的时候才能开始使用被保护的资源,使用完就释放信号量,下一个任务才能获取到信号量从而可用使用被保护的资源。但是**信号量会导致的另一个潜在问题,那就是任务优先级翻转。**而 FreeRTOS 提供的互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的影响,所以,用于临界资源的保护一般建议使用互斥量。

7.2 运作机制


用互斥量处理不同任务对临界资源的同步访问时,任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。

7.3 互斥量与递归互斥量

  • 互斥量更适合于可能会引起优先级翻转的情况。
  • 递归互斥量更适用于任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造成死锁的问题。

八、相关API说明

8.1 osMutexCreate

用于创建一个互斥量,并返回一个互斥量ID。

函数osMutexId osMutexCreate (const osMutexDef_t *mutex_def)
参数mutex_def: 引用由osMutexDef定义的互斥量
返回值成功返回互斥量ID,失败返回0

8.2 osRecursiveMutexCreate

用于创建一个递归互斥量,不是递归的互斥量由函数 osMutexCreate() 创建,且只能被同一个任务获取一次,如果同一个任务想再次获取则会失败。递归信号量则相反,它可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以减少优先级反转的反生。

函数osMutexId osRecursiveMutexCreate (const osMutexDef_t *mutex_def)
参数mutex_def: 引用由osMutexDef定义的互斥量
返回值成功返回互斥量ID,失败返回0

要想使用该函数必须在 Config parameters 中把 USE_RECURSIVE_MUTEXES 选择 Enabled 来使能。

8.3 osMutexDelete

用于删除一个互斥量。

函数osStatus osMutexDelete (osMutexId mutex_id)
参数mutex_id: 互斥量ID
返回值错误码

8.4 osMutexWait

用于获取互斥量,但是递归互斥量并不能使用这个 API 函数获取。

函数osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)
参数mutex_id: 互斥量ID

**millisec:**等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为 portMAX_DELAY ,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值错误码

8.5 osRecursiveMutexWait

用于获取递归互斥量的宏,与互斥量的获取函数一样,osMutexWait()也是一个宏定义,它最终使用现有的队列机制,实际执行的函数是 xQueueTakeMutexRecursive() 。 获取递归互斥量之前必须由 osRecursiveMutexCreate() 这个函数创建。要注意的是该函数不能用于获取由函数 osMutexCreate() 创建的互斥量。

函数osStatus osRecursiveMutexWait (osMutexId mutex_id, uint32_t millisec)
参数mutex_id: 互斥量ID

**millisec:**等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为 portMAX_DELAY ,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值错误码

要想使用该函数必须在 Config parameters 中把 USE_RECURSIVE_MUTEXES 选择 Enabled 来使能。

8.6 osMutexRelease

用于释放互斥量,但不能释放由函数 osRecursiveMutexCreate() 创建的递归互斥量。

函数osStatus osMutexRelease (osMutexId mutex_id)
参数mutex_id: 互斥量ID
返回值错误码

8.7 osRecursiveMutexRelease

用于释放一个递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。使用 osRecursiveMutexWait() 函数成功获取几次递归互斥量,就要使用 osRecursiveMutexRelease() 函数返还几次,在此之前递归互斥量都处于无效状态,别的任务就无法获取该递归互斥量。使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,每释放一该递归互斥量,它的计数值就减 1。当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级。

函数osStatus osRecursiveMutexRelease (osMutexId mutex_id)
参数mutex_id: 互斥量ID
返回值错误码

要想使用该函数必须在 Config parameters 中把 USE_RECURSIVE_MUTEXES 选择 Enabled 来使能。

九、示例

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"

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

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

osThreadId defaultTaskHandle;
osThreadId LowPriorityHandle;
osThreadId MidPriorityHandle;
osThreadId HighPriorityHandle;
osMutexId MuxSemHandle;
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
void StartDefaultTask(void const * argument);
void LowPriorityTask(void const * argument);
void MidPriorityTask(void const * argument);
void HighPriorityTask(void const * argument);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)

  /* USER CODE BEGIN 1 */
    
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    printf("The default value of parking space is 5, Press key1 to apply for parking space, and press key2 to release parking space!\\n\\n");
  /* USER CODE END 2 */

  /* Create the mutex(es) */
  /* definition and creation of MuxSem */
  osMutexDef(MuxSem);
  MuxSemHandle = osMutexCreate(osMutex(MuxSem));

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  /* definition and creation of LowPriority */
  osThreadDef(LowPriority, LowPriorityTask, osPriorityIdle, 0, 128);
  LowPriorityHandle = osThreadCreate(osThread(LowPriority), NULL);

  /* definition and creation of MidPriority */
  osThreadDef(MidPriority, MidPriorityTask, osPriorityIdle, 0, 128);
  MidPriorityHandle = osThreadCreate(osThread(MidPriority), NULL);

  /* definition and creation of HighPriority */
  osThreadDef(HighPriority, HighPriorityTask, osPriorityIdle, 0, 128);
  HighPriorityHandle = osThreadCreate(osThread(HighPriority), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  
  /* USER CODE END 3 */


/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)

  RCC_OscInitTypeDef RCC_OscInitStruct = 0;
  RCC_ClkInitTypeDef RCC_ClkInitStruct = 0;

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  
    Error_Handler();
  
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  
    Error_Handler();
  


/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)


  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  
    Error_Handler();
  
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */



/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)


  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);



/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)

  GPIO_InitTypeDef GPIO_InitStruct = 0;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, LED_G_Pin|LED_B_Pin|LED_R_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : KEY2_Pin */
  GPIO_InitStruct.Pin = KEY2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : KEY1_Pin */
  GPIO_InitStruct.Pin = KEY1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LED_G_Pin LED_B_Pin LED_R_Pin */
  GPIO_InitStruct.Pin = LED_G_Pin|LED_B_Pin|LED_R_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);



/* USER CODE BEGIN 4 */
/**
  * @brief 重定向c库函数printf到USARTx
  * @retval None
  */
int fputc(int ch, FILE *f)

  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;

 
/**
  * @brief 重定向c库函数getchar,scanf到USARTx
  * @retval None
  */
int fgetc(FILE *f)

  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;

/* USER CODE END 4 */

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)

  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  
    osDelay(1);
  
  /* USER CODE END 5 */


/* USER CODE BEGIN Header_LowPriorityTask */
/**
* @brief Function implementing the LowPriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_LowPriorityTask */
void LowPriorityTask(void const * argument)

  /* USER CODE BEGIN LowPriorityTask */
  static uint32_t i; 
  osStatus xReturn;
  /* Infinite loop */
  for(;;)
  
    printf("LowPriority_Task Get Mutex\\n");
    //获取互斥量 MuxSem,没获取到则一直等待 
    xReturn = osMutexWait(MuxSemHandle,     /* 互斥量句柄 */ 
                          osWaitForever);   /* 等待时间 */
    if(osOK == xReturn) 
    
        printf("LowPriority_Task Runing\\n\\n"); 
    
    
    for(i = 0; i < 2000000; i++) 
     //模拟低优先级任务占用互斥量 
        taskYIELD();//发起任务调度 
     
    
    printf("LowPriority_Task Release Mutex\\r\\n"); 
    xReturn = osMutexRelease(MuxSemHandle);//给出互斥量 
    
    osDelay(1000);
  
  /* USER CODE END LowPriorityTask */


/* USER CODE BEGIN Header_MidPriorityTask */
/**
* @brief Function implementing the MidPriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MidPriorityTask */
void MidPriorityTask(void const * argument)

  /* USER CODE BEGIN MidPriorityTask */
  /* Infinite loop */
  for(;;)
  
    printf("MidPriority_Task Runing\\n"); 
    osDelay(1000);
  
  /* USER CODE END MidPriorityTask */


/* USER CODE BEGIN Header_HighPriorityTask */
/**
* @brief Function implementing the HighPriority thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_HighPriorityTask */
void HighPriorityTask(void const * argument)

  /* USER CODE BEGIN HighPriorityTask */
  osStatus xReturn;
  /* Infinite loop */
  for(;;)
  
    printf("HighPriority_Task Get Mutex\\n"); 
    //获取互斥量 MuxSem,没获取到则一直等待 
    xReturn = osMutexWait(MuxSemHandle,     /* 互斥量句柄 */ 
                          osWaitForever);   /* 等待时间 */
    if(os

以上是关于STM32CubeMX学习笔记(31)——FreeRTOS实时操作系统使用(互斥量)的主要内容,如果未能解决你的问题,请参考以下文章

STM32学习笔记 二基于STM32F103C8T6和STM32CubeMX实现UART串口通信数据收发

STM32学习笔记 二基于STM32F103C8T6和STM32CubeMX实现UART串口通信数据收发

STM32学习笔记 二基于STM32F103C8T6和STM32CubeMX实现UART串口通信数据收发

STM32学习笔记 一基于STM32F103C8T6最小系统板和STM32CubeMX实现板载LED灯循环闪烁

STM32CubeMX学习笔记(32)——FreeRTOS实时操作系统使用(事件)

STM32学习笔记 一基于STM32F103C8T6最小系统板和STM32CubeMX实现LED灯循环闪烁