摘要

   简要介绍 SEGGER RTT 技术的特点(无需额外硬件接口、高速双向通信、低资源占用),及其在嵌入式调试中的核心价值(日志输出、数据上传、命令输入)。强调本文将深入解析常用 API,分享实用调试技巧,并提供实际项目中的典型用例。

一、 RTT 技术核心概念与优势回顾 (简述)

  1. 工作原理: 基于目标芯片内存中的特殊缓冲区进行通信。
  2. 主要优势:
    • 高速双向通信 (远快于传统 UART)。
    • 无需占用或依赖物理通信端口 (UART, SWO)。
    • 极低资源需求 (少量 RAM)。
    • 可在中断处理函数中使用。
    • 支持多个通道 (用于不同目的)。
    • 与调试器运行并行 (调试时不暂停)。
  3. 典型工作流程: J-Link 调试器 ↔ RTT 控制块 ↔ 应用代码 (SEGGER_RTT.h API)。

二、 核心 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. 控制与状态:

  1. SEGGER_RTT_SetTerminal(): 设置虚拟终端 (用于 printf 等)。
  2. SEGGER_RTT_SetFlagsUpBuffer() / SEGGER_RTT_SetFlagsDownBuffer(): 设置缓冲区标志 (如禁用、阻塞模式)。
  3. 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无需自定义地址和大小,其他缓冲区需手动配置。
  • 阻塞模式适用于实时性要求高的场景,但可能影响程序响应。

三、 高效调试技巧与最佳实践

  1. 可靠性保障:
    • 处理缓冲区溢出:监控 SEGGER_RTT_GetAvailWriteSpace 或设置溢出标志 (SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL)。
    • 多线程/中断环境下的安全访问:使用互斥锁或关中断保护 RTT 操作。
  2. 通道策略:
    • 通道 0: 默认用于 printf / Terminal,适合通用日志、交互命令。
    • 专用通道: 为特定任务创建新通道 (如高频率数据采集、错误日志、调试命令),避免干扰。
  3. 高级功能利用:
    • 数据上传: 使用专用通道上传大量数据 (如传感器原始数据、波形、内存块)。
    • 交互式调试: 通过 SEGGER_RTT_Read / GetKey 接收主机命令,动态调整参数、触发操作。
    • 时间戳: 结合系统时钟为日志添加时间戳。
    • J-Link RTT Viewer 高级功能: 使用不同颜色区分通道/日志级别、保存日志到文件、发送命令。
  4. 典型使用流程:
    • 初始化: 系统启动时自动初始化

    • 输出调试信息: 使用 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 模式操作步骤

  1. 代码移植

    • 获取SEGGER RTT组件(通常包含SEGGER_RTT.cSEGGER_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。常见格式符(具体支持情况需查阅官方文档或组件头文件):

        格式符 含义
        u4 4字节无符号整数
        i4 4字节有符号整数
        f4 4字节浮点数
        t4 4字节时间戳
      • 其他常见格式符示例

        // 单个有符号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));
      
  2. J-Scope配置

    • 新建工程时,在"Sampling Source"中选择"RTT"模式。
    • RTT模式下不需要加载可执行文件
    • 完成设备、接口等基本配置后即可连接。

5. 故障排除

  • 连接失败:检查J-Link与目标板的连接(VCC、GND、SWDIO、SWCLK等)是否可靠。确保目标MCU型号选择正确。
  • RTT模式无数据
    • 检查RTT组件是否已正确添加到工程并编译。
    • 确认SEGGER_RTT_ConfigUpBufferSEGGER_RTT_Write函数中的通道号一致
    • 尝试降低SWD时钟频率
    • 检查代码中配置的RTT缓冲区索引与J-Scope读取的通道是否一致(默认通常是1)。

6. 重要提示

  1. 配置顺序:确保先调用 SEGGER_RTT_ConfigUpBuffer,再使用 SEGGER_RTT_Write
  2. 结构体打包:使用 #pragma pack(push, 1) 确保没有内存对齐填充
  3. 缓冲区大小:确保缓冲区足够大,避免数据丢失
  4. 在J-Scope中:选择RTT模式,设备选择您的MCU型号

7. 工程用例

  1. 用例一:实时数据监控与采集
    • 场景: 电机控制 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));
}
  1. 用例三:交互式调试与参数调整
    • 场景: 动态修改 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; 
		} 
		} 
 } 
} 
  1. 用例四:崩溃分析与错误追踪
    • 场景: 系统 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 使用注意事项

  1. HardFault处理器的链接:在启动文件中确保HardFault_Handler使用Weak符号,或在链接脚本中正确链接。

  2. 内存安全:在错误处理中避免使用可能不稳定的内存操作。

  3. 中断处理:在崩溃处理开始时立即禁用所有中断。

  4. RTT缓冲区:确保RTT缓冲区足够大以容纳完整的错误信息。

这些例程提供了完整的RTT调试解决方案,可以根据具体需求进行裁剪和扩展。

附录 :

https://kb.segger.com/RTT

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐