你听说过 J-Link 的 RTT 么?官方的宣传是这样的:
简单来说,只要拥有了 J-Link,你就可以享受以下的便利:
- 无需占用 USART 或者 USB 转串口工具,将 printf 重定位到一个由 J-LINK 提供的虚拟串口上;
- 支持任何 J-LINK 声称支持的芯片
- 高速通信,不影响芯片的实时响应
它的缺点也是明显的:
- 你必须拥有一个 J-Link,如果你使用的是 CMSIS-DAP 或者 ST-Link 之类的第三方调试工具,就无法享受这一福利;
- 你必须在工程中手动插入一段代码。
在我之前的文章 正版 J-Link 速度很牛么?中国工程师用开源轻松拿捏 中,使用开源的 DAPLink 替代 J-Link,我已经实现了许多 J-Link 不具备的功能:
功能特点
- [√] 支持 SWD/JTAG 接口,下载调试速度超越 JLINK V12(时钟 10Mhz)
- [√] 支持 USB 转串口,最大 10M 波特率无丢包
- [√] 支持使用 OpenOCD 的 IDE 下载调试 ARM/RISC-V 等芯片
- [√] 支持大量 Cortex-M 系列芯片 U 盘拖拽下载,内置大量下载算法,自动识别目标芯片
- [√] 内置 ymodem 协议栈,U 盘拖拽文件自动触发 ymodem 通过串口传输文件到目标设备(需配合带 ymodem 协议的 bootloader)
- [√] 支持系统固件升级,为后续添加更多功能
- [√] 采用 winusb 对 window10 免驱,即插即用
- [√] 支持 3V3/5V 大电流输出电源
- [√] 内置防倒灌和过流保护,外部电流无法反向流入 USB 口,防止损坏 USB
尽管 DAPLink 已经实现了许多强大的功能,但评论区仍有不少用户表示无法替代 J-Link 的 “RTTView 和 J-Scope 功能”:
如果我告诉你,其实开源社区早就已经开发了支持 DAPLINK 的 RTTView 和 J-Scope 波形显示功能,你是否心动呢?
RTTView 开源地址:https://github.com/XIVN1987/R...,作者为XIVN1987
百度云下载链接:https://pan.baidu.com/s/1IoWH...
实现原理
RTTView
RTT 的原理很简单,SEGGER_RTT 定义了一个 SEGGER_RTT_CB
结构体,通过 RTT 打印和接收的数据会存储在 _SEGGER_RTT
变量中。上位机通过调试器读写 _SEGGER_RTT
的内容,即可实现 RTTView 功能。
SEGGER_RTT_CB 代码结构体如下:
typedef struct {
char acID[16];
int MaxNumUpBuffers;
int MaxNumDownBuffers;
SEGGER_RTT_BUFFER_UP aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS];
SEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS];
} SEGGER_RTT_CB;
SEGGER_RTT_PUT_CB_SECTION(SEGGER_RTT_CB_ALIGN(SEGGER_RTT_CB _SEGGER_RTT));
SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acUpBuffer [BUFFER_SIZE_UP]));
SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acDownBuffer[BUFFER_SIZE_DOWN]));
RTTView 的上位机就是通过调试器读写单片机内存中_SEGGER_RTT 结构体变量的数据,因为此变量存储了读写数据的所有信息, 要读此变量需要先知道此变量在内存中的位置,这可以通过查看 MDK 编译生成的.map 文件来实现,如下:
可知,_SEGGER_RTT 在地址 0x24000400 处,RTTView 的上位机可以自动检测设定地址起始的 128KB,来寻找_SEGGER_RTT 变量。
波形显示
波形显示的原理与 RTTView 显示的原理类似,上位机通过加载 MDK 编译生成的.map 文件,来获取全局变量的 RAM 位置, 然后通过调试器读取用户想要显示波形的变量地址,然后绘制波形。
RTTView 功能
MCU 移植 RTT:
RTT 的源码是作为 J-Link 软件包的一部分,在 Jlink 安装目录中的 Sample/RTT 中,如我电脑上的路径:
C:\Program Files (x86)\SEGGER\JLink_V632f\Samples\RTT
- 将以下文件复制到工程目录下,然后添加到代码中,添加头文件路径。
- 编写 MCU 代码
比如将 RTT 移植到 shell 命令行中
#include "SEGGER_RTT.h"
int reboot(void)
{
SCB->AIRCR = 0X05FA0000 | (uint32_t)0x04;
return 0;
}
MSH_CMD_EXPORT(reboot, reboot)
uint16_t shell_read_data(wl_shell_t *ptObj, char *pchBuffer, uint16_t hwSize)
{
return SEGGER_RTT_Read(0,(uint8_t \*)pchBuffer, hwSize);
}
uint16_t shell_write_data(wl_shell_t *ptObj, const char *pchBuffer, uint16_t hwSize)
{
return SEGGER_RTT_Write(0, pchBuffer,hwSize);
}
int main(void)
{
...
SEGGER_RTT_Init();
...
shell_ops_t s_tOps = {
.fnReadData = shell_read_data,
.fnWriteData = shell_write_data,
};
shell_init(&s_tShellObj,&s_tOps);
}
移植成功后,RTTView 可以显示 shell 的输出效果:
波形显示功能
为了展示波形数据,我们可以通过 MCU 输出一个正弦波形作为示例。以下是相关代码:
**USED uint32_t approx_t = 0;
**USED uint32_t sine_var;
#define MAX_VALUE 1000000
#define MIN_VALUE 0
#define PERIOD 1000
#define M_PI 3.14159265358979323846
#include <math.h>
uint32_t approx_sine(uint32_t t) {
double angle = 2.0 * M_PI * (t % PERIOD) / PERIOD;
double sine_value = (sin(angle) + 1.0) / 2.0;
return (uint32_t)(MIN_VALUE + sine_value \* (MAX_VALUE - MIN_VALUE));
}
int main(void)
{
...
while (1) {
...
sine_var = approx_sine(approx_t);
approx_t++;
HAL_Delay(1);
}
}
在编译生成的 .map
文件中,可以看到 sine_var
的地址,例如地址为 0x24001A68
:
配置 RTTView 显示波形
- 打开 RTTView,加载
.map
文件。 - 选择需要显示波形的变量(如
sine_var
)。 - 勾选波形显示。
效果如下:
MDK 下将 RTTView 重定向到 printf
步骤一:RTE 配置
- 打开 RTE 配置窗口(菜单:
Project -> Manage -> Run-Time Environment
)。 - 勾选以下选项:
- 在 CMSIS-Compiler 下勾选 CORE;
- 在 STDOUT(API) 下勾选 Custom;
如果你在 RTE 中找不到 CMSIS-Compiler 和 CMSIS-View,说明你的 MDK 版本较低——如果不想升级 MDK,则可以通过下面的链接从官方直接下载对应的 cmsis-pack:
https://www.keil.arm.com/packs/cmsis-compiler-arm/
https://www.keil.arm.com/packs/cmsis-view-arm/
或者老版本的 cmsis-pack 中,找到 Compiler:
步骤二:添加 stdout_putchar()
实现 stdout_putchar() 函数——用它来把 RTTView 重定向到 printf:
int stdout_putchar(int ch)
{
SEGGER_RTT_PutChar(0, ch);
return ch;
}
结语
如果你的产品不方便外接下载口,但是又有调试的需求,建议移植一个轻量级的 shell 命令行工具(如对接 UART、CAN 等外设)。这样既能在不影响程序正常运行的情况下实现异步输出,还能记录日志到 Flash 中,方便问题分析。
END
原文:裸机思维
专栏推荐文章
- 【为宏正名番外】用“C泛型”实现队列的例子:多类型支持、函数重载与线程安全
- 有没有一种可能性——Bootloader升级失败会变砖?
- 【玩转Arm-2D】如何制作具有物理质感的仪表指针
- 防御性编程(X)OOPC开发(√)
- Semihosting真的是嵌入式阑尾么?
如果你喜欢我的思维,欢迎订阅裸机思维欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。