板级支持包 (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
关键逻辑:
- 入参校验:
be / 32 != 0意味着be >= 32,即越界(无法映射到位图),返回错误码E_BSP_EVENT。这防止了非法枚举值导致位图越界/未定义行为。 - 原子置位:
bsp_event |= BIT(be)在OS_ENTER_CRITICAL()与OS_EXIT_CRITICAL()之间执行。CPU_SR_ALLOC()声明保存 CPU 状态的局部变量,进入临界区时关中断、退出时按保存值恢复中断状态——这保证 ISR 与主循环并发投递时不会丢失或错乱事件位。 - 可重入/并发安全:即使多个中断嵌套投递,由于每个投递都是"读-或-写"完整临界区操作,位图始终一致。
- 被注释掉的
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,让出执行权
流程要点
- 产生:系统定时器每 100ms 产生一次中断,在中断上下文调用
bsp_post_event(B_EVENT_100MS)。 - 置位:临界区内校验并置位,中断立即返回——ISR 开销为常数级(一次校验 + 一次位或操作)。
- 仲裁:主循环调用
bsp_loop()→bsp_get_event(),CLZ 在常数时间内选出优先级最高事件并清除对应位。 - 消费:
switch分发到具体处理函数;B_EVENT_100MS触发 NorFlash 缓存同步,保证外部 Flash 读写与缓存一致。 - 让权:
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_100MS | BSP_EVENT 枚举 | 0 | 100ms 周期事件,优先级最高(bit 0) |
B_NO_EVENT | BSP_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_event | unsigned 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
- 新增板级事件:在
BSP_EVENT枚举中追加0~31内的新值,并在bsp_loop()增加对应case。编号越小优先级越高,追加时注意保持既有事件的相对顺序。 - 复用 100ms 心跳:
B_EVENT_100MS已由系统定时器周期性投递,任何"周期性板级维护"(如 LED 扫描、看门狗喂狗、ADC 周期采样)都可以挂到该分支,无需新增定时器。 - 外部 Flash 缓存同步:
_norflash_cache_sync_timer(5)的步进参数可在B_EVENT_100MS分支调整,以权衡缓存同步粒度与 CPU 占用。 - 与上层调度集成:
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 协议栈事件处理、应用主循环调度(见对应目录页)