调试笔记--keil printf小技巧

Posted 不咸不要钱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了调试笔记--keil printf小技巧相关的知识,希望对你有一定的参考价值。

调试笔记–keil printf小技巧

单片机开发过程中,大部分项目都离不开printf,printf可以打印系统当前运行的日志信息从而快速定位问题原因,一种极为方便的人家交互手段。使用过printf的都知道,要是有printf前,需要对 fputc 函数进行重定向(printf的本质就是先将用户字符串进行格式化(处理%d、%f等格式),然后调用 fputc 函数将格式化后的字符串一个一个的处理一遍,如果将 fputc 定义为串口输出字符串,则printf最终会将字符串从串口输出,这是最常见的,当然也可以通过适配不同的 fputc 函数到达从spi、can或者屏幕等其他通信口输出。)。从通信口接口输出一般会比较耗时,一些实时性要求比较高的系统并不适用,为了解决实时性,一些大佬就发明将fputc重定向输出到ram,然后利用调试器从ram中读取固定格式的数据的方法。

本文参考安富莱专题教程第7期 http://www.armbbs.cn/forum.php?mod=viewthread&tid=87176&extra=page%3D1
本文参考安富莱专题教程第5期 http://www.armbbs.cn/forum.php?mod=viewthread&tid=86177&extra=page%3D1

使用keil的Event Recorder中间件进行打印

缺点:
需要代码中添加Event Recorder中间件,并会占用1KB左右的RAM
需要连接调试器并且keil处于debug状态
只能发不能收

keil添加组件

如果网不好,可以通过 http://www.armbbs.cn/forum.php?mod=viewthread&tid=96992&highlight=pack 镜像下载后,自行安装

在这里插入图片描述

工程添加组件并开启 fputc 重定向至EVR

在这里插入图片描述
同时记得勾选使用微库
在这里插入图片描述
Event Recorder中间件的 fputc 使用了关键字weak,因此不要在工程中定义自己的 fputc函数,否则会覆盖 Event Recorder中间件的 fputc

在这里插入图片描述

初始化组件

在这里插入图片描述
并添加一下代码进行初始化组件

#include "EventRecorder.h"
/* 初始化 EventRecorder 并开启 */
EventRecorderInitialize(EventRecordAll, 1U);

使用printf打印

这里顺带测量了打印耗时,可以发现使用Event Recorder中间件的printf打印耗时是us级的(和单片机有关,这里使用的是72MHz的stm32f105)

在这里插入图片描述

使用jlink-RTT Viewer组件进行打印

缺点:
需要代码中添加RTT Viewer组件,并会占用1KB左右(默认可调)的RAM
必须使用jlink调试器连接,实测最新版的jlink驱动也支持DAP-link了

安装jlink驱动

jlink官网 https://www.segger.com/downloads/jlink/ 一般下载最新版本即可

在这里插入图片描述
安装完驱动后,开始菜单就会有 j-Link RTT Viewer图标
在这里插入图片描述

移植RTT组件

  • 打开jlink驱动安装目录下的 \\Samples\\RTT\\SEGGER_RTT_V722\\RTT 文件夹 将文件复制到自己工程中
  • RTT是jlink的组件,因此也可以移植到IAR工程使用,只要使用的是jlink或者DAP-link调试器即可
    在这里插入图片描述
  • 将文件添加到工程中并添加文件路径
    在这里插入图片描述
    在这里插入图片描述

初始化RTT组件

RTT组件默认有3个上行(单片机到RTT Viewer软件)缓冲区和3个下行(RTT Viewer软件到单片机)缓冲区,其中缓冲区0 用作printf打印功能,其他缓冲区可以用作波形显示
缓冲区0 大小可以在SEGGER_RTT_Conf.h中配置

在这里插入图片描述

  • RTT的速度除了和jlink硬件有关外,还和缓冲区大小有关

segger官方测试RTT速度在 STM32F407 168 MHz 情况下,发送一个字符在1个us,当然了官方用的jlink应该是36MHz的
根据jlink速度和自己上行带宽,可以粗略计算出缓冲区大小,官方也给出了参考缓冲区大小意见 https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/

在这里插入图片描述

  • 使用手头72MHz的stm32f105,配合keil 的Event Recorder中间件对打印10个字符进行测速,发现会比使用Event Recorder中间件的printf速度快上一些,原因大概是Event Recorder中间件使用的是printf,而SEGGER的RTT则是自己精简了printf函数(不支持浮点数和中文)
    在这里插入图片描述

  • 只使用RTT组件的打印功能,只需要调用 SEGGER_RTT_Init()进行初始化即可。即使不初始化,在打印时,如果检测到没有初始化,会自行进行初始化,当然为了稳健最好还是调用SEGGER_RTT_Init()进行初始化

初始化将RTT的控制块名字赋值为"SEGGERRTT",并且将上行通道和下行通道0名字赋值为"Terminal",方便jlink通过扫描内存中的"SEGGERRTT"和"Terminal"找到RTT使用的内存。最重要的是将上行通道0和下行通道0和SEGGER_RTT.c中已经定义好了的缓冲区数组绑定起来(如果想使用其他通道,则需要自己开辟缓冲区数组)

在这里插入图片描述

RTT组件打印

#include "SEGGER_RTT.h"
/* BufferIndex只能是0  sFormat要打印的东西 */
/* 该函数不支持打印中文和浮点型 */
/* 如果想要打印中文和浮点型 可以使用printf函数,然后在 fputc 函数调用该函数 */
int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...);

在这里插入图片描述

jlink连接目标板,打开 j-Link RTT Viewer图标,设置单片机型号,连接成功后就会显示打印信息
在这里插入图片描述

在这里插入图片描述
修改Terminal窗口

j-Link RTT Viewer软件上有16个 Terminal窗口(都是利用上行缓冲区0发送数据的),在通过在SEGGER_RTT_printf函数打印前,使用 SEGGER_RTT_SetTerminal 函数设置 Terminal显示窗口,让不同信息显示在不同的Terminal窗口内,例如警告信息显示在一个Terminal窗口,错误信息显示在一个Terminal窗口,正常的log信息显示在一个窗口

/* TerminalId 通道范围 0 - 15 */
int     SEGGER_RTT_SetTerminal        (unsigned char TerminalId);

RTT组件接受数据

打印功能利用了RTT组件的上行缓冲区0,而接受则利用下行缓冲区0,如果要发送多个字符,可以适当改大下行缓冲区0的大小

/*
 *    SEGGER_RTT_GetKey
 *
 *    函数描述:从缓冲区0读取一个字符
 *
 *    返回值
 *    <  0 -   缓冲区没有数据
 *    >= 0 -   缓冲区字符
 */
int          SEGGER_RTT_GetKey                  (void);

/*
 *    SEGGER_RTT_HasData
 *
 *    函数描述:判断缓冲区是否有数据
 *    BufferIndex:缓冲区序号
 *    返回值
 *    == 0 -   没有数据
 *    != 0 -   有数据
 */
unsigned     SEGGER_RTT_HasData                 (unsigned BufferIndex);

/*
 *    SEGGER_RTT_HasKey
 *
 *    函数描述:判断缓冲区0是否有数据
 *
 *    返回值
 *    == 0 -   缓冲区没有数据
 *    == 1 -   缓冲区有数据
 */
int          SEGGER_RTT_HasKey                  (void);

/*
 *    SEGGER_RTT_Read
 *
 *    函数描述:从缓冲区读取N个字符
 *    BufferIndex:缓冲区序号
 *    pBuffer    :存放读取数据
 *    BufferSize :读取长度
 *    返回值     :实际读取的字节数
 */
unsigned     SEGGER_RTT_Read                    (unsigned BufferIndex,       void* pBuffer, unsigned BufferSize);

/*
 *    SEGGER_RTT_ReadNoLock 
 *
 *    函数描述:从缓冲区读取N个字符
 *    BufferIndex:缓冲区序号
 *    pBuffer    :存放读取数据
 *    BufferSize :读取长度
 *    返回值     :实际读取的字节数
 *    注意       :和SEGGER_RTT_Read的区别是不带临界区保护
 */
unsigned     SEGGER_RTT_ReadNoLock              (unsigned BufferIndex,       void* pData,   unsigned BufferSize);

/*
 *    SEGGER_RTT_GetBytesInBuffer
 *
 *    函数描述:获取缓冲区中数据数量
 *    BufferIndex:缓冲区序号
 *    返回值     :数据数量
 */
unsigned     SEGGER_RTT_GetBytesInBuffer        (unsigned BufferIndex);

这些API互相配合,接收数据十分简单
在这里插入图片描述

RTT组件临界区保护

RTT组件的临界区保护和freertos一样,使用basepri寄存器关闭中断,也就是说不受RTT组件临界区保护的中断服务函数中不能出现RTT组件的API函数。

在这里插入图片描述
默认0x20,在stm32中(stm32只用了高四位)只有中断优先级0和1的不受RTT组件临界区关中断影响,因此中断优先级0和1的中断服务函数中不能出现RTT组件的API函数,否则可能会出现错乱。
在这里插入图片描述
后缀带NoLock的函数均不使用关中断临界区保护,一般不要使用

以上是关于调试笔记--keil printf小技巧的主要内容,如果未能解决你的问题,请参考以下文章

调试笔记--keil 断点调试小技巧

调试笔记--jlink 变量转实时波形小技巧

《程序是调试出来的》嵌入式Keil5的调试技巧

vs环境下的调试小技巧

C语言调试小技巧

这个Keil的小技巧,你知道吗?