还在用 printf 调试嵌入式系统?还在为中断里打印日志导致系统崩溃而头疼?是时候认识一下 RTEdbg 了。
https://github.com/RTEdbg/RTEdbg
一、为什么我们需要更好的调试方案?
每一位嵌入式工程师都经历过这样的场景:一个诡异的 Bug 只在特定时序下才会出现,你挂上 JTAG 打断点,程序一停——通信超时了。实时系统最怕的就是"观察者效应":你越想看清系统在做什么,系统的行为就越不像原来的样子。
传统的调试手段各有痛点:
断点调试:暂停 CPU 会破坏实时性,对硬实时系统可能造成物理损坏。
printf 调试:串口速率有限、格式化耗时长、不可重入、消耗大量栈空间和 Flash。
SystemView / Tracealyzer:功能强大但入侵性较高,单次事件记录在 Cortex-M4 上可能需要约 200 个 CPU 周期和 150~510 字节的栈空间。
如果有一种方案,能以极低的开销在代码的任何位置(包括中断、异常处理、RTOS 内核)记录数据,并且在不停止程序运行的情况下将数据传出来分析——这就是 RTEdbg。
二、RTEdbg 是什么?
RTEdbg(Real-Time Embedded Debugger) 是一个开源的实时固件分析、测试和调试工具套件,由嵌入式实时控制领域的资深工程师 Branko Premzel 开发,采用 MIT 协议开源。
用一句话概括它的核心思想:
RTEdbg 是一个运行在主机上的可重入、带时间戳的 fprintf() 函数。
嵌入式目标端只做一件事——把原始二进制数据(带格式定义 ID 和时间戳)写入 RAM 中的循环缓冲区。所有耗时的格式化、解码、排序、统计工作,全部在主机端完成。
图 1:RTEdbg 整体架构与数据流向
RTEdbg 整体架构与数据流向 —— 目标端只写二进制,格式化全在主机完成。
三、核心优势:凭什么选 RTEdbg?
3.1 极致的低开销
在 Cortex-M4 上记录一个简单事件,RTEdbg 仅需约 35 个 CPU 周期 和 4 字节的栈空间。相比之下,SystemView 需要约 200 个 CPU 周期和最高 510 字节栈空间。这意味着:
- 在最高优先级中断里也能安心记录日志几乎不可能因为插桩导致栈溢出对系统实时性的影响可以忽略不计
3.2 真正的可重入设计
RTEdbg 的日志函数是完全可重入的。如果处理器支持原子操作(如 ARM 的 LDREX/STREX 指令),日志函数不需要关闭中断。这意味着你可以安全地在以下场景使用:
- 普通任务代码中断服务程序(ISR)RTOS 内核NMI(不可屏蔽中断)和致命异常处理程序
3.3 灵活的数据导出
RTEmsg 解码器基于 fprintf 语法,可以将同一条日志数据以不同格式输出到多个文件。你可以同时生成:
- 人类可读的文本日志(.log)供电子表格分析的 CSV 文件供 GTKWave 等波形查看工具使用的 VCD 文件自动化测试报告
3.4 极小的代码占用
RTEdbg 库对程序 Flash 的额外占用通常只有几百字节。格式化字符串不存储在目标端——它们只存在于主机端的头文件中。这对于资源受限的小型 MCU(如 Cortex-M0+)极为友好。
图 2:printf 调试 vs RTEdbg 方案对比
printf 调试 vs RTEdbg 方案对比 —— RTEdbg 将格式化开销完全转移到主机端。
四、工具套件组成
RTEdbg 不是一个单一的库,而是一整套协同工作的工具链:
| 组件 | 作用 |
|---|---|
| RTElib | 目标端数据采集库,可重入,极低开销 |
| RTEmsg | 主机端二进制数据解码器,支持多文件输出、统计、VCD 导出 |
| RTEgetData | 通过调试探针(GDB Server)或串口将二进制日志传输到主机 |
| RTEcomLib | 目标端串口传输库,用于通过串行通道传输日志数据 |
| RTOS trace | 轻量级 RTOS 跟踪宏,支持 FreeRTOS 等操作系统 |
五、快速上手:从零开始使用 RTEdbg
5.1 集成到项目的步骤
将 RTEdbg 集成到你的嵌入式项目中非常简单,只需三步:
图 3:RTEdbg 集成四步流程
RTEdbg 集成四步流程 —— 从添加库文件到解码分析,整个过程简洁明了。
5.2 Demo:温度监控系统的实时调试
下面通过一个温度监控系统的示例,展示 RTEdbg 的典型用法。假设我们有一个裸机系统,需要周期性采集温度并进行过温告警。
第一步:定义格式定义头文件 fmt_def.h
格式定义文件是 RTEdbg 的核心概念之一。它使用 printf 风格的格式字符串,但这些字符串只存在于主机端,不会编译进目标端固件。
// fmt_def.h — 格式定义文件(仅主机端使用)
// 每个宏对应一条消息的解码格式
// 消息组 0:系统事件
#define FMT_SYSTEM_INIT "System initialized, clock = %u MHzn"
#define FMT_SYSTEM_TICK "Tick: %un"
// 消息组 1:温度数据
// >>Temperature.csv 表示同时输出到 Temperature.csv 文件
#define FMT_TEMP_SAMPLE "Sensor[%u]: %.1f °Cn"
">>Temperature.csv: %u, %.1fn"
// 消息组 2:告警事件
// >>Warnings.log 表示告警信息额外输出到单独的告警文件
#define FMT_TEMP_WARNING "WARNING: Sensor[%u] over-temp! %.1f °C (limit: %.1f)n"
">>Warnings.log: Over-temp Sensor[%u] = %.1f °Cn"
// 消息组 3:定时统计
#define FMT_TIMING_STATS "ADC sampling took %.3f usn"
解析:格式定义文件的 >>filename 语法是 RTEmsg 的特色功能——它可以把同一条消息以不同格式输出到不同文件。比如温度数据既以人类可读格式写入主日志,又以 CSV 格式写入 Temperature.csv,方便后续用 Excel 或 Python 进行数据分析。
第二步:在固件中插桩
#include "rtedbg.h"
#define TEMP_LIMIT 85.0f
#define NUM_SENSORS 4
// 初始化 RTEdbg
void system_init(void)
{
rtedbg_init();
uint32_t clock_mhz = SystemCoreClock / 1000000;
RTE_MSG1(MSG_GRP0, FMT_SYSTEM_INIT, clock_mhz);
}
// 温度采集与监控任务
void temperature_monitor(void)
{
for (uint32_t i = 0; i < NUM_SENSORS; i++)
{
uint32_t t_start = get_timestamp();
float temp = adc_read_temperature(i);
uint32_t t_end = get_timestamp();
// 记录采样耗时(时序分析)
float elapsed_us = (float)(t_end - t_start) / TIMER_FREQ_MHZ;
RTE_MSG1(MSG_GRP3, FMT_TIMING_STATS, elapsed_us);
// 记录温度值(同时输出到日志和 CSV)
RTE_MSG2(MSG_GRP1, FMT_TEMP_SAMPLE, i, temp);
// 过温告警(额外输出到 Warnings.log)
if (temp > TEMP_LIMIT)
{
RTE_MSG3(MSG_GRP2, FMT_TEMP_WARNING, i, temp, TEMP_LIMIT);
}
}
}
// 硬件异常处理(Cortex-M HardFault)
void HardFault_Handler(void)
{
// 即使在致命异常中也能安全记录——仅需 32 字节额外代码
MSGN_FATAL_EXCEPTION();
while (1);
}
解析:
RTE_MSG1、RTE_MSG2、RTE_MSG3 中的数字表示参数个数(1~8 个参数均有对应宏)。
MSG_GRP0 ~ MSG_GRP3 是消息分组,RTEdbg 支持多达 32 个消息组,每个组可以在运行时通过消息过滤器独立启用或禁用。比如在正常运行时只开启告警组,需要详细分析时再开启全部组。致命异常处理中的日志记录仅需极少资源——32 字节代码和 20 字节栈,却能记录完整的寄存器转储信息。
第三步:传输与解码
目标端运行后,使用 RTEgetData 工具通过调试探针读取 RAM 中的日志缓冲区:
# 通过 GDB Server 传输日志数据
RTEgetData -gdb localhost:3333 -addr 0x20000000 -size 4096 -out data.bin
# 使用 RTEmsg 解码二进制数据
RTEmsg data.bin
解码后自动生成多个输出文件:
Main.log ← 完整的主日志
Temperature.csv ← 温度数据,可直接用 Excel 打开
Warnings.log ← 仅包含过温告警
Errors.log ← 解码过程中发现的错误(如有)
Main.log 输出示例:
N00001 0.000 System initialized, clock = 168 MHz
N00002 1.001 ADC sampling took 2.375 us
N00003 1.001 Sensor[0]: 24.5 °C
N00004 1.002 ADC sampling took 2.250 us
N00005 1.002 Sensor[1]: 25.1 °C
N00006 1.003 ADC sampling took 2.312 us
N00007 1.003 Sensor[2]: 87.3 °C
N00008 1.003 WARNING: Sensor[2] over-temp! 87.3 °C (limit: 85.0)
N00009 1.004 ADC sampling took 2.375 us
N00010 1.004 Sensor[3]: 23.8 °C
每条消息都有唯一编号(N00001)和精确时间戳(毫秒级),让你能清晰地还原事件发生的先后顺序和时间间隔。
六、消息过滤:驯服数据洪流
实时系统每秒可以产生海量数据。RTEdbg 的消息过滤机制是应对"数据洪流"的利器。
图 4:消息过滤机制
消息过滤机制 —— 32 个分组可独立启停,在详细追踪和长历史之间灵活切换。
通过在运行时修改消息过滤器的值,你可以在不重新编译固件的情况下选择:
详细模式:开启所有组,获得最完整的信息,但历史较短(缓冲区更快被填满)
精简模式:只开启关键组(如告警),获得更长的事件历史
这就像硬件测试中的"测试插头"——你有 32 个通道,可以自由选择接入哪些来观察当前关注的信息。
七、VCD 波形导出:让数据"可视化"
RTEdbg 的一大亮点是支持将日志数据导出为 VCD(Value Change Dump) 格式。VCD 是电子设计领域的标准波形文件格式,可以用开源工具 GTKWave 打开查看。
这对于 RTOS 任务调度分析尤其有价值:你可以像看逻辑分析仪波形一样,直观地看到每个任务的执行时间、切换时序、中断响应延迟等。
结合 FreeRTOS trace 功能,RTEdbg 可以自动生成任务执行的波形图,帮助你发现优先级反转、任务饥饿、死锁等问题。
八、写在最后
RTEdbg 诞生于一位深耕实时控制领域数十年的工程师之手。它不追求华丽的 GUI,而是专注于解决嵌入式调试中最本质的问题:如何以最小代价观察实时系统的真实行为。
对于嵌入式从业者来说,RTEdbg 是工具箱里值得拥有的一把利器。它学习成本低(基于 printf 语法)、集成简单(几个文件即可)、性能影响极小,并且一旦集成到项目中,就像给固件装上了"黑匣子"——无论是开发阶段的 Bug 追踪,还是产品交付后的现场故障分析,都能发挥价值。
项目地址: https://github.com/RTEdbg/RTEdbg
开始探索吧,你的下一个 Bug 或许就能靠它搞定。
320