完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第8章 STM32H7的終極調試組件Event Recorder
本章節為大家介紹終極調試方案Event Recoder,之所以叫終極解決方案,是因為所有Link通吃,支持時間測量,功耗測量,printf打印,RTX5及其所有中間件調試信息展示。
8.1 重要提示(必讀)
8.2 Event Recorder簡介
8.3 創建工程模板和注意事項
8.4 Event Recorder事件記錄的實現
8.5 Event Recoder 實現printf重定向
8.6 Event Statistics 時間測量功能的實現
8.7 Event Statistics 功耗測量功能的實現
8.8 Event Recoder對RTX5及其所有中間件的支持
8.9 JLINK配置說明
8.10 STLINK配置說明
8.11 CMSIS-DAP配置說明
8.12 ULINK配置說明
8.13 配套例子
8.14 總結
8.1 重要提示(必讀)
- 只要是MDK支持的調試下載器,基本都支持Event Recorder,本教程測試了JLINK,STLINK和CMSIS-DAP。
- 務必使用MDK5.25及其以上版本。
- 使用ARM_Compiler 軟件包V1.4.0及其以上版本。詳情看此貼:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87175。
- CMSIS軟件包 要是使用V5.3.0及其以上版本,詳情本教程8.3小節末尾的說明。
- 如果大家的MDK5.X應用不是很熟練的話,可以看論壇網友翻譯的MDK5.X入門手冊:http://www.armbbs.cn/forum.php?mod=viewthread&tid=31288。如果覺得看手冊上手慢的話,可以直接看KEIL官方做的MDK入門系列視頻,帶中文字幕:http://www.armbbs.cn/forum.php?mod=viewthread&tid=82667 。
- 為了實現Event Recorder組件的最高性能,最好將下載器的時鍾速度設置到所支持的最大值,另外,根據需要加大EventRecorderConf.h文件中的緩沖大小,默認可以緩沖64個消息(動態更新的FIFO空間)。
- 此調試組件不需要用到SWO引腳,使用標准的下載接口即可。以我們的開發板為例,用到VCC,GND,SWDIO,SWCLK和NRST。大家使用三線JLINK-OB也是沒問題的,僅需用到GND,SWDIO和SWCLK。
8.2 Event Recorder簡介
前面的專題教程中為大家講解了使用SEGGER的RTT功能來替代串口打印,比較方便。只是這種方法限制用戶必須使用JLINK才可以。而使用Event Recorder的話,無此限制,各種LINK通吃。只要是MDK支持的即可。
Event Recorder是MDK在5.22版本的時增加的功能,到了5.25版本后,這個功能就更加完善了,增加了時間測量和功耗測量的功能。
此調試組件不需要用到SWO引腳,使用標准的下載接口即可。以我們的開發板為例,用到VCC,GND,SWDIO,SWCLK和NRST。大家使用三線JLINK-OB也是沒問題的,僅需用到GND,SWDIO和SWCLK。
- JTAG接口和SWD接口區別
下圖分別是20pin的標准JTAG引腳和SWD( Serial Wire Debug)引腳,一般SWD接口僅需要Vref,SWDIO,SWCLK,RESET和GND五個引腳即可,SWO(Serial Wire Output)引腳是可選的。有了SWO引腳才可以實現數據從芯片到電腦端的數據發送。
- 詞條 SWV(Serial Wire Viewer)
SWV是由儀器化跟蹤宏單元ITM(Instrumentation Trace Macrocell)和SWO構成的。SWV實現了一種從MCU內部獲取信息的低成本方案,SWO接口支持輸出兩種格式的跟蹤數據,但是任意時刻只能使用一種。兩種格式的數據編碼分別是UART(串行)和Manchester(曼徹斯特)。當前JLINK僅支持UART編碼,SWO引腳可以根據不同的信息發送不同的數據包。當前M3/M4可以通過SWO引腳輸出以下三種信息:
- ITM支持printf函數的debug調用(工程需要做一下接口重定向即可)。ITM有32個通道,如果使用MDK的話,通道0用於輸出調試字符或者實現printf函數,通道31用於Event Viewer,這就是為什么實現Event Viewer需要配置SWV的原因。
- 數據觀察點和跟蹤DWT(Data Watchpoint and Trace)可用於變量的實時監測和PC程序計數器采樣。
- ITM 還附帶了一個時間戳的功能:當一個新的跟蹤數據包進入了ITM的FIFO 時,ITM 就會把一個差分的時間戳數據包插入到跟蹤數據流中。跟蹤捕獲設備在得到了這些時間戳后,就可以找出各跟蹤數據之間的時間相關信息。另外,在時間戳計數器溢出時也會發送時間戳數據包。
8.2.1 Event Recorder的特色
Event Recorder的特色主要有以下幾點:
- 提升應用程序動態執行期間的檢測能力。
- 支持的事件類型濾除機制,比如運行錯誤、API調用、內部操作和操作信息的區分。
- 可以在任務中、RTOS內核中和中斷服務程序中任意調用。
- 對於帶ITM功能的Cortex-M3/M4/M7/M33內核芯片,執行記錄期間,全程無需開關中斷操作。對於不帶ITM功能的Cortex-M0/M0+/M23,是需要開關中斷的。
- 支持printf重定向。
- 各種link通吃,支持SWD接口或者JTAG接口方式的JLINK、STLINK、ULINK和CMSIS-DAP。
- 對於帶DWT時鍾周期計數器功能的Cortex-M3/M4/M7/M33內核芯片,創建時間戳時,可以有效降低系統負擔,無需專用定時器來實現。
- Event Recorder執行時間具有時間確定性,即執行的時間是確定的,而且執行速度超快,因此,實際產品中的代碼依然可以帶有這部分,無需創建debug和release兩種版本。
- RTX5及其所有中間件都支持Event Recorder調試。
8.2.2 Event Recorder是如何工作的
首先來看下面這張圖:
在截圖的左下角有個Memory內存區,在這個內存區里面有一個緩沖Event Buffer,其實就是一個大數組。MDK通過訪問這個數組實現消息的圖形化展示。為了正確的圖形化展示,數組緩沖里面的數據就得有一定的數據格式。而這個數據格式就是通過左側截圖里面的Event Recorder和Event Filter來實現的。Event Recorder的API實現數據記錄和整理,Event Filter的API實現數據的篩選,從而可以選擇哪些數據可以在MDK的Event Recorder調試組件里面展示出來。
這就是Event Recorder的基本工作流程。
8.2.3 Event Statistics時間測量功能
Event Statistics提供的時間測量功能簡單易用,在測試代碼前后加上測量函數即可:
在本章教程程的8.6小節為大家詳細進行了講解。通過這個時間測量功能,用戶可以方便測試代碼的執行時間,從而根據需要,進行合理的優化,提高代碼執行效率。
8.2.4 Event Statistics功耗測量功能
Event Statistics提供的功耗測量功能,當前只有KEIL的ULINKplus支持此功能,由於ULINKplus價格不便宜,一套5000多,大家作為了解即可,實際效果如下:
8.2.5 Event Recorder的實現原理
每條Event Recorder消息是由16字節的數據組成,32位的ID,32位的時間戳,兩個32位的數據,共計16個字節。其中32位ID最重要,格式如下:
Level指定消息分類,主要用於消息篩選:
Component number指定事件消息所屬的軟件組件,也可用於過濾:
看了下Event Recorder的源碼,每條消息大體是一樣的:
typedef struct { uint32_t ts; // Timestamp (32-bit, Toggle bit instead of MSB) uint32_t val1; // Value 1 (32-bit, Toggle bit instead of MSB) uint32_t val2; // Value 2 (32-bit, Toggle bit instead of MSB) uint32_t info; // Record Information // [ 7.. 0]: Message ID (8-bit) // [15.. 8]: Component ID (8-bit) // [18..16]: Data Length (1..8) / Event Context // [19]: IRQ Flag // [23..20]: Sequence Number // [24]: First Record // [25]: Last Record // [26]: Locked Record // [27]: Valid Record // [28]: Timestamp MSB // [29]: Value 1 MSB // [30]: Value 2 MSB // [31]: Toggle bit } EventRecord_t;
其中參數成員info最重要,也就是前面說的32位ID,這里的說明與前面的說明稍有不同。這里是經過處理后,實際存儲到Event Recorder緩沖里面的數據。
對於Event Recorder,大家了解了這些知識點基本就夠用了。
8.3 創建工程模板和注意事項
Event Recorder工程的創建比較簡單,這里分步為大家做個介紹。
第1步:准備好一個使用MDK5.25或以上版本創建的工程模板。
第2步:安裝ARM_Compiler V1.4.0或以上版本(如果有最新版,直接安裝最新的),詳情見帖子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=87175 。
第3步:打開MDK5.25或以上版本創建的RTE環境。
第4步:通過RTE環境,為工程添加Event Recorder功能。
第5步:為了實現printf重定向,我們需要將STDOUT的輸出方式改為Event Recorder,即選項里面的EVR。
第6步:打開通過RTE環境為工程添加的文件EventRecorderConf.h,配置如下:
這里主要設置方框里面的兩個參數。
Number of Records:表示Event Recorder緩沖可以記錄的消息條數。
Time Stamp Source:表示時間戳來源,有如下四種可以選擇,我們這里使用DWT時鍾周期計數器。
由於選擇的是DWT,因此EventRecorderCong.h文件中的Systick Configuration配置就不用管了。
==========================
通過上面的6步就完成了Event Recorder功能的添加,效果如下:
添加完成后,還有非常重要的兩點要特別注意:
- 第1點:一定要使用當前最新的CMSIS軟件包,當前是V5.4.0(隨着時間的推移,如果升級了新版本,直接使用新版即可)。大家可以從這里下載:
http://www.keil.com/dd2/pack/ 。
下載並導入到MDK后,需要大家更新自己現有工程CMSIS文件里面的頭文件,可以直接將CMSIS文件夾中Include文件里面的所有文件全部刪掉,替換為MDK安裝目錄如下路徑里面的所有頭文件:
ARM\PACK\ARM\CMSIS\5.4.0\CMSIS\Include。保證頭文件都是最新的5.4.0版本。
- 第2點:由於使能了printf重定向,大家的工程里面一定不要再做重定向了,比如fpuc,fgetc。另外當前選擇了微庫MicroLib:
注意這兩點后,就可以使用Event Recorder的功能了。
8.4 Event Recorder事件記錄的實現
Event Recorder的使用也比較省事,這里也分步為大家進行說明:
第1步:初始化,僅需添加如下兩行代碼即可。
/* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart();
第2步:調用Event Recorder的API就可以使用了,主要有以下三個API:
EventRecord2:可以發送兩個32位數據。
EventRecord4:可以發送四個32位數據。
EventRecordData:可以發送字符串。
顯然這三個函數沒有printf使用方便,所以對於這三個函數,大家做個簡單的了解即可。教程配套例子里面有調用到這三個函數,可以操作熟悉下。這三個API的說明是在對應的help文檔中,即MDK安裝目錄路徑:/ARM/PACK/Keil/ARM_Compiler/1.6.0/Doc/General/html/index.html。
第3步:進入調試狀態,選上周期更新:
點擊全速運行:
然后將Event Recorder調試組件展示出來:
效果如下:
另外,這里有個知識點需要大家了解下,如果程序里面也調用了Event Statistics時間測量函數,那么也會在這個界面里面展示消息的,如何才能僅展示大家想看的功能呢?這就需要用到Event Recorder支持的篩選功能。使用這個功能需要大家先暫停全速運行,然后點擊下面這個選項:
彈出的界面里面可以設置哪些選項顯示,哪些選項不顯示(勾上表示顯示),我們這里取消Event Statistics的顯示,設置完畢后記得點擊OK按鈕。
這就不展示Event Statistics的內容了。再次啟動全速運行前,下面這個選項的對勾別忘了勾上。
8.5 Event Recorder實現printf重定向
實現printf輸出需要用到MDK調試組件中的Debug(printf) Viewer,輸出效果就跟大家使用串口調試軟件一樣,可以輸出中文和英文。
MDK的printf調試組件使用方法跟本章8.4小節中的說明一樣,點擊調試,選中周期運行,然后顯示Debug(printf) Viewer調試組件:
效果如下:
另外,還有一個知識點需要給大家做個補充,使用SWD接口的SWO引腳也是可以做串口打印的,並且也是通過這個調試組件Debug(printf) Viewer進行輸出。只是這種方式的性能沒有Event Viewer強,而且要多占用一個SWO引腳。
關於SWO輸出方式可以看此貼:http://www.armbbs.cn/forum.php?mod=viewthread&tid=526 。
8.6 Event Statistics 時間測量功能的實現
時間測量功能簡單易用,僅需一個起始函數,一個停止函數即可。當前支持4組,每組支持16路測量,也就是可以同時測量64路。
時間測量的API函數支持多任務和中斷里面隨意調用。
1、 測量起始函數:EventStartG (slot) 或者EventStartGv (slot, val1, val2)
- 函數中的字母G是表示分組A,B,C,D,即實際調用函數為EventStartA,EventStartB,EventStartC和EventStartD。
- 函數的第一個形參slot的范圍是0-15,也就是每個分組可以測試16路。
- 函數后面的兩個形象val1和val2是32位變量,用戶可以用這兩個形參來傳遞變量數值給Event Statistics調試組件里面,方便圖形化展示。簡單的說,這兩個變量僅僅起到一個傳遞變量數值的作用。
2、 測量停止函數:EventStopG (slot) 或者 EventStopGv (slot, val1, val2)
- 函數中的字母G是表示分組A,B,C,D,即實際調用函數為EventStopA,EventStopB,EventStopC和EventStopD。
- 函數的第一個形參slot的范圍是0-15,也就是每個分組可以測試16路。
- 函數后面的兩個形象val1和val2是32位變量,用戶可以用這兩個形參來傳遞變量數值給Event Statistics調試組件里面,方便圖形化展示。簡單的說,這兩個變量僅僅起到一個傳遞變量數值的作用。
這里也分步為大家說明Event Statistics時間測量功能的使用方法。
第1步:初始化,僅需添加如下兩行代碼即可。
/* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart();
第2步:在要測量的代碼前后加上起始和結束時間。
EventStartA(0); 測量的代碼部分 EventStopA(0);
這里是用分組A的測量通道0。
第3步:跟本章8.4小節講解的一樣,點擊調試,選擇周期更新選項,然后全速運行。
第4步:全速運行后,顯示Event Statistics調試組件。
比如我這里簡單的測試了一個5ms的延遲函數,效果如下(測量時間是動態更新的):
另外要注意一點,微秒的時間單位us可能無法正常顯示,這個是沒有關系的:
8.7 Event Statistics 功耗測量功能的實現
當前僅KEIL自家的ULINKplus支持功耗測量功能,這款下載器不便宜,一套5000多,大家有個了解即可,我們這里就不做講解了。
8.8 Event Recorder對RTX5及其所有中間件的支持
后面做RTX5及其所有中間件的教程時會為大家做講解,這里讓大家看下效果:
- RTX5組件和使用Event Recoder的效果:
- 網絡調試組件效果展示:
- 文件系統和USB協議棧的效果展示:
8.9 JLINK配置說明
為了幫助大家更好的使用JLINK,這里將JLINK配置中關鍵的幾個地方做個說明。
- 下面這個地方最重要,一定要正確設置當前系統工作的主頻,如果不正確,會導致Event Statistics的時間統計不正確(對於H7,Core部分要填400MHz)。
注:如果大家調試狀態彈出SWD配置時鍾超出范圍的問題,可以考慮將上面截圖中的Enable選項的對勾取消掉即可,但內核時鍾一定要修改為芯片的主頻。
另外,進入調試狀態后,右下角的時間是否正常更新都沒有關系:
- 其它選項配置如下(只要大家的工程能夠正常調試,配置就是沒問題的):
8.10 STLINK配置說明
為了幫助大家更好的使用STLINK,這里將STLINK配置中關鍵的幾個地方做個說明。
- 下面這個地方最重要,一定要正確設置當前系統工作的主頻,如果不正確,會導致Event Statistics的時間統計是不正確的(對於H7,Core部分要填400MHz)。
另外注意,進入調試狀態后,右下角的時間是否正常更新都沒有關系:
- 其它選項配置如下(只要大家的工程能夠正常調試,配置就是沒問題的):
8.11 CMSIS-DAP配置說明
為了幫助大家更好的使用CMSIS-DAP,這里將CMSIS-DAP配置中關鍵的幾個地方做個說明。
- 下面這個地方最重要,一定要正確設置當前系統工作的主頻,如果不正確,會導致Event Statistics的時間統計不正確(對於H7,Core部分要填400MHz)。
另外注意,進入調試狀態后,右下角的時間是否正常更新都沒有關系:
- 其它選項配置如下(只要大家的工程能夠正常調試,配置就是沒問題的):
8.12 ULINK配置說明
由於手頭沒有ULINK,這里就不做講解了。如果大家需要相關配置,按照前面小節三款LINK的配置照葫蘆畫瓢搞一下即可,或者在MDK安裝目錄的路徑ARM\Hlp下有對應的文檔說明:
8.13 配套例子
本章節教程配套了如下例程,僅MDK版本。
- V7-008_終極調試組件EventRecoder的使用
具體代碼實現也比較簡單,以V6開發板為例,定義一個TIM6的中斷,中斷頻率是500Hz,通過Event Statistics測量中斷的執行頻率。代碼如下:
#include "bsp.h" #include "EventRecorder.h" /* 定時器頻率,500Hz */ #define timerINTERRUPT_FREQUENCY 500 /* 中斷優先級 */ #define timerHIGHEST_PRIORITY 10 /* ********************************************************************************************************* * 函 數 名: vEventRecorderTest * 功能說明: 創建定時器 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void vEventRecorderTest(void) { bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0); EventStartB(0); } /* ********************************************************************************************************* * 函 數 名: TIM6_DAC_IRQHandler * 功能說明: TIM6中斷服務程序。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void TIM6_DAC_IRQHandler( void ) { if((TIM6->SR & TIM_FLAG_UPDATE) != RESET) { EventStopB(0); EventStartB(0); /* 清除更新標志 */ TIM6->SR = ~ TIM_FLAG_UPDATE; } }
效果如下,測量的平均頻率是1.98ms,與我們設計的500Hz基本符合:
應用程序的設計如下:
#include "bsp.h" /* 底層硬件驅動 */ #include "EventRecorder.h" /* ********************************************************************************************************* * 函數和變量 ********************************************************************************************************* */ extern void vEventRecorderTest(void); uint8_t s_ucBuf[10] = "armfly"; /* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參:無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ uint32_t t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0; /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); bsp_Init(); /* 硬件初始化 */ bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重裝的定時器 */ /* 測量中斷周期 */ vEventRecorderTest(); /* 進入主程序循環體 */ while (1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { EventStartA(0); EventStopA(0); EventStartA(1); bsp_DelayMS(5); EventStopA(1); EventStartA(2); bsp_DelayMS(30); EventStopA(2); t0++; EventStartAv(3, t0, t0); bsp_DelayMS(30); EventStopAv(3, t0, t0); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下 */ t1 += 1; t2 += 2; EventRecord2(1+EventLevelAPI, t1, t2); t3 += 3; t4 += 4; EventRecord4(2+EventLevelOp, t1, t2, t3, t4); EventRecordData(3+EventLevelOp, s_ucBuf, sizeof(s_ucBuf)); break; case KEY_DOWN_K2: /* K2鍵按下 */ printf("K2按鍵按下\r\n"); break; case KEY_DOWN_K3: /* K3鍵按下 */ printf("K3按鍵按下\r\n"); break; default: /* 其它的鍵值不處理 */ break; } } } }
應用程序里面主要實現了三個功能:
1、利用測量分組A實現4路時間的測量(第1路什么也沒有測量,可以用來表示這兩個函數本身執行占用的時間)。每100ms測量一次時間,效果如下:
2、利用函數EventRecord2,EventRecord4和EventRecordData發送消息事件。按下按鍵K1進行更新,效果如下:
3、基於Event Recorder的printf重定向。按下按鍵K2或者K3會打印消息,效果如下:
8.14 總結
Event Recoder還是非常實用的,建議大家多使用幾次,熟練掌握。基本用上幾次就上癮,離不開了,的確是工程調試的利器。