Skip to content

Concepts First

  • RTOS (Real-Time Operating System): an OS that lets multiple "tasks" run in turns; each task is a loop function, and the scheduler decides which one runs based on priority.
  • Task: created with xTaskCreate; each has its own stack. Consumers, producers, and the test process in this example are tasks.
  • Queue: a mailbox for passing data between tasks; xQueueSend puts data in, xQueueReceive takes it out, first-in first-out.
  • Semaphore: a "token" for mutual exclusion or synchronization. A binary semaphore (xSemaphoreCreateBinary) is taken/given once; this example uses it to protect queue access.
  • Mutex: allows only one task at a time into a critical section. Two tasks here protect a shared counter and demonstrate priority behavior around the lock.
  • Preemptive scheduling: a ready high-priority task preempts lower-priority tasks; the example creates one to demonstrate this.

Example Overview

This page is based on the freertos example in the official Bouffalo SDK (examples/freertos), an all-in-one FreeRTOS demo:

  • PHASE 1 Queue & Semaphore: creates a 5-slot queue and a binary semaphore; one producer sends a message every 500 ms, two consumers race to receive and print it (task number + timestamp);
  • PHASE 2 Priority & Mutex: creates a high-priority task (500,000 empty loop iterations) and two shared-resource tasks of different priorities; a mutex protects the shared counter so you can observe preemption and mutual exclusion;
  • After all tests, it prints All Test Complete. and suspends the scheduler in a dead loop.
  • Sibling example: examples/posix/freertos_posix (POSIX compatibility layer, see "POSIX Interface" in this section).

Note

Wireless examples (Wi-Fi, MQTT, etc.) all run on FreeRTOS (vTaskStartScheduler starts the scheduler). Understanding tasks/queues/semaphores here makes those examples much easier to follow.

Operation Steps

1
Enter the Example Directory

Open a terminal and enter the SDK FreeRTOS example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):

cd examples/freertos
2
Build the Project

Run the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:

make CHIP=bl616 BOARD=bl616dk
3
Flash the Firmware

Connect the board with a USB cable, hold the BOOT button (IO2 on the Ai-M61-32S-Kit), briefly press EN/RST to enter download mode, then flash (replace the serial port with the one on your computer):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
Run and Verify

Open a serial tool (baud rate 2000000) and watch the three test phases: PHASE 1 (queue + semaphore: 2 consumers + 1 producer exchanging messages), PHASE 2 (priority + mutex: a high-priority task preempts, two tasks access a shared counter exclusively), ending with All Test Complete..

Code Execution Flow

The flow from boot to completing all tests is:

APIs Used by the Example

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

Creates and registers a task. The example uses it for the test process, consumers, producer, high-priority task, and shared-resource tasks.

Parameters:

  • task: task function pointer
  • name: task name (for debugging)
  • stack: stack size in words (512 here)
  • arg: argument passed to the task (task ID here)
  • prio: priority (configMAX_PRIORITIES - 1 is highest)
  • handle: returned task handle

Returns: pdPASS (1) on success; errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY when memory is insufficient

xQueueCreate / xQueueSend / xQueueReceive(...)

Creates a queue and sends/receives messages. The queue has 5 slots of struct Message (task ID + timestamp).

Parameters:

  • uxQueueLength / uxItemSize: number of slots and per-message size
  • xItem: pointer to the message to send/receive
  • xTicksToWait: blocking time; 0 means don't wait

Returns: pdPASS (1) on success; errQUEUE_FULL/errQUEUE_EMPTY on failure

xSemaphoreCreateBinary / xSemaphoreTake / xSemaphoreGive(...)

Creates a binary semaphore and takes/gives it. The example gives it once at startup so it is available; producer and consumer take/give around queue access for mutual exclusion.

Parameters:

  • xSemaphore: semaphore handle
  • xBlockTime: blocking time; portMAX_DELAY waits forever

Returns: pdTRUE/pdPASS on success

xSemaphoreCreateMutex / vTaskStartScheduler(...)

xSemaphoreCreateMutex creates the mutex; shared-resource tasks take/give it around shared_counter. vTaskStartScheduler starts the scheduler and never returns.

Parameters:

  • No parameters for either

Returns: mutex handle; the scheduler never returns

Complete Code

The following is the complete source of freertos/main.c, identical to the official example, collapsed by default:

📜 Click to expand freertos/main.c full code
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

Why does the serial log look out of order?

That's normal. Multiple tasks print concurrently and interrupt each other's output — a direct illustration of concurrency. The [Consumer 1]/[Producer 1] tags tell you which task printed each line.

Which runs first, the high-priority task or the shared-resource tasks?

The high-priority task (configMAX_PRIORITIES - 1) preempts as soon as it becomes ready, so High priority task running appears first. In the mutex test, two tasks contend for one lock; while one holds it, the other waits (you see the value incremented by the same task repeatedly).

What is `configMAX_PRIORITIES`?

It is defined by the project config (in this example's FreeRTOSConfig.h). main asserts configMAX_PRIORITIES > 4 to keep the task priorities valid. Change the macro and rebuild to change the priority ceiling.

Can this example run on other chips?

Yes — the SDK README lists BL602/BL702/BL616/BL618 and more. Use the matching CHIP/BOARD when building; this page targets Ai-M6x (bl616).

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