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;
xQueueSendputs data in,xQueueReceivetakes 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 (
tasknumber + 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
Open a terminal and enter the SDK FreeRTOS example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):
cd examples/freertosRun the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:
make CHIP=bl616 BOARD=bl616dkConnect 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/ttyUSB0Open 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 pointername: task name (for debugging)stack: stack size in words (512 here)arg: argument passed to the task (task ID here)prio: priority (configMAX_PRIORITIES - 1is 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 sizexItem: pointer to the message to send/receivexTicksToWait: blocking time;0means 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 handlexBlockTime: blocking time;portMAX_DELAYwaits 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
#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).

