杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

实时操作系统与任务调度

AC63 蓝牙音频 SDK 基于 FreeRTOS V9.0.0 内核与杰理(JieLi)自研的 os_api 抽象层构建多任务实时系统。本文档介绍该 SDK 的 RTOS 内核选型、统一任务/信号量/消息队列 API、任务优先级与 taskq 调度机制,以及典型任务的创建与生命周期管理方式。

Purpose and Scope

本页面覆盖"系统运行时"目录下的 RTOS 内核与任务调度机制,包括:

  • 内核选型与双配置体系(FreeRTOS 内核 + os_api 统一接口层)
  • os_task_create 等任务生命周期 API 与 taskq 消息机制
  • 优先级、时基(tick)、调度器配置与典型任务的创建参数
  • 信号量、互斥锁等同步原语在 SDK 中的使用方式

与任务调度并列的系统运行时话题(如中断处理、系统定时器、电源管理、看门狗)属于目录下的其他页面,本文不展开;蓝牙协议栈、音频编解码等上层功能虽然依赖任务调度,但其内部实现归各自页面(如 Mesh、音频编解码)负责。

Overview

AC63 系列芯片的固件是一个典型的嵌入式实时多任务系统:音频播放、蓝牙协议栈、UI 交互、外设驱动等子系统各自运行在独立任务中,由 RTOS 内核统一调度。SDK 将内核能力封装为以 os_ 前缀开头的统一 API(声明于 os_api.h),使上层应用与底层内核(FreeRTOS 或厂商自研内核)解耦。

从仓库结构可以看到两套内核头文件并存:

  • include_lib/system/os/FreeRTOS/:FreeRTOS V9.0.0 标准内核头文件(FreeRTOS.h、task.h、queue.h、semphr.h)及其工程配置 FreeRTOSConfig.h;
  • include_lib/system/os/os_api.h / os_cfg.h / os_type.h / os_error.h:杰理统一 OS 抽象层,采用 µC/OS-II 风格 API(如 os_task_create、os_taskq_post、OS_SCHED_LOCK_EN 等),并扩展了 taskq(任务消息队列)机制。

上层代码(apps/、cpu/ 下的驱动与测试代码)几乎全部通过 os_api.h 的 os_* 接口创建任务与同步对象,而不是直接调用 FreeRTOS API。这一抽象让同一份应用代码可以跨内核移植,同时 os_cfg.h 中保留了 µC/OS-II 风格的可裁剪配置(任务数上限 31、tick 100Hz、时间片轮转等)。

Architecture

flowchart TD
    subgraph sg_App["应用层 apps/"]
        AppTask["音频/蓝牙/UI 任务<br/>audio_pwm_task / iic_slave_task"]
    end

    subgraph sg_Driver["驱动与测试 cpu/"]
        DriverTask["外设驱动任务<br/>uart_u_task / ledc"]
    end

    subgraph sg_OsApi["OS 抽象层 include_lib/system/os/"]
        OsApi["os_api.h<br/>os_task_create / os_taskq_post / os_sem_create"]
        OsCfg["os_cfg.h<br/>µC/OS-II 风格配置"]
        OsType["os_type.h / os_error.h"]
    end

    subgraph sg_Kernel["RTOS 内核"]
        FreeRTOS["FreeRTOS V9.0.0<br/>task.h / queue.h / semphr.h"]
        FrConfig["FreeRTOSConfig.h<br/>8 级优先级 / 100Hz tick"]
    end

    AppTask -->|"os_* 调用"| OsApi
    DriverTask -->|"os_* 调用"| OsApi
    OsApi -->|"封装调度原语"| FreeRTOS
    OsApi --> OsCfg
    OsApi --> OsType
    FreeRTOS --> FrConfig

架构设计意图:内核层只负责最底层的任务切换与同步原语;os_api.h 提供稳定的、面向业务的编程接口,并把"任务 + 私有消息队列(taskq)"这一常用模型合并到 os_task_create 的参数中(qsize 参数),从而在创建任务时就为它分配好消息队列——这是本 SDK 任务模型区别于裸 FreeRTOS 的关键设计,详见下文 taskq 机制。

内核与配置体系

SDK 中存在两套并行配置:

FreeRTOS 内核配置(FreeRTOSConfig.h):configUSE_PREEMPTION = 1(抢占式调度)、configTICK_RATE_HZ = 100(100Hz 时基)、configMAX_PRIORITIES = 8、configTOTAL_HEAP_SIZE = 30KB、configMAX_TASK_NAME_LEN = 12(任务名最长 12 字节,因此 os_task_create 的 name 参数同样受此限制)、启用互斥锁/递归互斥锁/计数信号量/静态分配,关闭软件定时器与任务通知。

OS 抽象层配置(os_cfg.h):OS_MAX_TASKS = 31(最多 31 个任务)、OS_TICKS_PER_SEC = 100、OS_LOWEST_PRIO = 0、OS_IDLE_PRIO = OS_LOWEST_PRIO(空闲任务优先级为 0,数值越大优先级越高)、OS_TIME_SLICE_EN = 1(时间片轮转)、OS_PRIORITY_INVERSION = 1(处理优先级翻转)、OS_SCHED_LOCK_EN = 1(支持 OSSchedLock/Unlock)、OS_PARENT_TCB = 1(记录父任务 TCB)。可裁剪模块包括 taskq(OS_TASKQ_EN)、互斥锁(OS_MUTEX_EN)、信号量(OS_SEM_EN)等,全部使能。

任务生命周期 API

os_api.h 是任务调度的核心接口文件,先以 typedef void *TaskHandle_t 定义任务句柄,再提供全套任务管理函数。所有任务相关 API 均返回 int 错误码。

任务创建:os_task_create

int os_task_create(void (*task)(void *p_arg),
                   void *p_arg,
                   u8 prio,
                   u32 stksize,
                   int qsize,
                   const char *name);

Source: os_api.h

参数语义(来自头文件注释):

参数含义
task任务回调函数,签名 void (*)(void *)
p_arg传给任务回调的参数指针
prio任务优先级(u8),数值越大优先级越高;空闲任务为 0
stksize任务栈大小,单位是 u32(4 字节字),与常见以字节为单位的 API 不同
qsize任务私有消息队列(taskq)大小,单位字节;传 0 表示不创建队列
name任务名,长度不能超过 configMAX_TASK_NAME_LEN(12 字节)

设计意图:把"栈 + 优先级 + 消息队列"打包成一个创建调用,使应用只需一行代码即可得到一个可接收 taskq 消息的完整任务。这是 SDK 各驱动任务(如 audio_pwm_task、iic_slave)的标准创建方式。

多核亲和创建:os_task_create_affinity_core

int os_task_create_affinity_core(void (*task)(void *p_arg),
                                 void *p_arg,
                                 u8 prio,
                                 u32 stksize,
                                 int qsize,
                                 const char *name,
                                 u8 core);

Source: os_api.h

在支持多核的平台上(os_cpu.h 定义 OS_CPU_CORE),通过末尾的 core 参数把任务绑定到指定核,用于把实时性要求高的任务(如音频、射频)与普通任务隔离。

任务删除协议

int os_task_del_req(const char *name);   /* 请求删除任务 */
int os_task_del_res(const char *name);   /* 任务响应删除,标记资源已释放 */
int os_task_del(const char *name);       /* 直接删除任务 */

Source: os_api.h

删除采用"请求-应答"两阶段协议(与 OS_TASK_DEL_REQ / OS_TASK_DEL_RES / OS_TASK_DEL_OK 状态常量对应):请求方调用 os_task_del_req,被删任务在合适时机调用 os_task_del_res 确认自身资源已释放(可以用 OS_TASK_SELF 指代自己),避免任务正在使用资源时被强行删除。任务句柄别名 OS_TASK_SELF(0x1)与 OS_TASK_FATHER(0x2)用于在回调中引用当前任务或父任务。

延时与当前任务查询

void os_time_dly(int time_tick);            /* 任务延时,单位 tick;中断或关总中断下禁止调用 */
const char *os_current_task();              /* 返回当前任务名,可用于日志/调试 */

Source: os_api.h

os_time_dly 以 tick 为单位让出 CPU,是任务循环中最常用的"节流"手段;头文件明确要求中断上下文或系统总中断关闭期间不得调用。

taskq 任务消息队列机制

os_api.h 定义了四类队列消息类型:

#define Q_MSG       0x100000
#define Q_EVENT     0x200000
#define Q_CALLBACK  0x300000
#define Q_USER      0x400000

Source: os_api.h

消息类型的高 8 位作为分类标签,供 os_taskq_accept 等接收接口区分消息来源:Q_MSG 普通消息、Q_EVENT 事件通知、Q_CALLBACK 回调消息、Q_USER 用户自定义消息。

发送:os_taskq_post

int os_taskq_post(const char *name, int argc, ...);

Source: os_api.h

向名为 name 的任务队列发送 Q_USER 类型消息。argc 为后续可变参数个数,最多 8 个 int;可变参数会打包进该任务创建时分配的 taskq。这是 SDK 中任务间通信最主要的途径:生产者不需要持有消费者任务的对象引用,只需知道任务名,天然解耦。

接收:os_taskq_accept(非阻塞)

/* 非阻塞方式查询 taskq
 * @param argc 最大可获取的 queue 长度,单位(int)
 * @param argv 存放 queue 的 buf
 */

Source: os_api.h

接收侧通常在主循环中调用非阻塞的 os_taskq_accept 轮询自己的队列,取出消息后按消息类型分发。这一"创建时带队列 + 主循环轮询队列"的模型构成了 SDK 绝大多数驱动任务的主循环骨架(见"核心流程")。

同步原语

除 taskq 外,os_api.h 还提供信号量、互斥锁、消息队列、定时器等同步接口。仓库中的典型用法(来自实际驱动代码):

os_sem_create(&pwm_need_resume_sem, 0);                       /* 创建初值 0 的信号量 */
os_task_create(audio_pwm_task, NULL, 2, 1024, 128, "audio_pwm_task");

Sources: audio_pwm.c

os_sem_create(&ledc0_sem, 1);                                 /* 创建初值 1 的信号量(互斥用法) */
os_sem_create(&ledc1_sem, 1);

Source: ledc.c

信号量初值为 1 时充当互斥锁保护共享外设寄存器(如 LEDC 通道),初值为 0 时用作事件通知(如 PWM 唤醒)。os_cfg.h 中 OS_SEM_EN、OS_MUTEX_EN、OS_PRIORITY_INVERSION 等开关表明内核原生支持信号量/互斥锁及优先级翻转处理,锁等待不会无限阻塞高优先级任务。

调度器行为与核心流程

抢占式调度与时间片

FreeRTOSConfig.h 中 configUSE_PREEMPTION = 1 启用抢占式调度:就绪态中优先级最高的任务立即获得 CPU,低优先级任务无需主动让出。configTICK_RATE_HZ = 100 提供 100Hz 时基,configUSE_16_BIT_TICKS = 0 使用 32 位 tick 计数(约 497 天溢出,远大于 16 位 tick 的 655 秒)。configUSE_PORT_OPTIMISED_TASK_SELECTION = 1 使用硬件指令优化任务选择。

同一优先级内,os_cfg.h 的 OS_TIME_SLICE_EN = 1 开启时间片轮转,配合 configIDLE_SHOULD_YIELD = 1,保证同级任务公平共享 CPU,空闲任务不独占总线。OS_IDLE_PRIO = 0 表明空闲任务处于最低优先级,任何业务任务都优先于它运行。

典型任务生命周期

一个典型驱动任务(如 iic_slave_task)的生命周期如下:

sequenceDiagram
    participant Main as 主流程/其他任务
    participant Api as os_api 抽象层
    participant Kern as RTOS 内核
    participant Task as 任务回调 (如 iic_slave_task)

    Main->>Api: os_task_create(task, p_arg, prio, stksize, qsize, name)
    Api->>Kern: 分配 TCB/栈/队列,加入就绪表
    Kern-->>Api: 返回错误码
    Api-->>Main: 返回错误码
    Note over Kern: 调度器按优先级运行最高就绪任务
    Kern->>Task: 切换上下文,进入任务函数
    loop 任务主循环
        Task->>Api: os_taskq_accept(&argc, argv) 非阻塞查询
        Api-->>Task: 消息或空
        Task->>Task: 分发处理 (Q_USER/Q_EVENT...)
        Task->>Api: os_time_dly(ticks) 延时让出 CPU
    end
    Task->>Api: os_task_del_res(OS_TASK_SELF) 响应删除请求

任务创建后即进入就绪队列,由内核按优先级调度;任务内部是一个"查询 taskq → 分发处理 → 延时"的无限循环。qsize > 0 时队列由内核在创建阶段一并分配,因此 os_taskq_post 无需再做分配即可投递消息。

优先级设计参考

从仓库中的实际任务创建参数可以归纳本 SDK 的优先级使用习惯(数值越大优先级越高,0 为空闲任务):

任务优先级栈大小(u32)qsize(字节)出处
空闲任务0——os_cfg.h
audio_pwm_task(音频 PWM)21024128cpu/bd19/audio_pwm.c
iic_slave(IIC 从机)30102464cpu/br25/iic_slave_test.c
uart_u_task(UART 测试)315120cpu/bd29/uart_test.c
uart_flow_ctrl_task311280cpu/br23/uart_test.c

可见:实时性要求不高的后台任务(音频 PWM)用低优先级;IIC、UART 等与外部交互的驱动任务用 30~31 高优先级;不接收 taskq 消息的任务(UART 测试)qsize 传 0,节省内存。应用开发时应在 1~31 之间为任务选取合适优先级,避免多个高优先级任务互相饿死。

使用示例

示例一:创建带 taskq 的驱动任务(音频 PWM)

cpu/bd19/audio_pwm.c 展示了"信号量通知 + 低优先级任务"的典型组合:PWM 中断或状态变化通过信号量唤醒任务,任务处理完毕后自行延时。

    clk_set("sys", 96 * 1000000L);
    os_sem_create(&pwm_need_resume_sem, 0);
    os_task_create(audio_pwm_task, NULL, 2, 1024, 128, "audio_pwm_task");

Source: audio_pwm.c

创建参数解读:优先级 2(低优先级后台任务)、栈 1024×4 字节、队列 128 字节、任务名 audio_pwm_task(符合 12 字节限制)。

示例二:创建高优先级外设任务(IIC 从机)

    printf("%s() %d\n", __func__, __LINE__);
    os_task_create(iic_slave_task, NULL, 30, 1024, 64, "iic_slave");

Source: iic_slave_test.c

IIC 从机要求低延迟响应主机请求,因此优先级给到 30,并分配 64 字节 taskq 用于接收上层控制消息。

示例三:音频编码任务与信号量(Mesh 应用)

        audio_encoder_task_create(encode_task, "audio_enc");
        ...
        os_sem_create(&demo_enc->pcm_frame_sem, 0);

Sources: audio_codec_demo.c

上层还封装了更高层的任务工厂(如 audio_encoder_task_create),其内部同样基于 os_task_create;pcm_frame_sem 初值为 0,用于 PCM 帧到达时唤醒编码任务——信号量在此作为"帧级事件通知"使用。

配置选项

FreeRTOS 内核配置(include_lib/system/os/FreeRTOS/FreeRTOSConfig.h)

配置宏值说明
configUSE_PREEMPTION1抢占式调度
configTICK_RATE_HZ100系统时基 100Hz(10ms/tick)
configMAX_PRIORITIES8内核级最大优先级数(抽象层为 31 级)
configTOTAL_HEAP_SIZE30×1024内核堆大小 30KB
configMINIMAL_STACK_SIZE256最小栈(u32 单位语境下为 256 字)
configMAX_TASK_NAME_LEN12任务名最大长度(字节)
configUSE_MUTEXES / configUSE_RECURSIVE_MUTEXES1 / 1互斥锁与递归互斥锁
configUSE_COUNTING_SEMAPHORES1计数信号量
configSUPPORT_STATIC_ALLOCATION1支持静态分配(配合 configUSE_MALLOC_FAILED_HOOK=1)
configUSE_16_BIT_TICKS032 位 tick 计数
configCHECK_FOR_STACK_OVERFLOW0未开启栈溢出运行时检查
configUSE_TIMERS0关闭软件定时器任务
configUSE_TASK_NOTIFICATIONS0关闭任务通知(消息传递依赖 taskq)

OS 抽象层配置(include_lib/system/os/os_cfg.h)

配置宏值说明
OS_MAX_TASKS31应用最大任务数
OS_TICKS_PER_SEC100每秒 tick 数,与内核时基一致
OS_LOWEST_PRIO / OS_IDLE_PRIO0最低/空闲任务优先级
OS_TIME_SLICE_EN1同优先级时间片轮转
OS_PRIORITY_INVERSION1处理优先级翻转
OS_SCHED_LOCK_EN1调度锁(OSSchedLock/Unlock)
OS_PARENT_TCB1记录父任务 TCB
OS_TASKQ_EN 及子开关1taskq 全套功能(post/pend/accept/flush/query)
OS_MUTEX_EN1互斥锁模块
OS_SEM_EN1信号量模块

API 参考(os_api.h 核心接口)

int os_task_create(void (*task)(void *p_arg), void *p_arg, u8 prio, u32 stksize, int qsize, const char *name)

创建任务并使其进入就绪态。stksize 单位为 u32(字),qsize 为 taskq 字节数,name 不得超过 12 字节。返回错误码,0 表示成功。

int os_task_create_affinity_core(...同上..., u8 core)

多核平台下将任务绑定到指定 core 运行。

int os_task_del_req(const char *name) / int os_task_del_res(const char *name) / int os_task_del(const char *name)

任务删除三接口:请求删除、任务自答(资源已释放)、直接删除。任务自身可用 OS_TASK_SELF 代替任务名;OS_TASK_DEL_REQ/RES/OK 三个常量标记删除协议状态。

int os_taskq_post(const char *name, int argc, ...)

向指定任务发送 Q_USER 消息,argc 个可变参数(最多 8 个 int)打包入目标任务的 taskq。Throws/返回:错误码;目标任务不存在或队列满时返回非 0。

int os_taskq_accept(int argc, int *argv)(非阻塞)

任务主循环中查询自身 taskq,argc 表示可容纳的最大消息数(int 单位),argv 为接收缓冲区。无消息时立即返回;消息分类标签为 Q_MSG/Q_EVENT/Q_CALLBACK/Q_USER。

void os_time_dly(int time_tick)

任务延时 time_tick 个 tick 并让出 CPU。约束:不得在中断或系统总中断关闭状态下调用。

const char *os_current_task(void)

返回当前运行任务的任务名,用于日志与调试。

void os_init(void) / void os_start(void) / void os_init_tick(int)

内核初始化/启动/时基初始化,保留接口,由系统启动代码在 main 早期调用。

失败模式、边界情况与并发

  • 任务名长度限制:configMAX_TASK_NAME_LEN = 12,超长任务名会被截断或导致创建失败;同时 os_taskq_post/os_task_del_* 依赖任务名定位任务,命名需唯一且稳定。
  • taskq 溢出:qsize 有限(常见 64~128 字节),高频率生产者可能把队列写满,os_taskq_post 返回非 0 错误码。生产者的调用方代码必须检查返回值,必要时改用 os_taskq_post_front(优先级插入)或加大 qsize。
  • 消息参数上限:单次 os_taskq_post 最多 8 个 int;传指针时需保证指针指向的对象在消费端处理完前保持有效(典型做法是传静态/全局对象地址)。
  • 优先级反转:OS_PRIORITY_INVERSION = 1 表明内核按 µC/OS-II 惯例用优先级继承/置顶处理互斥锁导致的优先级反转;信号量(非互斥锁)场景下无此保护,应避免用二值信号量保护共享资源。
  • 栈溢出:configCHECK_FOR_STACK_OVERFLOW = 0,运行时不做栈溢出检查,因此 stksize 必须按最深层调用链加中断现场余量估算;局部大数组/递归是栈溢出的主要来源。
  • 中断上下文约束:os_time_dly 及阻塞式 pend 接口禁止在中断中调用;中断与任务之间应通过"中断置信号量/发 taskq → 任务处理"的模式交互。
  • 删除安全性:两阶段删除协议(del_req → del_res)确保任务不会在持有资源时被销毁;被删任务应在主循环合适位置调用 os_task_del_res(OS_TASK_SELF)。

性能与运维

  • 时基 100Hz 意味着 os_time_dly(1) 的定时精度为 10ms;需要更高精度时使用硬件定时器(见系统定时器页面),不要依赖任务延时。
  • 高优先级任务(30~31)应保持"短小精悍":长时间运行会阻塞所有低优先级任务;长耗时操作应拆分为多步 + 状态机,或临时降低自身优先级。
  • configUSE_MALLOC_FAILED_HOOK = 1 提供分配失败钩子,便于捕获堆耗尽;configUSE_IDLE_HOOK = 1 提供空闲钩子,可在此实现低功耗/看门狗喂狗。
  • 任务栈与 qsize 的分配来自内核堆(30KB),任务数量(OS_MAX_TASKS = 31)与堆大小共同构成内存预算上限,新增任务前需评估剩余堆空间。

扩展点

  • 空闲钩子:configUSE_IDLE_HOOK = 1,实现 vApplicationIdleHook 可接管空闲期行为(低功耗、统计)。
  • 分配失败钩子:configUSE_MALLOC_FAILED_HOOK = 1,实现 vApplicationMallocFailedHook 处理堆耗尽。
  • 任务名寻址:os_taskq_post/os_task_del_req 均以任务名为寻址键,可通过封装"注册表 + 名称常量"统一管理任务名,避免字符串散落。
  • 高层任务工厂:如 apps/mesh/audio_codec_demo.c 中的 audio_encoder_task_create,可在 os_task_create 之上再封装领域任务(自带参数结构、队列、信号量),保持底层 API 稳定。

Related Links

  • os_api.h(OS 抽象层 API 声明)
  • os_cfg.h(OS 抽象层配置)
  • FreeRTOSConfig.h(FreeRTOS 内核配置)
  • task.h / queue.h / semphr.h(FreeRTOS 内核头文件)
  • 相关实现示例:audio_pwm.c、iic_slave_test.c、audio_codec_demo.c
  • 相邻页面:系统启动流程(os_init/os_start 调用链)、中断与事件机制、系统定时器
Next
消息事件机制