调试与配置组件
调试与配置组件是 AC63 蓝牙 SDK 中负责调试输出、断言处理和编译期调试开关的公共基础模块,覆盖 apps/common/debug/debug.c 调试输出层、各 CPU 的 asm/debug.h 调试入口以及板级构建配置与调试固件。
Purpose and Scope
本页面向 8-common-components/8.4-debug-config 目录,说明 SDK 中「调试与配置」这一公共能力的完整机制,包括:
- 调试输出层的桩函数实现(
apps/common/debug/debug.c)及其编译期开关语义; - 各 CPU 家族(bd19/bd29/br23/br25/br30/br34)统一的调试入口
debug_init()与异常解析入口exception_analyze(); - 控制调试行为的配置宏
CONFIG_DEBUG_ENABLE、CONFIG_DEBUG_LITE_ENABLE; - 板级构建配置头文件与 CPU
tools目录下的调试固件(如ota_debug.bin)。
以下内容属于兄弟页面、不在本页展开:具体板级的引脚/UART 配置(见板级 Board 配置相关页面)、OTA 升级机制(见 OTA 相关页面)、各外设驱动的配置(见对应驱动页面)。
Overview
AC63 BT SDK 的调试机制有一个鲜明特征:调试开关是编译期决定、运行期零开销的。整个 apps/common/debug/debug.c 文件被 #ifndef CONFIG_DEBUG_ENABLE 包裹——当该宏未定义(即发布版构建)时,编译器编译的是一组空壳桩函数(printf、puts、putchar、put_buf、put_u8hex、put_u16hex、put_u32hex、log_print、log_putbyte 全部为空实现),这些空函数会被优化器直接消除,不给固件增加任何代码体积与运行开销;当该宏已定义(调试版构建)时,真实调试输出实现由预编译驱动库提供(本仓库源码中不包含该分支实现)。
设计意图非常明确:
- 零成本发布:发布版固件不含任何调试字符串与输出路径,避免泄露调试信息、减小 flash 占用;
- 统一调用接口:应用层始终可以调用
printf/log_print等标准接口,无需为调试/发布写两套代码; - 断言永远生效:
assert_printf是桩文件中唯一「有实际行为」的函数——即使调试输出被关闭,它仍然调用cpu_assert_debug(),保证断言在任何构建下都能触发 CPU 的异常处理机制,这是嵌入式安全设计的关键。
同时,每个 CPU 家族在 include_lib/driver/cpu/<cpu>/asm/debug.h 中声明 debug_init()(初始化调试通道,如 UART)与 exception_analyze()(解析异常现场),部分 CPU 还提供 ram_protect_close()(关闭 RAM 保护以便调试探针访问)。CPU tools 目录下的 ota_debug.bin、uboot.boot_debug 等调试固件则用于新板 bring-up 阶段。
Architecture
flowchart TD
subgraph sg_Build["编译期配置层"]
CFG["app_config.h / board_*_cfg.h<br/>CONFIG_DEBUG_ENABLE<br/>CONFIG_DEBUG_LITE_ENABLE"]
end
subgraph sg_Debug["公共调试组件 apps/common/debug/debug.c"]
STUB["printf / puts / putchar<br/>put_buf / put_u8hex / put_u16hex / put_u32hex<br/>log_print / log_putbyte 桩函数"]
ASSERT["assert_printf"]
end
subgraph sg_Cpu["CPU 驱动层 include_lib/driver/cpu/*/asm/debug.h"]
INIT["debug_init()"]
EXC["exception_analyze()"]
PROTECT["ram_protect_close()"]
end
subgraph sg_HW["CPU 调试硬件"]
HW["UART 调试通道 / 异常捕获"]
end
CFG -->|"未定义 CONFIG_DEBUG_ENABLE"| STUB
CFG -->|"已定义 CONFIG_DEBUG_ENABLE"| LIB["预编译库中的真实调试实现"]
LIB --> HW
STUB --> ASSERT
ASSERT -->|"无条件调用"| HW
INIT --> HW
EXC --> HW
PROTECT --> HW
架构说明
- 编译期配置层:
CONFIG_DEBUG_ENABLE决定 debug.c 中编译的是桩函数还是(由预编译库提供的)真实实现;CONFIG_DEBUG_LITE_ENABLE进一步裁剪掉log_putbyte字节级输出钩子,形成「精简调试模式」。这些宏一般在板级工程的构建配置(如apps/hid/board/bd19/board_ac6321a_demo_cfg.h、board_ac6321a_demo_global_build_cfg.h)或app_config.h中定义。 - 公共调试组件:唯一在发布构建中也参与编译的源码文件;其桩函数保持标准 C 函数签名,确保上层调用点不需要任何条件编译。
- CPU 驱动层:调试硬件的初始化与异常分析按 CPU 家族分别实现(bd19、bd29、br23、br25、br30、br34 各有对应的
asm/debug.h),对外暴露统一的debug_init()/exception_analyze()接口。 - 调试硬件:UART 是最常见的调试通道;
cpu_assert_debug()与exception_analyze()直接对接 CPU 的异常处理寄存器现场。
调试输出层实现(apps/common/debug/debug.c)
文件结构与编译开关
整个文件的结构是一个大的条件编译块:#ifndef CONFIG_DEBUG_ENABLE … #endif(第 6 行到第 63 行)。其含义是:只有当调试宏未定义时才编译这些桩函数。反之(宏已定义)时,链接器从预编译库中解析同名的真实符号,源码层无需任何改动。
#include "app_config.h"
#include "typedef.h"
#ifndef CONFIG_DEBUG_ENABLE
int putchar(int a)
{
return a;
}
来源:debug.c
putchar 的桩实现返回传入参数本身,既满足 C 标准库对 putchar 的约定(返回写入的字符),又不会产生任何输出。puts 与 printf 返回 0,表示「成功但无输出」,调用方无需感知差异。
二进制/十六进制输出桩
void put_buf(const u8 *buf, int len)
{
}
void put_u8hex(u8 dat)
{
}
void put_u16hex(u16 dat)
{
}
void put_u32hex(u32 dat)
{
}
来源:debug.c
这组函数用于输出原始缓冲区与各宽度十六进制数值,是协议栈/驱动调试时最常用的辅助输出。发布版中它们为空函数,调用点被编译器完全消除。
分级日志接口 log_print
void log_print(int level, const char *tag, const char *format, ...)
{
}
来源:debug.c
log_print 是 SDK 的分级日志入口,level 表示日志级别(错误/警告/信息等),tag 标识日志来源模块,format 为格式化串。桩实现为空;真实实现(宏定义时)通常将日志按级别过滤并转发到 UART。level 与 tag 参数的存在说明库内真实实现支持按模块、按级别过滤,这是大型 SDK 调试的常见需求。
精简调试模式与 log_putbyte
#ifndef CONFIG_DEBUG_LITE_ENABLE
void log_putbyte(char c)
{
}
#endif /* #ifndef CONFIG_DEBUG_LITE_ENABLE */
来源:debug.c
log_putbyte 是字节级输出钩子,通常被底层驱动(如汇编级打印)调用。当定义 CONFIG_DEBUG_LITE_ENABLE 时,连这个钩子的桩都不编译——精简模式连字节级输出路径一并移除,进一步减小代码尺寸。这体现了 SDK 对 flash/ram 空间的精打细算:调试能力按需逐级裁剪。
断言处理 assert_printf
int assert_printf(const char *format, ...)
{
cpu_assert_debug();
return 0;
}
来源:debug.c
这是发布构建中唯一真正「做事」的函数:无论调试输出是否开启,断言触发时都会调用 cpu_assert_debug(),把 CPU 拉入异常处理流程(例如打印异常现场或进入死循环以便调试器抓取)。这是刻意的安全设计——断言不能因为关闭了日志就被静默吞掉。cpu_assert_debug() 由预编译驱动库提供,具体行为(停机、复位或进入调试模式)因 CPU 而异。
CPU 调试入口(include_lib/driver/cpu/*/asm/debug.h)
每个 CPU 家族在预编译驱动库的头文件中声明统一的调试接口。以 bd19 为例:
void ram_protect_close(void);
void debug_init();
void exception_analyze();
各 CPU 的声明位置一致(bd29/br23/br25/br30/br34 的 asm/debug.h 中 debug_init() 与 exception_analyze() 均位于第 5-7 行或 14-15 行附近),汇总如下:
| 接口 | 声明位置 | 职责 |
|---|---|---|
void debug_init(void) | 全部 CPU(bd19/bd29/br23/br25/br30/br34) | 初始化调试通道(如调试 UART 引脚与波特率),应在系统启动早期调用 |
void exception_analyze(void) | 全部 CPU | 在异常/断言触发后解析 CPU 寄存器现场,输出崩溃信息(函数名、PC、栈等) |
void ram_protect_close(void) | bd29/br23/br25/br30/br34 | 关闭 RAM 写保护,允许调试器/下载工具访问受保护内存区域 |
设计意图:把「硬件相关的调试初始化」和「与应用无关的异常分析」收敛到驱动库,应用层只依赖 debug_init()/exception_analyze() 两个稳定符号,便于跨 CPU 移植。ram_protect_close() 的存在说明 SDK 默认开启了 RAM 保护(防止野指针破坏关键数据),调试阶段需要显式关闭后才能用仿真器读写内存——这是量产安全与调试便利之间的一个折中。
核心编译流程
flowchart TD
Start([编译应用工程]) --> Config{"CONFIG_DEBUG_ENABLE<br/>是否已定义?"}
Config -->|"未定义 → 发布版"| Stub["编译 apps/common/debug/debug.c 桩函数<br/>printf/puts/log_print 等全部为空"]
Config -->|"已定义 → 调试版"| Lib["链接预编译库中的真实调试实现<br/>(UART 输出 / 分级日志过滤)"]
Stub --> Opt["优化器消除空函数调用<br/>固件零调试开销"]
Lib --> Init["启动早期调用 debug_init()<br/>初始化调试 UART"]
Init --> Out["log_print / printf 输出到调试通道"]
Stub --> Assert["assert_printf 无条件调用 cpu_assert_debug()"]
Lib --> Assert
Assert --> Exc["exception_analyze() 解析异常现场<br/>输出崩溃信息"]
Out --> Exc
流程说明
- 编译期分叉:预处理器依据
CONFIG_DEBUG_ENABLE选择桩函数还是真实实现。这一步发生在编译期,因此运行时不存在任何分支判断。 - 发布版路径:桩函数被编译后,优化器将空函数调用内联消除,调试字符串(若在
#if之外仍存在格式化代码)也随之消失,发布固件不携带调试信息。 - 调试版路径:应用在启动早期调用
debug_init()打开调试通道;此后log_print/printf等按级别过滤后输出。 - 断言路径(两条路径共用):
assert_printf无论构建类型都调用cpu_assert_debug(),随后可配合exception_analyze()定位崩溃点。这也是本组件最核心的运行时行为。
调试固件(cpu/*/tools)
每个 CPU 家族的 tools 目录都提供了用于调试的固件变体,例如:
cpu/bd19/tools/ota_debug.bin、cpu/bd29/tools/ota_debug.bin、cpu/br23/tools/ota_debug.bin、cpu/br23/tools/ota_all_debug.bin、cpu/br23/tools/ota_nor_debug.bin、cpu/br25/tools/ota_debug.bin、cpu/br30/tools/ota_debug.bin等——带调试输出的 OTA 引导/升级固件;cpu/bd19/tools/uboot.boot_debug、cpu/br23/tools/uboot.boot_debug等——带调试信息的 boot 固件;cpu/br25/tools/uboot_lrc.boot_debug、cpu/br30/tools/br30c_ota_debug.bin等——针对特定封装/容量的变体。
这些 .bin/.boot_debug 文件是烧录到板端用于 bring-up 与问题定位的调试版本固件,生产环境应改用对应的正式版本。它们的命名规律(_debug 后缀、uboot/ota 前缀)与 ota_all/ota_nor/ota_lrc 等容量/介质变体可以辅助确认某块板子该烧哪个调试固件。
Usage Examples
示例 1:发布版桩函数的完整形态(桩文件整体结构)
#ifndef CONFIG_DEBUG_ENABLE
int putchar(int a)
{
return a;
}
int puts(const char *out)
{
return 0;
}
int printf(const char *format, ...)
{
return 0;
}
...
int assert_printf(const char *format, ...)
{
cpu_assert_debug();
return 0;
}
#endif
来源:debug.c
这是理解本组件最关键的一段代码:整个调试输出层的「发布形态」就是一组空壳。应用层代码无需任何条件编译即可调用 printf 等标准接口——发布版自动变为无操作,调试版自动输出,这就是该设计带来的最大便利。
示例 2:断言在发布版中的实际行为
int assert_printf(const char *format, ...)
{
cpu_assert_debug();
return 0;
}
来源:debug.c
即使 CONFIG_DEBUG_ENABLE 未定义,assert_printf 仍会调用 cpu_assert_debug()。若你的应用发现发布版在某个断言处「卡死」,这并非死机,而是断言保护机制在起作用——此时应借助 exception_analyze() 或调试器读取异常现场。
示例 3:CPU 调试接口声明(以 bd19 为例)
void ram_protect_close(void);
void debug_init();
void exception_analyze();
应用在 board_init 或系统启动早期应调用 debug_init();崩溃回调/异常向量中应调用 exception_analyze() 输出现场信息。ram_protect_close() 只在调试器需要访问受保护 RAM 时使用,量产代码不应调用。
示例 4:板级配置头文件(配置宏的载体)
板级工程通过 board_*_cfg.h 与 board_*_global_build_cfg.h 组织构建配置,例如 bd19 平台的演示板:
- board_ac6321a_demo_cfg.h —— 板级硬件/外设配置;
- board_ac6321a_demo_global_build_cfg.h —— 全局构建开关(调试宏通常在此类文件中定义)。
说明:本页探索阶段未逐行读取这两个头文件的具体宏内容;
CONFIG_DEBUG_ENABLE的具体定义位置以实际板级工程为准(可能位于app_config.h或板级*_global_build_cfg.h)。
Configuration Options
| 配置宏 | 取值 | 默认行为 | 说明 |
|---|---|---|---|
CONFIG_DEBUG_ENABLE | 定义/未定义 | 未定义(发布形态) | 未定义时编译 apps/common/debug/debug.c 中的全部桩函数;定义时使用预编译库中的真实调试输出实现 |
CONFIG_DEBUG_LITE_ENABLE | 定义/未定义 | 未定义 | 精简调试模式。定义后连 log_putbyte 字节级输出钩子都被裁剪,进一步减小代码体积;通常与 CONFIG_DEBUG_ENABLE 配合用于资源受限场景 |
配置方式:以上宏为编译期开关,需要在包含 app_config.h 之前生效(debug.c 第一行即 #include "app_config.h")。建议在板级 *_global_build_cfg.h 或工程构建脚本中统一定义,避免散落在各处导致构建不一致。
调试/发布切换实践:
| 场景 | 推荐配置 |
|---|---|
| 量产发布 | 不定义 CONFIG_DEBUG_ENABLE(默认即发布形态,零开销) |
| 常规开发调试 | 定义 CONFIG_DEBUG_ENABLE |
| 时序敏感/资源紧张 | 同时定义 CONFIG_DEBUG_ENABLE + CONFIG_DEBUG_LITE_ENABLE |
| 新板 bring-up | 定义 CONFIG_DEBUG_ENABLE 并烧录对应 CPU 的 ota_debug.bin 引导 |
API Reference
以下接口均基于实际源码核实(发布构建桩实现位于 apps/common/debug/debug.c,调试构建真实实现由预编译库提供;驱动入口位于各 CPU 的 asm/debug.h)。
输出接口(发布版桩实现)
int putchar(int a)
- 描述:输出单字符。发布版直接返回入参,无副作用,用于满足 C 库字符输出契约。
- 参数:
a(int) — 待输出字符。 - 返回:传入的字符
a。
int puts(const char *out)
- 描述:输出字符串(含换行)。发布版为空操作。
- 参数:
out(const char*) — 以\0结尾的字符串。 - 返回:固定 0(表示成功)。
int printf(const char *format, ...)
- 描述:格式化输出。发布版为空操作;调试版经预编译库输出到调试通道。
- 参数:
format(const char*) — 格式化串;...— 可变参数。 - 返回:固定 0。
void put_buf(const u8 *buf, int len)
- 描述:按缓冲区整体输出(通常为十六进制转储)。
- 参数:
buf(const u8*) — 数据指针;len(int) — 字节数。
void put_u8hex(u8 dat) / void put_u16hex(u16 dat) / void put_u32hex(u32 dat)
- 描述:按 8/16/32 位宽度输出十六进制数值,便于直接观察寄存器与字段值。
void log_print(int level, const char *tag, const char *format, ...)
- 描述:分级日志入口。调试版按
level过滤(错误/警告/信息级别),tag标识来源模块。 - 参数:
level(int) — 日志级别;tag(const char*) — 模块标签;format(const char*) — 格式化串。
void log_putbyte(char c)
- 描述:字节级输出钩子(供底层驱动使用)。定义
CONFIG_DEBUG_LITE_ENABLE时该桩不编译。 - 参数:
c(char) — 待输出字节。
断言接口
int assert_printf(const char *format, ...)
- 描述:断言输出。发布版与调试版行为一致:均调用
cpu_assert_debug()触发 CPU 异常处理,随后返回 0。 - 参数:
format(const char*) — 断言信息格式化串。 - 返回:固定 0。
- 行为:触发
cpu_assert_debug()(由预编译驱动库实现,通常进入异常/停机状态)。
CPU 驱动入口(声明于 include_lib/driver/cpu/*/asm/debug.h)
void debug_init(void)
- 描述:初始化调试通道(调试 UART 等),应在系统启动早期调用一次。
- 声明位置:bd19 见 debug.h L14,br25 见 debug.h L35。
void exception_analyze(void)
- 描述:解析异常/断言触发后的 CPU 寄存器现场,输出崩溃信息用于定位问题。
void ram_protect_close(void)
- 描述:关闭 RAM 写保护(仅 bd29/br23/br25/br30/br34 声明)。调试阶段供仿真器/下载工具访问受保护内存;量产代码不应调用。
Failure Modes, Edge Cases & Concurrency
以下分析均基于已核实的源码行为(桩函数语义、编译开关、断言路径)与嵌入式系统的一般约束,推理部分已明确标注。
断言在发布版中「卡死」的误判
- 现象:发布版固件在特定条件下停止响应。
- 根因:
assert_printf即使在没有调试输出的构建中也调用cpu_assert_debug()(见 debug.c L56-L61)。这通常是断言保护触发而非随机死机。 - 处置:通过调试器读取异常现场,或调用
exception_analyze()解析;不要简单禁用断言来「修复」。
调试输出与实时性的冲突
- 定义
CONFIG_DEBUG_ENABLE后,printf/log_print的 UART 输出会阻塞/占用 CPU 时间,可能破坏对时序敏感的蓝牙协议栈行为(丢包、断连、超时)。 - 建议:时序相关路径(如中断、协议回调)避免高频打印;使用
CONFIG_DEBUG_LITE_ENABLE精简输出路径;调试完成后务必切换回发布构建验证。
调试宏不一致导致的构建问题
debug.c第一行#include "app_config.h",宏必须在包含该头文件前全局生效。若某些编译单元定义了CONFIG_DEBUG_ENABLE而另一些没有,会出现符号重复或符号缺失(桩函数与库函数同名冲突)的链接错误。- 建议:宏统一在板级
*_global_build_cfg.h或工程级构建脚本定义,保证全工程一致。
未初始化调试通道时的静默丢失
- 调试版构建若未在启动早期调用
debug_init()(或调用了ram_protect_close()而未开 UART),printf输出会被静默丢弃——不会报错,排查时容易误判为「打印函数失效」。
并发/中断上下文打印
- 桩函数没有锁或原子性保护;调试版真实实现的中断安全性由预编译库决定。在中断上下文与主循环同时打印时,理论上可能交错输出。嵌入式调试场景通常容忍该现象,但若输出用于协议分析,应在单一任务中集中打印。
边界:log_putbyte 的裁剪
- 定义
CONFIG_DEBUG_LITE_ENABLE时log_putbyte桩不编译(debug.c L49-L54)。若应用或驱动代码直接调用log_putbyte且构建为精简模式,将产生链接错误——这是刻意的「编译期强制约束」,提示你在精简模式下不要依赖字节级钩子。
Performance & Operational Considerations
- 发布版零开销:全部调试函数在编译期被替换为空实现并被优化器消除,不占 flash、不占栈、无运行分支。这是本组件最重要的性能特性。
- 调试版成本:UART 输出速率(波特率)决定吞吐上限;
log_print的级别过滤在库内完成,过滤开销极低,但格式化(printf类)在低主频 MCU 上是主要成本,应避免高频调用。 - 固件选择:bring-up 使用
ota_debug.bin/uboot.boot_debug等调试固件(见 cpu/bd19/tools 等目录);发布前必须切换为正式固件,防止调试通道与额外输出影响产测与功耗。 - 故障排查流程:异常触发 →
cpu_assert_debug()停机 → 调试器附加或exception_analyze()输出现场 → 依据 PC/栈回溯定位。建议所有量产固件保留断言(不要关闭assert_printf的cpu_assert_debug()调用)。
Extension Points
- 自定义输出通道:调试版真实实现位于预编译库,直接修改受限制;应用层可通过
log_print(level, tag, ...)约定模块标签,配合库内过滤实现按模块开关日志,无需改库。 - 新 CPU 移植:新增 CPU 家族需在
include_lib/driver/cpu/<cpu>/asm/debug.h提供debug_init()与exception_analyze()(对齐现有 bd19/br25 等声明),并在驱动库中实现cpu_assert_debug()与真实调试输出;apps/common/debug/debug.c无需改动。 - 精简模式扩展:
CONFIG_DEBUG_LITE_ENABLE提供了一档「最小调试」形态,若需要更细的裁剪粒度,可参照log_putbyte的条件编译模式,将更多底层输出钩子纳入条件编译。 - 调试固件扩展:
cpu/*/tools下调试固件命名规律清晰(ota_*_debug.bin、uboot*.boot_debug),新板型可参考同类固件生成对应的调试变体用于 bring-up。
Tests
本次探索未在仓库中找到针对 apps/common/debug/debug.c 或调试配置的单元测试/自动化测试文件。该模块的验证主要依赖:
- 构建验证:分别以定义/不定义
CONFIG_DEBUG_ENABLE编译,确认链接成功且行为符合预期(发布版无输出、调试版有输出); - 板级验证:烧录
ota_debug.bin调试固件,确认 UART 日志输出与断言触发路径(cpu_assert_debug→exception_analyze)工作正常; - 断言验证:构造一个触发
assert_printf的场景(如空指针/越界),确认发布版同样进入异常处理流程。
若后续版本补充了测试,建议覆盖:宏组合矩阵(4 种开关组合的编译结果)、桩函数返回契约(
putchar回显、puts/printf返回 0)、断言路径对cpu_assert_debug的调用。
Related Links
- 源码:apps/common/debug/debug.c(本组件核心实现)
- 源码:include_lib/driver/cpu/bd19/asm/debug.h(CPU 调试入口示例)
- 源码:include_lib/driver/cpu/br25/asm/debug.h(br25 调试入口)
- 配置:apps/hid/board/bd19/board_ac6321a_demo_cfg.h(板级配置示例)
- 配置:apps/hid/board/bd19/board_ac6321a_demo_global_build_cfg.h(全局构建开关示例)
- 固件目录:cpu/bd19/tools、cpu/br23/tools、cpu/br25/tools、cpu/br30/tools(调试引导/OTA 固件)
- 兄弟页面:板级 Board 配置(引脚、外设、时钟);OTA 升级机制;异常处理与看门狗组件(若目录中存在)