FreeRTOS的基本概念与其任务间通信方式

记录贴:记录学习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 状态)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值