使用 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.

学习目标

完成本教程后,您将能够:

  • 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

前置条件

OSAL 概述

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 任务 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 subsystem

  • osal_task_create() creates a new task

  • osal_start() starts the scheduler (never returns)

  • osal_task_delay() suspends the task for a specified time

任务 Priorities

OSAL supports multiple priority levels:

  • OSAL_TASK_PRIORITY_IDLE: Lowest priority (background tasks)

  • OSAL_TASK_PRIORITY_LOW: Low priority

  • OSAL_TASK_PRIORITY_NORMAL: Normal priority (default)

  • OSAL_TASK_PRIORITY_HIGH: High priority

  • OSAL_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 Problem: Shared Resources

When multiple tasks access shared resources, race conditions can occur:

/* WRONG: No protection */
static uint32_t shared_counter = 0;

static void task1(void* arg) {
    while (1) {
        shared_counter++;  /* Race condition! */
        osal_task_delay(10);
    }
}

static void task2(void* arg) {
    while (1) {
        shared_counter++;  /* Race condition! */
        osal_task_delay(10);
    }
}

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

生产者-消费者模式

/**
 * \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;
}

最佳实践

  1. Task Design: Keep tasks focused on a single responsibility

  2. Stack Size: Allocate sufficient stack for each task (monitor usage)

  3. Priority Assignment: Use appropriate priorities (avoid priority inversion)

  4. Synchronization: Always protect shared resources with mutexes

  5. Deadlock Prevention: - Always acquire mutexes in the same order - Use timeouts - Keep critical sections short

  6. Queue Sizing: Size queues appropriately for your data rate

  7. Error Handling: Always check return values from OSAL functions

  8. Task Cleanup: Tasks should run forever or call osal_task_delete() before returning

常见问题

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

下一步