嵌入式调试利器:SEGGER RTT 常用函数详解、实战技巧(J-Scope工具)与项目用例
嵌入式调试利器:SEGGER RTT 常用函数详解、实战技巧与项目用例
摘要
简要介绍 SEGGER RTT 技术的特点(无需额外硬件接口、高速双向通信、低资源占用),及其在嵌入式调试中的核心价值(日志输出、数据上传、命令输入)。强调本文将深入解析常用 API,分享实用调试技巧,并提供实际项目中的典型用例。
一、 RTT 技术核心概念与优势回顾 (简述)
- 工作原理: 基于目标芯片内存中的特殊缓冲区进行通信。
- 主要优势:
- 高速双向通信 (远快于传统 UART)。
- 无需占用或依赖物理通信端口 (UART, SWO)。
- 极低资源需求 (少量 RAM)。
- 可在中断处理函数中使用。
- 支持多个通道 (用于不同目的)。
- 与调试器运行并行 (调试时不暂停)。
- 典型工作流程: J-Link 调试器 ↔ RTT 控制块 ↔ 应用代码 (
SEGGER_RTT.hAPI)。
二、 核心 API 函数详解与用法
下面简单介绍一下主要的函数
2.1 常用API 函数
1. 初始化与基础配置:
SEGGER_RTT_ConfigUpBuffer(): 配置上行缓冲区 (目标 → 主机)。SEGGER_RTT_ConfigDownBuffer(): 配置下行缓冲区 (主机 → 目标)。SEGGER_RTT_Init(): (如果使用) 初始化 RTT 组件。
2. 数据输出 (目标 → 主机):
SEGGER_RTT_Write(): 向指定通道写入数据 (最常用)。SEGGER_RTT_WriteString(): 向指定通道写入字符串 (便捷函数)。SEGGER_RTT_printf(): 向 0 号通道格式化输出 (类似printf, 注意性能影响)。
3. 数据输入 (主机 → 目标):
SEGGER_RTT_Read(): 从指定通道读取数据。SEGGER_RTT_GetKey(): 从 0 号通道获取一个字符 (非阻塞)。SEGGER_RTT_WaitKey(): 从 0 号通道等待并获取一个字符 (阻塞)。SEGGER_RTT_HasKey(): 检查 0 号通道是否有可读字符。
4. 控制与状态:
SEGGER_RTT_SetTerminal(): 设置虚拟终端 (用于printf等)。SEGGER_RTT_SetFlagsUpBuffer()/SEGGER_RTT_SetFlagsDownBuffer(): 设置缓冲区标志 (如禁用、阻塞模式)。SEGGER_RTT_GetAvailWriteSpace(): 获取上行缓冲区剩余可用空间。
2.2 核心 API 函数用法
1. SEGGER_RTT_ConfigUpBuffer
int SEGGER_RTT_ConfigUpBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags)
- 功能:配置上行缓冲区(开发板→主机)。
非0通道确实需要先配置才能输出,不能直接使用。 - 参数说明:
BufferIndex:缓冲区索引(0为默认缓冲区,已预配置)。sName:缓冲区名称(可自定义:指向要显示为通道名称的 0 结尾字符串的指针)。pBuffer:自定义缓冲区地址(若使用默认缓冲区0,设为NULL)。BufferSize:缓冲区大小(默认缓冲区0时设为0)。Flags:工作模式(如SEGGER_RTT_MODE_NO_BLOCK_SKIP)。
宏定义关联:
BUFFER_SIZE_UP:默认缓冲区大小(通常为1024)。SEGGER_RTT_MAX_NUM_UP_BUFFERS:最大上行缓冲区数量(默认为3)。
工作模式选项:
SEGGER_RTT_MODE_NO_BLOCK_SKIP:缓冲区不足时丢弃全部数据。SEGGER_RTT_MODE_NO_BLOCK_TRIM:缓冲区不足时部分写入。SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL:阻塞等待缓冲区空间。
对于第1个参数BufferIndex = 0的时候,RTT组件已经为其配置了缓冲和默认的大小,其大小配置是在SEGGER_RTT_Conf.h 文件里面通过如下宏定义进行:
#define BUFFER_SIZE_UP (1024)
所以使用缓冲区0的时候,配置比较简单(第3个参数缓冲区地址设置为NULL,第4个参数缓冲区大小设置为0即可):
SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
还有两个与此函数相关的宏定义:
#define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3)
用于配置最大的上行缓冲数,也就是参数BufferIndex的范围。
#define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP
用于配置RTT的默认工作模式,也就是此函数的最后一个参数,这里是表示如果上行缓冲区不够
存储要发送的数据,将放弃写入缓冲区。除了这种配置还有如下两种:
SEGGER_RTT_MODE_NO_BLOCK_TRIM
表示如果上行缓冲区不够存储这些数据,能写多少写多少,无法写入的将丢弃。
SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL
表示如果上行缓冲区满,将被阻塞,等待有空间可用。
示例代码:
SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
2. SEGGER_RTT_ConfigDownBuffer
int SEGGER_RTT_ConfigDownBuffer(unsigned BufferIndex, const char* sName, char* pBuffer, int BufferSize, int Flags)
- 功能:配置下行缓冲区(主机→开发板)。
- 参数:与上行缓冲区配置类似,方向相反。
3. SEGGER_RTT_HasKey
int SEGGER_RTT_HasKey(void)
- 功能:检查接收缓冲区是否有数据。
- 返回值:
1:至少有一个数据。0:无数据。
4. SEGGER_RTT_GetKey
int SEGGER_RTT_GetKey(void)
- 功能:从下行缓冲区0读取一个字符。
- 注意:需配合
SEGGER_RTT_HasKey使用以避免阻塞。
5. SEGGER_RTT_SetTerminal
void SEGGER_RTT_SetTerminal(char TerminalId)
- 功能:指定RTT Viewer的终端窗口显示输出。
- 参数:
TerminalId为终端编号(如0、1等)。
6. SEGGER_RTT_WriteString
unsigned SEGGER_RTT_WriteString(unsigned BufferIndex, const char* s)
- 功能:向指定缓冲区写入字符串。
- 示例:
SEGGER_RTT_WriteString(0, "Hello RTT!\n");
7. SEGGER_RTT_printf
int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, …)
- 功能:格式化输出,类似标准
printf。 - 示例:
SEGGER_RTT_printf(0, "Value: %d, Name: %s\n", 42, "RTT");
注意事项:
- 缓冲区索引需在
SEGGER_RTT_MAX_NUM_UP_BUFFERS范围内。 - 默认缓冲区0无需自定义地址和大小,其他缓冲区需手动配置。
- 阻塞模式适用于实时性要求高的场景,但可能影响程序响应。
三、 高效调试技巧与最佳实践
- 可靠性保障:
- 处理缓冲区溢出:监控
SEGGER_RTT_GetAvailWriteSpace或设置溢出标志 (SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL)。 - 多线程/中断环境下的安全访问:使用互斥锁或关中断保护 RTT 操作。
- 处理缓冲区溢出:监控
- 通道策略:
- 通道 0: 默认用于
printf/Terminal,适合通用日志、交互命令。 - 专用通道: 为特定任务创建新通道 (如高频率数据采集、错误日志、调试命令),避免干扰。
- 通道 0: 默认用于
- 高级功能利用:
- 数据上传: 使用专用通道上传大量数据 (如传感器原始数据、波形、内存块)。
- 交互式调试: 通过
SEGGER_RTT_Read/GetKey接收主机命令,动态调整参数、触发操作。 - 时间戳: 结合系统时钟为日志添加时间戳。
- J-Link RTT Viewer 高级功能: 使用不同颜色区分通道/日志级别、保存日志到文件、发送命令。
- 典型使用流程:
-
初始化: 系统启动时
自动初始化 -
输出调试信息: 使用
SEGGER_RTT_WriteString()或SEGGER_RTT_printf() -
读取输入: 使用
SEGGER_RTT_HasKey() + SEGGER_RTT_GetKey() -
多通道: 可为不同模块分配不同缓冲区索引
-
四、 典型项目用例与场景分析
4.1 如何将数据输出到不同的终端中?
方法一:SEGGER_RTT_TerminalOut
- 向指定终端输出带格式的文本: 参数1:终端号 通过通道0 + 终端控制序列
// 这个函数实际上是通过通道0发送的!
SEGGER_RTT_TerminalOut(1, "Text");
// 等效于:
SEGGER_RTT_WriteString(0, "\x1B[2J"); // 清除屏幕控制序列
SEGGER_RTT_WriteString(0, "\x1B[1H"); // 光标定位控制序列
SEGGER_RTT_WriteString(0, "Text"); // 实际文本内容
实际上,SEGGER_RTT_TerminalOut(1, ...) 并不是输出到"通道1",而是:
-
使用通道0传输数据
-
数据中包含终端切换控制序列
-
RTT Viewer 识别这些序列后,在终端1标签页显示内容
#include "SEGGER_RTT.h"
static void _Delay(int period) {
int i = 100000*period;
do { ; } while (i--);
}
int main(void) {
int Cnt = 0;
SEGGER_RTT_Init();
SEGGER_RTT_WriteString(0, "Hello World from SEGGER!\n");
do {
SEGGER_RTT_printf(0, "%sCounter: %s%d\n",
RTT_CTRL_TEXT_BRIGHT_WHITE,
RTT_CTRL_TEXT_BRIGHT_GREEN,
Cnt);
if (Cnt > 100) {
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_RED"Counter overflow!\n");
Cnt = 0;
}
_Delay(100);
Cnt++;
} while (1);
}

方法二:使用虚拟终端
- 切换终端并输出: 通过
SEGGER_RTT_SetTerminal切换虚拟终端,然后通过通道0输出。 - 重要提示: 调用
SEGGER_RTT_SetTerminal后,后续通过通道0发送的数据会显示在对应的虚拟终端上。RTT Viewer支持最多16个虚拟终端。 - 在RTT Viewer中查看: 使用此方法时,RTT Viewer会为每个虚拟终端显示不同的标签页(例如
Terminal 0, Terminal 1...),无需手动切换通道。
#include "SEGGER_RTT.h"
#include "stm32f4xx.h"
int main(void) {
// 选择通道-方法一
SEGGER_RTT_TerminalOut(0, RTT_CTRL_TEXT_RED"SEGGER_RTT_TerminalOut Channel 0 data\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_GREEN"SEGGER_RTT_TerminalOut Channel 1 data\n");
SEGGER_RTT_TerminalOut(2, RTT_CTRL_TEXT_YELLOW"SEGGER_RTT_TerminalOut Channel 2 data\n");
SEGGER_RTT_TerminalOut(3, RTT_CTRL_TEXT_BLUE"SEGGER_RTT_TerminalOut Channel 3 data\n");
// 选择通道-方法二
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_RED"SEGGER_RTT_printf Channel 0 data\r\n");
SEGGER_RTT_SetTerminal(1);
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_GREEN"SEGGER_RTT_printf Channel 1 data\n");
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_GREEN"SEGGER_RTT_WriteString Channel 1 data\r\n");
SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_YELLOW"SEGGER_RTT_printf Channel 2 data\n");
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_YELLOW"SEGGER_RTT_WriteString Channel 2 data\r\n");
SEGGER_RTT_SetTerminal(3);
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BLUE"SEGGER_RTT_printf Channel 3 data\n");
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_BLUE"SEGGER_RTT_WriteString Channel 3 !!data\r\n");
}

4.2 可能在输出过程中遇到的问题
- 问题: 为什么在SEGGER_RTT_printf(0, “Channel 2 data: %d\n”, state);格式化输出state值时,需要对RTT Viewer重新链接才可以正常输出?
- 原因: 在SEGGER_RTT_printf输出时需要重新连接RTT Viewer才能看到数据的问题,通常和RTT的缓冲区机制、连接稳定性以及MCU与调试器的交互有关!
- 办法:
直接断掉重连!!!
#include "SEGGER_RTT.h"
#include "stm32f4xx.h"
int g_state = 10;
int main(void) {
int state = 10;
SEGGER_RTT_Init();//可有可无
SEGGER_RTT_printf(0, "Channel 0 data: %d\n", state);
state = 1000;
SEGGER_RTT_printf(0, "Channel 0 data: %d\n", state);
SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_printf(0, "Channel 2 data: %d\n", g_state );
g_state = 1000;
SEGGER_RTT_printf(0, "Channel 2 data: %d\n", g_state );
}

4.3 如何将数据输出波形?J-Scope工具使用方法—RTT模式
1. 数据输出波形的基本原理
- 波形数据的数学表示与格式(时域/频域)
- 常见波形输出接口:DAC、PWM、UART、SPI/I2C等硬件协议
- 数字信号到模拟信号的转换要点
2. 硬件连接与配置
- 目标设备要求:支持J-Link调试器的MCU(如STM32、NXP等)
- J-Link硬件连接方式:SWD接口接线图解
3. 配置连接参数
-
连接方式:通常选择USB。
-
目标设备:选择你的MCU型号,例如STM32F103C8T6或AT32F403A。
-
接口:根据你的硬件连接选择SWD或JTAG。
-
速度:可选择默认的4000kHz。

4. RTT 模式操作步骤
-
代码移植:
- 获取SEGGER RTT组件(通常包含
SEGGER_RTT.c和SEGGER_RTT.h等文件)并添加到你的工程。 - 在代码中调用
SEGGER_RTT_ConfigUpBuffer函数配置上行缓冲区。例如:// 示例:配置一个通道,传输两个无符号32位整数 SEGGER_RTT_ConfigUpBuffer(1, "JScope_u4u4", buffer, buffer_size, SEGGER_RTT_MODE_NO_BLOCK_SKIP);-
通道号:第一个参数是通道索引。
-
格式符:第二个参数指定数据格式,例如
"JScope_u4u4"表示两个uint32_t,"JScope_f4f4"表示两个float。常见格式符(具体支持情况需查阅官方文档或组件头文件):格式符 含义 u44字节无符号整数 i44字节有符号整数 f44字节浮点数 t44字节时间戳 -
其他常见格式符示例
// 单个有符号float "JScope_f4" // 三个有符号float (x, y, z坐标) "JScope_f4f4f4" // 有符号float + 有符号int32 "JScope_f4i4" // 有符号float + 无符号int32 "JScope_f4u4" // 时间戳 + 有符号float "JScope_t4f4" -
模式:常用
SEGGER_RTT_MODE_NO_BLOCK_SKIP(非阻塞,缓冲区满时丢弃新数据)或SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL(阻塞,缓冲区满时等待)。
-
- 在需要采样数据的地方(如定时器中断),调用
SEGGER_RTT_Write函数发送数据。// 示例:发送一个结构体数据 SEGGER_RTT_Write(1, &sensor_data, sizeof(sensor_data));
- 获取SEGGER RTT组件(通常包含
-
J-Scope配置:
- 新建工程时,在"Sampling Source"中选择"RTT"模式。
- RTT模式下不需要加载可执行文件。
- 完成设备、接口等基本配置后即可连接。
5. 故障排除
- 连接失败:检查J-Link与目标板的连接(VCC、GND、SWDIO、SWCLK等)是否可靠。确保目标MCU型号选择正确。
- RTT模式无数据:
- 检查RTT组件是否已正确添加到工程并编译。
- 确认
SEGGER_RTT_ConfigUpBuffer和SEGGER_RTT_Write函数中的通道号一致。 - 尝试降低SWD时钟频率。
- 检查代码中配置的RTT缓冲区索引与J-Scope读取的通道是否一致(默认通常是1)。
6. 重要提示
- 配置顺序:确保先调用
SEGGER_RTT_ConfigUpBuffer,再使用SEGGER_RTT_Write - 结构体打包:使用
#pragma pack(push, 1)确保没有内存对齐填充 - 缓冲区大小:确保缓冲区足够大,避免数据丢失
- 在J-Scope中:选择RTT模式,设备选择您的MCU型号
7. 工程用例
- 用例一:实时数据监控与采集
- 场景: 电机控制 PWM 波形、ADC 采样值、传感器数据流。
- 实现: 创建专用通道,使用
SEGGER_RTT_Write在 ISR 或任务中高速写入原始数据。在 RTT Viewer 中绘制波形或导出分析。 - 优势: 高速传输,无丢帧,实时性强。
用例1:完整的代码示例
#include "SEGGER_RTT.h"
char JS_RTT_UpBuffer[4096]; // J-Scope RTT Buffer
int JS_RTT_Channel = 1; // J-Scope RTT Channel
#pragma pack(push, 1)
struct {
unsigned int vU32;
signed int vI32;
} acValBuffer;
#pragma pack(pop)
static void _Delay(int period) {
int i = 100000*period;
do { ; } while (i--);
}
int main(void) {
int i;
SEGGER_RTT_ConfigUpBuffer(JS_RTT_Channel, "JScope_U4I4", &JS_RTT_UpBuffer[0], \
sizeof(JS_RTT_UpBuffer), SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL);
while (1)
{
if (i > 0)
{
acValBuffer.vU32 = 100;
acValBuffer.vI32 = -100;
i = 0;
}
else
{
acValBuffer.vU32 = 0;
acValBuffer.vI32 = 0;
i++;
}
SEGGER_RTT_Write(JS_RTT_Channel, &acValBuffer, sizeof(acValBuffer));
SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_printf(0, "Channel 2 data: %d\n", acValBuffer.vU32 );
SEGGER_RTT_printf(0, "Channel 2 data: %d\n", acValBuffer.vI32 );
_Delay(100);
}
}

用例2:完整的代码示例
#include "SEGGER_RTT.h"
#include <math.h>
char JS_RTT_UpBuffer[4096]; // J-Scope RTT Buffer
int JS_RTT_Channel = 1; // J-Scope RTT Channel
#pragma pack(push, 1)
struct {
float time;
float value;
} acValBuffer;
#pragma pack(pop)
static void _Delay(int period) {
int i = 100000*period;
do { ; } while (i--);
}
void generate_waveform_data(void) {
static float phase = 0.0f;
const float amplitude = 100.0f;
const float frequency = 1.0f; // Hz
for (int i = 0; i < 100; i++) {
acValBuffer.time = (float)i * 0.01f;
acValBuffer.value = amplitude * sinf(2 * 3.14159f * frequency * acValBuffer.time + phase);
// RTT Viewer或其他工具绘图
SEGGER_RTT_Write(JS_RTT_Channel, &acValBuffer, sizeof(acValBuffer));
SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_printf(0, "%.3f, %.3f\r\n", acValBuffer.time, acValBuffer.value);
_Delay(100);
}
phase += 0.1f;
}
int main(void)
{
SEGGER_RTT_ConfigUpBuffer(JS_RTT_Channel, "JScope_f4f4", &JS_RTT_UpBuffer[0], \
sizeof(JS_RTT_UpBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP);
while (1)
{
generate_waveform_data();
}
}

// 批量数据传输(适合大量数据记录)
void bulk_data_transfer(void) {
#define BATCH_SIZE 256
uint16_t data_buffer[BATCH_SIZE];
// 填充数据
for (int i = 0; i < BATCH_SIZE; i++) {
data_buffer[i] = read_sensor_fast();
}
// 批量传输(比逐个发送效率高很多)
SEGGER_RTT_Write(DATA_CHANNEL, data_buffer, sizeof(data_buffer));
}
- 用例三:交互式调试与参数调整
- 场景: 动态修改 PID 参数、设置工作模式、触发测试用例。
- 实现: 在主循环或专用任务中,使用
SEGGER_RTT_Read/HasKey/GetKey接收命令,解析并执行相应操作 (如修改变量值、调用函数)。 - 优势: 无需重新编译下载,快速验证想法。
#include "bsp.h"
#include "SEGGER_RTT.h"
int main(void)
{
uint8_t ucKeyCode; /* 按键代码 */
uint32_t i = 0;
int GetKey;
/*
由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。
启动文件 startup_stm32f4xx.s 会调用 system_stm32f4xx.c 中的 void SystemInit(void)。
SystemInit()函数配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。
安富莱STM32-V5开发板主晶振是25MHz, 内部PLL倍频到168MHz。如果需要更改主频,可以修改下面的文件:
\User\bsp_stm32f4xx\system_stm32f4xx.c
文件开头的几个宏是PLL倍频参数,修改这些宏就可以修改主频,无需更改硬件。
*/
bsp_Init(); /* 硬件初始化 */
bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
/* 配置通道0,上行配置*/
SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
/* 配置通道0,下行配置*/
SEGGER_RTT_ConfigDownBuffer(0, "RTTDOWN", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
/* 进入主程序循环体 */
while (1)
{
bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
/* 判断定时器超时时间 */
if (bsp_CheckTimer(0))
{
bsp_LedToggle(4);
}
/* 做一个简单的回环功能 */
if (SEGGER_RTT_HasKey())
{
GetKey = SEGGER_RTT_GetKey();
SEGGER_RTT_SetTerminal(0);
SEGGER_RTT_printf(0, "SEGGER_RTT_GetKey = %c\r\n", GetKey);
}
/* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下 */
SEGGER_RTT_SetTerminal(1);
SEGGER_RTT_WriteString(0,
RTT_CTRL_RESET"Red: " \
RTT_CTRL_TEXT_RED"This text is red. " \
RTT_CTRL_TEXT_BLACK"" \
RTT_CTRL_BG_BRIGHT_GREEN"This background is green. " \
RTT_CTRL_RESET"Normal text again.\r\n");
break;
case KEY_DOWN_K2: /* K2键按下 */
SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_BRIGHT_GREEN"KEY_DOWN_K2, ArmFly Real-Time-Terminal Sample\r\n");
break;
case KEY_DOWN_K3: /* K3键按下 */
SEGGER_RTT_SetTerminal(3);
SEGGER_RTT_printf(0, "KEY_DOWN_K3, i = %d\r\n", i++);
break;
default:
/* 其它的键值不处理 */
break;
}
}
}
}
- 用例四:崩溃分析与错误追踪
- 场景: 系统 HardFault 或其他严重错误。
- 实现: 在错误处理函数中 (如 HardFault_Handler),使用 RTT 输出关键寄存器值 (PC, LR, SP)、堆栈内容、错误代码。确保错误处理函数不使用可能导致二次错误的资源。
- 优势: 即使调试器无法正常连接,也能获取关键错误信息。
4.1 HardFault 错误处理
#include "SEGGER_RTT.h"
#include "core_cm4.h" // 根据你的Cortex内核选择
// HardFault处理函数
void HardFault_Handler(void) {
// 立即禁用所有中断
__disable_irq();
// 输出错误分隔符
SEGGER_RTT_WriteString(0, "\r\n" RTT_CTRL_TEXT_BRIGHT_RED);
SEGGER_RTT_WriteString(0, "!!! HARD FAULT !!!\r\n");
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_BRIGHT_WHITE);
// 获取关键寄存器
uint32_t stacked_r0 = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[0];
uint32_t stacked_r1 = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[1];
uint32_t stacked_r2 = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[2];
uint32_t stacked_r3 = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[3];
uint32_t stacked_r12 = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[4];
uint32_t stacked_lr = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[5];
uint32_t stacked_pc = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[6];
uint32_t stacked_psr = ((__attribute__((section(".stack"))) uint32_t*)__get_PSP())[7];
uint32_t cfsr = SCB->CFSR;
uint32_t hfsr = SCB->HFSR;
uint32_t dfsr = SCB->DFSR;
uint32_t afsr = SCB->AFSR;
uint32_t mmfar = SCB->MMFAR;
uint32_t bfar = SCB->BFAR;
// 输出寄存器状态
SEGGER_RTT_printf(0, "Stack Frame:\r\n");
SEGGER_RTT_printf(0, "R0 : 0x%08lX\r\n", stacked_r0);
SEGGER_RTT_printf(0, "R1 : 0x%08lX\r\n", stacked_r1);
SEGGER_RTT_printf(0, "R2 : 0x%08lX\r\n", stacked_r2);
SEGGER_RTT_printf(0, "R3 : 0x%08lX\r\n", stacked_r3);
SEGGER_RTT_printf(0, "R12: 0x%08lX\r\n", stacked_r12);
SEGGER_RTT_printf(0, "LR : 0x%08lX\r\n", stacked_lr);
SEGGER_RTT_printf(0, "PC : 0x%08lX\r\n", stacked_pc);
SEGGER_RTT_printf(0, "PSR: 0x%08lX\r\n", stacked_psr);
// 输出故障状态寄存器
SEGGER_RTT_printf(0, "Fault Status:\r\n");
SEGGER_RTT_printf(0, "CFSR: 0x%08lX\r\n", cfsr);
SEGGER_RTT_printf(0, "HFSR: 0x%08lX\r\n", hfsr);
SEGGER_RTT_printf(0, "DFSR: 0x%08lX\r\n", dfsr);
SEGGER_RTT_printf(0, "AFSR: 0x%08lX\r\n", afsr);
if (cfsr & (1 << 7)) { // MMFAR valid
SEGGER_RTT_printf(0, "MMFAR: 0x%08lX\r\n", mmfar);
}
if (cfsr & (1 << 15)) { // BFAR valid
SEGGER_RTT_printf(0, "BFAR: 0x%08lX\r\n", bfar);
}
// 分析故障原因
SEGGER_RTT_printf(0, "Fault Analysis:\r\n");
if (cfsr & (1 << 0)) SEGGER_RTT_WriteString(0, " - IACCVIOL: Instruction access violation\r\n");
if (cfsr & (1 << 1)) SEGGER_RTT_WriteString(0, " - DACCVIOL: Data access violation\r\n");
if (cfsr & (1 << 3)) SEGGER_RTT_WriteString(0, " - MUNSTKERR: MemManage fault on unstacking\r\n");
if (cfsr & (1 << 4)) SEGGER_RTT_WriteString(0, " - MSTKERR: MemManage fault on stacking\r\n");
if (cfsr & (1 << 5)) SEGGER_RTT_WriteString(0, " - MLSPERR: MemManage fault during FP lazy state\r\n");
if (cfsr & (1 << 7)) SEGGER_RTT_WriteString(0, " - MMARVALID: MMFAR is valid\r\n");
if (cfsr & (1 << 8)) SEGGER_RTT_WriteString(0, " - IBUSERR: Instruction bus error\r\n");
if (cfsr & (1 << 9)) SEGGER_RTT_WriteString(0, " - PRECISERR: Precise data bus error\r\n");
if (cfsr & (1 << 10)) SEGGER_RTT_WriteString(0, " - IMPRECISERR: Imprecise data bus error\r\n");
if (cfsr & (1 << 11)) SEGGER_RTT_WriteString(0, " - UNSTKERR: BusFault on unstacking\r\n");
if (cfsr & (1 << 12)) SEGGER_RTT_WriteString(0, " - STKERR: BusFault on stacking\r\n");
if (cfsr & (1 << 13)) SEGGER_RTT_WriteString(0, " - LSPERR: BusFault during FP lazy state\r\n");
if (cfsr & (1 << 15)) SEGGER_RTT_WriteString(0, " - BFARVALID: BFAR is valid\r\n");
if (cfsr & (1 << 16)) SEGGER_RTT_WriteString(0, " - UNDEFINSTR: Undefined instruction\r\n");
if (cfsr & (1 << 17)) SEGGER_RTT_WriteString(0, " - INVSTATE: Invalid state\r\n");
if (cfsr & (1 << 18)) SEGGER_RTT_WriteString(0, " - INVPC: Invalid PC load\r\n");
if (cfsr & (1 << 19)) SEGGER_RTT_WriteString(0, " - NOCP: No coprocessor\r\n");
if (cfsr & (1 << 24)) SEGGER_RTT_WriteString(0, " - UNALIGNED: Unaligned access\r\n");
if (cfsr & (1 << 25)) SEGGER_RTT_WriteString(0, " - DIVBYZERO: Division by zero\r\n");
// 输出堆栈内容(前256字节)
uint32_t* stack_ptr = (uint32_t*)__get_PSP();
SEGGER_RTT_printf(0, "Stack Dump (SP: 0x%08lX):\r\n", (uint32_t)stack_ptr);
for (int i = 0; i < 64; i++) { // 64 * 4 = 256 bytes
if (i % 4 == 0) SEGGER_RTT_printf(0, "0x%08lX: ", (uint32_t)(stack_ptr + i));
SEGGER_RTT_printf(0, "%08lX ", stack_ptr[i]);
if (i % 4 == 3) SEGGER_RTT_WriteString(0, "\r\n");
}
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_BRIGHT_RED "System Halted\r\n");
// 死循环,保持系统状态供分析
while (1) {
__asm("NOP");
}
}
4.2 断言失败处理
// 自定义断言处理
void custom_assert_failed(uint8_t* file, uint32_t line) {
__disable_irq();
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_BRIGHT_RED);
SEGGER_RTT_printf(0, "!!! ASSERTION FAILED !!!\r\n");
SEGGER_RTT_printf(0, "File: %s\r\n", file);
SEGGER_RTT_printf(0, "Line: %lu\r\n", line);
SEGGER_RTT_WriteString(0, RTT_CTRL_TEXT_BRIGHT_WHITE);
// 可以在这里添加更多调试信息
dump_system_state();
while (1) {
__asm("NOP");
}
}
// 系统状态转储
void dump_system_state(void) {
SEGGER_RTT_printf(0, "System State Dump:\r\n");
SEGGER_RTT_printf(0, "Tick: %lu\r\n", HAL_GetTick());
SEGGER_RTT_printf(0, "Free Heap: %lu bytes\r\n", xPortGetFreeHeapSize());
SEGGER_RTT_printf(0, "Min Free Heap: %lu bytes\r\n", xPortGetMinimumEverFreeHeapSize());
// RTOS任务状态(如果使用RTOS)
#ifdef USE_FREERTOS
SEGGER_RTT_WriteString(0, "Task States:\r\n");
vTaskList((char*)rtt_buffer); // 需要提供足够大的缓冲区
SEGGER_RTT_WriteString(0, rtt_buffer);
#endif
}
// 在main.h中重定义assert
#ifdef USE_FULL_ASSERT
#define assert_param(expr) ((expr) ? (void)0 : custom_assert_failed((uint8_t *)__FILE__, __LINE__))
#endif
4.3 使用注意事项
-
HardFault处理器的链接:在启动文件中确保HardFault_Handler使用Weak符号,或在链接脚本中正确链接。
-
内存安全:在错误处理中避免使用可能不稳定的内存操作。
-
中断处理:在崩溃处理开始时立即禁用所有中断。
-
RTT缓冲区:确保RTT缓冲区足够大以容纳完整的错误信息。
这些例程提供了完整的RTT调试解决方案,可以根据具体需求进行裁剪和扩展。
附录 :
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)