內容提要
1. ARM Cortex-M內核的DWT功能特性和應用代碼執行時間測量原理介紹2. Bare-Metal實現S32K144應用工程代碼執行時間測量實現2. 1 定義Cortex-M內核的DWT模塊訪問寄存器3. 基於FreeRTOS的S32K144應用工程代碼執行時間測量實現3.1 FreeRTOS的任務運行時間統計( Run Time Statistics)功能3.2 FreeRTOS還提供了擴展命令行接口(CLI--Command Line Interface)功能3.3 基於FreeRTOS的S32K144應用代碼運行時間統計實現引言
數據監測點(即數據斷點),讓CPU內核進入調試狀態(debug state)或者產生一個調試監測器異常(DebugMonitor exception);
數據跟蹤(data tracing);
與其他調試資源(比如ETM模塊)一起使用產生調試信號;
內核程序計數器值跟蹤(PC value tracing)
內核周期計數匹配(cycle count matching)
② 額外的內核程序計數器採樣(PC Sampling)PC採樣跟蹤輸出作為周期計數事件的結果
使用PC採樣寄存器的外部PC採樣
Note:NXP的S32K11x系列MCU使用的CM0+內核,雖然也包含DWT模塊,但是其功能比較簡單,不包含內周周期計數器,因此不能使用本文介紹的基於DWT的應用代碼執行時間測量方法。ARM Cotrex-M v6(CM0+)中DWT模塊功能和寄存器如下:/********************************************************************************** CORTEX-M - DWT TIMER*********************************************************************************/
#define ARM_CM_DEMCR (*(uint32_t *)0xE000EDFC)#define ARM_CM_DWT_CTRL (*(uint32_t *)0xE0001000)#define ARM_CM_DWT_CYCCNT (*(uint32_t *)0xE0001004)為了方便使用,定義如下數據結構以記錄代碼運行時間測量結果,其中包含測量開始時DWT定時器的計數器值、當前測量結果以及歷次測量統計的最大和最小結果:
/********************************************************************************** Measurement Result Data Structure*********************************************************************************/
typedef struct elapsed_time{ uint32_t start; /* DWT counter value at the measurement start point*/ uint32_t current; /* result of current measurement */ uint32_t max; /* the maximum measurement result */ uint32_t min; /* the minimum measurement result */} ELAPSED_TIME;並定義了能夠保存最多10個(用戶可以通過宏定義根據實際測量需要修改)代碼執行時間測量結果的結構體數組:#define ELAPSED_TIME_MAX_SECTIONS 10static ELAPSED_TIME elapsed_time_tbl[ELAPSED_TIME_MAX_SECTIONS];為了完成基於DWT定時器額應用代碼執行時間測量,定義了如下4個API函數:void elapsed_time_clr (uint32_t i); void elapsed_time_init (void); void elapsed_time_start (uint32_t i); void elapsed_time_stop (uint32_t i);具體實現代碼如下:
/********************************************************************************** MODULE INITIALIZATION** Note(s): Must be called before any of the other functions in this module*********************************************************************************/
void elapsed_time_init (void){ uint32_t i;
if (ARM_CM_DWT_CTRL != 0) /* check if DWT is available */ { ARM_CM_DEMCR |= 1 << 24; /* Set bit 24 */ ARM_CM_DWT_CYCCNT = 0; /* clean the DWT counter */ ARM_CM_DWT_CTRL |= 1 << 0; /* Set bit 0 */ }
/* clean all the measurement result */ for (i = 0; i < ELAPSED_TIME_MAX_SECTIONS; i++) { elapsed_time_clr(i); }}
/********************************************************************************** START THE MEASUREMENT OF A CODE SECTION*********************************************************************************/void elapsed_time_start (uint32_t i){ /* * get current DWT counter value and store it to elapsed time table * as the measurement start time */ elapsed_time_tbl[i].start = ARM_CM_DWT_CYCCNT;}
/********************************************************************************** STOP THE MEASUREMENT OF A CODE SECTION AND COMPUTE STATS*********************************************************************************/void elapsed_time_stop (uint32_t i){ uint32_t stop; ELAPSED_TIME *p_tbl;
stop = ARM_CM_DWT_CYCCNT; /* get current DWT counter value */ p_tbl = &elapsed_time_tbl[i]; /* get the elapsed time table for the measurement */
/* calculate the runtime measurement result */ p_tbl->current = stop - p_tbl->start;
/* find and record the maximum measurement result */ if (p_tbl->max < p_tbl->current) { p_tbl->max = p_tbl->current; }
/* find and record the minimum measurement result */ if (p_tbl->min > p_tbl->current) { p_tbl->min = p_tbl->current; }}
/********************************************************************************** CLEAR THE MEASUREMENTS STATS*********************************************************************************/void elapsed_time_clr (uint32_t i){ ELAPSED_TIME *p_tbl;
/* get the measurement result from the elapsed time table */ p_tbl = &elapsed_time_tbl[i];
p_tbl->start = 0; /*clean the start DWT counter value */ p_tbl->current = 0; /*clean the current measurement result */ p_tbl->min = 0xFFFFFFFF; /*clean the minimum measurement result */ p_tbl->max = 0; /*clean the maximum measurement result */}在開始應用代碼執行時間測量之前,需要調用API函數elapsed_time_init()完成對WDT定時器的初始化(使能)和全局測量結果的清除。然後就可以調用API函數elapsed_time_start()啟動測量,在測量結束時調用API函數elapsed_time_stop()獲取測量結果即可:
elapsed_time_init();
elapsed_time_start(x);App_Func(); elapsed_time_stop(x);如下測試代碼完了對系統初始化函數MCU_SystemInit()和FreeRTOS應用任務創建函數AppTasks_Create()執行時間的測量,相應的結果放在結果0和結果1中:elapsed_time_init();
elapsed_time_start(0); MCU_SystemInit(); elapsed_time_stop(0);
elapsed_time_start(1); AppTasks_Create(); elapsed_time_stop(1);實際測量結果如下(將elapsed_time_tbl數組添加到expression窗口中查看接口,當然,用戶也可以在應用代碼直接讀取elapsed_time_tbl數組,獲取結果):3. 基於FreeRTOS的S32K144應用工程代碼執行時間測量實現在S32K1xx SDK中集成了移植好的FreeRTOS給S32K14x系列MCU使用。一方面,FreeRTOS提供了任務運行時間統計(Run Time Statistics)方便用戶進行FreeRTOS任務執行時間的測量和統計,另一方面,FreeRTOS還提供了擴展命令行接口(CLI--Command Line Interface)方面將其結果輸出。下面我們就一起來看看這一功能在S32K144 MCU上的具體實現。3.1 FreeRTOS的任務運行時間統計( Run Time Statistics)功能FreeRTOS的任務運行時間統計功能可以收集有關每個任務已使用的處理時間的信息。然後,使用vTaskGetRunTimeStats()API函數以表格格式顯示此信息,如下圖所示。
使用FreeRTOS的任務運行時間統計功能的需要三個宏。這些可以在FreeRTOSConfig.h中定義。① configGENERATE_RUN_TIME_STATS通過將configGENERATE_RUN_TIME_STATS定義為1來啟用運行時統計信息的收集。一旦設置了該值,則還必須定義其他兩個宏才能成功編譯。② portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()Tips:運行時統計信息時基需要比滴答中斷具有更高的解析度–否則,統計信息可能太不準確而無法真正有用。建議使時基比滴答中斷快10至100倍。時基越快,統計信息將越準確-但計時器值也會越早溢出。如果將configGENERATE_RUN_TIME_STATS定義為1,則RTOS內核將在啟動時自動調用portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()(從vTaskStartScheduler() API函數中調用)。旨在使應用程式設計者使用宏來配置合適的時基。下面提供了一些示例。③ portGET_RUN_TIME_COUNTER_VALUE()此宏應僅返回由portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()配置的當前「時間」。Tips:關於FreeRTOS任務運行時間統計功能實現細節和使用方法及樣例代碼,請參考如下FreeRTOS官網連結,此處不再贅述:FreeRTOS Run Time Statistics
https://www.freertos.org/rtos-run-time-stats.html
3.2 FreeRTOS還提供了擴展命令行接口(CLI--Command Line Interface)功能FreeRTOS + CLI(命令行接口)提供了一種簡單,小型,可擴展且RAM資源佔用小的方法,使用戶的FreeRTOS應用程式能夠處理命令行輸入。按照如下流程,只需要簡單幾步即可添加用戶自定義的命令。Tips:關於FreeRTOS + CLI(命令行接口)的實現細節和使用方法及樣例代碼,請參考如下FreeRTOS官網連結,此處不再贅述:
FreeRTOS+CLI An Extensible Command Line Interface Framework
https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_CLI/FreeRTOS_Plus_Command_Line_Interface.html
3.3 基於FreeRTOS的S32K144應用代碼運行時間統計實現按照3.1小節和3.2小節的介紹,在S32DS for ARM v2018.R1IDE中新建S32K1xx SDK RTM3.0的S32K144應用工程,添加FreeRTOS組件,將FreeRTOS CLI移植到應用工程中,(配置CLI通信命令接口重定向到S32K144-EVB的OpenSDA虛擬串口(波特率:115200,字符長度:8-bit/char,無奇偶校驗位,1-bit停止位)),並配置使能FreeRTOS的運行時統計功能:FreeRTOS CLI中實現taskstats tasklist和runtime命令的關鍵代碼如下:
static BaseType_t prvTaskStatsCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString ){ int8_t *pcParameter1; BaseType_t xParameter1StringLength; BaseType_t xResult = pdPASS;
pcParameter1 = FreeRTOS_CLIGetParameter ( pcCommandString, 1, &xParameter1StringLength ); if(pcParameter1==NULL) { snprintf( pcWriteBuffer, xWriteBufferLen, xTaskStatsCommand.pcHelpString );
return pdFALSE; }
if( strncmp( pcParameter1, "tasklist", xParameter1StringLength ) == 0 ) { vTaskList( pcWriteBuffer ); UART_SendDataBlocking(&uart_pal1_instance,TaskList_String, strlen( TaskList_String ), 50); }
else if( strncmp( pcParameter1, "runtime", xParameter1StringLength ) == 0 ) { vTaskGetRunTimeStats(pcWriteBuffer); UART_SendDataBlocking(&uart_pal1_instance,TaskRunTime_String, strlen(TaskRunTime_String), 50); } else xResult = pdFAIL;
if( xResult == pdFAIL ) { snprintf( pcWriteBuffer, xWriteBufferLen, "parameters error\r\n\r\n" ); }
return pdFALSE;}在FreeRTOS組件中配置使能運行時間和任務統計功能(Run time and task stats):① 勾選「Collect runtime statistics」選項,配置宏定義configGENERATE_RUN_TIME_STATS為1,使能FreeRTOS的任務運行時間統計功能:② 配置portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()使用CM4內核的DWT模塊的周期計數器作為FreeRTOS的任務運行時間統計參考計數器:{*(uint32_t *)0xE000EDFC |= 1 << 24; \*(uint32_t *)0xE0001004 = 0; \*(uint32_t *)0xE0000100 |= 1 << 0; }③ 配置portGET_RUN_TIME_COUNTER_VALUE() 返回CM4內核的DWT模塊周期計數器值:(*(uint32_t *)0xE0001004) 保存配置,重新生成代碼,編譯下載後,在串口調試助手(推薦使用Win10應用商店中下載安裝的Serial Debug Assistant)中輸入taskstats runtime命令,即可獲得當前應用工程中所有用戶任務和系統任務的運行時間統計結果(可以看到當前MCU應用程式中FreeRTOS的空閒任務運行佔用了92%的CPU時間,FreeRTOS CLI任務佔用3%的CPU時間,CAN通信(CAN_Com)任務佔用大概3%的CPU時間,其中,run_count對應的是CPU內核的時鐘周期,即1/80MHz = 12.5ns):Tips:如果不定義portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()和portGET_RUN_TIME_COUNTER_VALUE(),僅僅勾線勾選「Collect runtime statistics」選項,則默認使用CM4內核的SysTick定時器作為運行時間統計參考,其結果將存在很大的誤差:本文介紹了ARM Cortex-M4F內核的DWT模塊功能和使用其實現應用程式代碼執行時間測量的Bare-Metal和FreeRTOS運行時間統計功能實現,前者的測量結果更加準確,使用也非常靈活,後者測量的代碼塊為FreeRTOS的任務,不包含MCU中斷ISR的執行時間,它雖然測量精度不如Bare-Metal方法,但是可以得到比較全面的內核性能比較結果。為了幫助大家更好的學習和掌握本文介紹的內容,我將相關實現代碼和樣例工程分享到如下百度雲盤連結,供大家參考:連結: https://pan.baidu.com/s/1pu5lsuNnsikWTWC-ePx-ZQ
本公眾號近期文章精選推薦(點擊文章標題即可直接跳轉閱讀):
1. 深入淺出談嵌入式MCU 內核之ARM Cortex-M系列CPU內核功能特性概述與對比(強烈推薦!!!);
2. 深入淺出談嵌入式MCU 內核之ARM Cortex-M系列CPU內核特權模式定義與切換方法詳解;
3. 淺談嵌入式MCU軟體開發之MCU在線調試功能正常而離線工作異常原因探究(以NXP汽車MCU為例);
4. S32K1xx ECU bootloader開發之RAM NVM驅動(S19文件)生成與集成調用和測試詳解;
5. 汽車電子ECU bootloader開發之S32K1xx系列MCU NVM驅動獨立安全bootloader開發詳解;
6. S32K1xx系列MCU應用指南之FlexCAN模塊功能與應用詳解;
7. S32K SDK使用詳解之can_pal組件和flexcan組件使用詳解(含RxFIFO DMA和ID濾波器以及總線關閉恢復等);
8. 淺談嵌入式MCU軟體開發之S32K1xx系列MCU的啟動過程和啟動時間優化方法詳細;
9. 淺談嵌入式MCU軟體開發之SEGGER實時傳輸(RTT)的移植和printf()重定向應用(附S32K144移植工程);
更多精彩文章,請進入本公眾號主頁,通過分類菜單訪問或者點擊以下歷史文章分類列表目錄獲取閱讀連結:
《歷史文章分類列表目錄(點擊文章標題即可直接跳轉閱讀,截止2020年11月15日)》;
原創寫作不易, 如果大家覺得本文對你的工作和學習有幫助,也歡迎大家讚賞鼓勵----我將堅持寫作,給大家帶來更多精彩的原創文章。
Tips:點擊本文文末的「喜歡作者」對本文進行讚賞或者「在看」進行分享,並留言提問,我將第一時間回復大家的關切!
歡迎在此與我一起學習/探討汽車電子和嵌入式系統軟硬體設計相關的技術。若對本公眾號或者分享的文章觀點有任何意見和建議也歡迎留言指出。您的點讚/關注/轉發分享是對我辛勤寫作的最大支持和肯定!
本公眾號已開通關鍵詞回復功能,請在公眾號主頁回復如下關鍵詞以獲取更多信息和精彩文章:
關於作者,請回復關鍵詞「作者簡介」;
聯繫作者,請回復關鍵詞「聯繫作者」;
獲取高清PDF版本公眾號文章,請回復關鍵詞「獲取文章」;
獲取專業及時的技術支持服務,請回復關鍵詞「專業服務」;
下載2017~2018年度原創技術文章集合高清PDF,請回復關鍵詞「文章全集」;
CodeWarrior IDE license購買及安裝使用問題諮詢,請回復關鍵詞「CW License」;
汽車乙太網轉工業乙太網轉換器購買,請回復關鍵詞「乙太網轉換器購買」;
獲取最新最全的公眾號原創技術分享文章目錄,請回復關鍵詞「文章目錄」;
鄭重聲明:本公眾號所有原創技術文章免費閱讀,文中所有觀點/結論均為個人觀點,不代表任何公司官方觀點意見;所有demo代碼/程序,僅作參考學習,不保證質量,若用於商業用途,責任自負;所有本公眾號文章,版權歸本人所有智慧財產權,一切未經本人同意的轉載均屬違法,盜版必究~!
如果你喜歡本公眾號的文章,請點擊文章最開始的公眾號關注或微信直接長按掃描識別下方二維碼關注,你也可以在微信添加朋友-->公眾號-->輸入"汽車電子expert成長之路"搜索-->點擊關注。若對本文觀點有任何意見和建議也歡迎留言指出。
您的關注、點讚、轉發分享是對我辛勤寫作的最大肯定!
另外,我在這裡也鄭重推薦大家掃描下方二維碼(長按掃描識別下方二維碼關注),下載專注於汽車電子技術分享和知識學習服務的「涅槃汽車APP」,同步閱讀我的汽車電子與嵌入式MCU軟硬體系統開發原創技術分享系列文章專輯(我後期的所有原創技術分享文章都將同步更新到此APP,在這裡能夠找到眾多汽車電子從業同行和業界專家大咖,大家一起交流學習!)Enwei Hu(胡恩偉)
2021年01月11日於山城·重慶