STM32使用ITM调试_通过仿真器实现串口打印

IDE:CLion

MCU:  STM32F407VET6

工具:OpenOCD Telnet

一、简介

        调试单片机时,如果要打印数据往往需要另接一根线通过USB转TTL接到电脑上。但这样做往往并不方便,尤其是身边没有USB转TTL工具时。这时可以使用单片机自带的ITM单元,仅靠ST-Link仿真器即可实现串口打印,并且可以轻易把波特率设置到M以上的级别。

        使用ITM进行串口打印并不是什么新方法,IAR、STM32CubeIDE等都集成了相应的调试工具窗口,或者直接使用ST-Link的客户端(可参考博客【嵌入式】CLion & CubeMX )。

        本篇介绍的是如何使用 OpenOCD 配合 ITM(Instrumentation Trace Macrocell)来实现串口打印功能,充了在 CLion 开发环境中如何配置和使用 ITM 进行调试信息输出,从而弥补 CLion 在串口打印支持方面的不足。

 二、准备

1,前置

        首先,需要大致了解 OpenOCD 这个工具。OpenOCD 是一个开源的调试工具,主要作用是直接与硬件(如 ST-Link、J-Link 等仿真器)进行交互。我们可以用相应的命令,让OpenOCD完成对应操作,比如

复位:reset

暂停:halt

烧录:flash write_image firmware.elf

擦除:flash erase_sector 0 0 last

……

        与 OpenOCD 配合使用的是 GDB(GNU 调试器),它是一个更高级的调试工具。通过 GDB,我们可以执行复杂的调试操作,例如设置断点、查看变量值、单步执行等。GDB 通过 GDB 远程协议 与 OpenOCD 通信,OpenOCD 则将这些请求转换为具体的硬件操作(如读写寄存器、访问内存等),从而实现对嵌入式系统的调试。

        更为通俗的解释是:你可以把OpenOCD当成是你写的ADC驱动,你提供了如下接口:

ADC初始化:ADC1_init()

ADC启动:ADC1_start()

ADC读取:get_ADC_value()

……

        而GDB可以当成是你写的业务,比如获取温度值这个操作,你需要启动ADC,读取ADC,并把ADC读取的值根据公式转为对应的温度,最终完成了这个操作。

        不过本篇没有用到GDB,虽然可以使用GDB的monitor命令

       

        除此之外,我们还会用到Telnet客户端,这个Telnet的作用就是与OpenOCD进行通信,比如向OpenOCD发送命令、OpenOCD把数据发送到Telnet。字面意义上,就是微信QQ那样的通信。不过一般这个功能需要手动打开,我们可以打开控制面板,在里面选择程序,再选择【启用或关闭Windows功能】

从中找到Telnet客户端这个功能,并启用。

 2,思路

        前置知识大致了解完了,现在实现的思路就很简单了。我们只需要让OpenOCD开通SWO这个端口(ITM发送数据的引脚),然后让OpenOCD接收数据就行了。

        不过由于我没在CLion中找到OpenOCD的控制台,所以就使用Telnet来向OpenOCD发送这些命令,让其完成前面的的操作。那么流程如下:

  • 单片机→启用ITM
  • 上位机→通过Telnet向OpenOCD发送命令

 三、正式开始

 1,启用ITM

        首先我们要对齐颗粒度,我指的是SWO的波特率。现在我们可以把波特率配置为2M,下面的函数里给了波特率的公式,CPU时钟除以对应的分频系数+1,此处为168M/(83+1)=2M。由于有些宏CMSIS头文件里不一定有,这里就定义了相关的宏

#define ITM_LAR_ACCESS_KEY    0xC5ACCE55  // ITM 解锁密钥
//#define ITM_TCR_ITMENA_Pos    0           // ITM 启用位
//#define ITM_TCR_SYNCENA_Pos   2           // 同步数据包启用位
#define ITM_TCR_TXENA_Pos     3           // TXENA 启用位
#define ITM_TER_PORTENA_Pos   0           // ITM 端口启用位

// 初始化 ITM 和 SWO
void ITM_Init()
{
    // 启用 ITM 和 TPIU
    DBGMCU->CR |= DBGMCU_CR_TRACE_IOEN;  // 启用跟踪引脚
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;  // 启用 ITM 和 DWT

    // 配置 TPIU
    TPI->ACPR = 83;  // 设置 SWO 波特率 = CPU 时钟 / (ACPR + 1)
    TPI->SPPR = 2;   // 设置 SWO 协议为异步模式(UART)
    TPI->FFCR = 0x00;  // 禁用格式化器

    // 启用 ITM
    ITM->LAR = ITM_LAR_ACCESS_KEY;  // 解锁 ITM
    ITM->TCR = (1 << ITM_TCR_ITMENA_Pos) |  // 启用 ITM
               (1 << ITM_TCR_SYNCENA_Pos) | // 启用同步数据包
               (1 << ITM_TCR_TXENA_Pos);    // 启用 TXENA
    ITM->TER = (1 << ITM_TER_PORTENA_Pos);  // 启用 ITM 端口 0
}

        在初始化ITM之后,就可以直接调用ITM_SendChar()来发送数据了,不过此时往往会重定向printf

        如果是GCC工具链,那么需要重写_write函数

// 重写 _write 函数
int _write(int file, char *ptr, int len) {
    for (int i = 0; i < len; i++) {
        ITM_SendChar(*ptr++);  // 使用 ITM_SendChar 发送字符
    }
    return len;
}

        如果是Keil的工具链,那么就需要重写fputc函数

// 重写 fputc 函数
int fputc(int ch, FILE *f) {
    ITM_SendChar(ch);  // 使用 ITM_SendChar 发送字符
    return ch;
}

        如果是IAR工具链,那么也需要重定向到_write函数

// 重写 __write 函数
size_t __write(int handle, const unsigned char *buffer, size_t size) {
    for (size_t i = 0; i < size; i++) {
        ITM_SendChar(buffer[i]);  // 使用 ITM_SendChar 发送字符
    }
    return size;
}

        一般可以使用宏来自动重定向,不过要注意,使用printf时如果不加换行,那么默认会积攒到1024个后,才会把缓冲区里的数据一次性发送过去。

// 判断编译器并定义对应的重定向函数
#if defined(__GNUC__)  // GCC 工具链
#include <unistd.h>  // 提供 _write 函数的声明

int _write(int file, char *ptr, int len)
{
    __BKPT(0);
    for (int i = 0; i < len; ++i)
    {
        ITM_SendChar(ptr[i]);  // 使用 ITM_SendChar 发送字符
    }
    return len;
}

#elif defined(__CC_ARM)  // Keil (ARMCC) 工具链
int fputc(int ch, FILE *f) {
    ITM_SendChar(ch);  // 使用 ITM_SendChar 发送字符
    return ch;
}

#elif defined(__IAR_SYSTEMS_ICC__)  // IAR 工具链
#include <LowLevelIOInterface.h>  // IAR 提供的低级 I/O 接口头文件

size_t __write(int handle, const unsigned char *buffer, size_t size) {
    for (size_t i = 0; i < size; i++) {
        ITM_SendChar(buffer[i]);  // 使用 ITM_SendChar 发送字符
    }
    return size;
}

#else
#error "Unsupported compiler!"  // 不支持其他编译器
#endif

 2,启用OpenOCD

        首先我们先进入调试状态,下面代码设置了三个按键,分别发送A、C、E

        然后打开终端

        OpenOCD默认监听的Telnet端口为4444,所以这时候我们使用Telnet连接到远程端口4444。连接成功后,会有下面提示

telnet localhost 4444

        接下来就可以向OpenOCD输入命令了,这里我们先输入下面tpiu命令,这句的命令是把输出连接到端口3444。因为找不到OpenOCD的控制台,那么只好让OpenOCD把它接收的数据放到另一个端口,比如3444,而端口4444就作为向OpenOCD发送命令的端口即可。

tpiu config internal :3444 uart off 168000000 2000000

        此外,还需要打开ITM的端口(这个是引脚端口),一般默认是端口0,这个命令没有任何提示

itm port 0 on  

接着,我们再打开一个终端,使用Telnet连接到端口3444。等待一会就能连接上了

Telnet localhost 3444

3,开始测试

        能用到的命令都在下面

telnet localhost 4444

tpiu config internal :3444 uart off 168000000 2000000
tpiu config internal swo.log uart off 168000000 2000000
itm port 0 on

         最后,可以通过按下按键或者你可以加个延时放进主循环里,使用ITM发送数据,可以看到下图的打印结果。

        回头看命令窗口,可以看到这两个窗口已经勾搭上了(使用Telnet连接端口3444时就已经握手了)

        

接下来是使用printf来打印大量数据,这里测试用的是发送CPU温度

.

        不过结果有些感人,发送字符串时呈阶梯状,调试时观察发送的数据长度没有问题,应该是终端窗口解析数据时没有回车导致的。这个与Windows有关,Linux/Unix的换行是\n

把换行换成符合Windows标准的形式\r\n后就可以正常显示了,看起来效果还不错

 printf("%f\r\n", get_adc1_temperature());

 4,输出到文件中

        前面介绍的方法是实时串口打印,现在提供另一种方法,把打印的数据保存到文件中。不过需要注意的是保存的文件类型是二进制,使用记事本打开会看到有很多“口”,可以使用python脚本来处理。

        操作与前面类似,只不过现在要输入下面命令,这段命令会把数据输出到swo.log文件中

tpiu config internal swo.log uart off 168000000 2000000

        如果直接输入命令,会有下面提示,因为tpiu已经启用了,不能再重新开始

        此时要么使用关闭命令,要么关闭调试再重新打开。   

        为了方便演示,这里就重新打开了

        然后开启ITM端口

        接着,使用单片机发送数据。等一会,我们就能看到工程目录下会有一个新文件

        打开后就能看到里面的内容,这些奇怪的SOH就是前面在终端看到的“空格”

        此时如果选择删除swo.log文件,那么就会提示该文件正在被OpenOCD使用,这是合乎情理的

四、Tips

        不过使用终端来显示这个串口打印结果有些不方便,如果后面有时间的话,我应该会开发一个插件或者一个桌面程序,既能显示打印结果,也能把接收到的数据显示在图表上。我个人更倾向于开发桌面应用程序,不过估计能鸽很久很久

        脚本试了好几种,包括在OpenOCD的配置文件里添加那两个命令,但没啥用,最后发现还得是GDB,简单好用。

先在项目根目录创建一个后缀名为gdb的文件,比如tpiu_itm_commands.gdb。monitor命令相当于传参,这里把后面的命令传递给OpenOCD了

monitor tpiu config internal :3444 uart off 168000000 2000000
monitor itm port 0 on

接着进入调试状态,进入这个GDB控制台

然后按暂停,不然无法在GDB控制台里输入命令

输入source命令,后面写tp字母,再按一下Tab键,脚本名称就自动补全了,如果没补全就再多输入几个字母

回车执行会闪一下,然后没有任何提示

接着全速运行,再开一个终端窗口,连接端口3444

Telnet localhost 3444

此时就能接收到数据了。

        相较于之前只少了使用Telnet这个步骤,其实还可以更进一步,直接打开终端。不过这个终端并非是CLion里的,而是新的终端窗口,黑乎乎的那种

monitor tpiu config internal :3444 uart off 168000000 2000000
monitor itm port 0 on
shell start cmd.exe /k telnet localhost 3444

        丑是丑了一点,但只有执行GDB脚本这一个步骤,比较适合两块电脑屏幕去看。话说cmd这个窗口,接收一段时间数据后,变得有些搞笑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952379.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络基础1 http1.0 1.1 http/2的演进史

http1.0 1.1 http/2的演进史&#x1f60e; &#xff08;连接复用 队头阻塞 服务器推送 2进制分帧&#xff09; 概述 我们主要关注的是应用层 传输层 http协议发展历史 http的报文结构&#xff1a;起始行 Header Body http的典型特征 http存在的典型问题 Keep Alive机制 chun…

汽车牌照识别系统的设计与仿真(论文+源码)

1设计原理 车牌识别系统的设计是一项利用车辆的动态视频或者静态图像实现牌照区域定位车牌号码识别的技术。其硬件部分通常包括触发设备、拍摄设备、照明设备、图像收集设备、进行车牌号码识别的处理器等&#xff0c;其软件的关键部分包含车牌区域定位的算法、车牌字符的分割算…

springboot 集成 mybatisplus

本案例版本 springboot 3.1.12 mybatis-plus 3.5.9 源码地址 stormlong/springboot-mybatisplus 集成 mybatis-plus 官方文档&#xff1a;快速开始 | MyBatis-Plus 引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-…

本地服务器Docker搭建个人云音乐平台Splayer并实现远程访问告别烦人广告

前言 大家好&#xff01;今天我要给大家分享的是如何在Ubuntu上用Docker快速搭建高颜值无广告的某抑云音乐播放器Splayer的详细流程&#xff0c;并且结合cpolar内网穿透工具实现远程访问。如果你是音乐爱好者&#xff0c;经常需要在外办公或旅行&#xff0c;这个教程绝对能让你…

phpenc加密程序源码

免费扩展加密程序&#xff0c;类似于sg11加密&#xff0c;支持单个PHP&#xff08;免费&#xff09;文件以及批量PHP文件&#xff08;ZIP压缩包格式&#xff09;源码加密的保护平台&#xff0c;加密后的源码文件保持原有代码结构&#xff0c;可以跨平台运行&#xff0c;可以运行…

牛客网刷题 ——C语言初阶(6指针)——BC106 上三角矩阵判定

1. 题目描述——BC106 上三角矩阵判定 牛客网OJ题链接 描述 KiKi想知道一个n阶方矩是否为上三角矩阵&#xff0c;请帮他编程判定。上三角矩阵即主对角线以下的元素都为0的矩阵&#xff0c;主对角线为从矩阵的左上角至右下角的连线。 示例 输入&#xff1a; 3 1 2 3 0 4 5 0 0…

kafka消费堆积问题探索

背景 我们的商城项目用PHP写的&#xff0c;原本写日志方案用的是PHP的方案&#xff0c;但是&#xff0c;这个方案导致资源消耗一直降不下来&#xff0c;使用了20个CPU。后面考虑使用通过kafka的方案写日志&#xff0c;商城中把产生的日志丢到kafka中&#xff0c;在以go写的项目…

计算机网络 笔记 数据链路层3(局域网,广域网,网桥,交换机)

局域网: LAN:在某一区域内由多台计算机互联成的计算机组&#xff0c;使用广播信道 特点&#xff1a; 覆盖范围有限&#xff1a;通常局限在几千米范围内&#xff0c;比如一栋办公楼、一个校园或一个工厂等相对较小的地理区域。 数据传输速率高&#xff1a;一般能达到 10Mbps…

03-51单片机定时器和串口通信

一、51单片机定时器 1.定时器介绍 1.1为什么要使用定时器 在前面的学习中&#xff0c;用到了 Delay 函数延时&#xff0c;这里学习定时器以后&#xff0c;就可以通过定时器来完成&#xff0c;当然定时器的功能远不止这些&#xff1a; 51 单片机的定时器既可以定时&#xff…

Vue.config.productionTip = false 不起作用的问题及解决

文章目录 一、问题描述二、解决方法 一、问题描述 当我们在代码页面上引入Vue.js(开发版本)时&#xff0c;运行代码会出现以下提示&#xff0c;这句话的意思是&#xff1a;您正在开发模式下运行Vue&#xff0c;在进行生产部署时&#xff0c;请确保打开生产模式 You are runni…

迅为iTOP-RK3576开发板/核心板适用于ARM PC、边缘计算、个人移动互联网设备及其他多媒体产品

迅为iTOP-3576开发板采用瑞芯微RK3576高性能、低功耗的应用处理芯片&#xff0c;集成了4个Cortex-A72和4个Cortex-A53核心&#xff0c;以及独立的NEON协处理器。它适用于ARM PC、边缘计算、个人移动互联网设备及其他多媒体产品。支持INT4/INT8/INT16/FP16/BF16/TF32混合运算&am…

vue3 实现 “ fly-cut 在线视频剪辑 ”

参考地址&#xff1a;https://fly-cut.videocovert.online/ git源码地址&#xff1a;https://github.com/x007xyz/fly-cut 改版示例图&#xff1a; 功能点&#xff1a; 1、通过视频链接地址在浏览器中下载下来&#xff0c;并加载到页面中&#xff0c;展示时间、时间刻度、进…

关于使用FastGPT 摸索的QA

近期在通过fastGPT&#xff0c;创建一些基于特定业务场景的、相对复杂的Agent智能体应用。 工作流在AI模型的基础上&#xff0c;可以定义业务逻辑&#xff0c;满足输出对话之外的需求。 在最近3个月来的摸索和实践中&#xff0c;一些基于经验的小问题点&#xff08;自己也常常…

游戏引擎学习第78天

Blackboard: Position ! Collision “网格” 昨天想到的一个点&#xff0c;可能本来就应该想到&#xff0c;但有时反而不立即思考这些问题也能带来一些好处。节目是周期性的&#xff0c;每天不需要全程关注&#xff0c;通常只是在晚上思考&#xff0c;因此有时我们可能不能那么…

Link1189: 超过65536 对象的库限制

使用命令&#xff1a; dumpbin /EXPORTS E:\xxx\UnrealEditor-xxx-Win64-DebugGame.lib > C:\Users\xxx\Desktop\EXPORTS3.txt 看到有哪些对象和函数在被导出了&#xff0c;清理掉不需要的。 dumpbin /EXPORTS E:\ProjectX\zdev\Project\Binaries\Win64\Game-Win64-Debug…

【Oracle篇】深入了解执行计划中的访问路径(含表级别、B树索引、位图索引、簇表四大类访问路径)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;从事IT领域✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(…

一些计算机零碎知识随写(25年1月)-1

我原以为世界上有技术的那批人不会那么闲&#xff0c;我错了&#xff0c;被脚本真实了。 今天正隔着画画呢&#xff0c;手机突然弹出几条安全告警通知。 急忙打开服务器&#xff0c;发现问题不简单&#xff0c;直接关服务器重装系统..... 首先&#xff0c;不要认为小网站&…

浅谈容灾技术方案详解

一、什么是容灾&#xff1f; 容灾指的是&#xff0c;在异地搭建一套或多套和主生产系统一样的IT系统&#xff0c;用于应对在系统因发生意外&#xff08;自然灾害、人为灾害、设备系统故障等&#xff09;造成业务影响的情况&#xff0c;达到尽量让生产业务损失最小的目的。 二…

江科大STM32入门——输入捕获笔记总结

wx&#xff1a;嵌入式工程师成长日记 输入捕获原理 输入捕获是指 STM32 的定时器可以对外部输入信号的边沿进行检测&#xff0c;并记录下相应的时刻。其基本原理基于定时器的计数器和相关寄存器。 当外部信号连接到定时器的输入捕获引脚时&#xff0c;定时器会根据设定的捕获…

系统看门狗配置--以ubuntu为例

linux系统配置看门狗 以 ubuntu 系统配置看门狗为例 配置看门狗使用的脚本文件&#xff0c;需要使用管理员权限来执行&#xff1a; 配置是&#xff1a;系统每 30S 喂一次狗&#xff0c;超过 60S 不进行投喂&#xff0c;就会自动重启。 1. 系统脚本内容&#xff1a; #!/bin/b…