1.1 背景
在單片機(jī)的固件開發(fā)過程中,有的時(shí)候需要評估固件代碼的執(zhí)行性能,會對部分關(guān)鍵程序代碼的執(zhí)行時(shí)間進(jìn)行測量。通常會用到的測量程序執(zhí)行時(shí)間的方法是使用示波器進(jìn)行測量。一般步驟是借助單片機(jī)的某一個(gè)GPIO口,假設(shè)默認(rèn)情況下GPIO口置1;在需要測量的程序代碼開始處將GPIO口清0,然后執(zhí)行程序代碼段,在代碼段的終止處將GPIO口重新置1;示波器設(shè)置成邊沿觸發(fā)方式,抓取GPIO口從清0到重新置1的這段波形,然后用示波器卡出GPIO口下降沿到上升沿的這段時(shí)間,也就是程序代碼段的執(zhí)行時(shí)間。
以上方法的不足之處在于需要用到示波器,而且需要借用MCU的一個(gè)GPIO進(jìn)行輔助測量,靈活性也欠佳,實(shí)際使用不是太方便。那有沒有更簡便的測量方法呢?答案是肯定的,那就是使用MCU的定時(shí)器進(jìn)行程序執(zhí)行時(shí)間的測量。當(dāng)然,為了提高時(shí)間的測量精度,MCU需要使用外部晶振來為其提供工作主頻。下面就對該方法進(jìn)行詳細(xì)講解。該方法結(jié)合下面提到的開發(fā)板,可以達(dá)到10ns以內(nèi)的測量分辨率和1us以內(nèi)的測量精度。
1.2 測試平臺
這里使用的開發(fā)環(huán)境和相關(guā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
- 本文對應(yīng)的例程代碼鏈接如下。
https://download.csdn.net/download/goodrenze/85162425
1.3 使用STM32定時(shí)器測量程序執(zhí)行時(shí)間的方法詳解
這里就結(jié)合開發(fā)板STM32F429I-DISCO上的STM32F429ZI單片機(jī)來演示使用SysTick系統(tǒng)定時(shí)器測量程序代碼段執(zhí)行時(shí)間的實(shí)現(xiàn)方法。
使用SysTick系統(tǒng)定時(shí)器測量程序執(zhí)行時(shí)間之前,必須先確認(rèn)定時(shí)器的以下參數(shù)。
- 定時(shí)器的時(shí)鐘源頻率。
- 定時(shí)器的定時(shí)周期。
- 定時(shí)器的計(jì)數(shù)方向。
這里的代碼基于STM32F429I-DISCO開發(fā)板,該開發(fā)板的MCU外接8MHz的石英晶振,代碼使用該外部晶振經(jīng)內(nèi)部PLL倍頻后,產(chǎn)生168MHz的主頻供MCU使用。這里的SysTick系統(tǒng)定時(shí)器的時(shí)鐘源直接來自168MHz的主頻,對該頻率進(jìn)行計(jì)數(shù),所以每過1000 / 168 = 5.95238ns時(shí)間,定時(shí)器計(jì)數(shù)值就會加1。這里將SysTick定時(shí)器的定時(shí)周期設(shè)置成1ms,即每過1ms,SysTick定時(shí)器就會產(chǎn)生一次定時(shí)器中斷。另外,SysTick定時(shí)器是倒計(jì)數(shù)定時(shí)器,即其計(jì)數(shù)值是遞減的,當(dāng)計(jì)數(shù)值減到為0時(shí),繼續(xù)減1時(shí)會重新加載重裝載值并繼續(xù)計(jì)時(shí),同時(shí)產(chǎn)生定時(shí)器溢出中斷。
確定了以上參數(shù)之后,后面的代碼實(shí)現(xiàn)就非常簡單了,只需要實(shí)現(xiàn)以下的幾個(gè)功能函數(shù)皆可。
1)SysTick系統(tǒng)定時(shí)器初始化函數(shù)和中斷處理函數(shù)。用于配置該定時(shí)器的定時(shí)周期為1ms,打開定時(shí)器中斷并啟動定時(shí),同時(shí)實(shí)現(xiàn)對應(yīng)的中斷處理函數(shù)使定時(shí)器計(jì)數(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的官方代碼,調(diào)用的SysTick_Config()函數(shù)在“core_cm4.h”頭文件中有現(xiàn)成的實(shí)現(xiàn)
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
// SysTick系統(tǒng)定時(shí)器中斷入口函數(shù)
void SysTick_Handler(void)
{
HAL_IncTick();
}
// SysTick系統(tǒng)定時(shí)器中斷處理函數(shù),對uwTick值進(jìn)行累加
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
2)獲取起始時(shí)間的函數(shù)。該函數(shù)用于獲取SysTick系統(tǒng)定時(shí)器當(dāng)前的毫秒計(jì)數(shù)值,以及當(dāng)前的定時(shí)器計(jì)數(shù)值。程序代碼如下。參數(shù)p_pdwStartMs為獲取到的起始毫秒計(jì)數(shù)值,p_pdwStartNsTicks為獲取到的起始定時(shí)器計(jì)數(shù)值。
void vGetStartTime(uint32_t* p_pdwStartMs, uint32_t* p_pdwStartNsTicks)
{
*p_pdwStartMs = HAL_GetTick();
*p_pdwStartNsTicks = SysTick->VAL;
}
3)獲取時(shí)間間隔的函數(shù)。該函數(shù)用于獲取當(dāng)前時(shí)間相對于起始時(shí)間的時(shí)間間隔。程序代碼如下。參數(shù)p_dwStartMs為起始毫秒計(jì)數(shù)值,p_dwStartNsTicks為起始定時(shí)器計(jì)數(shù)值,p_pdwIntervalMs為當(dāng)前時(shí)間相對于p_dwStartMs的毫秒時(shí)間間隔,p_pdwIntervalNsTicks為當(dāng)前時(shí)間相對于p_dwStartNsTicks的定時(shí)器計(jì)數(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í)器。
// 如果當(dāng)前的定時(shí)器計(jì)數(shù)值比起始計(jì)數(shù)值要小,SysTick未發(fā)生相對起始時(shí)刻不足1ms的定時(shí)器中斷,所以ms計(jì)數(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;
}
// 如果當(dāng)前的定時(shí)器計(jì)數(shù)值比起始計(jì)數(shù)值要大,SysTick發(fā)生了相對起始時(shí)刻不足1ms的定時(shí)器中斷,所以ms計(jì)數(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í)間的演示例程。用于演示如何使用以上提到的相關(guān)函數(shù)來測量程序代碼段的執(zhí)行時(shí)間。
int main(void)
{
uint32_t count = 0;
uint32_t l_dwStartMs, l_dwIntervalMs;
uint32_t l_dwStartNsTicks, l_dwIntervalNsTicks;
float l_fUs; // 微秒時(shí)間
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í)行時(shí)間
1.4 結(jié)語
通過以上提到的相關(guān)函數(shù),可以很方便地實(shí)現(xiàn)程序執(zhí)行時(shí)間的測量,而且可以在幾乎任何地方使用(中斷內(nèi)部使用需注意中斷優(yōu)先級的影響)。另外,如果結(jié)合串口打印調(diào)試信息的功能,可以直接將測量到的執(zhí)行時(shí)間直接打印輸出,方便查看。本文提到的執(zhí)行時(shí)間測量方法無需使用示波器,也不需要借用MCU的GPIO口進(jìn)行輔助測量,使用起來非常方便。
-
單片機(jī)
+關(guān)注
關(guān)注
6037文章
44558瀏覽量
635355 -
示波器
+關(guān)注
關(guān)注
113文章
6246瀏覽量
184963 -
GPIO
+關(guān)注
關(guān)注
16文章
1204瀏覽量
52104 -
程序執(zhí)行時(shí)間
+關(guān)注
關(guān)注
0文章
2瀏覽量
6699
發(fā)布評論請先 登錄
相關(guān)推薦
評論