本篇將介紹MDK下99%用戶都不知道的萬能printf方法。
一、說在前面的話
你聽說過J-Link的RTT么?官方的宣傳是這樣的:
簡單來說,只要擁有了J-Link,你就可以享受以下的便利:
無需占用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窗口中
你是否心動了呢?
二、部署從未如此簡單
2.1RTE配置
依次通過菜單 Project->Manage->Run-Time Environment 打開RTE配置窗口:
找到并展開Compiler選項卡,勾選Event Recorder,并確保Variant下拉列表選中的是默認的DAP。
展開Compiler下的I/O,勾選STDOUT,并在Variant下拉列表中選擇 EVR——這里EVR是Event Recorder的縮寫。單擊確定后,我們會在工程管理器中看到以下的內容:
至此,所需的工具都已經成功地加入到工程中了。
雖然這里EventRecorderConf.h是一個可以編輯的狀態,但實踐中,我們基本不用去碰他——使用默認配置即可。
2.2服務初始化
在包含main()函數的C代碼文件中,按照如下的格式添加對頭文件的包含:
#include#if defined(RTE_Compiler_EventRecorder) # include #endif
在main()函數中添加對EventRecorder服務的初始化:
void main(void) { ... #if defined(RTE_Compiler_EventRecorder) && defined(RTE_Compiler_IO_STDOUT_EVR) EventRecorderInitialize(0, 1); #endif ... }
如果你從未使用過EventRecorder也不必驚慌,這段代碼的主要作用是為printf專門開啟一個數據通道。
理論上,到這里,我們就已經完成了部署,可以在進入調試模式后,通過MDK的Debug (printf) View窗口來觀察printf的輸出結果了。比如,我們在main()函數中打印一個 "hello world ":
#include#include #if defined(RTE_Compiler_EventRecorder) # include #endif void main(void) { ... #if defined(RTE_Compiler_EventRecorder) && defined(RTE_Compiler_IO_STDOUT_EVR) EventRecorderInitialize(0, 1); #endif ... printf("Hello World "); ... }
編譯,一切順利的話,進入調試模式后通過菜單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”選項卡:
首先,一定要確保你勾選了圖中的“Use Memory Layout from Target Dialog”選項。在這一前提下,再次取消對它的勾選:
我們會看到,MDK基于當前的Memory Layout,為我們在Out目錄下生成了一個與工程同名的鏈接腳本(比如圖中的工程名叫example,因此生成的鏈接腳本為example.sct)。
單擊Edit按鈕,可以看到腳本的內容:
先別著急半路開香檳——該文件是系統自動生成的,如果我們不移動它的位置,那么只要哪次手抖勾選了“Use Memory Layout from Target Dialog”,它的內容就會立即被覆蓋掉——意味著我們在后續步驟中所做的修改就會付諸東流。
為了避免該問題,應該將它從Object目錄中移動到工程目錄下。具體步驟為:右鍵單擊腳本文件名:
選擇“Open Container Folder”來打開文件所在目錄:
找到Scatter Script腳本文件后,將其拷貝到上一級目錄下(也就是工程目錄):
重新打開工程配置窗口:
確保我們“沒有”選中“Use Memory Layout from Target Dialog”選項,并在Scatter File文本框中直接填寫我們剛剛拷貝出來的腳本文件名(由于我們直接放在工程目錄下,因此這里直接用相對路徑"./example.scat"或者"example.scat"就行)。單擊OK保存配置。
打開example.sct,在RW_IRAM1后面追加如下的代碼:
ZI_RAM_UNINIT +0 UNINIT { .ANY (.bss.noinit) }
效果大約類似這樣:
保存后重新編譯,再次進入 Debug 模式,問題就應該解決了。
這里步驟的核心思想是在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:
實現stdout_putchar()函數——用它來發送字符到具體的外設吧,比如:
int stdout_putchar(int ch) { if (' ' == ch) { int temp = ' '; while(Driver_USART0.Send(&temp, 1) != ARM_DRIVER_OK); } if (Driver_USART0.Send(&ch, 1) == ARM_DRIVER_OK) { return ch; } return -1; }
-
usb
+關注
關注
60文章
7977瀏覽量
265529 -
串口
+關注
關注
14文章
1557瀏覽量
76841 -
MDK
+關注
關注
4文章
209瀏覽量
32120 -
J-Link
+關注
關注
0文章
84瀏覽量
22173 -
Printf
+關注
關注
0文章
83瀏覽量
13692
原文標題:MDK下99%用戶都不知道的萬能printf方法
文章出處:【微信號:Ithingedu,微信公眾號:安芯教育科技】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論