最近一工程師向我反饋了一個問題,他使用ARM Cortex-M0+的MCU,在使用延時函數(shù)std_delayms延時1s時,如果勾選了KEIL中的Use MicroLIB會導致延時有5%的計時偏差,不勾選的話誤差只有1%。
首先進行問題的復現(xiàn),在程序中while(1)里調(diào)用std_delayms(1000),通過串口發(fā)送一個字符,在上位機上進行接收,可以清楚的看到勾選微庫之后誤差確實明顯增大,每次偏差大約為50ms。
這個現(xiàn)象看著比較奇怪,剛開始時也困擾了我,那么到底是什么原因呢?
首先要看延時函數(shù)是如何實現(xiàn)的,
std_delayms函數(shù)是通過systick定時器來實現(xiàn),采用的是阻塞的方式,實現(xiàn)代碼如下:
/**
* @brief us級延時函數(shù)(阻塞模式)
* @param count 計數(shù)周期
* @note 延時函數(shù)最大值受限于SysTick重載值寄存器的最大值0xFFFFFF(16777216)
* @note 該函數(shù)為weak函數(shù),用戶可選擇其他定時器重新定義實現(xiàn)該函數(shù)
* @retval 無
*/
__weak void std_delayus(uint32_t count)
{
count = STD_DELAY_US * count;
count = count > 16777216 ? 16777216 : count;
SysTick->LOAD = count - 1;
SysTick->VAL = 0;
while(!((SysTick->CTRL >> 16) & 0x1));
}
/**
* @brief us級延時函數(shù)(阻塞模式)
* @param count 計數(shù)周期
* @note 延時函數(shù)最大值受限于SysTick重載值寄存器的最大值0xFFFFFF(16777216)
* @note 該函數(shù)為weak函數(shù),用戶可選擇其他定時器重新定義實現(xiàn)該函數(shù)
* @retval 無
*/
__weak void std_delayus(uint32_t count)
{
count = STD_DELAY_US * count;
count = count > 16777216 ? 16777216 : count;
SysTick->LOAD = count - 1;
SysTick->VAL = 0;
while(!((SysTick->CTRL >> 16) & 0x1));
}
#define?STD_DELAY_US??????????????(SystemCoreClock/1000000)????????????/**<?STD中用于計時的基礎值,與計時周期有關?*/?
看起來似乎沒什么問題,就是調(diào)用1000次std_delayus(1000)函數(shù),std_delayus(1000)一次延時1ms,累計實現(xiàn)1s的延時。
勾選MicroLIB與否肯定不會影響systick計數(shù)器本身的計時,那么唯一能影響的就是其它為數(shù)不多的幾條賦值語句了。
最后定位到是SystemCoreClock/1000000 這個除法運算導致的,使用微庫后會讓除法運算執(zhí)行的更慢。
我們測一下執(zhí)行一次除法運算需要的時間。本次測試中,系統(tǒng)時鐘為8Mhz,通過GPIO翻轉(zhuǎn)的方式來測
LED1_OFF();
divide_test(1);
LED1_ON();
int divide_test(uint32_t loop )
{
????uint32_t?count;
while(loop--)
{
count = SystemCoreClock/1000000;
????}?????
return count;
}
可以看到勾選微庫后,執(zhí)行時間為49us
不勾選微庫,執(zhí)行時間為9us
兩者足足相差了好幾倍,勾選微庫后,執(zhí)行1000次除法運算時間大約就是50ms,正好和之前的現(xiàn)象就對應上了。
如果想讓定時更加準確,可以不使用除法,這樣誤差會更小,實測只有幾個ms的偏差。
原始代碼,每次調(diào)用std_delayus函數(shù)都進行一次除法運算是為了保證系統(tǒng)主頻會發(fā)生變化時依然能正確計時,實際使用中在已知特定主頻的情況下,可以不用每次都進行除法運算,只在主頻變化的情況下更新一次即可。
MicroLib 和 standard C library的主要區(qū)別有:
來源于:[MicroLib MDK-ARM Library (keil.com)](https://www.keil.com/arm/microlib.asp#:~:text=The primary differences between MicroLib,using the ARM standard library.)
MicroLIB更加詳細的介紹,大家可以在KEIL的Help里查看:
上面提到MicroLIB雖然減少code size,但是有些函數(shù)會執(zhí)行的更慢,這次遇到的問題恰恰就對應這一條。這其實對應的用時間換空間的概念,對于某些運算,MicroLIB執(zhí)行時間變長了,換回的好處是空間占用更小。實際使用中需要在空間和時間上做一定的取舍。
ARM Cortex-M0+沒有硬件除法器,除法是相對復雜的操作,相比加法、減法和乘法,需要更多的CPU周期才能完成。
測試一段代碼所需要的CPU周期,這里墻裂推薦傻孩子的超級嵌入式系統(tǒng)“性能/時間”工具箱perf_counter,在Github上已開源https://github.com/GorgonMeducer/perf_counter.git ,我在這里用上了,可以很方便的測試。具體使用大家可以參考他的【喂到嘴邊了的模塊】超級嵌入式系統(tǒng)“性能/時間”工具箱
比如測試這個函數(shù)的CPU周期數(shù),直接這么調(diào)用就行
??__cycleof__()?{
?? divide_test(1);??
??}
使用微庫時,可以看到占用了414個CPU周期,對應時間414/8000000=51.7us
而不用微庫時,可以看到占用了96個CPU周期,對應時間96/8000000=12us
所以就是這個問題的完整分析,希望對大家有所幫助。