这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » J-LinkRTT常用API函数讲解

共2条 1/1 1 跳转至页

J-LinkRTT常用API函数讲解

菜鸟
2026-11-08 22:36:42     打赏


目标:

  • 了解J-Link的常用API函数有哪些

  • 学会J-Link的函数的使用场景

这一节的内容比较偏向于理论讲解,笔者的语气要变得正式起来了(笔者坚信自己一直是一个严肃的人),这一节的内容可以当作一个笔记速查手册,方便大家快速的调用各个API(我是一名快乐的掉包侠~啦啦啦~)咳咳,不小心又偏了(笔者其实是故意的真的(祈求相信中...)),接下来要讲重点了,相信大家都知道对于数据传输和接收的时候,我们希望在传输和接收这些数据不受干扰,那怎么办呢,赶快打开我心爱的浏览器查一查----查到了(如果你没有查到笔者建议便宜卖给笔者,笔者以市场价0.001折的价格购买(这么良心的买家不多了)),接下来继续走向正题:常用的是三种方式Mutex互斥锁,临界区,关中断(当然不是简单除暴的直接关掉,需要先保留之前的中断寄存器的状态,方便后面进行中断系统的恢复---我重生了,这一世我要拿回属于我的一切(当然重生回去的时候要记得自己的经历,要是不记得,嘿嘿就变成了---我重生了,这一世我要继续受折磨哈哈)),RTT的临界区的实现就是关闭中断的方式同时通过SEGGER\RTT\SEGGER_RTT_ConfDefaults.h的#define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20)这个定义来屏蔽--笔者自己封装的日志就是依赖于类似的日志等级实现的(可以狠狠的期待一波了),踩坑指南:在RTT中,SEGGER_RTT官方已经通过SEGGER_RTT_LOCK()和SEGGER_RTT_UNLOCK()实现了遇到并发时的线程安全,所以就不需要自己加保护了(笔者当时就画蛇添足研究了对RTT输出的Mutex保护)

小科普:

  • Mutex(互斥锁):主要解决多个任务之间争夺共享资源的问题。拿不到锁的任务通常会进入阻塞状态,让其他任务运行。,不会影响其他任务的调度

  • 临界区(Critical Section):指访问共享资源、不能被不安全并发访问打断的代码区域,也可以指进入和退出这类区域的保护机制。此时os的其他任务的调度无法正常运行,同时会屏蔽部分中断(根据阈值进行判断:优先级小于屏蔽阈值的讲被沉默,反之,拥有霸体效果无法屏蔽正常运行)

  • 关中断(Disable Interrupt):是一种底层手段,通过屏蔽中断防止部分中断打断当前代码,此时os的其他任务的调度无法正常运行(调度源于中断)

踩坑指南:

  • 官方的RTT Viewer只支持通道0的终端输出显示,其他的通道不支持,请使用ozone的RTT功能(支持所有的通道但是一次只能看一个)

  • 官方的RTT Viewer不支持中文的显示,会乱码,请使用ozone的RTT功能(支持中文显示)(还是中文有亲切感)

1. API 速查

本文介绍的是在目标 MCU 上调用的 SEGGER RTT API。相关源码来自 SEGGER 官方 RTT 仓库。

函数作用方向 / 通道是否等待
SEGGER_RTT_ConfigDownBuffer()配置下行通道指定 Down不等待数据
SEGGER_RTT_ConfigUpBuffer()配置上行通道指定 Up不等待空间
SEGGER_RTT_GetKey()取一个输入字节Down 0不等待
SEGGER_RTT_HasKey()检查是否有输入Down 0不等待
SEGGER_RTT_Init()初始化控制块Up / Down不等待
SEGGER_RTT_printf()可变参数格式化输出指定 Up取决于 Flags
SEGGER_RTT_vprintf()va_list 格式化输出指定 Up取决于 Flags
SEGGER_RTT_Read()读取一批字节指定 Down不等待
SEGGER_RTT_SetTerminal()切换虚拟终端Up 0取决于 Flags
SEGGER_RTT_TerminalOut()临时向终端输出字符串Up 0取决于 Flags
SEGGER_RTT_WaitKey()等待并取一个输入字节Down 0会等待
SEGGER_RTT_Write()按长度写数据指定 Up取决于 Flags
SEGGER_RTT_WriteString()写 \0 结尾字符串指定 Up取决于 Flags
SEGGER_RTT_GetAvailWriteSpace()查询剩余空间指定 Up不等待

2. SEGGER_RTT_ConfigDownBuffer()

通过指定名称、内存、大小和 Flags,配置一个 Down Buffer,用于接收 PC → MCU 的数据。

int SEGGER_RTT_ConfigDownBuffer(unsigned BufferIndex, const char* sName,
                                void* pBuffer, unsigned BufferSize,
                                unsigned Flags);
参数含义
BufferIndex下行通道索引,必须小于 SEGGER_RTT_MAX_NUM_DOWN_BUFFERS
sName通道名称,指向以 \0 结尾的字符串
pBufferRTT 下行环形缓冲区的内存地址
BufferSize内存大小,单位为字节
Flags通道工作模式


返回值:本地实现成功返回 0,索引超范围返回 -1。此函数需要自己分配内存同时通道总数是固定的

static char DownBuffer1[64];  // 地址必须在通道使用期间一直有效

int RTT_ConfigInput(void) {
    return SEGGER_RTT_ConfigDownBuffer(1, "DataIn", DownBuffer1,
                                       sizeof(DownBuffer1),
                                       SEGGER_RTT_MODE_NO_BLOCK_SKIP);
}

pBuffer 和 sName 的地址会被保存;缓冲区要求是存在与整个生命周期的数组。对非 0 通道重复调用此函数配置时会重置读写偏移(RTT的环形缓冲区就是用头尾指针实现的我自己的环形缓冲区也是),可能丢掉尚未处理的数据。运行中修改模式或名称,应使用 SEGGER_RTT_SetFlagsDownBuffer() 或 SEGGER_RTT_SetNameDownBuffer()。

通道 0 在RTT由初始化代码已经完成了缓冲区的配置,所以调用此函数通常只更新 Flags(自己传入的数组没有作用不要像小丑一样骗自己哈哈);要改变它的大小,应该修改 BUFFER_SIZE_DOWN 这个变量。

3. SEGGER_RTT_ConfigUpBuffer()

配置一个 Up Buffer,用于发送 MCU → PC 的数据。

int SEGGER_RTT_ConfigUpBuffer(unsigned BufferIndex, const char* sName,
                              void* pBuffer, unsigned BufferSize,
                              unsigned Flags);

参数含义与 ConfigDownBuffer() 相同,但 BufferIndex 必须小于 SEGGER_RTT_MAX_NUM_UP_BUFFERS,内存用于上行方向。成功返回 0,索引无效返回 -1。

static char UpBuffer1[256];

int RTT_ConfigOutput(void) {
    return SEGGER_RTT_ConfigUpBuffer(1, "DataOut", UpBuffer1,
                                     sizeof(UpBuffer1),
                                     SEGGER_RTT_MODE_NO_BLOCK_SKIP);
}

通道 0 在RTT由初始化代码已经完成了缓冲区的配置,所以调用此函数通常只更新 Flags(自己传入的数组没有作用不要像小丑一样骗自己哈哈);要改变它的大小,应该修改 BUFFER_SIZE_UP 这个变量。

4. SEGGER_RTT_GetKey()

从下行通道 0 取出一个字节,没有输入就立即返回。

int SEGGER_RTT_GetKey(void);

返回 0~255 表示读到的字节,返回 -1 表示没有数据。

int key = SEGGER_RTT_GetKey();
if (key >= 0 && key == 'q') {
    SEGGER_RTT_WriteString(0, "Received q\n");
}

使用 int 保存返回值,以区分 -1 与有效的 0 字节;一次调用只取一个字节。

5. SEGGER_RTT_HasKey()

检查下行通道 0 是否至少有一个可读字节,不取走数据。

int SEGGER_RTT_HasKey(void);

返回 1 表示有数据,0 表示没有数据。

6. SEGGER_RTT_Init()

初始化 RTT 控制块,建立默认上行通道 0 和下行通道 0。

void SEGGER_RTT_Init(void);
void RTT_Start(void) {
    SEGGER_RTT_Init();
    SEGGER_RTT_WriteString(0, "RTT ready\n");
    // 自定义通道的 Config*Buffer() 放在 Init() 之后
}

SEGGER_RTT_Init() 会清空控制块、重建默认通道,使已有配置和待处理数据失效。同时会对通道0进行默认配置。(环形缓冲区的头尾指针也会重置)

7. SEGGER_RTT_printf()

把格式字符串和可变参数转换成文本,再写入指定上行通道。

int SEGGER_RTT_printf(unsigned BufferIndex, const char* sFormat, ...);

BufferIndex 是上行通道索引;sFormat 是以 \0 结尾的格式字符串;... 是与格式说明符对应的参数。非负数表示输出字节数、负数表示错误,

int speed = 120;
unsigned state = 0x2Au;
SEGGER_RTT_printf(0, "speed=%d, state=0x%02X\n", speed, state);

RTT实现支持 %c、%d、%u、%x、%X、%s、%p 和 %%。格式基本结构为:

%[flags][FieldWidth][.Precision]ConversionSpecifier

支持的常见写法:%05d(补零)、%-5d(左对齐)、%+d(显示正号)、%.4d(至少 4 位数字)、%.3s(最多 3 个字节)。本地 %x 和 %X 都输出大写 A~F。

注意此函数不支持浮点数的输出,需要自己进行转换

常用格式与实际输出

下面只展示正文,不包含换行;方括号用于看清空格和字段边界。

调用中的格式与参数输出正文说明
"%c", 'A'A输出一个字符
"%d", -120-120有符号十进制
"%u", 120u120无符号十进制
"0x%02X", 10u0x0A宽度至少 2 位,前缀由格式字符串提供
"[%5d]", 12[   12]数字右对齐,宽度不足时补空格
"[%-5d]", 12[12   ]数字左对齐,末尾补空格
"%05d", -12-0012总宽度包含负号
"%+d", 12+12正数也显示符号
"%.4d", 120012整数精度表示最少数字位数
"%.3s", "motor"mot字符串精度表示最多输出的字节数
"%.*s", 3, "motor"mot从额外的 int 参数读取精度
"load=100%%"load=100%输出字面量百分号

这就是复杂的格式化知识了,我们使用常用的就可以了

8. SEGGER_RTT_vprintf()

接收已经建立的可变参数列表,把它按格式字符串转换成文本并写入指定上行通道

int SEGGER_RTT_vprintf(unsigned BufferIndex, const char* sFormat,
                       va_list* pParamList);

这里第三个参数是 va_list*,与标准库常见的 vprintf(format, args) 签名不同。参数列表必须先由 va_start() 或 va_copy() 初始化,使用后由调用者 va_end()。

#include <stdarg.h>
#include "SEGGER_RTT.h"

int RTT_Log(const char* format, ...) {
    int result;
    va_list args;
    va_start(args, format);
    result = SEGGER_RTT_vprintf(0, format, &args);
    va_end(args);
    return result;
}

// RTT_Log("motor=%d, speed=%d\n", 1, 120);

调用链是:printf() / 自定义日志函数 → vprintf() 解析格式 → _StoreChar() 放入临时缓冲区 → 满块或结束时调用 Write() → 上行环形缓冲区 → J-Link / PC。临时缓存默认 64 字节,是分批写出的缓存,不是整条日志的长度限制;默认上行通道 0 的环形缓冲区是 1024 字节。

9. SEGGER_RTT_Read()

从指定 Down Buffer 取出 PC 已写入的数据,复制到应用接收数组。

unsigned SEGGER_RTT_Read(unsigned BufferIndex, void* pBuffer,
                         unsigned BufferSize);

BufferIndex 是下行通道索引;pBuffer 是应用接收数组;BufferSize 是本次最多读取的字节数。返回实际读取的 0~BufferSize 字节,没有数据时立即返回 0。

char rx[32];
unsigned n = SEGGER_RTT_Read(0, rx, sizeof(rx) - 1u);
if (n > 0u) {
    rx[n] = '\0';  // 只有这样才可把本次数据当作 C 字符串
    SEGGER_RTT_printf(0, "RX: %s\n", rx);
}

RTT 传输的是字节序列,不自动补 \0,二进制协议应按返回长度处理。一次 Read() 也不保证得到一条完整命令;应用要按换行、固定长度或协议帧边界自行累积解析。

10. SEGGER_RTT_SetTerminal()

切换上行通道 0 后续输出所使用的虚拟终端。

int SEGGER_RTT_SetTerminal(unsigned char TerminalId);

本地 _aTerminalId[16] 支持 0~15,编码为 0~9、A~F。成功返回 0;编号无效,或非阻塞模式下没有足够空间写入 2 字节切换命令时,返回 -1。

if (SEGGER_RTT_SetTerminal(1) == 0) {
    SEGGER_RTT_WriteString(0, "ERROR: motor timeout\n");
    if (SEGGER_RTT_SetTerminal(0) < 0) {
        // 恢复失败,后续输出仍可能发往终端 1
    }
}

切换后仍写通道 0,不要把终端编号当成通道编号。官方的RTT Viewer只支持通道 0,其他的通道需要自己找支持的上位机了,(这设计不合理,我要提issue太可恶了,找了半天就是找不到),笔者这边建议使用ozone的RTT功能,这里面支持,嘿嘿,勉强原谅你了(不可能的,没有一杯我满意的奶茶是哄不好我的)

11. SEGGER_RTT_TerminalOut()

临时向指定虚拟终端输出字符串,结束后恢复原先的终端。(只影响只一次的使用,对于我的日志太合适了)

int SEGGER_RTT_TerminalOut(unsigned char TerminalId, const char* s);

TerminalId 为目标终端,s 为 \0 结尾字符串。非负返回值为正文写入长度(不含控制字节),-1 表示错误;SKIP 模式下空间不足也可能返回 0。

SEGGER_RTT_TerminalOut(1, "ERROR: motor timeout\n");
SEGGER_RTT_WriteString(0, "Continue normal log\n");
// 第二行仍发往调用 TerminalOut() 之前的活动终端

它会额外写入切换和恢复命令,共 4 字节:SKIP 必须能容纳正文加 4 字节,否则整次跳过;TRIM 会为控制命令留空间并截短正文;BLOCK 会等待完整写出。

12. SEGGER_RTT_WaitKey()

等待下行通道 0 出现一个字节,取出后返回。

int SEGGER_RTT_WaitKey(void);

返回读到的 0~255 字节。它通过不断调用 GetKey() 忙等,没有超时,也不会自动让出 RTOS 任务;需要保持任务运行时应使用 GetKey() / Read() 轮询并自行安排延时。

13. SEGGER_RTT_Write()

把指定长度的数据写入 Up Buffer,可用于文本或二进制数据。

unsigned SEGGER_RTT_Write(unsigned BufferIndex, const void* pBuffer,
                          unsigned NumBytes);

pBuffer 不要求以 \0 结尾,NumBytes 严格决定发送长度。返回实际写入 RTT 缓冲区的字节数;返回类型是 unsigned,应与请求长度比较,不要用“小于 0”判断失败。

const unsigned char frame[] = {0xA5, 0x00, 0x12, 0x5A};
unsigned written = SEGGER_RTT_Write(0, frame, sizeof(frame));
if (written != sizeof(frame)) {
    // SKIP 可能整次丢弃;TRIM 可能只写入一部分
}

数据中有 0x00 不会提前结束。

14. SEGGER_RTT_WriteString()

写入一个 \0 结尾字符串,不发送末尾的结束符。

unsigned SEGGER_RTT_WriteString(unsigned BufferIndex, const char* s);

返回实际写入的字节数,空间不足时按通道 Flags 处理。它内部计算字符串长度后调用 Write(),不会自动添加换行,也不会解析 %d 等格式说明符。

SEGGER_RTT_WriteString(0, "Hello World from STM32!\n");

固定文本用 WriteString();二进制或已知长度数据用 Write();带变量的文本用 printf();自定义可变参数日志用 vprintf()。

15. SEGGER_RTT_GetAvailWriteSpace()

查询指定 Up Buffer 当前还能写入多少字节。

unsigned SEGGER_RTT_GetAvailWriteSpace(unsigned BufferIndex);

返回当前空闲可写字节数。本地函数不会调用 INIT(),应先初始化 RTT,并确保通道已配置。

const char msg[] = "motor ready\n";
const unsigned len = (unsigned)(sizeof(msg) - 1u);
if (SEGGER_RTT_GetAvailWriteSpace(0) >= len) {
    unsigned written = SEGGER_RTT_Write(0, msg, len);
    if (written != len) {
        // 查询之后,空间可能已被其他任务占用
    }
}

环形缓冲区会保留 1 字节区分空和满,因此大小为 N 时最大可保存 N - 1 字节。查询与写入之间空间可能变化,不能把查询结果当成写入保证;最终仍应检查 Write() 返回值。

16. SEGGER_RTT_printf.c 内部函数讲解

这一部分对应官方源码中的 Static code。三个函数均返回 void,通过修改 SEGGER_RTT_PRINTF_DESC 传递缓存状态和错误状态;应用层继续使用公开的 printf() / vprintf() 即可。

16.1 SEGGER_RTT_PRINTF_DESC:一次格式化输出的状态

它是结构体类型,不是 API。vprintf() 在栈上创建它,并关联本次调用的临时字符数组。

成员作用
pBuffer指向本次格式化使用的临时数组 acBuffer
BufferSize临时数组容量,默认 64 字节
Cnt当前临时数组中尚待写出的字节数
ReturnValue字符累计计数和错误状态;本版本存在 6.8 所述计数问题
RTTBufferIndex最终要写入的上行通道索引

这里的临时数组与 RTT 上行环形缓冲区是两块不同的内存:前者负责分批收集格式化字符,后者负责保存等待 J-Link 读取的数据。

16.2 _StoreChar():保存一个字符,满块时写出

static void _StoreChar(SEGGER_RTT_PRINTF_DESC* p, char c);

p 指向本次输出状态,c 是待输出的一个字符。执行流程为:

  1. 临时数组有空间时,把 c 放到 pBuffer[Cnt],递增 Cnt 和 ReturnValue。

  2. 当 Cnt == BufferSize 时,调用 SEGGER_RTT_Write() 写出这一整块。

  3. 若实际写入量等于请求量,把 Cnt 清零,继续收集字符;否则设置 ReturnValue = -1,上层检测到错误后结束格式化。

例如默认配置下,一条 70 字节日志会先写出 64 字节,再由 vprintf() 在结束时写出剩余 6 字节。临时数组不会把整条日志截断到 64 字节;最终是否完整写入仍由通道空间与工作模式决定。

16.3 _PrintUnsigned():把无符号整数转换成数字字符

static void _PrintUnsigned(SEGGER_RTT_PRINTF_DESC* pBufferDesc,
                           unsigned v, unsigned Base,
                           unsigned NumDigits, unsigned FieldWidth,
                           unsigned FormatFlags);
参数含义
pBufferDesc当前格式化输出状态
v要转换的无符号整数
Base进制;公开格式解析中使用 10 或 16
NumDigits最少数字位数,来自整数精度
FieldWidth最小字段宽度;数值更长时不会截断
FormatFlags左对齐、补零等格式标志

它先计算数字位数,再按宽度补空格或补零,随后从高位到低位取出数字,通过 _StoreChar() 逐字输出。十六进制查表使用 A~F,因此 %x 和 %X 的字母都大写。设置左对齐时,数字输出后再补空格。

例如 SEGGER_RTT_printf(0, "%04X", 42u) 会经由此函数按十六进制输出 002A。%.4u 中的 4 是最少数字位数;%4u 中的 4 是最小字段宽度,两者含义不同。

16.4 _PrintInt():处理有符号整数的符号和宽度

static void _PrintInt(SEGGER_RTT_PRINTF_DESC* pBufferDesc,
                      int v, unsigned Base,
                      unsigned NumDigits, unsigned FieldWidth,
                      unsigned FormatFlags);

参数含义与 _PrintUnsigned() 基本相同,但 v 是有符号整数。它计算数字宽度、为符号预留位置,输出负号或 +,最后把数值部分交给 _PrintUnsigned()。

例如 SEGGER_RTT_printf(0, "%+06d", 120) 输出 +00120:总宽度为 6,符号占 1 位,其余位置补零或写数字。

源码通过 -v 取得负数的绝对值,INT_MIN 的相反数无法用 int 表示,存在有符号溢出问题。若需要可靠输出整个 int 取值范围,可先使用标准库 snprintf() 转换,再调用 SEGGER_RTT_Write()。





关键词: 2026年     J-Link     RTT     LOG     日志     嵌    

菜鸟
2026-11-08 22:37:53     打赏
2楼

有一部分函数讲是借助ai的但是我都看过并在需要注意的地方添加说明了


共2条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]