Multi-Tasking with OSAL¶
This tutorial teaches you how to create multi-tasking applications using the Nexus OSAL (OS Abstraction Layer). You’ll learn how to create tasks, use synchronization primitives, and build concurrent applications.
Learning Objectives¶
By the end of this tutorial, you will:
Understand RTOS concepts and task scheduling
Create and manage multiple tasks
Use mutexes for resource protection
Use semaphores for task synchronization
Use message queues for inter-task communication
Implement producer-consumer patterns
Prerequisites¶
Completed Your First Nexus Application, GPIO Control Tutorial, and UART Communication Tutorial tutorials
STM32F4 Discovery board with FreeRTOS support
Understanding of concurrent programming concepts
OSAL Overview¶
The Nexus OSAL provides a portable interface to RTOS features:
Tasks: Independent threads of execution
Mutexes: Mutual exclusion for protecting shared resources
Semaphores: Signaling between tasks
Queues: Message passing between tasks
Timers: Software timers for periodic operations
The OSAL abstracts the underlying RTOS (FreeRTOS, RT-Thread, etc.), allowing your code to be portable.
Part 1: Creating Your First Task¶
Task Creation Workflow¶
The following diagram shows the workflow for creating and managing tasks:
flowchart TD
START([Start]) --> INIT_HAL[Initialize HAL]
INIT_HAL --> INIT_GPIO[Configure GPIO]
INIT_GPIO --> INIT_OSAL[Initialize OSAL]
INIT_OSAL --> CHECK_OSAL{OSAL Init OK?}
CHECK_OSAL -->|No| ERROR[Error Handler]
ERROR --> HALT[Halt System]
CHECK_OSAL -->|Yes| CREATE_TASK[Create Task]
CREATE_TASK --> CHECK_TASK{Task Created?}
CHECK_TASK -->|No| ERROR
CHECK_TASK -->|Yes| START_SCHED[Start OSAL Scheduler]
START_SCHED --> TASK_RUN[Task Running]
TASK_RUN --> TASK_WORK[Execute Task Code]
TASK_WORK --> TASK_DELAY[Task Delay]
TASK_DELAY --> TASK_RUN
style START fill:#e1f5ff
style INIT_HAL fill:#fff4e1
style INIT_OSAL fill:#ffe1f5
style CREATE_TASK fill:#e1ffe1
style START_SCHED fill:#f5e1ff
style TASK_RUN fill:#ffe1e1
style ERROR fill:#ffcccc
Basic Task Creation¶
#include "hal/hal.h"
#include "osal/osal.h"
/**
* \brief LED blink task
* \param[in] arg: Task argument (unused)
*/
static void led_task(void* arg) {
(void)arg; /* Unused */
while (1) {
/* Toggle LED */
hal_gpio_toggle(HAL_GPIO_PORT_D, 12);
/* Delay 500ms */
osal_task_delay(500);
}
}
int main(void) {
/* Initialize HAL */
hal_init();
/* Initialize LED */
hal_gpio_config_t led_config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_D, 12, &led_config);
/* Initialize OSAL */
if (osal_init() != OSAL_OK) {
while (1) { /* Error */ }
}
/* Create task */
osal_task_config_t task_config = {
.name = "LED",
.func = led_task,
.arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL,
.stack_size = 512
};
osal_task_handle_t task_handle;
if (osal_task_create(&task_config, &task_handle) != OSAL_OK) {
while (1) { /* Error */ }
}
/* Start OSAL scheduler - this function does not return */
osal_start();
return 0;
}
Key Points:
osal_init()initializes the OSAL subsystemosal_task_create()creates a new taskosal_start()starts the scheduler (never returns)osal_task_delay()suspends the task for a specified time
Task Priorities¶
OSAL supports multiple priority levels:
OSAL_TASK_PRIORITY_IDLE: Lowest priority (background tasks)OSAL_TASK_PRIORITY_LOW: Low priorityOSAL_TASK_PRIORITY_NORMAL: Normal priority (default)OSAL_TASK_PRIORITY_HIGH: High priorityOSAL_TASK_PRIORITY_REALTIME: Highest priority (time-critical tasks)
Higher priority tasks preempt lower priority tasks.
Part 2: Multiple Tasks¶
Creating Multiple Tasks¶
#define TASK_STACK_SIZE 1024
/**
* \brief Green LED task
*/
static void green_led_task(void* arg) {
(void)arg;
while (1) {
hal_gpio_toggle(HAL_GPIO_PORT_D, 12); /* Green */
osal_task_delay(500);
}
}
/**
* \brief Orange LED task
*/
static void orange_led_task(void* arg) {
(void)arg;
while (1) {
hal_gpio_toggle(HAL_GPIO_PORT_D, 13); /* Orange */
osal_task_delay(300);
}
}
/**
* \brief Red LED task
*/
static void red_led_task(void* arg) {
(void)arg;
while (1) {
hal_gpio_toggle(HAL_GPIO_PORT_D, 14); /* Red */
osal_task_delay(700);
}
}
int main(void) {
hal_init();
/* Initialize all LEDs */
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_D, 12, &config);
hal_gpio_init(HAL_GPIO_PORT_D, 13, &config);
hal_gpio_init(HAL_GPIO_PORT_D, 14, &config);
/* Initialize OSAL */
osal_init();
/* Create tasks */
osal_task_config_t task_configs[] = {
{.name = "Green", .func = green_led_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = TASK_STACK_SIZE},
{.name = "Orange", .func = orange_led_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = TASK_STACK_SIZE},
{.name = "Red", .func = red_led_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = TASK_STACK_SIZE}
};
for (size_t i = 0; i < 3; i++) {
osal_task_handle_t handle;
osal_task_create(&task_configs[i], &handle);
}
/* Start scheduler */
osal_start();
return 0;
}
Result: All three LEDs blink independently at different rates.
Part 3: Mutexes for Resource Protection¶
The Solution: Mutexes¶
static uint32_t shared_counter = 0;
static osal_mutex_handle_t counter_mutex;
/**
* \brief Increment counter safely
*/
static void increment_counter(void) {
/* Lock mutex */
if (osal_mutex_lock(counter_mutex, 1000) == OSAL_OK) {
/* Critical section - only one task can be here */
shared_counter++;
/* Unlock mutex */
osal_mutex_unlock(counter_mutex);
}
}
static void task1(void* arg) {
while (1) {
increment_counter();
osal_task_delay(10);
}
}
static void task2(void* arg) {
while (1) {
increment_counter();
osal_task_delay(10);
}
}
int main(void) {
hal_init();
osal_init();
/* Create mutex */
if (osal_mutex_create(&counter_mutex) != OSAL_OK) {
while (1) { /* Error */ }
}
/* Create tasks */
osal_task_config_t config1 = {
.name = "Task1", .func = task1, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = 1024
};
osal_task_config_t config2 = {
.name = "Task2", .func = task2, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = 1024
};
osal_task_handle_t h1, h2;
osal_task_create(&config1, &h1);
osal_task_create(&config2, &h2);
osal_start();
return 0;
}
Key Points:
Always lock mutex before accessing shared resource
Always unlock mutex after accessing shared resource
Use timeout to avoid deadlocks
Keep critical sections short
Part 4: Semaphores for Synchronization¶
Binary Semaphores¶
Use binary semaphores for signaling between tasks:
static osal_sem_handle_t button_sem;
/**
* \brief Button monitoring task
*/
static void button_task(void* arg) {
(void)arg;
while (1) {
/* Check button state */
if (hal_gpio_read(HAL_GPIO_PORT_A, 0) == HAL_GPIO_LEVEL_HIGH) {
/* Button pressed - signal LED task */
osal_sem_give(button_sem);
/* Wait for button release */
while (hal_gpio_read(HAL_GPIO_PORT_A, 0) == HAL_GPIO_LEVEL_HIGH) {
osal_task_delay(10);
}
}
osal_task_delay(10);
}
}
/**
* \brief LED control task
*/
static void led_control_task(void* arg) {
(void)arg;
while (1) {
/* Wait for button press signal */
if (osal_sem_take(button_sem, OSAL_WAIT_FOREVER) == OSAL_OK) {
/* Toggle LED */
hal_gpio_toggle(HAL_GPIO_PORT_D, 12);
}
}
}
int main(void) {
hal_init();
/* Initialize button and LED */
hal_gpio_config_t btn_config = {
.direction = HAL_GPIO_DIR_INPUT,
.pull = HAL_GPIO_PULL_DOWN,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_A, 0, &btn_config);
hal_gpio_config_t led_config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_D, 12, &led_config);
osal_init();
/* Create binary semaphore */
if (osal_sem_create_binary(&button_sem) != OSAL_OK) {
while (1) { /* Error */ }
}
/* Create tasks */
osal_task_config_t btn_task_config = {
.name = "Button", .func = button_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_HIGH, .stack_size = 1024
};
osal_task_config_t led_task_config = {
.name = "LED", .func = led_control_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = 1024
};
osal_task_handle_t h1, h2;
osal_task_create(&btn_task_config, &h1);
osal_task_create(&led_task_config, &h2);
osal_start();
return 0;
}
Counting Semaphores¶
Use counting semaphores to track resource availability:
#define MAX_RESOURCES 5
static osal_sem_handle_t resource_sem;
/**
* \brief Acquire resource
* \return true if acquired, false if timeout
*/
static bool acquire_resource(uint32_t timeout_ms) {
return (osal_sem_take(resource_sem, timeout_ms) == OSAL_OK);
}
/**
* \brief Release resource
*/
static void release_resource(void) {
osal_sem_give(resource_sem);
}
int main(void) {
hal_init();
osal_init();
/* Create counting semaphore with 5 resources */
if (osal_sem_create_counting(MAX_RESOURCES, MAX_RESOURCES, &resource_sem) != OSAL_OK) {
while (1) { /* Error */ }
}
/* Tasks can now acquire/release resources */
/* ... */
osal_start();
return 0;
}
Part 5: Message Queues¶
Producer-Consumer Pattern¶
/**
* \brief Sensor data structure
*/
typedef struct {
uint32_t timestamp;
int32_t temperature;
int32_t humidity;
} sensor_data_t;
#define QUEUE_SIZE 10
static osal_queue_handle_t sensor_queue;
/**
* \brief Producer task - reads sensors
*/
static void producer_task(void* arg) {
(void)arg;
uint32_t sample_count = 0;
while (1) {
/* Read sensor data (simulated) */
sensor_data_t data = {
.timestamp = hal_get_tick(),
.temperature = 25 + (sample_count % 10),
.humidity = 60 + (sample_count % 20)
};
/* Send to queue */
if (osal_queue_send(sensor_queue, &data, 100) == OSAL_OK) {
/* Success */
sample_count++;
} else {
/* Queue full */
hal_gpio_write(HAL_GPIO_PORT_D, 14, HAL_GPIO_LEVEL_HIGH); /* Red LED */
}
/* Sample every 1 second */
osal_task_delay(1000);
}
}
/**
* \brief Consumer task - processes data
*/
static void consumer_task(void* arg) {
(void)arg;
sensor_data_t data;
while (1) {
/* Receive from queue */
if (osal_queue_receive(sensor_queue, &data, OSAL_WAIT_FOREVER) == OSAL_OK) {
/* Process data */
/* In real application, would log or transmit data */
/* Toggle LED to show activity */
hal_gpio_toggle(HAL_GPIO_PORT_D, 12); /* Green LED */
/* Clear error LED */
hal_gpio_write(HAL_GPIO_PORT_D, 14, HAL_GPIO_LEVEL_LOW);
}
}
}
int main(void) {
hal_init();
/* Initialize LEDs */
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_D, 12, &config);
hal_gpio_init(HAL_GPIO_PORT_D, 14, &config);
osal_init();
/* Create queue */
if (osal_queue_create(sizeof(sensor_data_t), QUEUE_SIZE, &sensor_queue) != OSAL_OK) {
while (1) { /* Error */ }
}
/* Create tasks */
osal_task_config_t producer_config = {
.name = "Producer", .func = producer_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = 1024
};
osal_task_config_t consumer_config = {
.name = "Consumer", .func = consumer_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_HIGH, .stack_size = 1024
};
osal_task_handle_t h1, h2;
osal_task_create(&producer_config, &h1);
osal_task_create(&consumer_config, &h2);
osal_start();
return 0;
}
Part 6: Complete Example¶
Here’s a complete multi-tasking application:
/**
* \file multitask_demo.c
* \brief Complete OSAL Multi-Tasking Demo
*/
#include "hal/hal.h"
#include "osal/osal.h"
/*-----------------------------------------------------------------------*/
/* Configuration */
/*-----------------------------------------------------------------------*/
#define TASK_STACK_SIZE 1024
#define SENSOR_QUEUE_SIZE 10
/*-----------------------------------------------------------------------*/
/* Data Structures */
/*-----------------------------------------------------------------------*/
typedef struct {
uint32_t timestamp;
uint32_t sensor_id;
int32_t value;
} sensor_msg_t;
typedef struct {
uint32_t samples_produced;
uint32_t samples_consumed;
uint32_t queue_overflows;
} stats_t;
/*-----------------------------------------------------------------------*/
/* Global Variables */
/*-----------------------------------------------------------------------*/
static osal_queue_handle_t g_sensor_queue;
static osal_mutex_handle_t g_stats_mutex;
static osal_sem_handle_t g_data_ready_sem;
static stats_t g_stats = {0};
/*-----------------------------------------------------------------------*/
/* Helper Functions */
/*-----------------------------------------------------------------------*/
static void update_stats(int produced, int consumed, int overflow) {
if (osal_mutex_lock(g_stats_mutex, 100) == OSAL_OK) {
g_stats.samples_produced += produced;
g_stats.samples_consumed += consumed;
g_stats.queue_overflows += overflow;
osal_mutex_unlock(g_stats_mutex);
}
}
/*-----------------------------------------------------------------------*/
/* Tasks */
/*-----------------------------------------------------------------------*/
static void producer_task(void* arg) {
(void)arg;
uint32_t sensor_id = 0;
while (1) {
sensor_msg_t msg = {
.timestamp = hal_get_tick(),
.sensor_id = sensor_id,
.value = (int32_t)(hal_get_tick() % 1000)
};
if (osal_queue_send(g_sensor_queue, &msg, 10) == OSAL_OK) {
osal_sem_give(g_data_ready_sem);
update_stats(1, 0, 0);
} else {
update_stats(0, 0, 1);
hal_gpio_write(HAL_GPIO_PORT_D, 14, HAL_GPIO_LEVEL_HIGH);
}
sensor_id = (sensor_id + 1) % 4;
osal_task_delay(100);
}
}
static void consumer_task(void* arg) {
(void)arg;
sensor_msg_t msg;
while (1) {
if (osal_sem_take(g_data_ready_sem, 500) == OSAL_OK) {
if (osal_queue_receive(g_sensor_queue, &msg, 10) == OSAL_OK) {
update_stats(0, 1, 0);
hal_gpio_toggle(HAL_GPIO_PORT_D, 13);
}
}
}
}
static void heartbeat_task(void* arg) {
(void)arg;
while (1) {
hal_gpio_toggle(HAL_GPIO_PORT_D, 12);
osal_task_delay(500);
}
}
static void stats_task(void* arg) {
(void)arg;
stats_t local_stats;
while (1) {
osal_task_delay(2000);
if (osal_mutex_lock(g_stats_mutex, 100) == OSAL_OK) {
local_stats = g_stats;
osal_mutex_unlock(g_stats_mutex);
/* In real app, would print stats via UART */
if (local_stats.queue_overflows == 0) {
hal_gpio_write(HAL_GPIO_PORT_D, 14, HAL_GPIO_LEVEL_LOW);
}
}
}
}
/*-----------------------------------------------------------------------*/
/* Main Function */
/*-----------------------------------------------------------------------*/
int main(void) {
hal_init();
/* Initialize LEDs */
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_D, 12, &config);
hal_gpio_init(HAL_GPIO_PORT_D, 13, &config);
hal_gpio_init(HAL_GPIO_PORT_D, 14, &config);
/* Initialize OSAL */
osal_init();
/* Create synchronization objects */
osal_queue_create(sizeof(sensor_msg_t), SENSOR_QUEUE_SIZE, &g_sensor_queue);
osal_mutex_create(&g_stats_mutex);
osal_sem_create_counting(SENSOR_QUEUE_SIZE, 0, &g_data_ready_sem);
/* Create tasks */
osal_task_config_t tasks[] = {
{.name = "Producer", .func = producer_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_NORMAL, .stack_size = TASK_STACK_SIZE},
{.name = "Consumer", .func = consumer_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_HIGH, .stack_size = TASK_STACK_SIZE},
{.name = "Heartbeat", .func = heartbeat_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_LOW, .stack_size = TASK_STACK_SIZE},
{.name = "Stats", .func = stats_task, .arg = NULL,
.priority = OSAL_TASK_PRIORITY_LOW, .stack_size = TASK_STACK_SIZE}
};
for (size_t i = 0; i < 4; i++) {
osal_task_handle_t handle;
osal_task_create(&tasks[i], &handle);
}
/* Start scheduler */
osal_start();
return 0;
}
Best Practices¶
Task Design: Keep tasks focused on a single responsibility
Stack Size: Allocate sufficient stack for each task (monitor usage)
Priority Assignment: Use appropriate priorities (avoid priority inversion)
Synchronization: Always protect shared resources with mutexes
Deadlock Prevention: - Always acquire mutexes in the same order - Use timeouts - Keep critical sections short
Queue Sizing: Size queues appropriately for your data rate
Error Handling: Always check return values from OSAL functions
Task Cleanup: Tasks should run forever or call
osal_task_delete()before returning
Common Issues¶
Stack Overflow:
Symptoms: System crashes, hard faults Solution: Increase stack size, reduce local variables, avoid deep recursion
Priority Inversion:
Symptoms: High-priority task blocked by low-priority task Solution: Use priority inheritance mutexes
Deadlock:
Symptoms: System hangs Solution: Always acquire locks in same order, use timeouts
Queue Overflow:
Symptoms: Data loss Solution: Increase queue size, process data faster, add flow control
Next Steps¶
Explore the OS Abstraction Layer (OSAL) for complete OSAL API reference
Check out the FreeRTOS demo application in
applications/freertos_demo/Learn about Log Framework for logging from multiple tasks
Read about STM32F4 Platform Guide for platform-specific details