使用JLink调试(RTT、JScope)

概述

在很多调试过程中还是有一些高速的数据采集需求的,但是如果使用串口的话毕竟会消耗一部分性能,即使使用DMA也会出现在高频率情况下中断占用资源,我们希望能尽量去减轻这一部分的负担,这样也能让我们系统能尽量在这里少出一些问题,同时也能有更好的Debug环境。

我们有时候需求会比较简单,就实时画一下某个数据的图,或者打印一些简单的Debug信息,我们可以考虑一下RTT和JScope。当然串口的意义我们也不能忽视,在离开仿真器Debug的情况下或者输出一些日志的情况下,串口还是有他的无可替代性。我们只是讨论一种在我们开发时用于快速Debug的方案或者验证一些高速数据或者需要绘图的时候的一些简单快速的方法。

RTT概念

RTT全称是Real Time Transmit(实时传输)是Segger公司推出的调试手段之一。它是一种用于嵌入式中与用户进行交互的技术。

使用RTT可以从MCU快速输出调试信息和数据,且不影响MCU的实时性。只要支持J-Link的MCU就可使用RTT功能,兼容性非常强。

RTT支持两个方向的多个通道,上到主机,下到目标,它可以用于不同的目的,为用户提供尽可能多的自由。默认实现每个方向使用一个通道,用户可在在调试终端输入和输出。

使用J-Link RTT Viewer,可用于“虚拟”终端,允许打印到多个窗口(例如,一个用于标准输出,一个对于错误输出,一个用于调试输出)。

RTT在MCU的存储器中使用SEGGER RTT控制块结构管理数据读写。控制块对于每个可用的信道都在内存中包含了一个ID,通过J-Link或者环形缓冲结构区(链表)都可以通过ID找到对应的控制块。

可用信道的最大数目可以在编译时配置,并且每个缓冲区都可以在MCU运行时配置和使用。上下缓冲区可以分开处理。每个通道都可以配置为阻塞或非阻塞。

在阻塞模式下,应用程序将等待缓冲区写满,直到可以写入所有内存为止,这将导致应用程序处于阻塞状态,但可以防止数据丢失。

在非阻塞模式下,只会写入适合缓冲区的数据,或完全不写入缓冲区,其余的数据将被丢弃。这样即使没有连接调试器,也可以实时运行。开发人员不必创建特殊的调试版本,并且代码可以保留在发布应用程序中。

说这么多,原理很简单,固件代码将要输出的log数据按照RTT的格式写到确定地址的内存中去,然后RTT通过SWD口读取对应内存地址的数据,并显示到PC终端上。

平均一行文本可以在1微秒或更短的时间内输出。基本上相当于做一个memcopy()的时间。

想自行测试速度在RTT文件夹下也有例程,可以直接使用Main_RTT_SpeedTestApp.c

这里自行去测试就行,放一张别人的测试图和计算图:来源:https://zhuanlan.zhihu.com/p/595315158

RTT实现代码使用大约500字节的ROM和(n(通道数) * (24字节ID+24字节))的RAM。推荐的大小是1 kByte(上行信道)和16到32字节(下行信道),这取决于输入/输出的负载。

RTT的使用

看完原理其实我们也能发现和一部分DMA串口的逻辑很相似的,即使我们自己实现Debug串口也是需要做环形链表的,但是其实将数据按照对应格式存放到指定地址上也是CPU参与了的,所以其实更多的意义在于减少代码出错的可能和在一定程度上提高输出性能和运行的实时性。至于其性能损失其实相较于串口打印来说基本上是可以忽略不计的(毕竟DMA也是有总线控制权交换还有部分需要CPU参与的很少一部分地方,我们也知道DMA最大的优势还是在和速度较慢的外设交互的时候不阻塞CPU),官方宣称是可以保证我们在发布的时候也可以不修改任何代码,直接发布也不会有什么性能影响。但是本质即使没有接入调试器的时候,该存到环形缓冲区中的数据也是会存的,但是类似memcopy()操作确实可以一定程度上忽略其对性能的影响了。

对于使用来说个人感觉移植没有任何的难度。SEGGER\JLink_VXXX\Samples\RTT文件夹下的压缩包解压出来,里面RTT文件夹下的文件直接放到项目中编译,然后记得把解压出来文件夹中Config文件夹下的也复制出来。具体配置的话看文件里面注释有写,如果简单用一下不修改任何配置就行,然后就可以正常使用RTT的一些基本功能了。

基本的语句使用方法百度上很多教程,就不在这里写了,粘贴一点链接:

https://www.cnblogs.com/snowsad/p/12076740.html

https://zhuanlan.zhihu.com/p/163771273(这个人写了个log.h的文件,感觉思路挺好)

这里需要注意一下SEGGER_RTT_printf不支持打印浮点数,这里需要使用SEGGER_RTT_WriteString,也有人修改SEGGER_RTT_vprintf实现了支持:https://blog.csdn.net/qq_36075612/article/details/123432025

在他的修改中有两个代码段,一个是使用sprintf做的格式化,一个是使用数学的方法转换为字符串用的格式化,默认是第二种,因为sprintf的性能开销对于在RTT场景下是相对较大的,所以确实建议使用数学的方法进行浮点数输出。

RTT基础输出测试结果如下图:

JScope概念

J-Scope是SEGGER公司推出的,可以在目标MCU运行时,实时分析数据并图形化显示的软件。它不需要像SWO那样需要MCU上面额外的引脚,而是使用标准的调试接口。J-Link驱动4.90之后的版本都有这个软件。

J-Scope可以像示波器一样显示多个变量的值,通过读取一个ELF文件,允许选择一定数量的变量可视化(HSS),也可以通过RTT来显示输出的变量,他们俩在速率上有所区别,HSS在JLinkv9以后最高能支持1KHz的采样率,当然PRO和ULTRA版本v4以上的也没有限制。而RTT模式是需要在代码中实现RTT侵入代码中去实现,当然他的速度会快很多。

HSS Mode

我们其实看他的操作就可以猜到,HSS模式下需要读取elf文件,而elf文件中记录着内存分配状态,所以只有在编译时静态数据区的变量,也就是全局变量或者static等能够使用HSS模式来采集,其本质就是对静态数据区对应的地址下的数据进行采集,然后进行绘图,采样速度也会有限制。

RTT Mode

其本质上沿用了RTT的操作逻辑,我们可以把局部变量等也输出显示,但是需要侵入代码,这个我们实现的时候都试试,速率的话也就是传输速率,不像HSS这样是固定评率采样。

JScope的使用

HSS Mode

其最好的就是不需要对代码进行任何修改,我们直接打开JScopeExe就行。

这里默认采样时间100us,我们进入开始使用后就有警告提示的,我们v11版本,最高1kHz,这里演示一下警告,正常使用记得调到1000us

但其实这里实测v11最高能到10k的采样率

RTT Mode

这里我们首先就需要像上面一样把RTT移植一下,然后对代码进行以下修改:

//设置缓冲区参数和通道
char JS_RTT_UpBuffer[512];    // J-Scope RTT Buffer
int  JS_RTT_Channel = 1;       // J-Scope RTT Channel
//初始化一下
SEGGER_RTT_ConfigUpBuffer(JS_RTT_Channel, "JScope_I4I4I4", &JS_RTT_UpBuffer[0], sizeof(JS_RTT_UpBuffer), SEGGER_RTT_MODE_NO_BLOCK_SKIP);

这里的JScope_I4I4I4是有讲究的,讲究见官网文档:

这里其实我们可以发现我们可以发送单个数据,也可以直接发送一个结构体的包过去,当然要注意一个对齐的问题,因为结构体默认按照最大的变量的大小做的地址对齐,但是JScope为了优化性能,是将数据作为一个一个连续的块发过去的,所以在结构体定义的时候记得加上#pragma pack(push, 1)#pragma pack(pop)

然后我们自己写一个结构体

#pragma pack(push, 1)
struct {
    signed int Sine1;
    signed int Sine2;
    signed int Sine3;
} acValBuffer;
#pragma pack(pop)

在需要发送的地方写SEGGER_RTT_Write(JS_RTT_Channel, &acValBuffer, sizeof(acValBuffer));就行

其实总结下来,就是用了RTT输出一模一样的逻辑,只是用来缓冲区的名字来指示了是不是JScope使用,采用JScope_X来判定数据结构,本质上还是用来RTT多通道的思路。

这里在有一个博客中写到快速输出时候有卡死的问题,这里我并没有遇到,可以先在这里标记一下:https://blog.csdn.net/weixin_33795833/article/details/93430849

根据软件测试来看的话在这个缓冲区大小和v11下跑个100k的采样率(同RTT,和缓冲区大小也有关系)还是没有问题的:

上一篇