记录贴:记录学习RTOS中遇到的问题
一、FreeRTOS的基本概念
(1)如何理解裸机开发与操作系统?
在传统裸机开发中,程序通常围绕一个主循环 while(1) 构建,任务间通过状态判断和轮询处理事件。这种方式虽然简单直观,但在多任务并发、实时性需求强的场景下效率低下,易造成资源浪费,任务优先级难以管理。
FreeRTOS 是一种轻量级实时操作系统,提供了任务(Task)调度机制,使不同功能模块能并发独立执行。通过任务优先级、时间片、信号量、队列、事件组等机制,系统可以精准控制任务执行顺序和时机。与裸机相比,FreeRTOS 更易组织复杂逻辑,提高系统响应速度与代码可维护性。
FreeRTOS 将 CPU 时间更合理分配给多个任务,避免了“轮询死”等问题,适合对多任务协调、资源同步、响应实时性要求较高的嵌入式系统开发。
(2)FreeRTOS 的任务调度核心思想
-
优先级抢占调度为主(Preemptive)
-
系统总是运行“当前就绪状态中优先级最高”的任务。
-
一旦有更高优先级的任务就绪,当前低优先级任务会立即被中断挂起。
-
这是 FreeRTOS 默认的调度模式。
-
-
时间片轮转调度(Time Slicing)为辅
-
如果多个任务的优先级相同,FreeRTOS 会将 CPU 时间平均分配给这些任务(也就是“时间片”)。
-
每个任务在一个“滴答周期”(tick)内运行,到了时间就切换下一个同级任务。
-
(3)学习路线

二、任务间的通信方式
(1)消息队列
消息队列(Message Queue)是 FreeRTOS 中任务间通信的一种机制,用于在任务、定时器或中断之间传递数据。它像一个“邮箱”或“排队窗口”,允许任务之间 发送(send)和接收(receive)消息,以此实现任务解耦和数据同步。
在STM32中使用:
在cubemx中配置,



在keil中添加以下代码,实现任务间数据的通信。
#include "cmsis_os.h"
#include "usart.h"
#include <stdio.h>
#include "string.h"
osThreadId_t SenderTaskHandle;
osThreadId_t ReceiverTaskHandle;
osMessageQueueId_t myQueueHandle;
// 任务函数声明
void SenderTask(void *argument);
void ReceiverTask(void *argument);
void MX_FREERTOS_Init(void) {
// 创建队列:10个元素,每个为uint32_t大小
myQueueHandle = osMessageQueueNew(10, sizeof(uint32_t), NULL);
// 创建任务
SenderTaskHandle = osThreadNew(SenderTask, NULL, NULL);
ReceiverTaskHandle = osThreadNew(ReceiverTask, NULL, NULL);
}
void SenderTask(void *argument) {
uint32_t count = 0;
for (;;) {
osMessageQueuePut(myQueueHandle, &count, 0, 0);
count++;
osDelay(1000);
}
}
void ReceiverTask(void *argument) {
uint32_t received;
for (;;) {
if (osMessageQueueGet(myQueueHandle, &received, NULL, osWaitForever) == osOK) {
char msg[64];
sprintf(msg, "Received: %lu\r\n", received);
HAL_UART_Transmit(&hlpuart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
}
}
}
现象就是在SenderTask中发送count的值,在ReceiverTask中接受,并通过串口打印。
事实上通信队列的使用就只包含三部分:1.创建队列。2.创建任务。3在任务中发送与接收
(2)二值信号量
信号量(Semaphore)是 FreeRTOS 中用于任务间同步与资源互斥的一种机制,常用于控制任务执行顺序或访问共享资源时避免冲突。
在cubemx中配置:

在keil中添加代码:

在中断函数中添加按键中断处理函数 发送信号
在app_freertos.c中添加以下代码:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#include "stdio.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
osThreadId_t Task1Handle;
const osThreadAttr_t Task1_attributes = {
.name = "Task1",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
/* Definitions for Task2 */
osThreadId_t Task2Handle;
const osThreadAttr_t Task2_attributes = {
.name = "Task2",
.priority = (osPriority_t) osPriorityHigh,
.stack_size = 128 * 4
};
/* Definitions for myBinarySem01 */
osSemaphoreId_t myBinarySem01Handle;
const osSemaphoreAttr_t myBinarySem01_attributes = {
.name = "myBinarySem01"
};
void SenderTask(void *argument);
void MX_FREERTOS_Init(void);
void MX_FREERTOS_Init(void)
{
myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);
Task1Handle = osThreadNew(SenderTask, NULL, &Task1_attributes);
Task2Handle = osThreadNew(ReceiverTask, NULL, &Task2_attributes);
}
void SenderTask(void *argument)
{
/* USER CODE BEGIN SenderTask */
/* Infinite loop */
for(;;)
{
if (osSemaphoreAcquire(myBinarySem01Handle, osWaitForever) == osOK)
{
printf("按键被按下,打印一次信息\r\n");
}
}
/* USER CODE END SenderTask */
}
现象为按下按键后任务接受到信号量通过串口打印信息。
(3)事件标志组
事件标志组(Event Groups)是 FreeRTOS 提供的一种 任务同步机制,用于多个任务之间的事件通信与条件等待,它比信号量或消息队列更适合表达“等待多个事件条件”的场景。
-
事件组:一个
EventGroupHandle_t类型的变量,内部是 32 个二进制位(bit),每个 bit 表示一个事件。 -
事件位(Event Bits):每个任务可以设置或等待某个 bit 被置位(设为1),也可以清除某个 bit。多个任务可以 同时等待多个事件位
在cubemx中配置:

在keil中的app_freertos.c中添加代码:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#include "stdio.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
osThreadId_t Task1Handle;
const osThreadAttr_t Task1_attributes = {
.name = "Task1",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
/* Definitions for Task2 */
osThreadId_t Task2Handle;
const osThreadAttr_t Task2_attributes = {
.name = "Task2",
.priority = (osPriority_t) osPriorityHigh,
.stack_size = 128 * 4
};
/* Definitions for myBinarySem01 */
osSemaphoreId_t myBinarySem01Handle;
const osSemaphoreAttr_t myBinarySem01_attributes = {
.name = "myBinarySem01"
};
/* Definitions for myEventGroup */
osEventFlagsId_t myEventGroupHandle;
const osEventFlagsAttr_t myEventGroup_attributes = {
.name = "myEventGroup"
};
void SenderTask(void *argument);
void ReceiverTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);
Task1Handle = osThreadNew(SenderTask, NULL, &Task1_attributes);
Task2Handle = osThreadNew(ReceiverTask, NULL, &Task2_attributes);
myEventGroupHandle = osEventFlagsNew(&myEventGroup_attributes);
}
void SenderTask(void *argument)
{
for(;;)
{
xEventGroupSetBits(myEventGroupHandle, BIT_0);
printf("发送成功\n");
osDelay(1000);
}
}
void ReceiverTask(void *argument)
{
for(;;)
{
if (osSemaphoreAcquire(myBinarySem01Handle, osWaitForever) == osOK)
{
EventBits_t bits = xEventGroupWaitBits(
myEventGroupHandle,
BIT_0,
pdTRUE, // 清除标志
pdFALSE, // OR关系
portMAX_DELAY);
if (bits & BIT_0) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
printf("同步成功\n");
}
}
}
现象:任务一发送标志去告诉任务二翻转LED灯。LED灯以1秒的间隔闪烁。
(4)互斥量
互斥量(Mutex)是一种特殊的信号量,用来保护共享资源,防止多个任务“同时访问”造成数据冲突。
核心特点:
在stm32中使用:

在keil中添加代码:
-
同一时刻只能被一个任务拥有;
-
其他任务只能等待;
-
通常用于保护临界区(例如:共享的全局变量、外设资源);
-
支持 优先级继承机制,避免优先级反转问题
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#include "string.h"
int counter = 0;
osThreadId_t Task1Handle;
const osThreadAttr_t Task1_attributes = {
.name = "Task1",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
osThreadId_t myTask02Handle;
const osThreadAttr_t myTask02_attributes = {
.name = "myTask02",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 128 * 4
};
osMutexId_t myMutex01Handle;
const osMutexAttr_t myMutex01_attributes = {
.name = "myMutex01"
};
void SenderTask(void *argument);
void StartTask02(void *argument);
void myTimerCallback(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
myMutex01Handle = osMutexNew(&myMutex01_attributes);
Task1Handle = osThreadNew(SenderTask, NULL, &Task1_attributes);
myTask02Handle = osThreadNew(StartTask02, NULL, &myTask02_attributes);
}
void SenderTask(void *argument)
{
for(;;)
{
osMutexAcquire(myMutex01Handle, osWaitForever); // 请求互斥量
counter++;
printf("Task1: counter = %d\r\n", counter);
osMutexRelease(myMutex01Handle); // 释放互斥量
osDelay(500);
}
}
void StartTask02(void *argument)
{
for(;;)
{
osMutexAcquire(myMutex01Handle, osWaitForever);
counter--;
printf("Task2: counter = %d\r\n", counter);
osMutexRelease(myMutex01Handle);
osDelay(1000);
}
}
现象:

当修改代码,任务一获取互斥量但是不释放后:


任务二被堵塞,无法再获取count的值。
三、软件定时器
软件定时器是 FreeRTOS 提供的一个轻量级机制,用于在指定时间后触发一个回调函数,而不需要持续占用任务资源。
它由 Timer Service Task 管理,并不直接占用 CPU。
在stm32中使用:
cubemx中配置

在keil中添加代码:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#include "string.h"
osThreadId_t Task1Handle;
const osThreadAttr_t Task1_attributes = {
.name = "Task1",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
/* Definitions for myTimer01 */
osTimerId_t myTimer01Handle;
const osTimerAttr_t myTimer01_attributes = {
.name = "myTimer01"
};
void SenderTask(void *argument);
void myTimerCallback(void *argument);
void MX_FREERTOS_Init(void);
void MX_FREERTOS_Init(void) {
myTimer01Handle = osTimerNew(myTimerCallback, osTimerPeriodic, NULL, &myTimer01_attributes);
Task1Handle = osThreadNew(SenderTask, NULL, &Task1_attributes);
osTimerStart(myTimer01Handle, 1000);
}
void SenderTask(void *argument)
{
for(;;)
{
osDelay(500);
printf("默认任务运行中...\r\n");
}
}
void myTimerCallback(void *argument)
{
printf("定时器触发了!\r\n");
}
现象:

定时器时间到了之后就会打印触发信息。当然,定时回调函数中可以写很多功能。
四、其他
(1)生产者消费者模型
“生产者-消费者模型”是操作系统中任务/线程通信与同步的经典模式,用来解决两个任务之间的数据协作问题。
模型核心思想
-
生产者(Producer)任务:负责“产生资源”(例如:数据、消息等),将资源放入一个缓冲区。
-
消费者(Consumer)任务:负责“消费资源”,从缓冲区中取出资源并处理
-
厨师 = 生产者
他负责炒菜、做好食物,放到窗口。 -
顾客 = 消费者
你来窗口取餐。
但厨师做好饭前,你不能吃;窗口满了,厨师也不能继续放
在 FreeRTOS 中如何实现?:使用信号量(或消息队列)来控制:
-
缓冲区空位:用计数信号量限制最多放入几个
-
缓冲区是否有数据:用二值信号量或另一个计数信号量控制消费者是否能取
(2) osDelay是阻塞式的吗?在一个任务中写,另外一个任务会等待执行完吗?
osDelay() 是阻塞当前任务的函数,但不会阻塞其他任务的运行。
当某个任务调用 osDelay(n)(或 vTaskDelay(n)),它会进入阻塞态(Blocked)让出 CPU 控制权,直到延迟时间过去。
此时:当前任务不再占用 CPU。任务调度器会调度其他就绪任务(Ready)继续运行,不管是低优先级任务还是高优先级任务
(3)互斥量与volatile修饰的变量作用区分?
volatile的作用是告诉 编译器 该变量可能会被中断、外部线程、外设等异步地修改,不要对该变量的读写操作进行优化(比如寄存器缓存、合并读写等)。本质上volatile解决的是编译器优化的问题,而不是线程安全问题。
而互斥量(Mutex)的作用是防止多个线程同时访问共享资源(如变量、外设、文件等),从而避免竞态条件(数据错误、异常行为等)。本质上互斥量解决的是原子性 + 临界区保护问题。
1.volatile仅仅是告知编译器不要优化访问方式,它不会同步数据访问;
2.互斥量才是真正用于线程同步和资源保护的机制;
3.在实际使用中,经常是两者配合使用(如 volatile bool flag + 互斥访问 flag 状态)。
278

被折叠的 条评论
为什么被折叠?



