1.1 背景
在單片機(jī)的固件開(kāi)發(fā)過(guò)程中,有的時(shí)候需要評(píng)估固件代碼的執(zhí)行性能,會(huì)對(duì)部分關(guān)鍵程序代碼的執(zhí)行時(shí)間進(jìn)行測(cè)量。通常會(huì)用到的測(cè)量程序執(zhí)行時(shí)間的方法是使用示波器進(jìn)行測(cè)量。一般步驟是借助單片機(jī)的某一個(gè)GPIO口,假設(shè)默認(rèn)情況下GPIO口置1;在需要測(cè)量的程序代碼開(kāi)始處將GPIO口清0,然后執(zhí)行程序代碼段,在代碼段的終止處將GPIO口重新置1;示波器設(shè)置成邊沿觸發(fā)方式,抓取GPIO口從清0到重新置1的這段波形,然后用示波器卡出GPIO口下降沿到上升沿的這段時(shí)間,也就是程序代碼段的執(zhí)行時(shí)間。
以上方法的不足之處在于需要用到示波器,而且需要借用MCU的一個(gè)GPIO進(jìn)行輔助測(cè)量,靈活性也欠佳,實(shí)際使用不是太方便。那有沒(méi)有更簡(jiǎn)便的測(cè)量方法呢?答案是肯定的,那就是使用MCU的定時(shí)器進(jìn)行程序執(zhí)行時(shí)間的測(cè)量。當(dāng)然,為了提高時(shí)間的測(cè)量精度,MCU需要使用外部晶振來(lái)為其提供工作主頻。下面就對(duì)該方法進(jìn)行詳細(xì)講解。該方法結(jié)合下面提到的開(kāi)發(fā)板,可以達(dá)到10ns以?xún)?nèi)的測(cè)量分辨率和1us以?xún)?nèi)的測(cè)量精度。
1.2 測(cè)試平臺(tái)
這里使用的開(kāi)發(fā)環(huán)境和相關(guān)硬件如下。
- 操作系統(tǒng):Ubuntu 20.04.2 LTS x86_64(使用uname -a命令查看)
- 集成開(kāi)發(fā)環(huán)境(IDE):Eclipse IDE for Embedded C/C++ Developers,Version: 2021-06 (4.20.0)
- 硬件開(kāi)發(fā)板:STM32F429I-DISCO
- 本文對(duì)應(yīng)的例程代碼鏈接如下。
https://download.csdn.net/download/goodrenze/85162425
1.3 使用STM32定時(shí)器測(cè)量程序執(zhí)行時(shí)間的方法詳解
這里就結(jié)合開(kāi)發(fā)板STM32F429I-DISCO上的STM32F429ZI單片機(jī)來(lái)演示使用SysTick系統(tǒng)定時(shí)器測(cè)量程序代碼段執(zhí)行時(shí)間的實(shí)現(xiàn)方法。
使用SysTick系統(tǒng)定時(shí)器測(cè)量程序執(zhí)行時(shí)間之前,必須先確認(rèn)定時(shí)器的以下參數(shù)。
- 定時(shí)器的時(shí)鐘源頻率。
- 定時(shí)器的定時(shí)周期。
- 定時(shí)器的計(jì)數(shù)方向。
這里的代碼基于STM32F429I-DISCO開(kāi)發(fā)板,該開(kāi)發(fā)板的MCU外接8MHz的石英晶振,代碼使用該外部晶振經(jīng)內(nèi)部PLL倍頻后,產(chǎn)生168MHz的主頻供MCU使用。這里的SysTick系統(tǒng)定時(shí)器的時(shí)鐘源直接來(lái)自168MHz的主頻,對(duì)該頻率進(jìn)行計(jì)數(shù),所以每過(guò)1000 / 168 = 5.95238ns時(shí)間,定時(shí)器計(jì)數(shù)值就會(huì)加1。這里將SysTick定時(shí)器的定時(shí)周期設(shè)置成1ms,即每過(guò)1ms,SysTick定時(shí)器就會(huì)產(chǎn)生一次定時(shí)器中斷。另外,SysTick定時(shí)器是倒計(jì)數(shù)定時(shí)器,即其計(jì)數(shù)值是遞減的,當(dāng)計(jì)數(shù)值減到為0時(shí),繼續(xù)減1時(shí)會(huì)重新加載重裝載值并繼續(xù)計(jì)時(shí),同時(shí)產(chǎn)生定時(shí)器溢出中斷。
確定了以上參數(shù)之后,后面的代碼實(shí)現(xiàn)就非常簡(jiǎn)單了,只需要實(shí)現(xiàn)以下的幾個(gè)功能函數(shù)皆可。
1)SysTick系統(tǒng)定時(shí)器初始化函數(shù)和中斷處理函數(shù)。用于配置該定時(shí)器的定時(shí)周期為1ms,打開(kāi)定時(shí)器中斷并啟動(dòng)定時(shí),同時(shí)實(shí)現(xiàn)對(duì)應(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ù),對(duì)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í)間相對(duì)于起始時(shí)間的時(shí)間間隔。程序代碼如下。參數(shù)p_dwStartMs為起始毫秒計(jì)數(shù)值,p_dwStartNsTicks為起始定時(shí)器計(jì)數(shù)值,p_pdwIntervalMs為當(dāng)前時(shí)間相對(duì)于p_dwStartMs的毫秒時(shí)間間隔,p_pdwIntervalNsTicks為當(dāng)前時(shí)間相對(duì)于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ā)生相對(duì)起始時(shí)刻不足1ms的定時(shí)器中斷,所以ms計(jì)數(shù)無(wú)需額外減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ā)生了相對(duì)起始時(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ù)來(lái)測(cè)量程序代碼段的執(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é)語(yǔ)
通過(guò)以上提到的相關(guān)函數(shù),可以很方便地實(shí)現(xiàn)程序執(zhí)行時(shí)間的測(cè)量,而且可以在幾乎任何地方使用(中斷內(nèi)部使用需注意中斷優(yōu)先級(jí)的影響)。另外,如果結(jié)合串口打印調(diào)試信息的功能,可以直接將測(cè)量到的執(zhí)行時(shí)間直接打印輸出,方便查看。本文提到的執(zhí)行時(shí)間測(cè)量方法無(wú)需使用示波器,也不需要借用MCU的GPIO口進(jìn)行輔助測(cè)量,使用起來(lái)非常方便。
-
單片機(jī)
+關(guān)注
關(guān)注
6043文章
44623瀏覽量
638783 -
示波器
+關(guān)注
關(guān)注
113文章
6283瀏覽量
185891 -
GPIO
+關(guān)注
關(guān)注
16文章
1217瀏覽量
52411 -
程序執(zhí)行時(shí)間
+關(guān)注
關(guān)注
0文章
2瀏覽量
6709
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論