Skip to content

概念先知道

  • RTOS(实时操作系统):让多个“任务”轮流跑的操作系统,每个任务是一个死循环函数,由调度器按优先级决定谁先运行。
  • 任务(Task):用 xTaskCreate 创建,有自己的栈;例程里消费者、生产者、测试进程都是任务。
  • 队列(Queue):任务间传递数据的“信箱”,xQueueSend 放入、xQueueReceive 取出,先进先出。
  • 信号量(Semaphore):用来互斥或同步的“令牌”。二值信号量 xSemaphoreCreateBinary 拿(Take)/放(Give)各一次,例程用它保护队列读写。
  • 互斥锁(Mutex):同一时刻只允许一个任务进入临界区。例程里两个任务用它保护共享计数器,并演示高优先级任务让出后优先级的恢复。
  • 优先级抢占:高优先级任务就绪会打断低优先级任务;例程专门建了一个高优先级任务演示这一行为。

例程功能简介

本页对应博流官方 SDK 的 freertos 例程(examples/freertos),是学习 FreeRTOS 的“多合一”演示:

  • PHASE 1 队列与信号量:创建 5 格队列、二值信号量;1 个生产者每 500ms 发一条消息,2 个消费者竞争接收并打印(task 号 + 时间戳);
  • PHASE 2 优先级与互斥锁:创建高优先级任务(纯计算 50 万次空循环)和两个不同优先级的共享资源任务,用互斥锁保护共享计数器,观察“高优先级任务先跑、互斥锁持有期间低优先级无法进入”;
  • 所有任务测试完毕打印 All Test Complete.,随后挂起调度器进入死循环。
  • 同族例程examples/posix/freertos_posix(POSIX 兼容层,见本栏目的「POSIX 接口」页)。

提示

Wi-Fi、MQTT 等无线例程都跑在 FreeRTOS 上(vTaskStartScheduler 启动调度器),先理解本页的任务/队列/信号量,再看无线例程会轻松很多。

操作步骤

1
进入例程目录

在终端进入 SDK 的 FreeRTOS 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/freertos
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000),观察三个阶段日志:PHASE 1(队列+信号量:2 个消费者 + 1 个生产者收发消息)、PHASE 2(优先级+互斥锁:高优先级任务抢占、两个任务互斥访问共享变量)、最后打印 All Test Complete.

代码执行流程

例程从启动到完成全部测试的流程如下:

例程调用的 API 介绍

xTaskCreate(task, name, stack, arg, prio, handle)

创建并注册一个任务。例程用它创建测试进程、消费者、生产者、高优先级任务与共享资源任务。

参数

  • task:任务函数指针
  • name:任务名(调试用)
  • stack:栈大小(字,例程 512)
  • arg:传给任务的参数(例程传任务编号)
  • prio:优先级(configMAX_PRIORITIES - 1 最高)
  • handle:返回任务句柄

返回值pdPASS(1)成功;errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 内存不足

xQueueCreate / xQueueSend / xQueueReceive(...)

创建队列、发送/接收消息。例程队列长度 5、每条消息是 struct Message(任务号 + 时间戳)。

参数

  • uxQueueLength / uxItemSize:队列格数与每条消息大小
  • xItem:要发送/接收的消息指针
  • xTicksToWait:阻塞等待时间,0 表示不等待

返回值pdPASS(1)成功;errQUEUE_FULL/errQUEUE_EMPTY 失败

xSemaphoreCreateBinary / xSemaphoreTake / xSemaphoreGive(...)

创建二值信号量并执行拿/放。例程先 Give 一次让信号量可用,之后生产者、消费者在读写队列前后 Take/Give 实现互斥。

参数

  • xSemaphore:信号量句柄
  • xBlockTime:阻塞时间,portMAX_DELAY 表示一直等

返回值pdTRUE/pdPASS 成功

xSemaphoreCreateMutex / vTaskStartScheduler(...)

xSemaphoreCreateMutex 创建互斥锁,共享资源任务在修改 shared_counter 前后 Take/Give;vTaskStartScheduler 启动调度器,从此不再返回。

参数

  • xSemaphoreCreateMutex 无参数
  • vTaskStartScheduler 无参数

返回值:互斥锁句柄;调度器永不返回

完整代码

以下为 freertos/main.c 完整源码,与官方示例一致,默认折叠,点击展开:

📜 点击展开 freertos/main.c 完整代码
c
#include <FreeRTOS.h>
#include "semphr.h"
#include "board.h"

#define DBG_TAG "MAIN"
#define QUEUE_LENGTH 5
#define QUEUE_SIZE sizeof(struct Message)
#include "log.h"

typedef enum {
    Que_Sem_Test,
    Priority_Mutex_Test,
    End
} TestProcedure_t;
TestProcedure_t phase;

struct Message {
    uint8_t task;
    uint32_t timestamp;
};

QueueHandle_t xqueue;
SemaphoreHandle_t xsemaphore;
SemaphoreHandle_t xMutex;
static volatile uint16_t shared_counter = 0;

static TaskHandle_t TestProcess_handle;
static TaskHandle_t consumer_handle_1;
static TaskHandle_t consumer_handle_2;
static TaskHandle_t producer_handle;
static TaskHandle_t ht_task_handle;
static TaskHandle_t sharetask_handle_1;
static TaskHandle_t sharetask_handle_2;

/*******************Test Queue,Semaphore************************/
static void consumer_task(void *pvParameters) {
    struct Message msg;
    int consumer_id = (int)pvParameters;

    LOG_I("[Consumer %d]: task started\r\n", consumer_id);
    vTaskDelay(pdMS_TO_TICKS(1000));

    while (1) {
        if (phase != Que_Sem_Test) {
            LOG_I("[Consumer %d]: Exiting due to phase change\r\n", consumer_id);
            vTaskDelete(NULL);
        }

        if (xSemaphoreTake(xsemaphore, portMAX_DELAY)) {
            if (xQueueReceive(xqueue, &msg, 0) == pdPASS) {
                LOG_I("[Consumer %d]: received msg (task:%d, ts:%lu)\r\n",
                      consumer_id, msg.task, (unsigned long)msg.timestamp);
            } else {
                LOG_I("[Consumer %d]: queue empty\r\n", consumer_id);
            }
            xSemaphoreGive(xsemaphore);
        }
        vTaskDelay(pdMS_TO_TICKS(500));
    }
    vTaskDelete(NULL);
}

static void producer_task(void *pvParameters) {
    struct Message msg;
    int producer_id = (int)pvParameters;

    msg.task = producer_id;

    while (1) {
        if (phase != Que_Sem_Test) {
            LOG_I("[Producer %d]: Exiting due to phase change\r\n", producer_id);
            vTaskDelete(NULL);
        }

        msg.timestamp = xTaskGetTickCount();
        if (xSemaphoreTake(xsemaphore, portMAX_DELAY)) {
            if (xQueueSend(xqueue, &msg, 0) != pdPASS) {
                LOG_I("[Producer %d]: queue full\r\n", producer_id);
            }else
            {
                LOG_I("[Producer %d]: message send success\r\n", producer_id);
            }

            xSemaphoreGive(xsemaphore);
        }
        vTaskDelay(pdMS_TO_TICKS(500));
    }
    vTaskDelete(NULL);
}

/*******************Test Mutex************************/
static void SharedResourceTask(void *pvParameters) {
    int task_id = (int)pvParameters;
    while(1) {
        if (phase != Priority_Mutex_Test) {
            LOG_I("[Mutex Task %d]: Exiting due to phase change\r\n", task_id);
            vTaskDelete(NULL);
        }

        LOG_I("[Mutex Task %d]: Attempt to acquire mutex\r\n", task_id);
        if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
            uint32_t temp = shared_counter;
            shared_counter = temp + 1;
            LOG_I("[Mutex Task %d]: Aquired.\r\n",task_id);
            LOG_I("[Mutex Task %d]: value = %d\r\n", task_id, shared_counter);
            LOG_I("[Mutex Task %d]: Holding mutex for 500ms\r\n", task_id);
            vTaskDelay(pdMS_TO_TICKS(500));
            LOG_I("[Mutex Task %d]: Release mutex\r\n", task_id);
            xSemaphoreGive(xMutex);

        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

/****************Test Preemptive scheduling*******************/
static void HighPriorityTask(void* pvParameters) {
    if (phase != Priority_Mutex_Test) {
        LOG_I("[High Priority Task]: Exiting due to phase change\r\n");
        vTaskDelete(NULL);
    }

    vTaskDelay(pdMS_TO_TICKS(2000));
    LOG_I("[High Priority Task]: High priority task running\r\n"); // Correct spelling
    for (size_t i = 0; i < 500000; i++)
    {
    }

    LOG_I("[High Priority Task]: Completed\r\n");
    vTaskDelete(NULL);
}

static void TestProcessTask(void * pvParameters) {

    LOG_I("[Process Task]: Test Start\r\n");


    LOG_I("\n===== PHASE 1: Queue/Semaphore Test =====\r\n");
    phase = Que_Sem_Test;

    LOG_I("[Process Task]: Starting consumer tasks...\r\n");
    xTaskCreate(consumer_task, "cons1", 512, (void *)1, configMAX_PRIORITIES - 2, &consumer_handle_1);
    xTaskCreate(consumer_task, "cons2", 512, (void *)2, configMAX_PRIORITIES - 2, &consumer_handle_2);

    LOG_I("[Process Task]: Starting producer task...\r\n");
    xTaskCreate(producer_task, "prod1", 512, (void *)1, configMAX_PRIORITIES - 3, &producer_handle);

    vTaskDelay(pdMS_TO_TICKS(5000));    // Run 5s
    LOG_I("\n===== END PHASE 1 =====\r\n");


    LOG_I("\n===== PHASE 2: Priority/Mutex Test =====\r\n");
    phase = Priority_Mutex_Test;
    vTaskDelay(pdMS_TO_TICKS(200));     // ensure task already exit

    LOG_I("[Process Task] Starting High Priority task...\r\n");
    xTaskCreate(HighPriorityTask, "ht1", 512, NULL, configMAX_PRIORITIES - 1, &ht_task_handle);

    LOG_I("[Process Task] Starting Shared Resource tasks...\r\n");
    // Create different priority task to test priority convert
    xTaskCreate(SharedResourceTask, "mutex_high", 512, (void *)3, configMAX_PRIORITIES - 1, &sharetask_handle_1);
    xTaskCreate(SharedResourceTask, "mutex_low", 512, (void *)4, configMAX_PRIORITIES - 3, &sharetask_handle_2);

    vTaskDelay(pdMS_TO_TICKS(5000));    // Run 5s
    LOG_I("\n===== END PHASE 2 =====\r\n");

    // All Complete
    phase = End;
    vTaskDelay(pdMS_TO_TICKS(1000));    // Wait for exit

    LOG_I("[Process Task] All Test Complete.\r\n");

    // Stop all task
    vTaskSuspendAll();
    for(;;);
}

int main(void)
{
    board_init();
    configASSERT((configMAX_PRIORITIES > 4));

    /* Init source */
    xqueue = xQueueCreate(QUEUE_LENGTH, QUEUE_SIZE);
    xsemaphore = xSemaphoreCreateBinary();
    xMutex = xSemaphoreCreateMutex();

    if (xqueue == NULL || xsemaphore == NULL || xMutex == NULL) {
        LOG_E("Resource creation failed! Halting...\r\n");
        while(1);
    }
    xSemaphoreGive(xsemaphore);

    /* Process Task Create */
    xTaskCreate(TestProcessTask, "TestProc", 512, NULL, configMAX_PRIORITIES - 1, &TestProcess_handle);

    vTaskStartScheduler();

    while (1);
}

FAQ

为什么串口日志顺序看起来乱?

正常。多个任务并发打印,串口输出会被打断;这正是多任务并发的直观体现。日志行首的 [Consumer 1]/[Producer 1] 等标签可以帮助对应到具体任务。

高优先级任务和共享资源任务谁先跑?

高优先级任务(configMAX_PRIORITIES - 1)就绪后立即抢占,所以日志里先出现 High priority task running;互斥锁测试里两个任务同抢一把锁,持锁期间另一个只能等待(日志表现为 value 连续由同一任务 +1)。

`configMAX_PRIORITIES` 是多少?

由工程配置决定(例程 FreeRTOSConfig.h 中定义),main 开头用 configASSERT((configMAX_PRIORITIES > 4)) 保证任务优先级设置有效。想改优先级上限,改这个宏后重新编译。

这个例程能在其他芯片上跑吗?

SDK README 标注支持 BL602/BL702/BL616/BL618 等多款芯片,编译时把 CHIP/BOARD 换成对应芯片即可;本页按 Ai-M6x(bl616)编写。

Released under the MIT License. Build Time 2026-09-11 14:52:23