BSP 板级支持包
BSP(Board Support Package,板级支持包)是 SDK 中位于应用层与芯片底层驱动之间的基础支撑层,负责系统启动初始化、板级事件分发与 Flash 存储子系统初始化,为上层应用提供统一的运行环境。
Purpose and Scope
本页面向 sdk/apps/app/bsp/ 目录下的板级支持能力,重点讲解:
- 启动初始化(
start/init.c的system_init()):系统上电后的初始化顺序与各阶段职责; - 板级事件循环(
start/bsp_loop.c/h):基于位掩码 + CLZ 指令的高效事件分发机制,包括bsp_post_event、bsp_get_event、bsp_loop; - Flash 子系统初始化(
start/flash_init.c/h):Flash 存储初始化及其在启动序列中的位置; - 上述模块涉及的配置宏(
TFG_EXT_FLASH_EN、BLE_EN、UPDATE_V2_EN、SYS_TIMER_EN等)与扩展方式。
以下内容属于兄弟页面,不在本页展开:bsp/common/ 下的音频编解码器(decoder)、音频通路(audio)、均衡器(audio_eq)、配置文件工具(config)等具体功能模块各有独立页面;应用层升级流程(app_update_init)属于升级(Update)主题;驱动层 tick_timer_driver、device、vfs 等属于芯片驱动(Driver)主题。本页只说明 BSP 如何在启动序列中调用它们。
Overview
在嵌入式 SDK 中,BSP 层承担"承上启下"的职责:对上,它向应用提供 bsp_loop() 事件循环和 bsp_post_event() 事件投递接口;对下,它封装芯片的定时器(tick_timer_driver)、消息系统(msg)、设备驱动框架(device/devices_init_api)与 Flash 存储(flash_system_init)。
BSP 的核心设计思路可以归纳为三点:
- 启动顺序即依赖顺序。
system_init()严格按照"基础时钟 → 消息系统 → 设备框架 → Flash → 配置/升级"的顺序推进,每一步都为下一步提供前提条件。init.c通过#pragma段属性将初始化代码放入专门的.init.text段,便于链接器在启动早期阶段布局与回收。 - 事件用位掩码表达。BSP 事件是一个 32 位整型
bsp_event,每一位代表一类事件(如B_EVENT_100MS),通过OS_ENTER_CRITICAL临界区保护,可在中断上下文与任务上下文之间安全投递。 - 事件获取用 CLZ 单指令。
bsp_get_event()借助 CPU 的clz(Count Leading Zeros)指令在常数时间内取出当前优先级最高的事件,无需遍历 32 位,体现了对 MCU 资源受限环境的极致优化。
Architecture
flowchart TD
subgraph sg_App["应用层 (app)"]
AppModule["App Modules"]
BLE["BLE / BT 配置"]
Update["升级模块 (UPDATE_V2)"]
end
subgraph sg_BSP["BSP 板级支持包 (sdk/apps/app/bsp)"]
subgraph sg_Start["start/ 启动与事件"]
Init["init.c<br/>system_init()"]
Loop["bsp_loop.c/h<br/>事件循环"]
Flash["flash_init.c/h<br/>flash_system_init()"]
end
subgraph sg_Common["common/ 公共组件"]
Decoder["decoder 解码器"]
Audio["audio 音频通路"]
CfgTools["config 配置工具"]
end
end
subgraph sg_Driver["芯片驱动层"]
TickTimer["tick_timer_driver"]
Msg["msg 消息系统"]
Device["device / devices_init_api"]
Vfs["vfs"]
end
AppModule -->|"bsp_post_event / bsp_loop"| Loop
BLE -->|"cfg_bin_init / cfg_file_parse"| Init
Update -->|"app_update_init"| Init
Init --> TickTimer
Init --> Msg
Init --> Device
Init --> Flash
Flash --> Vfs
Loop -->|"B_EVENT_100MS"| Flash
AppModule --> Decoder
AppModule --> Audio
AppModule --> CfgTools
Decoder --> Device
Audio --> Device
架构说明:
- **
init.c(system_init)**是 BSP 的启动入口,按依赖顺序初始化tick_timer、消息系统、设备框架和 Flash,并在条件编译宏开启时继续完成 BLE 配置解析与升级初始化。 bsp_loop.c/h提供板级事件机制:bsp_event_init()清零事件位,bsp_post_event()在临界区中置位,bsp_loop()取出事件并分发给处理逻辑(如B_EVENT_100MS触发 norflash 缓存同步)。flash_init.c/h提供flash_system_init(),在配置解析(cfg_file_parse)之前完成 Flash 子系统就绪,保证上层可安全读写存储。common/目录收纳音频解码、音频通路、配置工具等可复用的板级公共组件,属于本包但功能独立,各自有专题页面。
启动初始化:system_init()
system_init() 定义在 sdk/apps/app/bsp/start/init.c,是整个 SDK 在 BSP 层面的初始化入口。函数体很短,但每一行的调用顺序都体现了硬性的依赖关系:
void system_init(void)
{
/* my_malloc_init(); */
#if SYS_TIMER_EN
/* sys_timer_init(); */
#endif
tick_timer_init();
message_init();
/* app_system_init(); */
devices_init_api();
flash_system_init();
#if defined(BLE_EN) && (1 == BLE_EN)
cfg_bin_init();
cfg_file_parse(0);
#endif
#if defined(UPDATE_V2_EN) && (1 == UPDATE_V2_EN)
//升级初始化
app_update_init();
#endif
}
来源:init.c
初始化阶段解读
| 阶段 | 调用 | 作用 | 设计意图 |
|---|---|---|---|
| 1. 基础时钟 | tick_timer_init() | 初始化系统 tick 定时器驱动 | 一切延时、超时、周期性事件的基础,必须先于任何依赖时间的模块就绪 |
| 2. 消息系统 | message_init() | 初始化内核消息队列 | 任务间/中断间通信的前提,事件循环与各模块消息传递都依赖它 |
| 3. 设备框架 | devices_init_api() | 注册并初始化芯片设备驱动 API | 为上层(解码器、音频通路等)提供统一设备访问接口 |
| 4. Flash 存储 | flash_system_init() | 初始化 Flash 子系统(见 flash_init.h) | 必须在配置解析之前完成,保证配置读写安全 |
| 5. BLE 配置(可选) | cfg_bin_init()、cfg_file_parse(0) | 解析二进制配置文件 | 仅在 BLE_EN == 1 时编译;从 Flash 读取用户配置,供蓝牙协议栈使用 |
| 6. 升级初始化(可选) | app_update_init() | 初始化固件升级框架 | 仅在 UPDATE_V2_EN == 1 时编译;升级需要早期介入,因此放在初始化尾段 |
值得注意的两点设计:
- 被注释的备选路径:
my_malloc_init()、sys_timer_init()、app_system_init()均被注释保留,说明这些模块在不同产品配置下可能由其它位置初始化或不再需要,代码保留了切换余地。 - 条件编译隔离:BLE 与升级逻辑分别用
BLE_EN、UPDATE_V2_EN宏包裹,保证未启用这些功能的产品不会引入额外代码与 RAM 占用。
段属性(#pragma)的意义
init.c 顶部通过一组 #pragma 将整个文件放入专用段:
#pragma bss_seg(".init.data.bss")
#pragma data_seg(".init.data")
#pragma const_seg(".init.text.const")
#pragma code_seg(".init.text")
#pragma str_literal_override(".init.text.const")
来源:init.c
这段代码把初始化数据(BSS/数据段)、只读常量与代码分别放到 .init.* 段中。这样链接脚本可以将启动期代码/数据与运行期代码/数据分离:BSP 初始化代码在完成使命后,这些段可以被复用或释放,节省宝贵的 RAM/Flash 资源——这是小型 MCU SDK 常见的"一次性初始化段"优化手法。
板级事件循环:bsp_loop
事件机制的核心数据结构是全局位掩码 bsp_event,配合 BSP_EVENT 枚举使用:
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);
来源:bsp_loop.h
B_EVENT_100MS = 0 表示事件位 bit0;B_NO_EVENT = 32 并非一个真实事件位,而是"无事件"的哨兵值(32 位掩码中第 32 位不存在),同时它也是合法事件号的上界——bsp_post_event 正是用 be / 32 来校验事件号是否越界。
事件投递: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;
}
来源:bsp_loop.c
要点:
- 临界区保护:
OS_ENTER_CRITICAL/OS_EXIT_CRITICAL关中断后对bsp_event做读改写,保证该接口可以从中断服务函数(ISR)中安全调用——投递方不限于任务上下文。 - 越界校验:
be / 32 != 0即事件号 ≥ 32(或为负),返回错误码E_BSP_EVENT,防止BIT(be)位移溢出导致未定义行为。 - 幂等投递:同一事件多次投递只保留一个位,事件是"电平触发"而非"计数触发",语义是"某类事件至少发生过一次"。
事件获取:bsp_get_event
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;
}
来源:bsp_loop.c
要点:
- CLZ 单指令取最高优先级事件:
clz(Count Leading Zeros)统计bsp_event二进制表示中前导 0 的个数。若event_cls = k,则最高置位位是第31 - k位,即当前数值最大(优先级最高)的事件。整个查找过程只需一条内联汇编指令,无循环、无分支预测开销。 - 取走即清:取到事件后立即
bsp_event &= ~BIT(event)清除该位,保证事件不会被重复处理;清除与读取在同一临界区内完成,避免与bsp_post_event竞争。 - 无事件哨兵:
clz(0) = 32,此时返回B_NO_EVENT,调用方据此跳过处理。
事件处理:bsp_loop
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;
}
来源:bsp_loop.c
bsp_loop() 由上层应用周期性调用(通常放在主循环/空闲任务中):每次调用取一个事件并分发。当前仅处理 B_EVENT_100MS——在使能外部 Flash(TFG_EXT_FLASH_EN)时调用 _norflash_cache_sync_timer(5) 执行 norflash 缓存同步的定时步进,这是对 Flash 缓存一致性的周期维护。函数恒返回 0,default 分支吸收所有未知事件,保证循环不会被意外事件卡死。
Flash 子系统初始化
flash_system_init() 声明于 start/flash_init.h,在 system_init() 中被调用,位于 devices_init_api() 之后、cfg_bin_init()/cfg_file_parse(0) 之前。这一位置说明了它的依赖契约:
- 上游依赖:依赖设备框架已就绪(Flash 是设备的一种,需要设备抽象层支持);
- 下游提供:为配置解析(
cfg_file_parse)与升级(app_update_init)提供可用的 Flash 读写能力。
换言之,Flash 是"配置系统与升级系统的地基",因此 BSP 必须在任何持久化访问发生前完成其初始化。bsp_loop 中的 _norflash_cache_sync_timer 则从运行期侧面维护 Flash 缓存一致性,与初始化阶段形成闭环。
核心流程
下面用时序图展示从系统上电到事件被处理的完整链路:
sequenceDiagram
participant RST as 上电复位
participant Init as init.c system_init()
participant Tick as tick_timer_driver
participant Msg as msg 消息系统
participant Dev as devices_init_api
participant Flash as flash_system_init
participant ISR as 中断/定时器 ISR
participant Loop as bsp_loop()
participant Nor as _norflash_cache_sync_timer
RST->>Init: 跳转至系统初始化
Init->>Tick: tick_timer_init()
Init->>Msg: message_init()
Init->>Dev: devices_init_api()
Init->>Flash: flash_system_init()
Note over Flash: 配置/升级的前置条件
Init-->>ISR: 初始化完成,系统进入主循环
loop 主循环每次迭代
ISR->>Loop: 周期触发 B_EVENT_100MS 投递
ISR->>Loop: bsp_post_event(B_EVENT_100MS)
Loop->>Loop: bsp_get_event()(临界区 + CLZ 取位)
alt 取到 B_EVENT_100MS
Loop->>Nor: TFG_EXT_FLASH_EN 开启时调用 sync_timer(5)
Nor-->>Loop: 同步完成
else 无事件
Loop->>Loop: 返回 B_NO_EVENT,跳过处理
end
Loop-->>ISR: 返回 0,继续下一轮
end
流程要点:
- 启动阶段:
system_init()按"时钟 → 消息 → 设备 → Flash"的依赖链完成 BSP 初始化;BLE 与升级逻辑由编译宏决定是否追加。 - 运行阶段:周期性事件源(如 100ms 定时器)在中断上下文中调用
bsp_post_event()置位bsp_event位掩码。 - 分发阶段:主循环每次调用
bsp_loop(),经bsp_get_event()在临界区中执行 CLZ 取最高位事件并清除;switch分发到具体处理分支。 - 无事件快速路径:掩码为 0 时直接返回
B_NO_EVENT,主循环开销极小,适合轮询式 MCU 架构。
使用示例
事件投递与处理(BSP 对外接口)
以下代码展示了 BSP 事件机制的完整使用形态——ISR/任务投递事件,主循环消费事件:
void bsp_event_init(void)
{
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
bsp_event = 0;
OS_EXIT_CRITICAL();
}
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;
}
来源:bsp_loop.c
bsp_event_init() 在临界区内清零掩码,用于系统启动早期复位事件状态;bsp_post_event() 是事件投递入口,返回 0 表示成功,返回 E_BSP_EVENT 表示事件号越界。
事件消费(主循环侧)
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;
}
来源:bsp_loop.c
上层应用在每次主循环迭代中调用 bsp_loop()。扩展新事件时,只需在 BSP_EVENT 枚举中新增成员(保持 < 32),并在 switch 中增加对应 case。
系统启动序列(BSP 初始化入口)
void system_init(void)
{
tick_timer_init();
message_init();
devices_init_api();
flash_system_init();
#if defined(BLE_EN) && (1 == BLE_EN)
cfg_bin_init();
cfg_file_parse(0);
#endif
#if defined(UPDATE_V2_EN) && (1 == UPDATE_V2_EN)
//升级初始化
app_update_init();
#endif
}
来源:init.c
这是板级初始化的标准调用序列,任何需要接入 BSP 的启动代码都应遵循"先基础、后外围"的顺序;条件编译块体现了按产品特性裁剪的能力。
配置选项
BSP 相关行为由编译期宏控制,宏的开关直接影响初始化路径与事件处理逻辑:
| 宏 | 类型 | 默认 | 作用 |
|---|---|---|---|
SYS_TIMER_EN | 整型宏 | 视工程配置 | 控制系统定时器 sys_timer_init() 是否启用(当前 init.c 中该调用被注释保留) |
BLE_EN | 整型宏 | 视工程配置 | 置 1 时编译 BLE 相关初始化:cfg_bin_init() 与 cfg_file_parse(0) |
UPDATE_V2_EN | 整型宏 | 视工程配置 | 置 1 时编译升级框架初始化:app_update_init()(含 code_v2/update.h 路径) |
TFG_EXT_FLASH_EN | 整型宏 | 视工程配置 | 置 1 时 B_EVENT_100MS 事件触发 _norflash_cache_sync_timer(5) 外置 Flash 缓存同步 |
B_EVENT_100MS | 枚举值 | 0 | 事件位索引(bit0),代表 100ms 周期性事件 |
B_NO_EVENT | 枚举值 | 32 | "无事件"哨兵,同时作为合法事件号上界(事件号必须 < 32) |
宏开关通过
#if defined(...) && (1 == ...)方式使用,配置集中在工程级配置头文件中(app_config.h体系),BSP 代码本身不定义默认值。
API 参考
void bsp_event_init(void)
复位板级事件状态。在临界区(OS_ENTER_CRITICAL)内将全局位掩码 bsp_event 清零。
参数: 无 返回: 无
int bsp_post_event(BSP_EVENT be)
向 BSP 事件位掩码投递一个事件,可在中断上下文调用。
参数:
be(BSP_EVENT):要投递的事件号,必须满足0 <= be < 32。
返回:
0:投递成功;E_BSP_EVENT:事件号越界(be / 32 != 0)。
说明: 同一事件重复投递只保留一个位(幂等,电平语义)。
int bsp_loop(void)
取一个待处理事件并分发。每次调用至多处理一个事件;无事件时返回 B_NO_EVENT 对应的空转路径。当前实现处理 B_EVENT_100MS(外置 Flash 缓存同步),其余事件落入 default 分支忽略。
参数: 无 返回: 恒为 0(当前实现)。
void system_init(void)
BSP 层系统初始化入口。按依赖顺序调用 tick_timer_init → message_init → devices_init_api → flash_system_init,并按编译宏追加 BLE 配置解析与升级初始化。
参数: 无 返回: 无
void flash_system_init(void)
Flash 子系统初始化(声明于 flash_init.h)。由 system_init() 在设备框架初始化之后、配置解析之前调用。具体实现细节见 flash_init.c,本页不展开。
参数: 无 返回: 无
失败模式、边界情况与并发
- 事件号越界:
bsp_post_event对be >= 32(含负数)返回E_BSP_EVENT,调用方应检查返回值;若忽略错误,事件将不会被投递,可能表现为功能静默缺失。 - 事件丢失语义(非计数):位掩码是"电平"语义,同一事件在两次
bsp_loop()之间多次触发只保留一位。若应用需要统计触发次数,必须在 BSP 事件之上自行计数。 - 并发安全:
bsp_event的读写全部在OS_ENTER_CRITICAL/OS_EXIT_CRITICAL临界区内完成。临界区会短暂关中断,投递频率过高可能影响中断实时性;bsp_post_event本身开销极小,通常不是瓶颈。 - 取事件与清位原子性:
bsp_get_event在同一临界区内完成"CLZ 取位 + 清位",避免事件被取出后、清除前被重复消费。 - 未知事件:
bsp_loop的default分支吞掉未定义事件,保证主循环不因枚举扩展而崩溃;但新增事件若忘记在switch中添加case,将表现为"事件被静默忽略"。 - CLZ 对 0 的处理:
clz(0) = 32被显式判断为无事件(B_NO_EVENT),该分支是主循环热路径,开销仅一条指令加一次比较。
性能与运维
- O(1) 事件获取:CLZ 单指令替代逐位遍历,32 位掩码下取事件恒定一条指令,适合高频轮询的主循环。
- 极简空转路径:无事件时
bsp_loop()仅执行"取事件 + 比较 + 返回",对 CPU 占用几乎为零,允许上层以高频率轮询。 - 缓存一致性维护:
B_EVENT_100MS驱动的_norflash_cache_sync_timer(5)周期性同步外置 Flash 缓存,属运行期必需维护;若关闭TFG_EXT_FLASH_EN,则此维护被完全编译掉,不产生运行时开销。 - 一次性初始化段:
.init.*段将启动期代码/数据与运行期分离,链接器可复用或回收,降低 RAM/Flash 占用——在资源受限 MCU 上是显著的收益。
扩展点
- 新增板级事件:在
BSP_EVENT枚举中追加成员(编号 < 32),在bsp_loop()的switch中增加处理分支。例如新增B_EVENT_1S,在 1 秒定时器 ISR 中bsp_post_event(B_EVENT_1S)。 - 调整初始化序列:
system_init()是唯一初始化编排点;新增模块可仿照 BLE/升级的写法,用条件编译宏包裹自己的初始化调用,置于正确的依赖位置。 - 启用/禁用外设:通过
TFG_EXT_FLASH_EN、BLE_EN、UPDATE_V2_EN、SYS_TIMER_EN等宏裁剪功能,代码中对应的初始化调用与处理分支会被编译器整体移除。
测试
仓库中未发现针对 BSP 启动与事件循环的独立单元测试文件(sdk/apps/app/bsp/ 下无 test 目录)。该层代码依赖芯片硬件环境(中断、定时器、Flash),验证方式以板级联调为主:bsp_loop.c 中被注释的 log_info("post_event :0x%x\n", ...) 日志即为调试手段的痕迹,可临时开启日志观察事件投递与消费时序。
Related Links
- init.c 系统初始化
- bsp_loop.c 事件循环实现
- bsp_loop.h 事件定义与接口
- flash_init.h Flash 初始化声明
- 相关主题:驱动层(tick_timer_driver、device、vfs)、升级流程(Update)、BLE 配置(bt_config_tool/user_cfg)——这些能力由各自专题页面覆盖