杰理 SDK 文档中心
首页
首页
  • 项目概览

    • SDK 简介与核心特性
    • 芯片平台与硬件资料
    • SDK 版本与发布信息
  • 快速开始

    • 环境搭建与工具链
    • 编译工程
    • 烧录与量产工具
  • 工程结构与构建系统

    • 工程目录布局
    • 构建与链接配置
  • 应用层开发

    • mbox_flash 应用框架
    • 板级支持包 (BSP)
    • 公共应用模块
    • UI 显示子系统
  • 蓝牙子系统

    • BLE 控制器、链路层与 HCI 传输
    • GATT 服务框架
    • BLE 应用示例:遥控器 / Dongle / 对讲机
    • 经典蓝牙支持
  • 音频子系统

    • 音频编解码器
    • 音频设备接口 (DAC / ADC / APA)
    • 音效处理与 EQ
    • 播放、录音与 MIO 工作流
  • 设备与文件系统

    • 存储设备驱动 (NorFlash / SDMMC / USB)
    • 文件系统 (FAT / nor_fs / SYDF)
    • 设备管理框架 (dev_mg)
  • 系统服务与电源管理

    • 消息机制 (msg / hot_msg)
    • 配置与参数存储 (app_config / VM)
    • 电源管理 (SOFT OFF / POWER DOWN)
  • 固件升级

    • 升级框架总览 (code_v1 / code_v2)
    • 双 Bank 升级机制
    • 升级通道:UART / 测试盒 / BLE OTA / USB / SD
  • 补丁包与版本维护

    • 版本升级补丁链 (v1.1.0 → v1.4.0)
    • 问题修复补丁
    • 固件裁剪与资源优化
  • 开发工具与支持

    • 辅助工具与脚本
    • 文档、配置说明与常见问题

板级支持包 (BSP)

板级支持包(Board Support Package)为 AW30N BLE SDK 提供基于事件驱动的板级主循环(bsp_loop)与事件分发机制,是所有外设驱动与应用逻辑在板级汇聚的执行入口。

Purpose and Scope

本页介绍 AW30N SDK 中 sdk/apps/app/bsp/ 目录下的板级支持包实现,重点包括:

  • BSP 事件模型(BSP_EVENT 枚举与 32 位事件位图 bsp_event)的设计与工作原理;
  • 事件投递(bsp_post_event)、事件获取(bsp_get_event)与板级主循环(bsp_loop)的完整控制流;
  • 与实时操作系统临界区(OS_ENTER_CRITICAL / OS_EXIT_CRITICAL)的并发协作方式;
  • 100ms 周期性事件对外部 NorFlash 缓存同步(_norflash_cache_sync_timer)的驱动作用;
  • 构建期板级配置(post_build/bd49/AW30N_config_tool/conf/source/board* Lua 脚本)与运行期 BSP 的分工。

以下主题属于其它页面,不在本页展开:具体外设驱动(如 UART、I2C、SPI)的内部实现、协议栈(BLE)事件处理、以及应用层业务逻辑。

Overview

在 JieLi(杰理)系列嵌入式 SDK 中,BSP 层承担"板级粘合"职责:它把硬件定时器中断、外设回调等异步事件统一抽象为有限集合的软件事件(BSP_EVENT),再由一个单线程主循环串行消化,避免多中断上下文直接竞争共享资源。

AW30N 的 BSP 实现刻意保持轻量——整个运行期代码只有 bsp_loop.c / bsp_loop.h 两个文件,核心是一个 32 位无符号位图:

  • 每个 bit 对应一个 BSP_EVENT(当前定义 B_EVENT_100MS);
  • 投递事件 = 置位;获取事件 = 用 CPU 的 CLZ(Count Leading Zeros)指令找到最高优先级的置位 bit 并清除;
  • 事件读取/清除全程处于临界区保护下,保证中断上下文与主循环上下文之间无竞态。

该设计使得"外设中断 → 置位事件 → 主循环处理"成为整个板级软件的标准协作范式:中断里只做最少的置位操作,重活全部挪到 bsp_loop 中执行,缩短中断关断时间、提升实时性。

构建期板级配置由 AW30N 配置工具侧的 Lua 脚本(board-main.lua、board_common/board-main.lua)完成,负责引脚复用、时钟与存储布局等静态参数,运行期 BSP 则消费这些参数运行。

Architecture

flowchart TD
    subgraph sg_ISR["中断上下文 (ISR / Timer)"]
        HW_Timer["硬件定时器中断"]
        HW_Event["外设/系统事件"]
    end

    subgraph sg_BSP["BSP 板级支持包 (bsp/start)"]
        PostEvent["bsp_post_event()<br/>置位事件位图"]
        EventMask["bsp_event (u32 位图)<br/>bit N = BSP_EVENT N"]
        GetEvent["bsp_get_event()<br/>CLZ 取最高优先级事件"]
        Loop["bsp_loop()<br/>板级主循环"]
        Sync["_norflash_cache_sync_timer(5)<br/>NorFlash 缓存同步"]
    end

    subgraph sg_OS["RTOS 支撑"]
        Critical["OS_ENTER/EXIT_CRITICAL<br/>CPU_SR_ALLOC"]
    end

    subgraph sg_APP["应用层"]
        AppLoop["应用主循环 / 调度器"]
    end

    HW_Timer -->|"B_EVENT_100MS"| PostEvent
    HW_Event -->|"自定义 BSP_EVENT"| PostEvent
    PostEvent --> EventMask
    EventMask --> GetEvent
    Critical --> PostEvent
    Critical --> GetEvent
    GetEvent --> Loop
    Loop -->|"TFG_EXT_FLASH_EN 开启时"| Sync
    Loop --> AppLoop

架构说明

  • 中断上下文(左侧):硬件定时器或外设中断是事件的产生方。中断处理中调用 bsp_post_event(),只做"置位"这一原子操作后即可返回,中断占用时间极短。
  • BSP 核心(中间):bsp_event 位图是事件的中枢存储;bsp_get_event() 通过 clz 指令在常数时间内挑选出优先级最高的事件(bit 编号越小优先级越高),并自动清除该位,保证同一事件不会被重复消费。
  • RTOS 支撑(下方):所有对位图的读写都包裹在 OS_ENTER_CRITICAL() / OS_EXIT_CRITICAL() 之间,配合 CPU_SR_ALLOC() 保存/恢复中断状态,实现关中断级的互斥。这是典型的"关中断保护共享变量"嵌入式范式,无需信号量即可安全跨上下文传递事件。
  • 板级主循环(右侧):bsp_loop() 是消费方,每轮取一个事件并分发处理。当前唯一处理项是 B_EVENT_100MS,在启用外部 Flash(TFG_EXT_FLASH_EN)时周期性调用 _norflash_cache_sync_timer(5),维持 NorFlash 数据缓存的一致性。

核心实现解析

1. 事件模型:BSP_EVENT 与 32 位事件位图

bsp_loop.h 定义了板级事件的枚举与对外 API:

typedef enum {
    B_EVENT_100MS = 0,

    B_NO_EVENT = 32,
} BSP_EVENT;

void bsp_event_init(void);
int bsp_post_event(BSP_EVENT be);
int bsp_loop(void);

Source: bsp_loop.h

设计要点:

  • B_EVENT_100MS = 0:100ms 周期性事件,由系统定时器中断周期性投递,是整个 BSP 的"心跳"。
  • B_NO_EVENT = 32:不是真正的事件,而是"无事件"哨兵值。它恰好等于位图的位数(32),因此永远不可能落在位图中,用于 bsp_get_event() 返回"当前无事件"。
  • 枚举值被设计为 0 ~ 31 的连续范围,与 unsigned int bsp_event(32 位)一一对应。这是后续 be / 32 校验和 BIT(be) 置位能够成立的前提。

2. 事件投递:bsp_post_event

int bsp_post_event(BSP_EVENT be)
{
    int err = 0;
    CPU_SR_ALLOC();
    OS_ENTER_CRITICAL();
    if (0  != (be / 32)) {
        err = E_BSP_EVENT;
    } else {
        bsp_event |= BIT(be);
    }
    OS_EXIT_CRITICAL();
    return err;
}

Source: bsp_loop.c

关键逻辑:

  1. 入参校验:be / 32 != 0 意味着 be >= 32,即越界(无法映射到位图),返回错误码 E_BSP_EVENT。这防止了非法枚举值导致位图越界/未定义行为。
  2. 原子置位:bsp_event |= BIT(be) 在 OS_ENTER_CRITICAL() 与 OS_EXIT_CRITICAL() 之间执行。CPU_SR_ALLOC() 声明保存 CPU 状态的局部变量,进入临界区时关中断、退出时按保存值恢复中断状态——这保证 ISR 与主循环并发投递时不会丢失或错乱事件位。
  3. 可重入/并发安全:即使多个中断嵌套投递,由于每个投递都是"读-或-写"完整临界区操作,位图始终一致。
  4. 被注释掉的 log_info 表明这里原本可打印每次投递的事件值,方便调试;默认关闭以避免高频日志开销。

3. 事件获取:bsp_get_event(静态,CLZ 优先级仲裁)

static BSP_EVENT bsp_get_event(void)
{
    u32 i;
    CPU_SR_ALLOC();
    OS_ENTER_CRITICAL();
    u32 event_cls;
    BSP_EVENT event = B_NO_EVENT;
    __asm__ volatile("%0 = clz(%1)":"=r"(event_cls):"r"(bsp_event));
    if (event_cls != 32) {
        event = 31 - event_cls;
        bsp_event &= ~BIT(event);
    }
    OS_EXIT_CRITICAL();
    return event;
}

Source: bsp_loop.c

这是整个 BSP 最精妙的部分:

  • CLZ 指令:clz(Count Leading Zeros)统计 bsp_event 二进制表示中从最高位起的连续 0 个数。event = 31 - event_cls 即得到最高置位 bit 的编号。
  • 固定优先级仲裁:由于 CLZ 总是返回最高置位位,位图低编号 bit(如 B_EVENT_100MS = 0)拥有最高优先级。多个事件同时到达时,主循环每轮只取出一个最高优先级事件处理,天然实现"高优先级事件优先、低优先级事件排队"的调度。
  • 取走即清:bsp_event &= ~BIT(event) 在同一个临界区内清除该位,保证"取事件"是原子的,不会出现两个上下文取到同一事件的问题。
  • 空判断:event_cls == 32 表示 bsp_event == 0(32 位全 0),返回 B_NO_EVENT 哨兵。
  • 值得注意:bsp_event 是全局非静态变量(unsigned int bsp_event;),而获取函数是静态的——外部模块通过 bsp_post_event 投递、通过 bsp_loop 消费,无法直接篡改位图,封装性良好。

4. 板级主循环:bsp_loop

void _norflash_cache_sync_timer(u32 sync_step);
int bsp_loop(void)
{
    u32 event = bsp_get_event();

    switch (event) {
    case B_EVENT_100MS:
#if TFG_EXT_FLASH_EN
        _norflash_cache_sync_timer(5);
#endif
        break;
    default:
        break;
    }
    return 0;
}

Source: bsp_loop.c

  • bsp_loop() 每被调用一次,就消费一个最高优先级事件。它本身不循环,真正的调度循环由上层(应用主循环/调度器)驱动,这使 BSP 保持"被动服务"角色,便于与 RTOS 任务模型集成。
  • B_EVENT_100MS 分支在 TFG_EXT_FLASH_EN(外部 Flash 使能)为真时调用 _norflash_cache_sync_timer(5)。该函数由 NorFlash 驱动提供,负责按步进同步缓存;5 是每次同步的步进参数。若未启用外部 Flash,则整个分支为空操作,避免无意义开销。
  • default 分支静默忽略未识别事件,保证扩展新事件时不会破坏既有行为(fail-open 风格)。
  • 文件头部通过 LOG_TAG_CONST NORM / LOG_TAG "[bsp loop]" 接入 SDK 日志系统,后续排查可用该 TAG 过滤 BSP 日志。

5. 初始化:bsp_event_init

void bsp_event_init(void)
{
    CPU_SR_ALLOC();
    OS_ENTER_CRITICAL();
    bsp_event = 0;
    OS_EXIT_CRITICAL();
}

Source: bsp_loop.c

在系统启动早期调用,将位图清零,建立干净的初始状态。同样置于临界区内,避免初始化瞬间被中断投递污染。

Core Flow:事件从产生到消费的完整链路

sequenceDiagram
    participant T as 硬件定时器/ISR
    participant P as bsp_post_event()
    participant M as bsp_event 位图
    participant G as bsp_get_event()
    participant L as bsp_loop()
    participant F as _norflash_cache_sync_timer()

    T->>P: 周期触发,投递 B_EVENT_100MS
    activate P
    P->>P: 校验 be/32 == 0
    P->>M: 临界区内 bsp_event |= BIT(be)
    deactivate P
    P-->>T: 返回 0(成功)

    Note over M: 主循环空闲若干周期后轮到 BSP

    L->>G: 调用 bsp_get_event()
    activate G
    G->>M: 临界区内读取 bsp_event
    G->>G: clz 计算最高置位 bit = 0
    G->>M: bsp_event &= ~BIT(0)(取走并清除)
    deactivate G
    G-->>L: 返回 B_EVENT_100MS

    L->>L: switch 命中 B_EVENT_100MS
    L->>F: TFG_EXT_FLASH_EN 开启时调用 (5)
    F-->>L: 缓存同步步进完成
    L-->>上层调度器: return 0,让出执行权

流程要点

  1. 产生:系统定时器每 100ms 产生一次中断,在中断上下文调用 bsp_post_event(B_EVENT_100MS)。
  2. 置位:临界区内校验并置位,中断立即返回——ISR 开销为常数级(一次校验 + 一次位或操作)。
  3. 仲裁:主循环调用 bsp_loop() → bsp_get_event(),CLZ 在常数时间内选出优先级最高事件并清除对应位。
  4. 消费:switch 分发到具体处理函数;B_EVENT_100MS 触发 NorFlash 缓存同步,保证外部 Flash 读写与缓存一致。
  5. 让权:bsp_loop() 返回后,控制权交还上层调度器,继续处理其它任务。

该闭环体现了嵌入式事件驱动设计的核心权衡:用关中断的短临界区换取跨上下文通信的绝对安全,用单消费循环 + 优先级位图换取确定的调度行为,同时把中断上下文的工作量压到最小。

Usage Examples

基础用法:系统启动时初始化 BSP

在系统上电、RTOS 初始化之前调用 bsp_event_init(),确保事件位图处于已知状态:

#include "bsp_loop.h"

void board_early_init(void)
{
    bsp_event_init();   /* 清零事件位图,建立干净的板级状态 */
}

Source: bsp_loop.h(API 声明来源)

中断上下文投递事件(推荐范式)

任何外设中断或定时器回调中投递事件,只置位、不处理,把耗时逻辑留给主循环:

/* 定时器中断回调:每 100ms 投递一次板级心跳事件 */
void sys_timer_isr(void *priv)
{
    bsp_post_event(B_EVENT_100MS);
}

Source: bsp_loop.c(bsp_post_event 实现与投递语义来源)

主循环消费事件

/* 上层调度循环中周期调用,一次调用消费一个最高优先级事件 */
void app_scheduler_poll(void)
{
    ...
    bsp_loop();   /* 内部自动取最高优先级事件并分发 */
    ...
}

Source: bsp_loop.c(bsp_loop 实现来源)

扩展自定义板级事件

向 BSP_EVENT 枚举追加 0~31 范围内的新值,并在 bsp_loop() 的 switch 中增加对应 case 即可(注意 B_NO_EVENT = 32 之后为非法值):

typedef enum {
    B_EVENT_100MS = 0,
    B_EVENT_KEY_SCAN,      /* 新增:按键扫描事件 */
    B_EVENT_PMU_DETECT,    /* 新增:电源管理检测事件 */

    B_NO_EVENT = 32,
} BSP_EVENT;

Source: bsp_loop.h(枚举结构与边界约束来源)

设计意图:由于 bsp_get_event() 使用 CLZ 仲裁,枚举值越小优先级越高。新增事件时应按优先级从低到高分配编号(即尽量靠后追加),避免改变既有事件的相对优先级。

Configuration Options

选项类型默认值说明
B_EVENT_100MSBSP_EVENT 枚举0100ms 周期事件,优先级最高(bit 0)
B_NO_EVENTBSP_EVENT 枚举32无事件哨兵值,不占用位图;所有新增事件必须小于 32
TFG_EXT_FLASH_EN编译宏由构建配置决定使能外部 NorFlash 时,B_EVENT_100MS 分支调用 _norflash_cache_sync_timer(5) 做缓存同步
E_BSP_EVENT错误码由 errno-base.h 定义bsp_post_event 在入参越界(be >= 32)时返回的错误码
bsp_eventunsigned int 全局量0(经 bsp_event_init)事件位图本体,仅 BSP 内部读写

构建期板级参数(引脚复用、时钟、存储布局等)不在运行期代码中,而是由 AW30N 配置工具侧的 Lua 脚本管理:

  • post_build/bd49/AW30N_config_tool/conf/source/board/board-main.lua:具体板型(board)的板级配置入口;
  • post_build/bd49/AW30N_config_tool/conf/source/board_common/board-main.lua:板级公共配置,供各板型复用。

API Reference

void bsp_event_init(void)

初始化 BSP 事件系统。在临界区内将 bsp_event 位图清零,建立干净的初始状态。应在系统启动早期、任何事件投递之前调用一次。

参数: 无

返回: 无

int bsp_post_event(BSP_EVENT be)

投递一个板级事件。中断上下文与主循环上下文均可调用(临界区保护,可重入)。

参数:

  • be(BSP_EVENT):要投递的事件。取值必须在 [0, 31] 范围内(即 be / 32 == 0)。

返回:

  • 0:投递成功(事件位已置位);
  • E_BSP_EVENT:入参越界(be >= 32),位图未修改。

并发语义: 整个"校验 + 置位"在 OS_ENTER_CRITICAL / OS_EXIT_CRITICAL 临界区内原子完成,可与 ISR 并发安全调用。

int bsp_loop(void)

板级主循环入口。从位图中取出一个最高优先级事件并分发处理;当前仅处理 B_EVENT_100MS(可选触发 NorFlash 缓存同步)。由上层调度器周期性调用。

参数: 无

返回:

  • 0:本轮回调正常完成(无论是否有事件被消费)。

static BSP_EVENT bsp_get_event(void)(内部)

从位图中原子取出并清除最高优先级置位事件。

返回:

  • 已清除的最高置位事件编号(0~31);
  • B_NO_EVENT:位图为空时返回的哨兵值。

并发语义: "读位图 → CLZ 仲裁 → 清除该位"整体处于临界区内,保证单事件只被消费一次。

Failure Modes, Edge Cases & Concurrency

事件越界(be >= 32)

bsp_post_event 通过 be / 32 校验拦截越界事件并返回 E_BSP_EVENT。若绕过该校验(例如直接改写全局 bsp_event,虽然头文件未导出该变量、外部不可见),BIT(be) 在 be >= 32 时行为未定义,可能破坏位图。约定:新增事件必须小于 B_NO_EVENT(32),且枚举按优先级从高到低排列。

位图饱和与事件丢失

bsp_event 是单一位图,同一事件在未消费前重复投递只会置位一次("或"操作幂等)。若 100ms 周期短于主循环处理耗时,投递会被合并,表现为事件节流而非丢失语义——这是该设计的预期行为。若业务需要"计数型"事件(如统计中断次数),不应依赖 BSP 位图,而应在中断中单独累加计数器。

并发与竞态

  • 跨上下文安全:所有位图读写都在关中断临界区内完成,ISR 与主循环之间无竞态,也不需要信号量/互斥锁,规避了"在 ISR 中获取锁"这类经典死锁场景。
  • 临界区长度:bsp_post_event 与 bsp_get_event 的临界区都是几条指令,关中断时间极短,对系统实时性影响可忽略。
  • CLZ 的原子性:CLZ 指令本身是单条 CPU 指令,但"读位图 + 计算 + 清位"必须整体处于临界区,否则可能在读与清之间被中断插入新事件导致误清。实现已正确包裹。

空事件时的行为

位图为空时 bsp_get_event 返回 B_NO_EVENT(32),bsp_loop 落入 default 分支直接返回 0。上层可以高频轮询 bsp_loop() 而无需担心空转开销(每轮仅是临界区 + CLZ + switch)。

依赖项缺失风险

_norflash_cache_sync_timer 由 NorFlash 驱动模块提供,且仅在 TFG_EXT_FLASH_EN 为真时引用。若该宏开启而驱动未链接,会产生链接错误;反之宏关闭时该调用被预编译剔除,无运行时开销。修改该宏时需保证 NorFlash 驱动同步接入。

Performance & Operational Notes

  • 常数级复杂度:投递(置位)与获取(CLZ 仲裁)都是 O(1),不随事件数量增长,适合资源受限的 MCU。
  • ISR 开销最小化:中断上下文只做一次置位即返回,满足"中断里只做最少工作"的实时性原则。
  • 日志:bsp_post_event 内含注释掉的 log_info,排查事件丢失/优先级问题时可按 LOG_TAG "[bsp loop]" 临时开启日志观察投递序列,但高频事件下应避免长期开启。
  • 调试建议:若怀疑事件未按预期到达,可在 bsp_get_event 返回后、switch 前打印 event 值;注意该函数为静态,需在 bsp_loop 内临时加日志。

Extension Points

  1. 新增板级事件:在 BSP_EVENT 枚举中追加 0~31 内的新值,并在 bsp_loop() 增加对应 case。编号越小优先级越高,追加时注意保持既有事件的相对顺序。
  2. 复用 100ms 心跳:B_EVENT_100MS 已由系统定时器周期性投递,任何"周期性板级维护"(如 LED 扫描、看门狗喂狗、ADC 周期采样)都可以挂到该分支,无需新增定时器。
  3. 外部 Flash 缓存同步:_norflash_cache_sync_timer(5) 的步进参数可在 B_EVENT_100MS 分支调整,以权衡缓存同步粒度与 CPU 占用。
  4. 与上层调度集成:bsp_loop() 是被动服务模型,可被任意任务/调度循环周期调用;如需更细粒度调度,可拆分为多个 bsp_loop_xxx() 入口(当前未实现,属未来扩展)。

Related Links

  • bsp_loop.c — BSP 主循环与事件实现
  • bsp_loop.h — BSP 事件枚举与 API 声明
  • board-main.lua — 板型构建期配置(AW30N 配置工具)
  • board_common/board-main.lua — 板级公共配置
  • 相关主题:外设驱动、BLE 协议栈事件处理、应用主循环调度(见对应目录页)
Prev
mbox_flash 应用框架
Next
公共应用模块