ESP-IDF(FreeRTOS)

文章目录

1.队列操作

任务间的同步

同步是不同任务之间或任务与外部事件之间的协同工作方式,确保多个并发任务按预期顺序 / 时机执行;涉及线程 / 任务的通信与协调机制,目的是避免数据竞争、解决竞态条件,保障系统正确运行`

互斥

互斥是指某一资源同时仅允许一个访问者访问,具有唯一性和排他性。

c 复制代码
QueueHandle_t xQueueCreate( //创建一个队列,成功返回队列句柄
UBaseType_t uxQueueLength, //队列容量
UBaseType_t uxItemSize //每个队列项所占内存的大小(单位是字节)
);
c 复制代码
BaseType_t xQueueSend( //向队列头部发送一个消息
QueueHandle_t xQueue, //队列句柄
const void * pvItemToQueue, //要发送的消息指针
TickType_t xTicksToWait ); //等待时间
c 复制代码
BaseType_t xQueueSendToBack( //向队列尾部发送一个消息
QueueHandle_t xQueue, //队列句柄
const void * pvItemToQueue, //要发送的消息指针
TickType_t xTicksToWait ); //等待时间
c 复制代码
//这是 FreeRTOS 中用于队列消息接收的 API 函数,
//核心作用是从指定队列中取出一条消息,
//并将消息内容拷贝到pvBuffer指向的缓冲区中。
BaseType_t xQueueReceive( //从队列接收一条消息
QueueHandle_t xQueue, //队列句柄
void * pvBuffer, //指向接收消息缓冲区的指针
TickType_t xTicksToWait ); //等待时间
c 复制代码
BaseType_t xQueueSendFromISR( //xQueueSend的中断版本
QueueHandle_t xQueue, //队列句柄
const void * pvItemToQueue, //发送的消息指针
BaseType_t * pxHigherPriorityTaskWoken ); 
//如果发送到队列导致任务解除阻塞,且解除阻塞的任务的优先级高于当前运行的任务
//,则设置为pdTRUE

参数pxHigherPriorityTaskWoken的作用

若消息发送后,解除了更高优先级任务的阻塞,则该参数会被置为pdTRUE。

此时需在中断退出前调用上下文切换函数(如portYIELD_FROM_ISR()),以确保高优先级任务及时运行。

c 复制代码
//队列操作函数
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "freertos/queue.h"
#include "esp_log.h"

QueueHandle_t queue_handle = NULL;

typedef struct 
{
    /* data */
    int value;
}queue_data_t;

void taskA(void* param)
{
  //从消息队列中接收数据,并打印
  queue_data_t data;
  while(1)
  {
     if(xQueueReceive(queue_handle,&data,portMAX_DELAY) == pdTRUE)
     {
         ESP_LOGI("task","value = %d",data.value);
     }
  }
  
}

void taskB(void* param)
{
  queue_data_t data;
  memset(&data,0,sizeof(queue_data_t));
  //向消息队列中发送数据
  while(1)
  {
     xQueueSend(queue_handle,&data,300);
     vTaskDelay(pdMS_TO_TICKS(1000));
     data.value++;
  }
}

void app_main(void)
{ 
   queue_handle = xQueueCreate(10,sizeof(queue_data_t));   
   xTaskCreate(taskA,"taskA",2048,NULL,10,NULL);
   xTaskCreate(taskB,"taskB",2048,NULL,10,NULL);
}

2.信号量

c 复制代码
//创建二值信号量,成功则返回信号量句柄(二值信号量最大只有1个)
SemaphoreHandle_t xSemaphoreCreateBinary( void );

//创建计数信号量,成功则返回信号量句柄
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, //最大信号量数
UBaseType_t uxInitialCount); //初始信号量数

这是 FreeRTOS 中两种信号量的创建函数:

  • 二值信号量(xSemaphoreCreateBinary):
    仅支持 0 和 1 两种状态,常用于互斥访问或同步单次事件(如中断通知任务)。创建后初始值默认为 0(需通过xSemaphoreGive释放后变为 1)。
  • 计数信号量(xSemaphoreCreateCounting):
    支持uxMaxCount以内的数值,常用于资源计数(如有限数量的外设)。
    uxInitialCount为初始可用的信号量数量。
c 复制代码
//获取一个信号量,如果获得信号量,则返回pdTRUE
xSemaphoreTake(
    SemaphoreHandle_t xSemaphore, //信号量句柄
    TickType_t xTicksToWait);    //等待时间

//释放一个信号量
xSemaphoreGive(
    SemaphoreHandle_t xSemaphore); //信号量句柄

//删除信号量
void vSemaphoreDelete(
    SemaphoreHandle_t xSemaphore);

函数功能解读

这是FreeRTOS中信号量的核心操作函数:

  1. xSemaphoreTake(获取信号量)

    • 尝试获取信号量:若信号量可用(值>0),则信号量值减1,返回pdTRUE
    • 若信号量不可用,任务会阻塞xTicksToWait个系统节拍,超时则返回pdFALSE
    • 用于申请资源等待同步事件
  2. xSemaphoreGive(释放信号量)

    • 释放信号量,使信号量值加1(二值信号量最多到1,计数信号量不超过最大限制);
    • 若有任务因等待该信号量而阻塞,会唤醒优先级最高的任务。
    • 用于释放资源触发同步事件
  3. vSemaphoreDelete(删除信号量)

    • 销毁指定的信号量,释放其占用的内存;
    • 需确保后续不再使用该信号量句柄。
c 复制代码
//创建一个互斥锁
SemaphoreHandle_t xSemaphoreCreateMutex( void )
  1. 互斥锁的创建函数
    xSemaphoreCreateMutex是FreeRTOS中创建互斥锁的API,调用后返回互斥锁的句柄(创建失败则返回NULL)。

  2. 互斥锁的特性

    互斥锁与二值信号量功能相似(均用于资源互斥访问),但核心区别是实现了优先级继承机制

    • 当低优先级任务持有互斥锁时,若高优先级任务请求该锁,低优先级任务会临时继承高优先级任务的优先级,避免"优先级反转"问题(即高优先级任务因等待低优先级任务持有的资源而长时间阻塞)。
c 复制代码
//二值信号量代码实现
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "freertos/queue.h"
#include "esp_log.h"

SemaphoreHandle_t bin_sem;

void taskA(void* param)
{
   //释放信号量
   while(1)
   {
      xSemaphoreGive(bin_sem);
      vTaskDelay(pdMS_TO_TICKS(1000));
   }
}

void taskB(void* param)
{
   //等待信号量成功后,打印提示
   while(1)
   {
      if(pdTRUE == xSemaphoreTake(bin_sem,portMAX_DELAY))
      {
          ESP_LOGI("bin","task");
      }
   }
}

void app_main(void)
{
    bin_sem = xSemaphoreCreateBinary();

    xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
    xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}

3.事件组

  1. 事件位:用于指示事件是否发生,事件位通常称为事件标志。
  2. 事件组:是一组事件位,事件组中的事件位通过位编号来引用。
c 复制代码
//创建一个事件组,返回事件组句柄,失败返回NULL
EventGroupHandle_t xEventGroupCreate( void );
c 复制代码
//等待事件组中某个标志位,用返回值以确定哪些位已完成设置
EventBits_t xEventGroupWaitBits(
    EventGroupHandle_t xEventGroup,    //事件组句柄
    const EventBits_t uxBitsToWaitFor, //哪些位需要等待
    const BaseType_t xClearOnExit,     //是否自动清除标志位
    const BaseType_t xWaitForAllBits,  //是否等待的标志位都成功了才返回
    TickType_t xTicksToWait            //最大阻塞时间
);

这是FreeRTOS中事件组的核心等待函数,用于任务等待事件组中指定事件位的触发,参数含义如下:

  1. xEventGroup :目标事件组的句柄(需提前通过xEventGroupCreate创建)。
  2. uxBitsToWaitFor :需要等待的事件位(用位掩码表示,例如0x01表示等待第0位,0x03表示等待第0、1位)。
  3. xClearOnExit :是否在函数返回前自动清除已满足的事件位:
    • pdTRUE:自动清除等待的事件位;
    • pdFALSE:事件位保持当前状态。
  4. xWaitForAllBits :等待条件的类型:
    • pdTRUE:等待所有指定事件位都被置位才返回;
    • pdFALSE:等待任意一个指定事件位被置位就返回。
  5. xTicksToWait:最大阻塞时间(单位为系统节拍Tick),超时则返回当前已置位的事件位。
c 复制代码
//设置标志位
EventBits_t xEventGroupSetBits(
    EventGroupHandle_t xEventGroup,  //事件组句柄
    const EventBits_t uxBitsToSet    //设置哪个位
);

//清除标志位
EventBits_t xEventGroupClearBits(
    EventGroupHandle_t xEventGroup,  //事件组句柄
    const EventBits_t uxBitsToClear  //清除的标志位
);

功能与参数解读

这是FreeRTOS中事件组的置位/清除操作函数,用于控制事件组中事件位的状态:

  1. xEventGroupSetBits(设置标志位)

    • 作用:将事件组中指定的事件位置为"1"(触发事件);
    • 参数:
      • xEventGroup:目标事件组的句柄;
      • uxBitsToSet:要置位的事件位(位掩码,如0x02表示置位第1位);
    • 返回值:返回操作后事件组的当前位状态。
    • 场景:任务完成某个操作后,置位对应的事件位,通知等待该事件的任务。
  2. xEventGroupClearBits(清除标志位)

    • 作用:将事件组中指定的事件位置为"0"(重置事件);
    • 参数:
      • xEventGroup:目标事件组的句柄;
      • uxBitsToClear:要清除的事件位(位掩码,如0x03表示清除第0、1位);
    • 返回值:返回操作前事件组的位状态(因操作是原子性的,可用于判断清除前的状态)。
    • 场景:等待事件的任务处理完成后,手动清除已触发的事件位,以便后续再次等待。
c 复制代码
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "freertos/queue.h"
#include "esp_log.h"
#include "freertos/event_groups.h"

#define NUM1_BIT BIT0
#define NUM2_BIT BIT1
static EventGroupHandle_t test_event;

void taskA(void* param)
{
   //定时设置不同的事件位
   while(1)
   {
       xEventGroupSetBits(test_event,NUM1_BIT);
       vTaskDelay(pdMS_TO_TICKS(1000));
       xEventGroupSetBits(test_event,NUM2_BIT);
       vTaskDelay(pdMS_TO_TICKS(1000));
   }
}

void taskB(void * param)
{
   //等待事件位
   EventBits_t ev;
   while(1)
   {
       ev = xEventGroupWaitBits(test_event,NUM1_BIT|NUM2_BIT,pdTRUE,pdFALSE,pdMS_TO_TICKS(5000));
       if(ev & NUM1_BIT)
       {
          ESP_LOGI("ev","get BIT0 event");
       }
       if(ev & NUM2_BIT)
       {
          ESP_LOGI("ev","get BIT1 event");
       }
   }
}

void app_main(void)
{
    test_event = xEventGroupCreate();

    xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,3,NULL,1);
    xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1);
}

4.直达任务通知

直达任务通知的定义与核心特性

  1. 基础属性

    每个RTOS任务都包含一个任务通知数组,每条通知有"挂起/非挂起"的状态,同时附带32位通知值。

  2. 本质特点

    是直接发送给任务的事件,无需队列、事件组、信号量等中间对象中转;向任务发送直达任务通知时,会将目标任务的对应通知置为"挂起"状态(此"挂起"仅针对通知,并非挂起任务本身)。

1. 任务通知相关函数
c 复制代码
//用于将通知直接发送到RTOS任务并可能取消该任务的阻塞状态
BaseType_t xTaskNotify(
    TaskHandle_t xTaskToNotify,    //要通知的任务句柄
    uint32_t ulValue,              //携带的通知值
    eNotifyAction eAction          //执行的操作
);

//等待接收任务通知
BaseType_t xTaskNotifyWait(
    uint32_t ulBitsToClearOnEntry,  //进入函数清除的通知值位
    uint32_t ulBitsToClearOnExit,   //退出函数清除的通知值位
    uint32_t *pulNotificationValue, //通知值
    TickType_t xTicksToWait         //等待时长
);
2. eAction参数的操作说明
eAction设置 已执行的操作
eNoAction 目标任务接收事件,但其通知值未更新。这种情况下,不使用ulValue。
eSetBits 目标任务的通知值 使用ulValue 按位或运算
eIncrement 目标任务的通知值自增1(类似信号量的give操作)
eSetValueWithOverwrite 目标任务的通知值 无条件设置为ulValue。
eSetValueWithoutOrwrite 如果目标任务没有挂起的通知,则其通知值 将设置为ulValue。 如果目标任务已经有挂起的通知,则不会更新其通知值。

5.原生和esp-idf区别(freertos)

ESP-IDF中FreeRTOS的特殊适配与建议

一、ESP-IDF对FreeRTOS的修改点
  1. 优先级与多核:优先级机制在多核场景不适用(多任务可同时运行)。
  2. 自动创建的系统任务 :包含空闲、定时器、app_main、IPC、ESP定时器等任务。
  3. 内存堆管理:不使用原生FreeRTOS堆管理,实现了自定义堆。
  4. 任务创建 :需用xTaskCreatePinnedToCore()(指定运行核心)。
  5. 任务删除:避免删除另一个核心上的任务。
  6. 临界区同步:用自旋锁保证同步。
  7. 浮点运算任务:必须指定运行核心,不能由系统自动安排。
二、开发建议
  1. 任务核心指定 :应用开发创建任务时需指定内核,不建议用tskNO_AFFINITY(由系统自动分配)。
  2. 核心分工:无线网络相关任务(如WiFi、蓝牙)固定到CPU0(PRO_CPU);应用其余任务固定到CPU1(APP_CPU)。
相关推荐
brave and determined8 小时前
传感器学习(day11):MEMS摄像头:颠覆手机影像的未来
嵌入式硬件·智能手机·嵌入式开发·mems·电子设计·嵌入式应用·嵌入式设计
影阴8 小时前
stm32实现CAN通讯测试
stm32·单片机·嵌入式硬件·hal
Silicore_Emma8 小时前
芯谷科技—D2010:高效电机控制与保护的卓越之选
单片机·电机控制·工业自动化·电动工具调速·智能家电设备·绍兴芯谷·d2010
xiaohai@Linux9 小时前
基于 TCP 的IOT物联网云端服务端和设备客户端通信架构设计与实现
嵌入式硬件·物联网·网络协议·tcp/ip
创界工坊工作室9 小时前
DPJ-137 基于单片机的公交车自动报站系统设计(源代码+proteus仿真)
stm32·单片机·嵌入式硬件·51单片机·proteus
qs70169 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
疯狂的豆包9 小时前
ESP32与MAX98357:打造智能收音机的奇妙之旅
单片机
啃硬骨头9 小时前
Aurix TC387 Ethernet代码解析之六_MAC的LwIP初始化3
单片机·嵌入式硬件
福尔摩斯张9 小时前
【实战】C/C++ 实现 PC 热点(手动开启)+ 手机 UDP 自动发现 + TCP 通信全流程(超详细)
linux·c语言·c++·tcp/ip·算法·智能手机·udp