MCU上的代碼執(zhí)行時間
在許多實時應(yīng)用程序中,二八原則并不生效,CPU 可以花費95%(或更多)的時間在不到5% 的代碼上。電動機(jī)控制、引擎控制、無線通信以及其他許多對時間敏感的應(yīng)用程序都是如此。這些嵌入式系統(tǒng)通常是用c編寫的,而且開發(fā)人員常常被迫對代碼進(jìn)行手工優(yōu)化,可能會回到匯編語言,以滿足性能的需求。測量代碼部分的實際執(zhí)行時間可以幫助找到代碼中的熱點。本文將說明如何可以方便地測量和顯示在基于Cortex-M MCU的實時執(zhí)行時間。
測量代碼的執(zhí)行時間
測量代碼執(zhí)行時間的方法有很多。作為一個嵌入式工程師,經(jīng)常使用一個或多個數(shù)字輸出和一個示波器。需要在執(zhí)行要監(jiān)視的代碼之前設(shè)置一個高的輸出,然后將輸出降低。當(dāng)然,在做這些之前有相當(dāng)多的設(shè)置工作: 找到一個或多個自由輸出,確保它們可以輕松訪問,將端口配置為輸出,編寫代碼,編譯,設(shè)置范圍等等。一旦有了一個信號,你可能需要對它進(jìn)行一段時間的監(jiān)視,以便看到最小值和***值。 數(shù)字存儲示波器使這個過程更容易,但是還有其他更簡單的方法。
另一種測量執(zhí)行時間的方法是使用可跟蹤調(diào)試接口。只需要運行代碼,查看跟蹤,計算 delta時間(通常是手動的) ,并將CPU周期轉(zhuǎn)換為微秒。不幸的是,這個跟蹤給了一個執(zhí)行的實例,可能不得不在追蹤捕獲中進(jìn)一步查找最壞情況下的執(zhí)行時間。這是一個乏味的過程。
Cortex-M 周期計數(shù)器
在大多數(shù)Cortex-M的處理器中調(diào)試端口包含一個32位的自由運行計數(shù)器,它可以計算 CPU 的時鐘周期。計數(shù)器是 Debug 觀察和跟蹤(DWT)模塊的一部分,可以很容易地用于測量代碼的執(zhí)行時間。下面的代碼是啟用和初始化這個特性非常有用。
- #define ARM_CM_DEMCR (*(uint32_t *)0xE000EDFC)
- #define ARM_CM_DWT_CTRL (*(uint32_t *)0xE0001000)
- #define ARM_CM_DWT_CYCCNT (*(uint32_t *)0xE0001004)
- if (ARM_CM_DWT_CTRL != 0) { // See if DWT is available
- ARM_CM_DEMCR |= 1 << 24; // Set bit 24
- ARM_CM_DWT_CYCCNT = 0;
- ARM_CM_DWT_CTRL |= 1 << 0; // Set bit 0
- }
使用DWT周期計數(shù)器來測量代碼執(zhí)行時間
可以通過在目標(biāo)代碼之前和之后讀取周期計數(shù)器的值來測量和計算代碼段的執(zhí)行時間,如下所示。 當(dāng)然,這意味著必須設(shè)置代碼,但能夠得到一個非常準(zhǔn)確的值。
- uint32_t start;
- uint32_t stop;
- uint32_t delta;
- start = ARM_CM_DWT_CYCCNT;
- // Code to measure
- stop = ARM_CM_DWT_CYCCNT;
- delta = stop – start;
因為使用的是無符號運算,delta表示所測量代碼的實際執(zhí)行時間(CPU 時鐘周期)。
在測量開始和停止讀數(shù)之間的代碼執(zhí)行時間時,可能會發(fā)生中斷,所以每次執(zhí)行這個序列很可能會有不同的值。在這種情況下,可能希望在測量過程中禁用中斷,但是要清楚禁用中斷是暫時的,只用于測量。盡管如此,也許應(yīng)該把中斷的任務(wù)包括進(jìn)來,因為它們會影響到代碼的***執(zhí)行時間。
- Disable Interrupts;
- start = ARM_CM_DWT_CYCCNT;
- // Code to measure
- stop = ARM_CM_DWT_CYCCNT;
- Enable Interrupts;
- delta = stop – start;
如果所測代碼包含條件語句、循環(huán)或任何可能導(dǎo)致變化的東西,那么獲得的值可能不代表最壞情況下的執(zhí)行時間。為了糾正這個問題,需要添加一個峰值檢測器,如下圖所示。當(dāng)然,在進(jìn)行任何測量之前,需要將 max 聲明并初始化為最小值(即0)。
- start = ARM_CM_DWT_CYCCNT;
- // Code to measure
- stop = ARM_CM_DWT_CYCCNT;
- delta = stop – start;
- if (max < delta) {
- max = delta;
- }
同樣,知道最短執(zhí)行時間也是有趣且有用的 在進(jìn)行任何測量之前,只需要聲明和初始化***可能值(即0xFFFFFFFF)。下面是新的代碼: ``` tart = ARMCMDWT_CYCCNT;
- // Code to measure
- stop = ARMCMDWT_CYCCNT;
- delta = stop – start;
- if (max < delta) {
- max = delta;
- }
- if (min > delta) {
- min = delta;
} ``` 就像 Cortex-M4處理器和 Cortex-M7那樣,執(zhí)行時間還取決于CPU是否配備了緩存。如果系統(tǒng)中使用了指令或數(shù)據(jù)緩存,對同一段代碼的多重測量可能不一致。這時,可以考慮禁用緩存以測量最壞的情況。
大多數(shù)調(diào)試器允許顯示這些變量值。如果是這樣,則需要在全局范圍內(nèi)聲明顯示變量,以保留它們的值并允許實時監(jiān)控。不幸的是,這些值代表的是CPU時鐘周期,而且大多數(shù)調(diào)試器還不夠成熟,無法為了顯示目的而對變量進(jìn)行縮放。假設(shè)一個16兆赫的CPU時鐘速度,顯示70.19微秒比顯示1123個周期要方便得多。實際上還有一種更好的方法來顯示這些變量,這也提供了規(guī)模化能力,可以以一種更加可讀的形式看待它們。
經(jīng)過的時間模塊
當(dāng)然,可以將代碼片段嵌入到應(yīng)用程序中,但還可以可以使用一個簡單的模塊。 elapsedtime.c與elapsedtime.h,它僅由4個函數(shù)組成。
方法如下:
- 按照慣例,#include
- 在使用elapsedtime.c 中的其他函數(shù)之前,調(diào)用 elapsedtime_init()
- 通過設(shè)置"ELAPSEDTIMEMAX_SECTIONS"來定義時間測量結(jié)構(gòu)的***數(shù)目。這與用 stop/start代碼包裝的不同代碼段相對應(yīng)
- 調(diào)用elapsedtimestart()并傳遞要監(jiān)視的代碼片段的索引(即0 到ELAPSEDTIMEMAX_SECTIONS-1)
- 調(diào)用elapsedtimestop()并傳遞在運行時啟動時所使用的相同索引
- 如果調(diào)試器允許監(jiān)視變量(即當(dāng)目標(biāo)正在運行時) ,則可以顯示elapsedtimetbl[],并查看對應(yīng)索引的運行時間結(jié)構(gòu)
- 重復(fù)執(zhí)行步驟4到6,并將代碼置于最壞和***的情況下,以便ELAPSED_TIME數(shù)據(jù)結(jié)構(gòu)中的Min 和max 字段可以很好地表示所測量代碼片段的執(zhí)行時間
需要注意的是, 沒有在測量過程中禁用中斷,因為ISR可能會涉及到,也需要了解這會如何影響感知的執(zhí)行時間。
- void main (void)
- {
- // Some code
- elapsed_time_init(); // Iitialize the module
- // Some code
- }
- void MyCode (void)
- {
- // Some code here
- elapsed_time_start(0); // Start measurement of code snippet #0
- // Code being measured
- elapsed_time_stop(0); // Stop and
- // Some other code
- }
當(dāng)然,最小和***的執(zhí)行時間取決于測量的頻率,以及代碼是否分別受到***和最差條件的限制。
另外,沒有必要顯示起始字段,因為它只用于在測量開始時記錄DWT周期計數(shù)器的值,然而,啟動字段可以用來顯示出來。換句話說,當(dāng)看到這個值變化時,就會知道測量正在發(fā)生。
使用 uc / probe 的示例顯示
使用了elapsed_time.c 和 uc/probe,來測量一下代碼片段的執(zhí)行時間。
圖1| IAR 和 uc/probe 的樹視圖
圖1顯示了使用IAR的LiveWatch (左)和 uc / probe 的樹視圖(右)。截圖是在不同的時間拍攝的,是一個存儲不同代碼片段的測量值的數(shù)組。
可以將min/max/current分配給計量表和數(shù)字指示器,如圖2所示。CPU 運行在80mhz,這些值以微秒顯示,應(yīng)用了0.0125的縮放因子。左側(cè)的按鈕用于重置統(tǒng)計數(shù)據(jù),從而迫使重新計算最小值和***值。
圖2 | 使用uc/probe 的儀表顯示***執(zhí)行時間
Uc/probe 的一個強(qiáng)大特性是能夠與微軟的 Excel 對接,從而在電子表格中顯示這些值(實時) ,如圖3所示。
圖3 | 使用 Excel 顯示實時數(shù)據(jù)
小結(jié)
作為嵌入式開發(fā)人員,有許多工具可以用來測試和驗證設(shè)計。對于代碼執(zhí)行時間,可以很容易地使用 Cortex-M 處理器眾多特性中的一個,即DWT周期計數(shù)器。
uc/probe 提供了很多功能,允許使用計量表、儀表盤、數(shù)字指示器、 Excel界面或圖表來監(jiān)控應(yīng)用程序中的許多變量。通過內(nèi)置的示波器功能,一旦觸發(fā)條件滿足,還可以捕獲多達(dá)7個額外變量值。
附錄代碼
elapsed_time.c
- #include <stdint.h>
- #include <elapsed_time.h>
- /*
- ********************************************************************************
- * 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)
- /*
- ********************************************************************************
- * Data Structure
- ********************************************************************************
- */
- typedef struct elapsed_time {
- uint32_t start;
- uint32_t current;
- uint32_t max;
- uint32_t min;
- } ELAPSED_TIME;
- /*
- ********************************************************************************
- * STORAGE FOR ELAPSED TIME MEASUREMENTS
- ********************************************************************************
- */
- static ELAPSED_TIME elapsed_time_tbl[ELAPSED_TIME_MAX_SECTIONS];
- /*
- ********************************************************************************
- * 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) { // See if DWT is available
- ARM_CM_DEMCR |= 1 << 24; // Set bit 24
- ARM_CM_DWT_CYCCNT = 0;
- ARM_CM_DWT_CTRL |= 1 << 0; // Set bit 0
- }
- 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)
- {
- 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;
- p_tbl = &elapsed_time_tbl[i];
- p_tbl->current = stop - p_tbl->start;
- if (p_tbl->max < p_tbl->current) {
- p_tbl->max = p_tbl->current;
- }
- 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;
- p_tbl = &elapsed_time_tbl[i];
- p_tbl->start = 0;
- p_tbl->current = 0;
- p_tbl->min = 0xFFFFFFFF;
- p_tbl->max = 0;
- }
- elapsed_time.h
- /*
- ********************************************************************************
- * MODULE TO MEASURE EXECUTION TIME
- ********************************************************************************
- */
- /*
- ********************************************************************************
- * MAXIMUM NUMBER OF ELAPSED TIME MEASUREMENT SECTIONS
- ********************************************************************************
- */
- #define ELAPSED_TIME_MAX_SECTIONS 10
- /*
- ********************************************************************************
- * FUNCTION PROTOTYPES
- ********************************************************************************
- */
- void elapsed_time_clr (uint32_t i); // Clear measured values
- void elapsed_time_init (void); // Module initialization
- void elapsed_time_start (uint32_t i); // Start measurement
- void elapsed_time_stop (uint32_t i); // Stop measurement
參考文獻(xiàn)
https://www.micrium.com/ucprobe/about/
https://www.iar.com/iar-embedded-workbench/
https://www.arm.com/products/processors/cortex-m
【本文來自51CTO專欄作者“老曹”的原創(chuàng)文章,作者微信公眾號:喔家ArchiSelf,id:wrieless-com】