OS 抽象层

[English]

OS 抽象层介绍

  • OS抽象层主要为了适配Armino平台不同的操作系统

  • 对于不同的操作系统,OS抽象层对外提供一套统一的接口

  • 目前Armino平台OS抽象层支持的操作系统为FreeRTOS.

  • 目前Armino平台posix接口仅支持FreeRTOS V10操作系统,默认关闭,若使用,需要打开CONFIG_FREERTOS_POSIX配置开关

  • 本版本支持SMP(对称多处理)系统,任务可以在多个CPU核心上运行

  • API头文件:include/os/os.h

  • 实现源文件:components/bk_rtos/freertos/v10/rtos_pub_smp.c

备注

  • 在使用FreeRTOS的posix功能的时候,在引用posix相关头文件之前,需要先引用FreeRTOS_POSIX.h头文件;

  • 可以在components/bk_rtos/freertos/posix/freertos_impl/include/portable/bk/FreeRTOS_POSIX_portable.h中自定义相关配置,比如某些功能或者数据结构想使用自定义或者编译器自带的,可以在该文件中屏蔽掉posix相关功能。

  • 在移植posix时,如果遇到与编译器自带的头文件有冲突的情况,请优先检查以上两条。

抽象层与原生FreeRTOS的主要差别

为了提供统一的操作系统接口,抽象层在以下几个方面与原生FreeRTOS有所不同:

优先级反转

在FreeRTOS原生系统中,数值越大代表优先级越高。而在OS抽象层中,为了统一管理,采用了相反的规则:

  • 抽象层优先级: 数值越小,优先级越高(0为最高优先级,9为最低优先级)

  • 原生FreeRTOS优先级: 数值越大,优先级越高

  • 转换公式: 原生优先级 = RTOS_HIGHEST_PRIORITY - 抽象层优先级

例如:当您在抽象层设置优先级为7时,实际对应的FreeRTOS原生优先级会根据系统配置的最大优先级自动转换。

统一的句柄类型

抽象层使用void指针类型的统一句柄,屏蔽了不同操作系统的底层实现差异:

  • beken_thread_t: 任务句柄

  • beken_queue_t: 队列句柄

  • beken_semaphore_t: 信号量句柄

  • beken_mutex_t: 互斥锁句柄

  • beken_timer_t: 定时器句柄

统一的错误码

抽象层定义了统一的错误码,而不是直接使用FreeRTOS的pdPASS/pdFAIL等返回值:

  • kNoErr: 操作成功

  • kGeneralErr: 一般错误

  • kTimeoutErr: 超时错误

  • kUnsupportedErr: 不支持的操作

超时参数统一

抽象层使用毫秒作为超时时间单位,并提供了统一的宏定义:

  • BEKEN_WAIT_FOREVER (0xFFFFFFFF): 永久等待

  • BEKEN_NO_WAIT (0): 不等待

  • 其他数值: 超时时间(毫秒)

而FreeRTOS原生接口使用系统时钟节拍(tick)作为单位,抽象层会自动进行转换。

任务名处理方式

抽象层与原生FreeRTOS在任务名处理方式上有所不同,并且可以通过配置宏 CONFIG_USE_STATIC_TASK_NAME 来选择使用哪种方式。该宏默认开启(CONFIG_USE_STATIC_TASK_NAME = y):

静态任务名方式(CONFIG_USE_STATIC_TASK_NAME = y)
  • 采用传指针的方式,TCB中保存的是任务名指针(char *pcTaskName

  • 直接保存传入的任务名指针,不会拷贝字符串内容

  • 这是抽象层推荐的方式,可以节省内存空间

  • 在创建任务时,传入的任务名字符串必须保持有效(例如使用字符串常量),不能使用栈上的临时变量

拷贝方式(CONFIG_USE_STATIC_TASK_NAME = n)
  • 采用拷贝的方式,TCB中保存的是任务名数组(char pcTaskName[configMAX_TASK_NAME_LEN]

  • 会在内部创建任务名字符串的副本,拷贝到TCB结构体中

  • 这是FreeRTOS原生的方式,任务名字符串会被安全地保存

  • 可以传入临时变量,因为会进行字符串拷贝

  • 可以通过宏 CONFIG_DYNAMIC_TASK_NAME_LEN 配置最大的任务名长度(默认值为16)

重要

  • CONFIG_USE_STATIC_TASK_NAME = y 时,传入的任务名字符串必须保持有效(例如使用字符串常量),不能使用栈上的临时变量。

  • CONFIG_USE_STATIC_TASK_NAME = n 时,可以使用临时变量,因为会进行字符串拷贝。

抽象层API

任务创建:

  • 指定在sram中创建线程:

    bk_err_t rtos_create_sram_thread( beken_thread_t* thread,
                                    uint8_t priority,
                                    const char* name,
                                    beken_thread_function_t function,
                                    uint32_t stack_size,
                                    beken_thread_arg_t arg )
    
    该函数内部不绑定CPU核心,创建的任务会根据调度策略在不同核心上运行。
    
  • 指定在psram中创建线程:

    bk_err_t rtos_create_psram_thread( beken_thread_t* thread,
                                     uint8_t priority,
                                     const char* name,
                                     beken_thread_function_t function,
                                     uint32_t stack_size,
                                     beken_thread_arg_t arg )
    
    该函数内部不绑定CPU核心,创建的任务会根据调度策略在不同核心上运行。
    
  • 创建任务:

    bk_err_t rtos_create_thread( beken_thread_t* thread,
                                uint8_t priority,
                                const char* name,
                                beken_thread_function_t function,
                                uint32_t stack_size,
                                beken_thread_arg_t arg )
    
    参数说明:
    -thread: 指向创建任务的句柄指针。如果传入NULL,则不保存任务句柄
    -priority: 任务优先级,取值范围0-9
        需要注意的是:对于freertos操作系统内核,任务对应的优先级数字越大,则实际的优先级越大。
        而对于应用层而言则相反,创建任务时配置的优先级数字越大,实际的优先级越小。
        这是因为在SDK中,操作系统适配层做了统一的管理。所以用户视角看到的最大优先级为0,最小优先级为9。
    
        优先级表:
            * 0: 最高优先级(系统保留)
            * 1-5: 库任务和SDK内部任务
            * 6-8: 应用任务(推荐使用范围)
            * 9: 最低优先级(接近空闲任务)
    
        推荐值:
            * BEKEN_APPLICATION_PRIORITY (7): 普通应用任务
            * BEKEN_DEFAULT_WORKER_PRIORITY (6): 工作任务
    -name :创建的任务名称字符串
    -function:任务体入口函数指针,函数原型为 void function(beken_thread_arg_t arg)
    -stack_size:任务堆栈的大小,单位为字节。推荐值:
        * 0x400 (1024字节): 简单任务
        * 0x800 (2048字节): 一般任务
        * 0x1000 (4096字节): 复杂任务
    -arg:传递给任务函数体的参数,可以为NULL
    
    返回值:
    -kNoErr:任务创建成功
    -kGeneralErr: 任务创建失败
    
    **FreeRTOS对应接口**: xTaskCreate()
    
    使用示例::
    
        #include <os/os.h>
    
        beken_thread_t task_handle = NULL;
    
        void my_task_entry(beken_thread_arg_t arg)
        {
            int count = 0;
            while(1) {
                BK_LOGI(NULL, "Task running, count=%d\r\n", count++);
                rtos_delay_milliseconds(1000);  // 延时1秒
            }
        }
    
        void create_task_example(void)
        {
            bk_err_t ret;
    
            ret = rtos_create_thread(&task_handle,
                                    BEKEN_APPLICATION_PRIORITY,
                                    "my_task",
                                    my_task_entry,
                                    0x800,  // 2KB栈空间
                                    NULL);
    
            if (ret != kNoErr) {
                BK_LOGI(NULL, "Failed to create task\r\n");
            }
        }
    

重要

  • 该函数内部会依据宏进行区分,如果同时开启了CONFIG_TASK_STACK_IN_PSRAM 和 CONFIG_PSRAM_AS_SYS_MEMORY,则 调用的是rtos_create_psram_thread,否则调用的是rtos_create_sram_thread

备注

  • 任务创建后会立即进入就绪态,等待调度器调度运行

  • 任务函数一般设计为无限循环,如果需要退出应调用rtos_delete_thread()

  • 建议在任务中使用延时函数,避免长时间占用CPU

  • 栈空间不足会导致任务运行异常,需要根据实际使用情况设置合适的栈大小

SMP(对称多处理)任务创建接口:

本版本支持SMP系统,提供以下接口用于在不同CPU核心上创建任务:

  • 在CPU0上创建任务:

    bk_err_t rtos_core0_create_thread( beken_thread_t* thread,
                                       uint8_t priority,
                                       const char* name,
                                       beken_thread_function_t function,
                                       uint32_t stack_size,
                                       beken_thread_arg_t arg )
    
    将任务固定绑定到CPU0核心运行
    
    参数说明:与rtos_create_thread相同
    
    返回值:
    -kNoErr:任务创建成功
    -kGeneralErr: 任务创建失败
    
    **FreeRTOS对应接口**: xTaskCreatePinnedToCore()
    
  • 在CPU0的PSRAM中创建任务:

    bk_err_t rtos_core0_create_psram_thread( beken_thread_t* thread,
                                             uint8_t priority,
                                             const char* name,
                                             beken_thread_function_t function,
                                             uint32_t stack_size,
                                             beken_thread_arg_t arg )
    
    在CPU0核心上创建任务,且任务栈位于PSRAM中
    
  • 在CPU1上创建任务(需CONFIG_CPU_CNT > 1):

    bk_err_t rtos_core1_create_thread( beken_thread_t* thread,
                                       uint8_t priority,
                                       const char* name,
                                       beken_thread_function_t function,
                                       uint32_t stack_size,
                                       beken_thread_arg_t arg )
    
    将任务固定绑定到CPU1核心运行
    
    参数说明:与rtos_create_thread相同
    
    返回值:
    -kNoErr:任务创建成功
    -kGeneralErr: 任务创建失败
    
  • 在CPU1的PSRAM中创建任务(需CONFIG_CPU_CNT > 1):

    bk_err_t rtos_core1_create_psram_thread( beken_thread_t* thread,
                                             uint8_t priority,
                                             const char* name,
                                             beken_thread_function_t function,
                                             uint32_t stack_size,
                                             beken_thread_arg_t arg )
    
  • 在指定CPU核心上创建任务(带亲缘性设置):

    bk_err_t rtos_create_thread_with_affinity( beken_thread_t* thread,
                                               uint32_t affinity,
                                               uint8_t priority,
                                               const char* name,
                                               beken_thread_function_t function,
                                               uint32_t stack_size,
                                               beken_thread_arg_t arg )
    
    参数说明:
    -affinity: CPU核心亲缘性掩码,可以指定任务运行的CPU核心
        * 0: 绑定到CPU0
        * 1: 绑定到CPU1
        * tskNO_AFFINITY: 不绑定特定核心,由调度器决定
    
    返回值:
    -kNoErr:任务创建成功
    -kGeneralErr: 任务创建失败
    

备注

  • SMP系统中,任务可以在任意CPU核心上运行,也可以通过核心绑定接口将任务固定到特定核心

  • 不指定核心绑定时,调度器会根据负载均衡原则自动分配任务到合适的CPU核心

  • 核心绑定的任务只能运行在指定的CPU核心上,无法迁移到其他核心

  • 使用核心绑定可以提高缓存局部性,减少跨核心迁移的开销

任务挂起与恢复:

  • 挂起某个任务:

    void rtos_suspend_thread(beken_thread_t* thread)
    
    参数说明:
    -thread:任务的句柄,如果传入的参数为NULL,代表是挂起或恢复自身
    
  • 在挂起调度器:

    void rtos_suspend_all_thread(void)
    
    在SMP系统中,该函数只能挂起当前CPU核心的调度器,无法挂起其他核心的调度器。
    
  • 恢复某个任务:

    void rtos_resume_thread(beken_thread_t* thread)
    
  • 恢复调度器:

    void rtos_resume_all_thread(void)
    
    在SMP系统中,该函数只能恢复当前CPU核心的调度器,无法恢复其他核心的调度器。
    

其他任务相关接口:

除了对底层标准的任务接口进行封装外,适配层还提供了以下和任务相关的接口.

  • 任务删除:

    bk_err_t rtos_delete_thread( beken_thread_t* thread )
    
    参数说明:
    -thread: 要删除的任务句柄指针。如果传入NULL,则删除当前任务自身
    
    返回值:
    -kNoErr:任务删除成功
    -kGeneralErr: 任务删除失败
    
    **FreeRTOS对应接口**: vTaskDelete()
    
    使用示例::
    
        // 删除指定任务
        rtos_delete_thread(&task_handle);
    
        // 任务自删除
        void task_entry(beken_thread_arg_t arg)
        {
            // 执行任务逻辑
            BK_LOGI(NULL, "Task complete\r\n");
    
            // 任务完成后自删除
            rtos_delete_thread(NULL);
        }
    
  • 判断指定的线程是否为当前正在运行的线程:

    bool rtos_is_current_thread(beken_thread_t *thread)
    
    参数说明:
    -thread:指定的任务句柄指针
    
    返回值:
    -true:是当前任务
    -false: 不是当前任务
    
    **FreeRTOS对应接口**: xTaskGetCurrentTaskHandle()
    
  • 获取当前正在运行的任务句柄:

    beken_thread_t* rtos_get_current_thread(void)
    
    返回值:
    -返回当前任务句柄指针
    
    **FreeRTOS对应接口**: xTaskGetCurrentTaskHandle()
    
  • 设置指定任务的优先级:

    bk_err_t rtos_thread_set_priority(
                beken_thread_t *thread,
                int priority)
    
    参数说明:
    -thread: 要设置优先级的任务句柄指针
    -priority: 新的优先级,取值范围0-9
    
    返回值:
    -kNoErr:设置成功
    -kGeneralErr: 设置失败
    
    **FreeRTOS对应接口**: vTaskPrioritySet()
    
  • 将一个任务挂起指定的时间,单位为秒:

    void rtos_thread_sleep(uint32_t seconds)
    
    参数说明:
    -seconds: 休眠时间,单位为秒
    
    **FreeRTOS对应接口**: vTaskDelay()
    
  • 将一个任务挂起指定的毫秒时间:

    bk_err_t rtos_delay_milliseconds(uint32_t num_ms )
    
    参数说明:
    -num_ms: 延时时间,单位为毫秒
    
    返回值:
    -kNoErr:延时成功
    
    **FreeRTOS对应接口**: vTaskDelay()
    
    使用示例::
    
        void periodic_task(beken_thread_arg_t arg)
        {
            while(1) {
                // 执行周期性任务
                process_data();
    
                // 延时100毫秒
                rtos_delay_milliseconds(100);
            }
        }
    

备注

  • 任务延时会触发系统调度,让出CPU给其他任务运行

  • 延时精度与系统时钟节拍(tick)有关,默认为1ms

  • 实际延时时间可能略大于设置值,这是正常现象

  • 等待线程结束:

    bk_err_t rtos_thread_join(beken_thread_t* thread)
    
    阻塞等待指定的线程执行完成。该函数会持续轮询直到线程结束。
    
    参数说明:
    -thread: 要等待的线程句柄指针
    
    返回值:
    -kNoErr:等待成功(线程已结束)
    
    **使用场景**:
    - 等待后台线程完成后再继续执行
    - 线程同步和资源清理
    - 确保线程完全结束后再释放相关资源
    

备注

  • 该函数会阻塞当前任务,直到指定线程结束

  • 函数内部使用轮询方式检查线程状态,轮询间隔为10ms

  • 强制唤醒任务:

    bk_err_t rtos_thread_force_awake( beken_thread_t* thread )
    
    强制唤醒处于阻塞状态的任务,使其立即返回并继续执行。
    
    参数说明:
    -thread: 要唤醒的任务句柄指针
    
    返回值:
    -kNoErr:唤醒成功
    
    **FreeRTOS对应接口**: xTaskAbortDelay()
    
    **使用场景**:
    - 需要立即中断任务的阻塞等待
    - 实现超时或取消机制
    - 紧急唤醒任务处理事件
    

队列:

队列是任务间通信的重要机制,用于在任务之间传递消息。

  • 初始化一个队列:

    bk_err_t rtos_init_queue( beken_queue_t* queue,
                            const char* name,
                            uint32_t message_size,
                            uint32_t number_of_messages )
    参数说明:
    -queue:队列句柄指针
    -name: 队列的名称字符串,可以为NULL
    -message_size: 队列中每条消息的大小(字节数)
    -number_of_messages:队列深度,即队列中最多可以存放的消息数量
    
    返回值:
    -kNoErr:队列创建成功
    -kGeneralErr: 队列创建失败
    
    **FreeRTOS对应接口**: xQueueCreate()
    
    使用示例::
    
        typedef struct {
            int cmd;
            int value;
        } msg_t;
    
        beken_queue_t my_queue = NULL;
    
        // 创建一个能存放10条消息的队列,每条消息大小为msg_t
        ret = rtos_init_queue(&my_queue,
                             "my_queue",
                             sizeof(msg_t),
                             10);
    
  • 往队列尾部发送消息:

    bk_err_t rtos_push_to_queue( beken_queue_t* queue,
                                 void* message,
                                 uint32_t timeout_ms )
    
    内部会判断当前是处于中断上下文还是任务上下文,从而调用不同的内核接口实现对应的功能
    
    参数说明:
    -queue: 队列句柄指针
    -message: 指向要发送的消息数据的指针
    -timeout_ms:超时时间(毫秒),可配置为:
        * 0: 不等待,如果队列满立即返回
        * BEKEN_WAIT_FOREVER: 一直阻塞等待直到发送成功
        * 其他值: 等待指定的超时时间(毫秒)
    
    返回值:
    -kNoErr:消息发送成功
    -kTimeoutErr: 超时,队列满
    -kGeneralErr: 发送失败
    
    **FreeRTOS对应接口**: xQueueSend() / xQueueSendFromISR()
    
  • 往队列头部发送消息:

    bk_err_t rtos_push_to_queue_front( beken_queue_t* queue,
                                       void* message,
                                       uint32_t timeout_ms )
    
    内部会判断当前是处于中断上下文还是任务上下文,从而调用不同的内核接口实现对应的功能
    
    参数说明:与rtos_push_to_queue相同
    
    返回值:
    -kNoErr:消息发送成功
    -kTimeoutErr: 超时
    -kGeneralErr: 发送失败
    
    **FreeRTOS对应接口**: xQueueSendToFront() / xQueueSendToFrontFromISR()
    
  • 从队列接收消息:

    bk_err_t rtos_pop_from_queue( beken_queue_t* queue,
                                  void* message,
                                  uint32_t timeout_ms )
    
    内部调用的是xQueueReceive,仅支持在任务上下文使用
    
    参数说明:
    -queue: 队列句柄指针
    -message: 指向接收消息缓冲区的指针
    -timeout_ms:超时时间(毫秒),可配置为:
        * 0: 不等待,如果队列空立即返回
        * BEKEN_WAIT_FOREVER: 一直阻塞等待直到接收成功
        * 其他值: 等待指定的超时时间(毫秒)
    
    返回值:
    -kNoErr:消息接收成功
    -kTimeoutErr: 超时,队列空
    -kGeneralErr: 接收失败
    
    **FreeRTOS对应接口**: xQueueReceive()
    
  • 删除一个队列:

    bk_err_t rtos_deinit_queue( beken_queue_t* queue )
    
    参数说明:
    -queue: 队列句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: vQueueDelete()
    
  • 判断一个队列是否为空:

    bool rtos_is_queue_empty( beken_queue_t* queue )
    
    仅能在中断上下文中调用
    
    参数说明:
    -queue: 队列句柄指针
    
    返回值:
    -true: 队列为空
    -false: 队列不为空
    
    **FreeRTOS对应接口**: xQueueIsQueueEmptyFromISR()
    
  • 判断一个队列是否已满:

    bool rtos_is_queue_full( beken_queue_t* queue )
    
    仅能在中断上下文中调用
    
    参数说明:
    -queue: 队列句柄指针
    
    返回值:
    -true: 队列已满
    -false: 队列未满
    
    **FreeRTOS对应接口**: xQueueIsQueueFullFromISR()
    

完整使用示例(参考components/demos/os/os_queue/os_queue.c):

#include <os/os.h>

typedef struct {
    int value;
} msg_t;

static beken_queue_t os_queue = NULL;

// 接收任务
void receiver_thread(beken_thread_arg_t arg)
{
    bk_err_t err;
    msg_t received = {0};

    while(1) {
        // 从队列接收消息,永久等待
        err = rtos_pop_from_queue(&os_queue, &received, BEKEN_WAIT_FOREVER);
        if (err == kNoErr) {
            BK_LOGI(NULL, "Received: value=%d\r\n", received.value);
        }
    }
}

// 发送任务
void sender_thread(beken_thread_arg_t arg)
{
    bk_err_t err;
    msg_t my_message = {0};

    while(1) {
        my_message.value++;
        // 向队列发送消息
        err = rtos_push_to_queue(&os_queue, &my_message, BEKEN_WAIT_FOREVER);
        if (err == kNoErr) {
            BK_LOGI(NULL, "Sent: value=%d\r\n", my_message.value);
        }
        rtos_delay_milliseconds(100);
    }
}

void queue_demo_start(void)
{
    bk_err_t err;

    // 创建队列,深度为3
    err = rtos_init_queue(&os_queue, "queue", sizeof(msg_t), 3);
    if (err != kNoErr) {
        BK_LOGI(NULL, "Failed to create queue\r\n");
        return;
    }

    // 创建发送任务
    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "sender", sender_thread, 0x500, NULL);

    // 创建接收任务
    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "receiver", receiver_thread, 0x500, NULL);
}

备注

  • 队列传递的是消息的拷贝,不是指针

  • 队列满时发送会阻塞,队列空时接收会阻塞

  • 可以在中断中使用rtos_push_to_queue和rtos_push_to_queue_front,函数内部会自动判断上下文

  • 队列可用于任务间同步和数据传递

信号量:

信号量用于任务间同步和资源计数。

  • 创建一个计数信号量,设置初始值为0:

    bk_err_t rtos_init_semaphore( beken_semaphore_t* semaphore,
                                  int maxCount )
    
    参数说明:
    -semaphore: 信号量句柄指针
    -maxCount: 最大计数值,通常设置为1或更大值
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    .. note::
        此函数创建的信号量初始值为0,如需设置初始值请使用rtos_init_semaphore_ex
    
    **FreeRTOS对应接口**: xSemaphoreCreateCounting()
    
  • 创建一个计数信号量,可设置初始值:

    bk_err_t rtos_init_semaphore_ex( beken_semaphore_t* semaphore,
                                     const char* name,
                                     int maxCount,
                                     int init_count )
    
    参数说明:
    -semaphore: 信号量句柄指针
    -name: 信号量名称,可以为NULL
    -maxCount: 最大计数值
    -init_count: 初始计数值
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    **FreeRTOS对应接口**: xSemaphoreCreateCounting()
    
  • 获取当前的信号量计数值:

    int rtos_get_semaphore_count( beken_semaphore_t* semaphore )
    
    参数说明:
    -semaphore: 信号量句柄指针
    
    返回值:
    -返回当前信号量的计数值
    
    **FreeRTOS对应接口**: uxSemaphoreGetCount()
    
  • 释放信号量:

    int rtos_set_semaphore( beken_semaphore_t* semaphore )
    
    支持在中断上下文或任务上下文使用,函数内部会自动判断
    
    参数说明:
    -semaphore: 信号量句柄指针
    
    返回值:
    -kNoErr:释放成功
    -kGeneralErr: 释放失败
    
    **FreeRTOS对应接口**: xSemaphoreGive() / xSemaphoreGiveFromISR()
    
  • 获取信号量:

    bk_err_t rtos_get_semaphore( beken_semaphore_t* semaphore,
                                uint32_t timeout_ms )
    
    仅支持在任务上下文使用
    
    参数说明:
    -semaphore: 信号量句柄指针
    -timeout_ms: 超时时间(毫秒),可配置为:
        * 0: 不等待,立即返回
        * BEKEN_WAIT_FOREVER: 一直阻塞等待直到获取成功
        * 其他值: 等待指定的超时时间(毫秒)
    
    返回值:
    -kNoErr:获取成功
    -kTimeoutErr: 超时
    -kGeneralErr: 获取失败
    
    **FreeRTOS对应接口**: xSemaphoreTake()
    
  • 删除一个已创建的信号量:

    bk_err_t rtos_deinit_semaphore( beken_semaphore_t* semaphore )
    
    参数说明:
    -semaphore: 信号量句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: vSemaphoreDelete()
    

完整使用示例(参考components/demos/os/os_sem/os_sem.c):

#include <os/os.h>

static beken_semaphore_t os_sem = NULL;

// 释放信号量的任务
void set_semaphore_thread(beken_thread_arg_t arg)
{
    bk_err_t err;

    while(1) {
        // 每500ms释放一次信号量
        rtos_delay_milliseconds(500);

        err = rtos_set_semaphore(&os_sem);
        if (err == kNoErr) {
            BK_LOGI(NULL, "Semaphore released\r\n");
        }
    }
}

// 获取信号量的任务
void get_semaphore_thread(beken_thread_arg_t arg)
{
    bk_err_t err;

    while(1) {
        // 等待信号量
        err = rtos_get_semaphore(&os_sem, BEKEN_WAIT_FOREVER);
        if (err == kNoErr) {
            BK_LOGI(NULL, "Semaphore acquired\r\n");
            // 执行同步任务
        }
    }
}

void semaphore_demo_start(void)
{
    bk_err_t err;

    // 创建二值信号量,初始值为0
    err = rtos_init_semaphore(&os_sem, 1);
    if (err != kNoErr) {
        BK_LOGI(NULL, "Failed to create semaphore\r\n");
        return;
    }

    // 创建获取信号量任务
    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "get_sem", get_semaphore_thread, 0x500, NULL);

    // 创建释放信号量任务
    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "set_sem", set_semaphore_thread, 0x500, NULL);
}

备注

  • 计数信号量常用于资源计数

  • 信号量不具有所有权概念,任何任务都可以释放或获取

  • 获取信号量会将计数值减1,释放信号量会将计数值加1

  • 信号量计数值不会超过maxCount

  • 可以在中断中释放信号量,但不能在中断中获取信号量

互斥锁:

互斥锁用于保护共享资源,防止多个任务同时访问。互斥锁具有优先级继承机制,可以防止优先级反转问题。

  • 初始化一个互斥锁:

    bk_err_t rtos_init_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 互斥锁句柄指针
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    **FreeRTOS对应接口**: xSemaphoreCreateMutex()
    
  • 尝试获取一个互斥锁,超时时间为0:

    bk_err_t rtos_trylock_mutex(beken_mutex_t *mutex)
    
    参数说明:
    -mutex: 互斥锁句柄指针
    
    返回值:
    -kNoErr:获取成功
    -kGeneralErr: 获取失败(互斥锁已被占用)
    
    **FreeRTOS对应接口**: xSemaphoreTake(mutex, 0)
    
  • 无限等待获取一个互斥锁:

    bk_err_t rtos_lock_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 互斥锁句柄指针
    
    返回值:
    -kNoErr:获取成功
    -kGeneralErr: 获取失败
    
    **FreeRTOS对应接口**: xSemaphoreTake(mutex, portMAX_DELAY)
    
  • 释放一个互斥锁:

    bk_err_t rtos_unlock_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 互斥锁句柄指针
    
    返回值:
    -kNoErr:释放成功
    -kGeneralErr: 释放失败
    
    **FreeRTOS对应接口**: xSemaphoreGive()
    
  • 删除一个互斥锁:

    bk_err_t rtos_deinit_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 互斥锁句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: vSemaphoreDelete()
    
  • 初始化一个递归互斥锁:

    bk_err_t rtos_init_recursive_mutex( beken_mutex_t* mutex )
    
    递归互斥锁允许同一任务多次获取同一互斥锁,需要对应次数的释放操作
    
    参数说明:
    -mutex: 递归互斥锁句柄指针
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    **FreeRTOS对应接口**: xSemaphoreCreateRecursiveMutex()
    
  • 获取一个递归互斥锁:

    bk_err_t rtos_lock_recursive_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 递归互斥锁句柄指针
    
    返回值:
    -kNoErr:获取成功
    -kGeneralErr: 获取失败
    
    **FreeRTOS对应接口**: xSemaphoreTakeRecursive()
    
  • 释放一个递归互斥锁:

    bk_err_t rtos_unlock_recursive_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 递归互斥锁句柄指针
    
    返回值:
    -kNoErr:释放成功
    -kGeneralErr: 释放失败
    
    **FreeRTOS对应接口**: xSemaphoreGiveRecursive()
    
  • 删除一个递归互斥锁:

    bk_err_t rtos_deinit_recursive_mutex( beken_mutex_t* mutex )
    
    参数说明:
    -mutex: 递归互斥锁句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: vSemaphoreDelete()
    

完整使用示例(参考components/demos/os/os_mutex/os_mutex.c):

#include <os/os.h>

static beken_mutex_t os_mutex = NULL;
static int shared_resource = 0;

// 保护共享资源的函数
void access_shared_resource(const char *task_name)
{
    bk_err_t err;

    // 获取互斥锁
    err = rtos_lock_mutex(&os_mutex);
    if (err != kNoErr) {
        BK_LOGI(NULL, "%s: Failed to lock mutex\r\n", task_name);
        return;
    }

    // 访问共享资源(临界区)
    BK_LOGI(NULL, "%s: Accessing shared resource, value=%d\r\n",
             task_name, shared_resource);
    shared_resource++;
    rtos_delay_milliseconds(10);  // 模拟资源访问

    // 释放互斥锁
    err = rtos_unlock_mutex(&os_mutex);
    if (err != kNoErr) {
        BK_LOGI(NULL, "%s: Failed to unlock mutex\r\n", task_name);
    }
}

void task1_entry(beken_thread_arg_t arg)
{
    while(1) {
        access_shared_resource("Task1");
        rtos_delay_milliseconds(100);
    }
}

void task2_entry(beken_thread_arg_t arg)
{
    while(1) {
        access_shared_resource("Task2");
        rtos_delay_milliseconds(100);
    }
}

void mutex_demo_start(void)
{
    bk_err_t err;

    // 创建互斥锁
    err = rtos_init_mutex(&os_mutex);
    if (err != kNoErr) {
        BK_LOGI(NULL, "Failed to create mutex\r\n");
        return;
    }

    // 创建两个任务,它们将竞争访问共享资源
    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "task1", task1_entry, 0x400, NULL);

    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "task2", task2_entry, 0x400, NULL);
}

备注

  • 互斥锁具有所有权概念,只有获取互斥锁的任务才能释放它

  • 互斥锁支持优先级继承,可以避免优先级反转问题

  • 不能在中断中使用互斥锁

  • 获取互斥锁后应尽快释放,避免长时间持有

  • 互斥锁和信号量的区别:
    • 互斥锁有所有权,信号量没有

    • 互斥锁支持优先级继承,信号量不支持

    • 互斥锁用于资源保护,信号量用于同步和计数

  • 递归互斥锁允许同一任务多次获取,需要相同次数的释放操作

软件定时器:

软件定时器运行在定时器服务任务中,可以在指定时间后执行回调函数。分为单次定时器和周期定时器。

单次定时器相关接口:

  • 初始化一个单次软件定时器:

    bk_err_t rtos_init_oneshot_timer( beken2_timer_t *timer,
                                      uint32_t time_ms,
                                      timer_2handler_t function,
                                      void* larg,
                                      void* rarg )
    
    参数说明:
    -timer: 定时器句柄指针
    -time_ms: 定时时间(毫秒)
    -function: 定时器回调函数,原型为 void function(void *larg, void *rarg)
    -larg: 回调函数的左参数
    -rarg: 回调函数的右参数
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    **FreeRTOS对应接口**: xTimerCreate()
    
  • 开启单次软件定时器:

    bk_err_t rtos_start_oneshot_timer( beken2_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:启动成功
    -kGeneralErr: 启动失败
    
    **FreeRTOS对应接口**: xTimerStart()
    
  • 停止单次软件定时器:

    bk_err_t rtos_stop_oneshot_timer( beken2_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:停止成功
    -kGeneralErr: 停止失败
    
    **FreeRTOS对应接口**: xTimerStop()
    
  • 复位单次软件定时器:

    bk_err_t rtos_oneshot_reload_timer( beken2_timer_t* timer )
    
    重新启动定时器,从当前时间重新计时
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:复位成功
    -kGeneralErr: 复位失败
    
    **FreeRTOS对应接口**: xTimerReset()
    
  • 复位单次软件定时器,并重新设置定时时间与回调:

    bk_err_t rtos_oneshot_reload_timer_ex(beken2_timer_t *timer,
                                          uint32_t time_ms,
                                          timer_2handler_t function,
                                          void *larg,
                                          void *rarg)
    
    参数说明:与rtos_init_oneshot_timer相同
    
    返回值:
    -kNoErr:复位成功
    -kGeneralErr: 复位失败
    
  • 判断单次软件定时器是否初始化:

    bool rtos_is_oneshot_timer_init( beken2_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -true: 已初始化
    -false: 未初始化
    
  • 判断单次软件定时器是否在运行:

    bool rtos_is_oneshot_timer_running( beken2_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -true: 正在运行
    -false: 未运行
    
  • 删除单次软件定时器:

    bk_err_t rtos_deinit_oneshot_timer( beken2_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: xTimerDelete()
    

周期定时器相关接口:

  • 创建周期软件定时器:

    bk_err_t rtos_init_timer( beken_timer_t *timer,
                              uint32_t time_ms,
                              timer_handler_t function,
                              void* arg )
    
    参数说明:
    -timer: 定时器句柄指针
    -time_ms: 定时周期(毫秒)
    -function: 定时器回调函数,原型为 void function(void *arg)
    -arg: 回调函数的参数
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    **FreeRTOS对应接口**: xTimerCreate()
    
  • 开启周期软件定时器:

    bk_err_t rtos_start_timer( beken_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:启动成功
    -kGeneralErr: 启动失败
    
    **FreeRTOS对应接口**: xTimerStart()
    
  • 停止周期软件定时器:

    bk_err_t rtos_stop_timer( beken_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:停止成功
    -kGeneralErr: 停止失败
    
    **FreeRTOS对应接口**: xTimerStop()
    
  • 复位周期软件定时器:

    bk_err_t rtos_reload_timer( beken_timer_t* timer )
    
    重新启动定时器,从当前时间重新计时
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:复位成功
    -kGeneralErr: 复位失败
    
    **FreeRTOS对应接口**: xTimerReset()
    
  • 修改周期软件定时器定时时间:

    bk_err_t rtos_change_period( beken_timer_t* timer,
                                 uint32_t time_ms)
    
    参数说明:
    -timer: 定时器句柄指针
    -time_ms: 新的定时周期(毫秒)
    
    返回值:
    -kNoErr:修改成功
    -kGeneralErr: 修改失败
    
    **FreeRTOS对应接口**: xTimerChangePeriod()
    
  • 删除周期软件定时器:

    bk_err_t rtos_deinit_timer( beken_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: xTimerDelete()
    
  • 获取周期软件定时器到期时间:

    uint32_t rtos_get_timer_expiry_time( beken_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -返回定时器到期的系统时间(tick)
    
    **FreeRTOS对应接口**: xTimerGetExpiryTime()
    
  • 判断周期软件定时器是否初始化:

    bool rtos_is_timer_init( beken_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -true: 已初始化
    -false: 未初始化
    
  • 判断周期软件定时器是否在运行:

    bool rtos_is_timer_running( beken_timer_t* timer )
    
    参数说明:
    -timer: 定时器句柄指针
    
    返回值:
    -true: 正在运行
    -false: 未运行
    
    **FreeRTOS对应接口**: xTimerIsTimerActive()
    

完整使用示例(参考components/demos/os/os_timer/os_timer.c):

#include <os/os.h>

beken_timer_t timer_handle1, timer_handle2;

// 周期定时器回调函数
void timer1_callback(void *arg)
{
    BK_LOGI(NULL, "Timer1 triggered\r\n");
}

// 单次定时器回调函数
void timer2_callback(void *larg, void *rarg)
{
    BK_LOGI(NULL, "Timer2 triggered (one-shot), stopping all timers\r\n");

    rtos_stop_timer(&timer_handle1);
    rtos_deinit_timer(&timer_handle1);
}

void timer_demo_start(void)
{
    bk_err_t err;

    // 创建周期定时器,每500ms触发一次
    err = rtos_init_timer(&timer_handle1, 500, timer1_callback, NULL);
    if (err != kNoErr) {
        BK_LOGI(NULL, "Failed to create timer1\r\n");
        return;
    }

    // 创建单次定时器,2600ms后触发一次
    err = rtos_init_oneshot_timer(&timer_handle2, 2600, timer2_callback,
                                  NULL, NULL);
    if (err != kNoErr) {
        BK_LOGI(NULL, "Failed to create timer2\r\n");
        return;
    }

    // 启动周期定时器
    rtos_start_timer(&timer_handle1);

    // 启动单次定时器
    rtos_start_oneshot_timer(&timer_handle2);
}

备注

  • 当前软件定时器服务任务的优先级已被设置为系统最高优先级,以确保定时器回调能够及时执行

  • 软件定时器的精度与系统时钟节拍周期有关,如果配置的系统节拍数为1000,也就是节拍时钟为1ms

  • 软件定时器的回调函数运行在定时器服务任务上下文中,不是中断上下文

  • 定时器回调函数中应快进快出,不允许使用任何可能引起软件定时器任务挂起或者阻塞的API接口

  • 在回调函数中不能出现死循环

  • 不建议在定时器回调中执行耗时操作,可以使用信号量或队列通知其他任务处理

  • 单次定时器只触发一次后自动停止,周期定时器会持续周期性触发

  • 定时器的实际触发时间可能会有轻微延迟,这取决于定时器服务任务的调度情况

重要

定时器删除后继续使用的常见问题:

实际应用中经常遇到软件定时器已经被删除后又继续使用的问题,常见场景包括:

  • 在定时器回调函数中删除定时器后继续使用:在回调函数中调用删除接口后,如果继续使用该定时器句柄进行操作(如启动、停止等),会导致系统异常

  • 删除定时器后未检查状态:调用删除接口后没有检查返回值或使用状态检查接口(如rtos_is_timer_init())确认删除成功,直接继续使用定时器句柄

  • 多任务环境下竞争使用:一个任务删除定时器,另一个任务仍在尝试使用该定时器句柄,导致未定义行为

  • 删除后句柄未置NULL:删除定时器后,定时器句柄指针没有置为NULL,后续代码可能误判定时器仍有效

使用建议:

  • 删除定时器前,先停止定时器(如果正在运行),然后调用删除接口

  • 删除定时器后,将定时器句柄置为NULL,避免后续误用

  • 在使用定时器句柄前,使用rtos_is_timer_init()或rtos_is_timer_running()检查定时器状态

  • 在多任务环境下,使用互斥锁或其他同步机制保护定时器的创建、使用和删除操作

  • 避免在定时器回调函数中删除自身定时器,建议通过信号量或队列通知其他任务执行删除操作

事件:

事件组用于任务间的事件同步,一个事件组包含多个事件标志位,任务可以等待一个或多个事件标志位。

  • 创建事件:

    bk_err_t rtos_init_event_flags( beken_event_t* event_flags )
    
    参数说明:
    -event_flags: 事件句柄指针
    
    返回值:
    -kNoErr:创建成功
    -kGeneralErr: 创建失败
    
    **FreeRTOS对应接口**: xEventGroupCreate()
    
  • 等待事件标志:

    beken_event_flags_t rtos_wait_for_event_flags( beken_event_t* event_flags,
                                    uint32_t flags_to_wait_for,
                                    beken_bool_t clear_set_flags,
                                    beken_event_flags_wait_option_t wait_option,
                                    uint32_t timeout_ms )
    
    参数说明:
    -event_flags: 事件句柄指针
    -flags_to_wait_for: 一个按位或的值,指定需要等待事件组中的哪些位置1
        例如:0x01 | 0x04 表示等待第0位和第2位
    -clear_set_flags: 是否自动清除事件标志位
        * 1 (pdTRUE): 等待成功后自动清除指定的事件标志位
        * 0 (pdFALSE): 不清除事件标志位
    -wait_option: 等待选项
        * WAIT_FOR_ALL_EVENTS (pdTRUE): 逻辑与,flags_to_wait_for指定的所有位都置位时才返回
        * WAIT_FOR_ANY_EVENT (pdFALSE): 逻辑或,flags_to_wait_for指定的任意位置位时就返回
    -timeout_ms:超时等待时间(毫秒)
        * 0: 不等待,立即返回
        * BEKEN_WAIT_FOREVER: 永久等待
        * 其他值: 等待指定时间
    
    返回值:
    -返回事件组中当前置位的标志位值,需要判断是否是期望的事件位
    -如果超时,返回值可能不包含期望的事件位
    
    **FreeRTOS对应接口**: xEventGroupWaitBits()
    
  • 设置事件标志:

    void rtos_set_event_flags( beken_event_t* event_flags, uint32_t flags_to_set )
    
    参数说明:
    -event_flags: 事件句柄指针
    -flags_to_set:指定要置位的事件标志位,例如 0x01 | 0x04 表示设置第0位和第2位
    
    **FreeRTOS对应接口**: xEventGroupSetBits()
    
  • 清除事件标志:

    beken_event_flags_t rtos_clear_event_flags( beken_event_t* event_flags, uint32_t flags_to_clear )
    
    参数说明:
    -event_flags: 事件句柄指针
    -flags_to_clear:指定要清除的事件标志位
    
    返回值:
    -返回清除前的事件标志位值
    
    **FreeRTOS对应接口**: xEventGroupClearBits()
    
  • 同步事件标志:

    beken_event_flags_t rtos_sync_event_flags( beken_event_t* event_flags,
                                               uint32_t flags_to_set,
                                               uint32_t flags_to_wait_for,
                                               uint32_t timeout_ms)
    
    原子地设置事件组中的位,然后等待同一事件组中的位组合被设置。常用于多任务同步点。
    
    参数说明:
    -event_flags: 事件句柄指针
    -flags_to_set:要设置的事件标志位
    -flags_to_wait_for:要等待的事件标志位(通常是所有任务需要同步的位)
    -timeout_ms:超时时间(毫秒)
    
    返回值:
    -返回事件组中当前置位的标志位值
    
    **FreeRTOS对应接口**: xEventGroupSync()
    
  • 删除事件:

    bk_err_t rtos_deinit_event_flags( beken_event_t* event_flags )
    
    参数说明:
    -event_flags: 事件句柄指针
    
    返回值:
    -kNoErr:删除成功
    -kGeneralErr: 删除失败
    
    **FreeRTOS对应接口**: vEventGroupDelete()
    

完整使用示例(参考components/demos/os/os_event/os_event_group.c):

#include <os/os.h>
#include <driver/timer.h>

#define EVENT_BIT_0  ( 1 << 0 )
#define EVENT_BIT_3  ( 1 << 3 )
#define EVENT_BIT_4  ( 1 << 4 )
#define EVENT_BIT_8  ( 1 << 8 )
#define EVENT_BIT_15 ( 1 << 15 )
#define ALL_SYNC_BITS ( EVENT_BIT_4 | EVENT_BIT_8 | EVENT_BIT_15 )

static beken_event_t event_handler;

// 定时器中断回调,在中断中设置事件
static void timer0_isr(timer_id_t timer_id)
{
    // 在中断上下文中设置事件标志
    rtos_set_event_flags(&event_handler, EVENT_BIT_0);
}

// 线程1:设置EVENT_BIT_3,并参与同步
static void thread1_function(beken_thread_arg_t arg)
{
    uint32_t uxReturn;

    for(;;) {
        // 设置EVENT_BIT_3
        rtos_set_event_flags(&event_handler, EVENT_BIT_3);
        rtos_delay_milliseconds(2000);

        // 发送EVENT_BIT_4并等待所有同步位
        uxReturn = rtos_sync_event_flags(&event_handler,
                                        EVENT_BIT_4,
                                        ALL_SYNC_BITS,
                                        BEKEN_WAIT_FOREVER);
        if ((uxReturn & ALL_SYNC_BITS) == ALL_SYNC_BITS) {
            BK_LOGI(NULL, "Thread1: sync event ok\r\n");
        }
    }
}

// 线程2:等待多个事件(逻辑与),并参与同步
static void thread2_function(beken_thread_arg_t arg)
{
    uint32_t wait_event;

    for (;;) {
        // 等待EVENT_BIT_0和EVENT_BIT_3都置位(逻辑与)
        wait_event = rtos_wait_for_event_flags(&event_handler,
                                               EVENT_BIT_0 | EVENT_BIT_3,
                                               true,  // 自动清除
                                               WAIT_FOR_ALL_EVENTS,  // 逻辑与
                                               BEKEN_WAIT_FOREVER);

        if ((wait_event & (EVENT_BIT_0 | EVENT_BIT_3)) ==
            (EVENT_BIT_0 | EVENT_BIT_3)) {
            BK_LOGI(NULL, "Thread2: got EVENT_BIT_0 & EVENT_BIT_3\r\n");
        }

        // 发送EVENT_BIT_8并等待同步
        wait_event = rtos_sync_event_flags(&event_handler,
                                          EVENT_BIT_8,
                                          ALL_SYNC_BITS,
                                          BEKEN_WAIT_FOREVER);
        if ((wait_event & ALL_SYNC_BITS) == ALL_SYNC_BITS) {
            BK_LOGI(NULL, "Thread2: sync event ok\r\n");
        }
    }
}

// 线程3:等待多个事件(逻辑或),并参与同步
static void thread3_function(beken_thread_arg_t arg)
{
    uint32_t wait_event;

    for (;;) {
        // 等待EVENT_BIT_0或EVENT_BIT_3任一置位(逻辑或)
        wait_event = rtos_wait_for_event_flags(&event_handler,
                                               EVENT_BIT_0 | EVENT_BIT_3,
                                               false,  // 不自动清除
                                               WAIT_FOR_ANY_EVENT,  // 逻辑或
                                               BEKEN_WAIT_FOREVER);

        // 检查具体是哪个事件
        if ((wait_event & (EVENT_BIT_0 | EVENT_BIT_3)) ==
            (EVENT_BIT_0 | EVENT_BIT_3)) {
            BK_LOGI(NULL, "Thread3: got both events\r\n");
        } else if (wait_event & EVENT_BIT_0) {
            BK_LOGI(NULL, "Thread3: got EVENT_BIT_0\r\n");
        } else if (wait_event & EVENT_BIT_3) {
            BK_LOGI(NULL, "Thread3: got EVENT_BIT_3\r\n");
        }

        rtos_delay_milliseconds(1000);

        // 发送EVENT_BIT_15并等待同步
        wait_event = rtos_sync_event_flags(&event_handler,
                                          EVENT_BIT_15,
                                          ALL_SYNC_BITS,
                                          BEKEN_WAIT_FOREVER);
        if ((wait_event & ALL_SYNC_BITS) == ALL_SYNC_BITS) {
            BK_LOGI(NULL, "Thread3: sync event ok\r\n");
        }
    }
}

void os_event_demo_start(void)
{
    bk_err_t err;

    // 初始化定时器,每4秒触发一次中断
    bk_timer_driver_init();
    bk_timer_start(TIMER_ID0, 4000, timer0_isr);

    // 创建事件组
    err = rtos_init_event_flags(&event_handler);
    if (err != kNoErr) {
        BK_LOGI(NULL, "Failed to create event group\r\n");
        return;
    }

    // 创建三个线程,演示不同的事件等待方式
    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "thread1", thread1_function, 0x1000, NULL);

    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY,
                      "thread2", thread2_function, 0x1000, NULL);

    rtos_create_thread(NULL, BEKEN_APPLICATION_PRIORITY - 1,
                      "thread3", thread3_function, 0x1000, NULL);
}

示例说明:

此示例展示了事件组的多种使用场景:

  1. 中断中设置事件:定时器中断中设置EVENT_BIT_0

  2. 逻辑与等待:thread2等待EVENT_BIT_0和EVENT_BIT_3同时置位

  3. 逻辑或等待:thread3等待EVENT_BIT_0或EVENT_BIT_3任一置位

  4. 事件同步:三个线程使用rtos_sync_event_flags实现同步点

  5. 自动清除与手动清除:thread2使用自动清除,thread3不自动清除

备注

  • 事件组最多支持24位事件标志(FreeRTOS限制)

  • 事件标志位可以组合使用,支持逻辑与/或等待

  • 可以在中断中设置事件标志

  • 事件组适合一对多或多对多的任务同步场景

  • rtos_sync_event_flags常用于实现多任务同步点(屏障)

时间管理:

时间相关的接口以系统时钟为基础,提供给应用程序所有和时间有关的服务。基准时间为单个tick的时间,由宏``CONFIG_FREERTOS_TICK_RATE_HZ``配置,该值默认为1000,即1ms一个tick。

  • 获取系统tick计数:

    uint64_t bk_get_tick(void)
    
    获取自系统启动以来的tick计数值,可用于精确的时间测量和超时判断
    
    返回值:
    -返回系统启动以来的tick总数(64位)
    
    **FreeRTOS对应接口**: xTaskGetTickCount() / xTaskGetTickCountFromISR()
    
    .. note::
        此函数会自动判断调用上下文(任务或中断),可以在任务和中断中安全调用
    
  • 获取系统运行秒数:

    uint32_t bk_get_second(void)
    
    获取系统启动以来运行的秒数,相当于 bk_get_tick() / bk_get_ticks_per_second()
    
    返回值:
    -返回系统运行的秒数(32位)
    
    使用场景:
    - 记录系统运行时间
    - 简单的时间戳
    - 超时判断(秒级精度)
    
  • 获取系统tick频率:

    uint32_t bk_get_ticks_per_second(void)
    
    获取系统每秒的tick数,即configTICK_RATE_HZ的值
    
    返回值:
    -返回每秒tick数(默认1000,即每秒1000个tick)
    
    **对应FreeRTOS配置**: configTICK_RATE_HZ
    
  • 获取tick周期(毫秒):

    uint32_t bk_get_ms_per_tick(void)
    
    获取每个tick对应的毫秒数,相当于 1000 / configTICK_RATE_HZ
    
    返回值:
    -返回每个tick的毫秒数(默认1ms)
    
    使用场景:
    - tick到毫秒的转换
    - 时间计算
    

使用示例:

#include <components/system.h>

void time_measurement_example(void)
{
    uint64_t start_tick, end_tick;
    uint32_t elapsed_ms;

    // 记录开始时间
    start_tick = bk_get_tick();

    // 执行某些操作
    perform_operation();

    // 记录结束时间
    end_tick = bk_get_tick();

    // 计算耗时(毫秒)
    elapsed_ms = (end_tick - start_tick) * bk_get_ms_per_tick();
    BK_LOGI(NULL, "Operation took %d ms\r\n", elapsed_ms);
}

void system_info_example(void)
{
    uint32_t uptime_seconds;
    uint32_t tick_rate;
    uint32_t ms_per_tick;

    // 获取系统运行时间
    uptime_seconds = bk_get_second();
    BK_LOGI(NULL, "System uptime: %d seconds\r\n", uptime_seconds);

    // 获取系统配置
    tick_rate = bk_get_ticks_per_second();
    ms_per_tick = bk_get_ms_per_tick();
    BK_LOGI(NULL, "Tick rate: %d Hz, %d ms per tick\r\n",
             tick_rate, ms_per_tick);
}

// 超时检测示例
bool wait_with_timeout(uint32_t timeout_ms)
{
    uint64_t start_tick = bk_get_tick();
    uint32_t timeout_ticks = timeout_ms / bk_get_ms_per_tick();

    while (!condition_met()) {
        if ((bk_get_tick() - start_tick) > timeout_ticks) {
            BK_LOGI(NULL, "Timeout!\r\n");
            return false;
        }
        rtos_delay_milliseconds(10);
    }
    return true;
}

备注

  • tick计数会在系统运行过程中持续增加,不会复位

  • 使用64位tick计数可以避免溢出问题(在1000Hz时可运行约584年)

  • 时间精度受系统tick频率限制,默认为1ms

  • 可以通过修改CONFIG_FREERTOS_TICK_RATE_HZ来调整tick频率(例如1000表示1ms精度)

  • tick频率越高,定时器精度越高,但系统开销也越大

系统内存信息:

系统提供了获取堆内存使用情况的接口,可以用于内存监控、调试和优化。系统支持SRAM堆和PSRAM堆两种堆内存。

SRAM堆内存信息:

  • 获取SRAM堆总大小:

    size_t rtos_get_total_heap_size(void)
    
    获取SRAM堆的总大小(字节数)
    
    返回值:
    -返回SRAM堆的总大小(字节数)
    
    **FreeRTOS对应接口**: prvHeapGetTotalSize()
    
  • 获取SRAM堆空闲大小:

    size_t rtos_get_free_heap_size(void)
    
    获取SRAM堆当前空闲内存大小(字节数)。该值会随着内存的分配和释放动态变化。
    
    返回值:
    -返回SRAM堆当前空闲内存大小(字节数)
    
    **FreeRTOS对应接口**: xPortGetFreeHeapSize()
    
    使用场景:
    - 内存使用监控
    - 内存泄漏检测
    - 动态调整内存分配策略
    
  • 获取SRAM堆历史最小空闲大小:

    size_t rtos_get_minimum_free_heap_size(void)
    
    获取系统运行以来SRAM堆的最小空闲内存大小(字节数)。该值记录了历史最低点,可用于评估系统的最坏内存使用情况。
    
    返回值:
    -返回SRAM堆历史最小空闲内存大小(字节数)
    
    **FreeRTOS对应接口**: xPortGetMinimumEverFreeHeapSize()
    
    使用场景:
    - 评估系统内存配置是否合理
    - 发现内存泄漏趋势
    - 确定系统内存需求的下限
    

PSRAM堆内存信息:

  • 获取PSRAM堆总大小:

    size_t rtos_get_psram_total_heap_size(void)
    
    获取PSRAM堆的总大小(字节数)
    
    返回值:
    -返回PSRAM堆的总大小(字节数)
    
    **FreeRTOS对应接口**: xPortGetPsramTotalHeapSize()
    
  • 获取PSRAM堆空闲大小:

    size_t rtos_get_psram_free_heap_size(void)
    
    获取PSRAM堆当前空闲内存大小(字节数)。该值会随着内存的分配和释放动态变化。
    
    返回值:
    -返回PSRAM堆当前空闲内存大小(字节数)
    
    **FreeRTOS对应接口**: xPortGetPsramFreeHeapSize()
    
    使用场景:
    - PSRAM内存使用监控
    - PSRAM内存泄漏检测
    - 动态调整PSRAM内存分配策略
    
  • 获取PSRAM堆历史最小空闲大小:

    size_t rtos_get_psram_minimum_free_heap_size(void)
    
    获取系统运行以来PSRAM堆的最小空闲内存大小(字节数)。该值记录了历史最低点,可用于评估PSRAM的最坏内存使用情况。
    
    返回值:
    -返回PSRAM堆历史最小空闲内存大小(字节数)
    
    **FreeRTOS对应接口**: xPortGetPsramMinimumFreeHeapSize()
    
    使用场景:
    - 评估PSRAM内存配置是否合理
    - 发现PSRAM内存泄漏趋势
    - 确定系统PSRAM内存需求的下限
    

使用示例:

#include <os/os.h>

void memory_info_example(void)
{
    size_t total_heap, free_heap, min_free_heap;
    size_t total_psram, free_psram, min_free_psram;

    // 获取SRAM堆信息
    total_heap = rtos_get_total_heap_size();
    free_heap = rtos_get_free_heap_size();
    min_free_heap = rtos_get_minimum_free_heap_size();

    BK_LOGI(NULL, "SRAM Heap: Total=%d bytes, Free=%d bytes, MinFree=%d bytes\r\n",
             total_heap, free_heap, min_free_heap);

    // 获取PSRAM堆信息
    total_psram = rtos_get_psram_total_heap_size();
    free_psram = rtos_get_psram_free_heap_size();
    min_free_psram = rtos_get_psram_minimum_free_heap_size();

    BK_LOGI(NULL, "PSRAM Heap: Total=%d bytes, Free=%d bytes, MinFree=%d bytes\r\n",
             total_psram, free_psram, min_free_psram);

    // 计算内存使用率
    uint32_t sram_usage = ((total_heap - free_heap) * 100) / total_heap;
    uint32_t psram_usage = ((total_psram - free_psram) * 100) / total_psram;

    BK_LOGI(NULL, "Memory usage: SRAM=%d%%, PSRAM=%d%%\r\n",
             sram_usage, psram_usage);
}

void memory_monitor_task(beken_thread_arg_t arg)
{
    while(1) {
        size_t free_heap = rtos_get_free_heap_size();
        size_t min_free_heap = rtos_get_minimum_free_heap_size();

        // 如果空闲内存接近历史最低值,发出警告
        if (free_heap < min_free_heap + 1024) {
            BK_LOGI(NULL, "Warning: Free heap (%d) is close to minimum (%d)\r\n",
                     free_heap, min_free_heap);
        }

        // 如果空闲内存低于阈值,发出严重警告
        if (free_heap < 2048) {
            BK_LOGI(NULL, "Critical: Free heap (%d) is too low!\r\n", free_heap);
        }

        rtos_delay_milliseconds(1000);  // 每秒检查一次
    }
}

备注

  • SRAM堆和PSRAM堆是独立的内存池,分别用于不同的用途

  • 空闲内存大小是动态变化的,会随着内存分配和释放实时更新

  • 历史最小空闲大小从系统启动开始记录,不会自动重置

  • 这些接口可以在任务和中断上下文中安全调用

  • 建议定期监控内存使用情况,特别是长时间运行的应用程序

  • 如果空闲内存持续减少,可能存在内存泄漏问题

  • PSRAM堆仅在配置了PSRAM支持时可用

中断与临界区:

中断与临界区是操作系统中的重要概念,用于保护共享资源和实现原子操作。抽象层提供了统一的中断控制和临界区管理接口。

中断控制接口:

  • 禁用中断:

    uint32_t rtos_disable_int(void)
    
    禁用全局中断,返回之前的中断状态。此函数会自动判断当前上下文(任务或中断)并调用相应的底层接口。
    
    底层实现机制:
    
        使用PRIMASK寄存器实现中断控制
        * 设置PRIMASK寄存器值为1:禁止除NMI和硬fault外的所有中断
        * 设置PRIMASK寄存器值为0:使能中断
        * 通过CPSID I指令设置PRIMASK,通过CPSIE I指令清除PRIMASK
    
    
    返回值:
    -返回禁用前的中断状态标志,需要保存此值用于后续恢复中断
    
    **FreeRTOS对应接口**: port_disable_interrupts_flag() / vPortEnterCritical()
    
    使用场景:
    - 保护非常短的临界代码段(建议使用临界区接口)
    - 需要精确控制中断使能/禁用的场合
    

警告

  • 禁用中断期间不能调用任何可能引起任务切换的API

  • 应尽快恢复中断,避免影响系统实时性

  • 禁用时间过长会导致中断丢失和看门狗复位

  • 启用中断:

    void rtos_enable_int(uint32_t int_level)
    
    恢复之前保存的中断状态
    
    参数说明:
    -int_level: 由rtos_disable_int()返回的中断状态标志
    
    **FreeRTOS对应接口**: port_enable_interrupts_flag() / vPortExitCritical()
    
  • 检查中断是否禁用:

    bool rtos_local_irq_disabled(void)
    
    检查当前中断是否被禁用
    
    返回值:
    -true: 中断已禁用
    -false: 中断未禁用
    
    **FreeRTOS对应接口**: platform_local_irq_disabled()
    
  • 检查是否在中断上下文中:

    bool rtos_is_in_interrupt_context(void)
    
    判断当前代码是否运行在中断服务程序(ISR)中
    
    返回值:
    -true: 在中断上下文中
    -false: 在任务上下文中
    
    **FreeRTOS对应接口**: platform_is_in_interrupt_context()
    
    使用场景:
    - 编写可以在任务和中断中调用的通用函数
    - 根据上下文选择不同的API调用方式
    

临界区接口:

  • 进入临界区:

    uint32_t rtos_enter_critical(void)
    
    进入临界区,禁用中断并获取自旋锁。在SMP系统中,临界区的实现结合了中断禁用和自旋锁(spinlock)机制,确保多个CPU核心之间的互斥访问。
    临界区可以嵌套使用,内部通过计数器管理嵌套层级。
    
    实现机制:
    - 首先禁用全局中断(调用rtos_disable_int())
    - 然后获取自旋锁(spinlock),防止其他CPU核心同时进入临界区
    - 在单核系统中,主要依赖中断禁用实现互斥
    - 在SMP系统中,需要同时禁用中断和获取自旋锁,防止跨核心竞争
    
    返回值:
    -返回进入临界区前的中断状态标志
    
    **FreeRTOS对应接口**: vPortEnterCritical() / taskENTER_CRITICAL()
    
  • 退出临界区:

    void rtos_exit_critical(uint32_t int_level)
    
    退出临界区,释放自旋锁并恢复之前保存的中断状态。必须与rtos_enter_critical()成对使用。
    
    实现机制:
    - 首先释放自旋锁(spinlock)
    - 然后恢复中断状态(调用rtos_enable_int())
    
    参数说明:
    -int_level: 由rtos_enter_critical()返回的中断状态标志
    
    **FreeRTOS对应接口**: vPortExitCritical() / taskEXIT_CRITICAL()
    

使用示例:

#include <os/os.h>

// 示例1:使用中断禁用保护临界代码
void update_shared_variable(void)
{
    uint32_t int_level;

    // 禁用中断
    int_level = rtos_disable_int();

    // 临界代码段 - 快进快出
    shared_counter++;
    shared_flag = true;

    // 恢复中断
    rtos_enable_int(int_level);
}

// 示例2:使用临界区保护
void access_critical_resource(void)
{
    uint32_t int_level;

    // 进入临界区
    int_level = rtos_enter_critical();

    // 临界代码段
    critical_resource.data = new_value;
    critical_resource.updated = true;

    // 退出临界区
    rtos_exit_critical(int_level);
}

// 示例3:上下文感知的函数
void context_aware_function(void)
{
    if (rtos_is_in_interrupt_context()) {
        // 在中断中的处理逻辑
        BK_LOGI(NULL, "Running in ISR\r\n");
    } else {
        // 在任务中的处理逻辑
        BK_LOGI(NULL, "Running in task\r\n");
    }
}

// 示例4:使用宏简化临界区操作
void macro_example(void)
{
    GLOBAL_INT_DECLARATION();

    GLOBAL_INT_DISABLE();
    // 临界代码段
    shared_data = new_value;
    GLOBAL_INT_RESTORE();
}

备注

  • 临界区 vs 互斥锁
    • 临界区:通过禁用中断和自旋锁实现,保护时间极短的代码段(微秒级)

    • 互斥锁:通过任务调度实现,保护时间较长的代码段(毫秒级),支持优先级继承

  • SMP系统中的临界区
    • 在SMP系统中,临界区使用中断禁用+自旋锁双重保护机制

    • 中断禁用防止同一核心的中断打断临界代码

    • 自旋锁防止其他CPU核心同时访问临界资源

    • 自旋锁会持续轮询直到获取到锁,因此临界区代码必须非常短小

  • 临界区中禁用了中断,不能调用任何可能引起任务切换或阻塞的API

  • 临界区代码应该尽可能短小,通常只用于保护几条汇编指令

  • 临界区嵌套使用时,必须保证进入和退出成对调用

  • 临界区时间过长会影响系统实时性和中断响应时间,在SMP系统中还会导致其他核心长时间自旋等待

  • 不建议在临界区中调用BK_LOGI等可能耗时的函数

  • 在SMP系统中,跨核心的共享资源访问应优先考虑使用互斥锁而非临界区

参考示例代码

完整的OS抽象层使用示例可以在以下路径找到:

  • API头文件:includeos/os.h

  • 任务示例:components/demos/os/os_thread/os_thread.c

  • 队列示例:components/demos/os/os_queue/os_queue.c

  • 信号量示例:components/demos/os/os_sem/os_sem.c

  • 互斥锁示例:components/demos/os/os_mutex/os_mutex.c

  • 定时器示例:components/demos/os/os_timer/os_timer.c

  • 事件组示例:components/demos/os/os_event/os_event_group.c

这些示例展示了OS抽象层API的典型使用方法,建议开发者参考学习。

示例代码特点:

  • os_thread: 展示任务创建、删除和线程间协作

  • os_queue: 展示队列的创建、发送和接收消息

  • os_sem: 展示二值信号量用于任务同步

  • os_mutex: 展示互斥锁保护共享资源

  • os_timer: 展示单次定时器和周期定时器的使用

  • os_event: 展示事件组的多种等待模式和同步机制,包括:
    • 中断中设置事件标志

    • 逻辑与等待(所有事件都满足)

    • 逻辑或等待(任一事件满足)

    • 多任务事件同步点

    • 自动清除和手动清除事件标志