1.1 背景
在單片機的固件開發(fā)過程中,有的時候需要評估固件代碼的執(zhí)行性能,會對部分關鍵程序代碼的執(zhí)行時間進行測量。通常會用到的測量程序執(zhí)行時間的方法是使用示波器進行測量。一般步驟是借助單片機的某一個GPIO口,假設默認情況下GPIO口置1;在需要測量的程序代碼開始處將GPIO口清0,然后執(zhí)行程序代碼段,在代碼段的終止處將GPIO口重新置1;示波器設置成邊沿觸發(fā)方式,抓取GPIO口從清0到重新置1的這段波形,然后用示波器卡出GPIO口下降沿到上升沿的這段時間,也就是程序代碼段的執(zhí)行時間。
以上方法的不足之處在于需要用到示波器,而且需要借用MCU的一個GPIO進行輔助測量,靈活性也欠佳,實際使用不是太方便。那有沒有更簡便的測量方法呢?答案是肯定的,那就是使用MCU的定時器進行程序執(zhí)行時間的測量。當然,為了提高時間的測量精度,MCU需要使用外部晶振來為其提供工作主頻。下面就對該方法進行詳細講解。該方法結合下面提到的開發(fā)板,可以達到10ns以內的測量分辨率和1us以內的測量精度。
1.2 測試平臺
這里使用的開發(fā)環(huán)境和相關硬件如下。
- 操作系統(tǒng):Ubuntu 20.04.2 LTS x86_64(使用uname -a命令查看)
- 集成開發(fā)環(huán)境(IDE):Eclipse IDE for Embedded C/C++ Developers,Version: 2021-06 (4.20.0)
- 硬件開發(fā)板:STM32F429I-DISCO
- 本文對應的例程代碼鏈接如下。
https://download.csdn.net/download/goodrenze/85162425
1.3 使用STM32定時器測量程序執(zhí)行時間的方法詳解
這里就結合開發(fā)板STM32F429I-DISCO上的STM32F429ZI單片機來演示使用SysTick系統(tǒng)定時器測量程序代碼段執(zhí)行時間的實現(xiàn)方法。
使用SysTick系統(tǒng)定時器測量程序執(zhí)行時間之前,必須先確認定時器的以下參數(shù)。
- 定時器的時鐘源頻率。
- 定時器的定時周期。
- 定時器的計數(shù)方向。
這里的代碼基于STM32F429I-DISCO開發(fā)板,該開發(fā)板的MCU外接8MHz的石英晶振,代碼使用該外部晶振經內部PLL倍頻后,產生168MHz的主頻供MCU使用。這里的SysTick系統(tǒng)定時器的時鐘源直接來自168MHz的主頻,對該頻率進行計數(shù),所以每過1000 / 168 = 5.95238ns時間,定時器計數(shù)值就會加1。這里將SysTick定時器的定時周期設置成1ms,即每過1ms,SysTick定時器就會產生一次定時器中斷。另外,SysTick定時器是倒計數(shù)定時器,即其計數(shù)值是遞減的,當計數(shù)值減到為0時,繼續(xù)減1時會重新加載重裝載值并繼續(xù)計時,同時產生定時器溢出中斷。
確定了以上參數(shù)之后,后面的代碼實現(xiàn)就非常簡單了,只需要實現(xiàn)以下的幾個功能函數(shù)皆可。
1)SysTick系統(tǒng)定時器初始化函數(shù)和中斷處理函數(shù)。用于配置該定時器的定時周期為1ms,打開定時器中斷并啟動定時,同時實現(xiàn)對應的中斷處理函數(shù)使定時器計數(shù)值累加。程序代碼如下。
// 該函數(shù)為STM32的官方代碼
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
// 該函數(shù)為STM32的官方代碼,調用的SysTick_Config()函數(shù)在“core_cm4.h”頭文件中有現(xiàn)成的實現(xiàn)
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
// SysTick系統(tǒng)定時器中斷入口函數(shù)
void SysTick_Handler(void)
{
HAL_IncTick();
}
// SysTick系統(tǒng)定時器中斷處理函數(shù),對uwTick值進行累加
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
2)獲取起始時間的函數(shù)。該函數(shù)用于獲取SysTick系統(tǒng)定時器當前的毫秒計數(shù)值,以及當前的定時器計數(shù)值。程序代碼如下。參數(shù)p_pdwStartMs為獲取到的起始毫秒計數(shù)值,p_pdwStartNsTicks為獲取到的起始定時器計數(shù)值。
void vGetStartTime(uint32_t* p_pdwStartMs, uint32_t* p_pdwStartNsTicks)
{
*p_pdwStartMs = HAL_GetTick();
*p_pdwStartNsTicks = SysTick->VAL;
}
3)獲取時間間隔的函數(shù)。該函數(shù)用于獲取當前時間相對于起始時間的時間間隔。程序代碼如下。參數(shù)p_dwStartMs為起始毫秒計數(shù)值,p_dwStartNsTicks為起始定時器計數(shù)值,p_pdwIntervalMs為當前時間相對于p_dwStartMs的毫秒時間間隔,p_pdwIntervalNsTicks為當前時間相對于p_dwStartNsTicks的定時器計數(shù)間隔。
void vGetIntervalTime(uint32_t p_dwStartMs, uint32_t p_dwStartNsTicks, uint32_t* p_pdwIntervalMs, uint32_t* p_pdwIntervalNsTicks)
{
uint32_t l_dwCurMs = HAL_GetTick();
uint32_t l_dwCurNsTicks = SysTick->VAL;
uint32_t l_dwReloadValue = SysTick->LOAD;
// STM32F429ZI的定時器為倒數(shù)定時器。
// 如果當前的定時器計數(shù)值比起始計數(shù)值要小,SysTick未發(fā)生相對起始時刻不足1ms的定時器中斷,所以ms計數(shù)無需額外減1
if(l_dwCurNsTicks <= p_dwStartNsTicks)
{
if(l_dwCurMs >= p_dwStartMs)
{
*p_pdwIntervalMs = l_dwCurMs - p_dwStartMs;
}
else
{
*p_pdwIntervalMs = ~(p_dwStartMs - l_dwCurMs) + 1;
}
*p_pdwIntervalNsTicks = p_dwStartNsTicks - l_dwCurNsTicks;
}
// 如果當前的定時器計數(shù)值比起始計數(shù)值要大,SysTick發(fā)生了相對起始時刻不足1ms的定時器中斷,所以ms計數(shù)需要額外減1
else
{
if(l_dwCurMs >= p_dwStartMs)
{
*p_pdwIntervalMs = l_dwCurMs - p_dwStartMs - 1;
}
else
{
*p_pdwIntervalMs = ~(p_dwStartMs - l_dwCurMs);
}
*p_pdwIntervalNsTicks = p_dwStartNsTicks + (l_dwReloadValue - l_dwCurNsTicks) + 1;
}
}
4)獲取程序代碼段執(zhí)行時間的演示例程。用于演示如何使用以上提到的相關函數(shù)來測量程序代碼段的執(zhí)行時間。
int main(void)
{
uint32_t count = 0;
uint32_t l_dwStartMs, l_dwIntervalMs;
uint32_t l_dwStartNsTicks, l_dwIntervalNsTicks;
float l_fUs; // 微秒時間
HAL_Init();
/* Configure the system clock to 168 MHz */
SystemClock_Config();
BSP_LED_Init(LED3);
BSP_LED_Init(LED4);
vGetStartTime(&l_dwStartMs, &l_dwStartNsTicks);
#if 1
while (1)
{
if (count == 0x3fffff)
{
BSP_LED_Toggle(LED3);
BSP_LED_Toggle(LED4);
count = 0;
break;
}
count++;
}
#else
vDelayUs(1000);
#endif
vGetIntervalTime(l_dwStartMs, l_dwStartNsTicks, &l_dwIntervalMs, &l_dwIntervalNsTicks);
l_fUs = l_dwIntervalMs * 1000 + l_dwIntervalNsTicks * NS_PER_SYS_TICK / 1000.0f;
while(1);
}
圖1 以上演示例程代碼段的執(zhí)行時間
1.4 結語
通過以上提到的相關函數(shù),可以很方便地實現(xiàn)程序執(zhí)行時間的測量,而且可以在幾乎任何地方使用(中斷內部使用需注意中斷優(yōu)先級的影響)。另外,如果結合串口打印調試信息的功能,可以直接將測量到的執(zhí)行時間直接打印輸出,方便查看。本文提到的執(zhí)行時間測量方法無需使用示波器,也不需要借用MCU的GPIO口進行輔助測量,使用起來非常方便。
-
單片機
+關注
關注
6030文章
44489瀏覽量
631994 -
示波器
+關注
關注
113文章
6164瀏覽量
184316 -
GPIO
+關注
關注
16文章
1188瀏覽量
51836 -
程序執(zhí)行時間
+關注
關注
0文章
2瀏覽量
6691
發(fā)布評論請先 登錄
相關推薦
評論