基于MDK的万能printf方法!
来源:嵌入式大杂烩发布时间:2023-03-243052浏览
询问 AI你听说过J-Link的RTT么?官方的宣传是这样的:

- 无需占用USART或者USB转串口工具,将printf重定位到一个由J-LINK提供的虚拟串口上;
- 支持任何J-LINK声称支持的芯片
- 高速通信,不影响芯片的实时响应
它的缺点也是明显的:
- 你必须拥有一个J-Link,如果你使用的是CMSIS-DAP或者ST-Link之类的第三方调试工具,就无法享受这一福利;
- 你必须在工程中手动插入一段代码
曾几何时,J-Link的这一福利让多少非J-Link用户羡慕嫉妒恨,看看手中的ST-Link、ULINKpro和各类廉价的CMSIS-DAP板载调试器——“隔壁邻居的小孩都馋哭了”

如果我告诉你,其实MDK中内置了一种非常简单廉价的方式,可以让你实现类似的功能,并具有以下特点:
支持所有的调试仿真器,哪怕自己手搓的CMSIS-DAP都行
MDK
原生功能,连CMSIS-Pack都不用安装
- 点几下鼠标就可以通过RTE完成部署
- 除了简单的初始化函数外,无需手动插入代码
- 可以将你的printf输出直接打印在MDK的Debug (printf) View窗口中
你是否心动了呢?

【部署从未如此简单】
步骤一:RTE配置
依次通过菜单 Project->Manage->Run-Time Environment 打开RTE配置窗口:

找到并展开Compiler选项卡,勾选Event Recorder,并确保Variant下拉列表选中的是默认的DAP。

展开Compiler 下的 I/O ,勾选STDOUT,并在 Variant 下拉列表中选择 EVR——这里 EVR 是 Event Recorder 的缩写。单击确定后,我们会在工程管理器中看到以下的内容:

至此,所需的工具都已经成功地加入到工程中了。
虽然这里EventRecorderConf.h是一个可以编辑的状态,但实践中,我们基本不用去碰他——使用默认配置即可。
在包含main() 函数的C代码文件中,按照如下的格式添加对头文件的包含:
在 main() 函数中添加对EventRecorder服务的初始化:#include<RTE_Components.h>#if defined(RTE_Compiler_EventRecorder)# include <EventRecorder.h>#endif
如果你从未使用过EventRecorder也不必惊慌,这段代码的主要作用是为printf专门开启一个数据通道。voidmain(void){...#ifdefined(RTE_Compiler_EventRecorder)\defined(RTE_Compiler_IO_STDOUT_EVR)EventRecorderInitialize(0, 1);#endif...}
理论上,到这里,我们就已经完成了部署,可以在进入调试模式后,通过MDK的 Debug (printf) View窗口来观察 printf 的输出结果了。比如,我们在 main() 函数中打印一个 "hello world\r\n":
#include <stdio.h>#include <RTE_Components.h>#if defined(RTE_Compiler_EventRecorder)# include <EventRecorder.h>#endifvoid main(void){...#if defined(RTE_Compiler_EventRecorder) \defined(RTE_Compiler_IO_STDOUT_EVR)EventRecorderInitialize(0, 1);#endif...printf("Hello World\r\n");...}
编译,一切顺利的话,进入调试模式后通过菜单 View->Serial Windows->Debug (printf) View 打开窗口:

运行后,可以在 Debug (printf) View窗口中看到如下的结果:


【常见问题】
如果你的工程中从未提供过对 ".bss.noinit"数据段的处理,那么很可能会发现通过上述方法实现的 printf 输出似乎不是很稳定——时有时无——处于一种薛定谔的状态。这是由于EventRecorder 有一段数据放置在了 “.bss.noinit” section中——以求芯片复位后不会破坏其中原有的内容。如果你的工程没有专门针对 “.bss.noinit” 的处理,那么就会在进入调试模式后,从Command 窗口中看到类似如下的信息:

即:
Warning: Event Recorder not located in uninitialized memory!如果遇到这种情况应该怎么办呢?
打开工程配置窗口“Options for Target”,切换到“Linker”选项卡:


单击Edit按钮,可以看到脚本的内容:


为了避免该问题,应该将它从 Object 目录中移动到工程目录下。具体步骤为:右键单击脚本文件名:




打开example.sct,在 RW_IRAM1 后面追加如下的代码:
ZI_RAM_UNINIT+0UNINIT{.ANY(.bss.noinit)}
效果大约类似这样:

这里步骤的核心思想是在 scatter script内紧接着为 RW和ZI的 execution region为 .bss.noinit提供一个属性为UNINIT的专属execution region。
在领会精神的情况下,如果你的工程原本就使用了scatter script也可以如法炮制。俗话说解铃还须系铃人,如果你还是不知道怎么处理,那么就去找 你工程中scatter script 的作者吧。
值得强调的是:如果你的MDK版本太老,为了确保最佳的用户体验,还是推荐尽快升级吧。您可以在关注【裸机思维】公众号后发送关键字【MDK】来获取其最新的网盘链接。
【说在后面的话】
总的来说,MDK通过 EventRecorder 为我们提供了一个通用便捷的方式来重定向 printf——无论你使用什么调试仿真器,甚至是FVP,都可以享受来自“MDK”的阳光普照。
对很多有分发自己工程作为模板的小伙伴来说,使用该方法后将不再限制用户必须使用 J-Link 之类的工具,而是可以放开手脚,获得了“开袋即食”的调试体验。最后强调一下哦,EventRecorder只在调试阶段有意义,如果我们需要在产品的正常工作模式下使用 printf,还是老老实实把Compiler->IO->STDOUT 配置为 User:

int stdout_putchar(int ch){if ('\n' == ch) {int temp = '\r';while(Driver_USART0.Send(temp, 1) != ARM_DRIVER_OK);}if (Driver_USART0.Send(ch, 1) == ARM_DRIVER_OK) {return ch;}return -1;}
本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
在公众号聊天界面回复1024,可获取嵌入式资源;回复m,可查看文章汇总
新闻来源:嵌入式大杂烩,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。