入门指南:了解ESP32 FreeRTOS的基础知识

了解 ESP32 FreeRTOS:初学者指南

  • ESP32 FreeRTOS是什么?
  • 如何使用FreeRTOS?
  • 哪些常用的函数?
  • xTaskCreate()
  • vTaskDelete()
  • vTaskDelay()
  • xTicksToDelay()
  • xSemaphoreCreateBinary()
  • xSemaphoreGive()
  • xSemaphore:要释放的信号量的句柄。
  • xSemaphore:要获取的信号量的句柄。
  • xQueueCreate()
  • xQueueSend()
  • xQueueReceive()
  • 简单示例:创建两个任务并打印任务名称
  • 使用队列示例
  • ESP32 FreeRTOS是什么?

    ESP32 FreeRTOS是针对ESP32微控制器的一个实时操作系统(RTOS),它采用了FreeRTOS内核,可以帮助开发人员在ESP32芯片上进行多任务处理。简单来说,FreeRTOS提供了一种方式来管理软件任务并协调它们的执行。

    ESP32是一个功能强大的嵌入式系统,可以用于构建各种物联网应用程序。其中,FreeRTOS是一个广泛使用的实时操作系统,它针对嵌入式系统的需求进行了优化。本文将介绍ESP32 FreeRTOS的基础知识,包括如何配置FreeRTOS内核、如何创建任务和使用消息队列进行任务通信。

    对于学习单片机的人,ESP32 FreeRTOS可以让他们更轻松地编写和维护复杂的程序,而无需手动跟踪和调度任务。此外,它还可以帮助学习者更好地理解实时系统设计和多任务处理概念,并使他们能够更好地应对未来的嵌入式系统开发挑战。

    如何使用FreeRTOS?

    1. 导入正确的FreeRTOS库

    2. 配置FreeRTOS内核,在编译器根据程序的内存分配堆栈大小、选择调度算法、启动任务通知等。

    3. 创建好任务后编写任务代码,可以使用FreeRTOS提供的API来管理任务和同步。

    4. 编译和调试

    哪些常用的函数?

    以下是对于ESP32使用FreeRTOS常用函数的详细介绍:

    xTaskCreate()

    函数原型:TaskHandle_t xTaskCreate(TaskFunction_t pvTaskCode, const char *const pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask)

    功能:创建一个新的任务,并将其加入到FreeRTOS任务调度器中。

    参数:

  • pvTaskCode:指向任务函数的指针。
  • pcName:任务名称。可选参数,可以为NULL。
  • usStackDepth:任务的堆栈大小。单位为字节。
  • pvParameters:传递给任务函数的参数。可选参数,可以为NULL。
  • uxPriority:任务的优先级。
  • pxCreatedTask:指向一个TaskHandle_t变量的指针。可选参数,如果不需要返回任务句柄,则可以为NULL。
  • 返回值:如果成功创建了任务,则返回任务句柄;否则返回NULL。

    vTaskDelete()

    函数原型:void vTaskDelete(TaskHandle_t xTaskToDelete)

    功能:删除指定的任务。

    参数:

  • xTaskToDelete:要删除的任务的句柄。
  • 返回值:无。

    vTaskDelay()

    函数原型:void vTaskDelay(const TickType_t xTicksToDelay)

    功能:延时指定的时间。

    参数:

    xTicksToDelay()

    延时的时间,以FreeRTOS系统滴答计数器的节拍数为单位。

    返回值:无。

    xSemaphoreCreateBinary()

    函数原型:SemaphoreHandle_t xSemaphoreCreateBinary(void)

    功能:创建一个二进制信号量。

    参数:无。

    返回值:如果成功创建了信号量,则返回信号量句柄;否则返回NULL。

    xSemaphoreGive()

    函数原型:BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)

    功能:释放一个二进制信号量或者计数信号量的资源。

    参数:

    xSemaphore:要释放的信号量的句柄。

    返回值:如果释放成功,则返回pdTRUE;否则返回pdFALSE。

    1. xSemaphoreTake()

    函数原型:BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)

    功能:获取一个二进制信号量或者计数信号量的资源。

    参数:

    xSemaphore:要获取的信号量的句柄。

  • xTicksToWait:等待获取信号量的超时时间,以FreeRTOS系统滴答计数器的节拍数为单位。如果该参数设置为0,则非阻塞地尝试获取信号量。
  • 返回值:如果成功获取了信号量,则返回pdTRUE;如果等待超时,则返回pdFALSE。

    xQueueCreate()

    函数原型:QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)

    功能:创建一个队列。

    参数:

  • uxQueueLength:队列的可容纳元素个数。
  • uxItemSize:队列中一个元素的大小(以字节为单位)。
  • 返回值:如果成功创建了队列,则返回队列的句柄;否则返回NULL。

    xQueueSend()

    函数原型:BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)

    功能:将消息发送到队列中。

    参数:

  • xQueue:要发送消息的队列的句柄。
  • pvItemToQueue:指向要发送的数据的指针。
  • xTicksToWait:等待发送消息的超时时间,以FreeRTOS系统滴答计数器的节拍数为单位。如果该参数设置为0,则非阻塞地尝试发送消息。
  • 返回值:如果成功发送了消息,则返回pdPASS;否则返回errQUEUE_FULL或者errQUEUE_BLOCKED(如果xTicksToWait大于0)。

    xQueueReceive()

    函数原型:BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)

    功能:从队列中接收消息。

    参数:

  • xQueue:要接收消息的队列的句柄。
  • pvBuffer:
  • 简单示例:创建两个任务并打印任务名称

    /*
    如何创建freertos任务
    如何分配内存、任务优先级
    创建任务后loop循环还能不能使用
    */
    #include <Arduino.h>
    #include <freertos/FreeRTOS.h>
    #include <freertos/task.h>
    
    
    #if CONFIG_FREERTOS_UNICORE
    #define ARDUINO_RUNNING_CORE 0
    #else
    #define ARDUINO_RUNNING_CORE 1
    #endif
    
    //创建任务函数
    void Task1(void *pvParameters);
    void Task2(void *pvParameters);
    
    
    void setup() {
      // put your setup code here, to run once:
      Serial.begin(115200);
      xTaskCreatePinnedToCore(
        Task1, "Task1"  // 任务名称
        ,
        1024  // 任务栈大小
        ,
        NULL  // 任务参数指针
        ,
        2  // 任务优先级大小 -- 值越大优先级越大
        ,
        NULL  // 任务句柄指针
        ,
        ARDUINO_RUNNING_CORE);  // 处理器核心编号
    
    
      xTaskCreatePinnedToCore(
        Task2, "Task2"  // 任务名称
        ,
        1024  // 任务栈大小
        ,
        NULL  // 任务参数指针
        ,
        1  // 任务优先级大小 -- 值越大优先级越大
        ,
        NULL  // 任务句柄指针
        ,
        ARDUINO_RUNNING_CORE);  // 处理器核心编号
    }
    
    
    void loop() {
      // 空闲?
      Serial.println("loop");
      delay(1000);
    }
    
    
    void Task1(void *pvParameters) {  // 任务1
      for (;;) {
        //
        Serial.println("task1");
        vTaskDelay(pdMS_TO_TICKS(1000));
        if (digitalRead(12) == HIGH) {
          Serial.println("GPIO12 is LOW. Deleting task...");
          vTaskDelete(NULL); // 删除当前任务
        }
      }
    }
    
    
    void Task2(void *pvParameters) {  // 任务2
      for (;;) {
        Serial.println("task2");
        vTaskDelay(pdMS_TO_TICKS(1000));
      }
    }
    

    部分代码理解:

    #if CONFIG_FREERTOS_UNICORE 
    #define ARDUINO_RUNNING_CORE 0 
    #else #define ARDUINO_RUNNING_CORE 1 
    #endif
    

    当FreeRTOS配置为单核模式时,ARDUINO_RUNNING_CORE宏被定义为0,表示应用程序在主核心上运行。而当FreeRTOS配置为双核模式时,ARDUINO_RUNNING_CORE宏被定义为1,表示应用程序在第二个核心上运行。

    在ESP32上,可以使用两个独立的处理器核心来运行应用程序和操作系统。在双核模式下,一个核心运行FreeRTOS调度程序,另一个核心则可用于运行用户应用程序。这种方式可以提高系统性能和响应速度。

    使用队列示例

    #include <Arduino.h>
    #include "freertos/queue.h"
    
    // 定义结构体类型
    typedef struct {
      int id;
      char name[20];
    } data_t;
    
    
    // 定义队列句柄和队列长度
    QueueHandle_t queue;
    const int queueLen = 10;
    
    
    void task1(void *pvParameters) {
      // 定义结构体变量并初始化
      data_t data = {1, "John"};
    
      while (1) {
        // 将结构体数据发送到队列中
        data.id++;
        xQueueSend(queue, &data, portMAX_DELAY);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
      }
    }
    
    void task2(void *pvParameters) {
      while (1) {
        // 从队列中接收数据
        data_t data;
        xQueueReceive(queue, &data, portMAX_DELAY);
    
        // 打印接收到的数据
        Serial.printf("ID: %d, Name: %s\n", data.id, data.name);
      }
    }
    
    void setup() {
      Serial.begin(115200);
      // 创建队列
      queue = xQueueCreate(queueLen, sizeof(data_t));
      // 创建两个任务
      xTaskCreate(task1, "Task 1", 10000, NULL, 1, NULL);
      xTaskCreate(task2, "Task 2", 10000, NULL, 2, NULL);
    }
    
    void loop() {
    }
    

    使用队列的步骤如下:

    1. 使用xQueueCreate()函数创建一个队列,并指定队列大小和元素大小。
    2. 在生产者任务中,使用xQueueSend()函数将数据加入到队列中。
    3. 在消费者任务中,使用xQueueReceive()函数从队列中获取数据。
    4. 可以使用uxQueueMessagesWaiting()函数查看队列中当前的消息数目,以及使用uxQueueSpacesAvailable()函数查看队列中剩余空间的数量。

    xQueueSend()函数用于将数据加入到队列中,其参数包括队列句柄、要发送的数据指针和等待时间。下面是这个函数的详细说明:

  • myQueue:队列句柄,由xQueueCreate()函数返回。

  • &data:要发送的数据的指针。此处使用&符号获取数据变量的地址,并将其传递给xQueueSend()函数。

  • portMAX_DELAY:等待时间,表示如果队列已满,则一直阻塞任务,直到队列有空间可用。也可以指定一个数值,表示最多等待多少个系统时钟节拍,例如500表示最多等待500个时钟节拍后退出等待。

  • 需要注意的是,在向队列中添加数据时,通常需要检查xQueueSend()函数返回的值,以确定数据是否成功加入到队列中。如果队列已满,则xQueueSend()函数将返回errQUEUE_FULL错误代码。如果队列操作成功,则返回pdPASS代码。可以根据返回值采取相应的处理措施,以确保程序的正确性。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 入门指南:了解ESP32 FreeRTOS的基础知识

    发表评论