單片機中有很多延時的實現方式,這里參考了魚鷹談單片機的,安福萊的原子的等網上信息,做一個整理。更加細節可以參考魚鷹的文章,很詳細。
1、匯編延時,nop指令,這個51當中就有了,332位單片機未驗證也不想找了。一般不用,屬於死等方式。
2、軟件延時,這個方式就是for循環,屬於死等方式,這個方式延時不太准確,nop不用。
3、systick定時器的方式,這個是原子或野火中常用到的,時間延時是基本上准確的,但是也屬於死等方式。
當然,systick有中斷的方式的,那么基本上是1ms的定時中斷,我們可以在裸機的HAL庫中重新寫systick定時中斷回調函數,而且hal_delay也是使用的這個systick的。其實可以用dwt來重寫,因為hal庫是若定義的。
索性把systick弄弄明白:
/**
* @brief This function provides minimum delay (in milliseconds) based
* on variable incremented.
* @note In the default implementation , SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals where uwTick
* is incremented.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @param Delay specifies the delay time length, in milliseconds.
* @retval None
*/
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick(); //首先得到基准的時刻
uint32_t wait = Delay; //獲取延時值
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY) //如果小於0xFFFFFFFFU 32位變量
{
wait += (uint32_t)(uwTickFreq); //遞增1,根據uwTickFreq定義來,默認是1ms,也就是1 HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT; /* 1KHz */
}
while ((HAL_GetTick() - tickstart) < wait) //(當前的值)-(過去的基准值)< 延時時間 ,就死等,其實就是一種死等的方式了,
{
}
}
uwTick這個變量是在SysTick ISR中每1ms中遞增的,
/**
* @brief This function is called to increment a global variable "uwTick"
* used as application time base.
* @note In the default implementation, this variable is incremented each 1ms
* in SysTick ISR.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval None
*/
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
Src\stm32f1xx_it.c函數中的定時中如下:
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
HAL_SYSTICK_IRQHandler();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
/**
* @brief Provides a tick value in millisecond.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @retval tick value
*/
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
小結起來就是,cubemx會把systick開啟1ms的定時中斷,而且uwTick這個變量會在定時中斷中每1ms遞增一個。HAL_Delay的注釋如上所示。
因此,在裸機工程中,systick可以使用死等的方式延時,也可以定時中斷的方式延時。具體問題具體分析
4、dwt數據觀察點與跟蹤
這個和systick一樣是cm3內核自帶的,cm4也有,因此可以直接使能拿來使用。
初始化后就可以使用,而且是可重入的。這個在魚鷹的文章有詳細的分析,但是還是屬於死等的延時方式。安福來有移植好的文件,傑傑的公眾號也有寫過一篇《精確延時ns的文章》,這個方式的延時精度相當高了。這個計數器是主時鍾每震盪一次,就增加一次,stm32F103的72Mhz,那么精度1/72000000,當然實際上不可能這么精確,畢竟代碼執行需要時間,但是1us這個級別肯定是足夠了。
DWT中有剩余的計數器,它們典型地用於應用程序代碼的“性能速寫”(profiling)。可以編程它們,讓它們在計數器溢出時發出事件(以跟蹤數據包的形式)。最典型地,就是使用CYCCNT寄存器來測量執行某個任務所花的周期數,這也可以用作時間基准相關的目的(操作系統中統計 CPU 使用率可以用到它)。————《權威指南》原話
具體的可以參考安福萊的論壇:http://www.armbbs.cn/forum.php?mod=viewthread&tid=89128&highlight=dwtDWT
《實現一個精確微秒延遲的參考例程 》
而且,keil調試的時候,也是使用dwt來計算運行時間的。可以參考魚鷹談單片機的文章。
5、stm32的基本定時器作為延時,這個在51單片機中就已經是在熟悉不過了,其中基本上tim6、7兩個都只有定時功能,於是可以拿來做是定時中斷,其實systick中斷也和這個類似,只是systick我們基本上是1ms的,不怎們修改,基本定時器通常可以修改定時周期。分頻器+周期的設置,就可以達到我們想要的定時時長。
通常的方式:定時到了,設置一個標志位,在主循環中,判斷標志位是否置位,有就執行,並清零標志位。這種方式基本上沒多大問題,但是仔細分析下,假如定時的頻率是20ms,但是main函數中的查詢頻率比較慢,30ms一次。理想情況下,到了60ms,主函數查詢了2次,定時器中斷觸發3次,剛剛好差不多重疊了,但是注意,這是裸機,順序執行,不會發生可沖入的問題。那么主函數中,恰恰到了這個子函數了,立馬定時中斷也觸發了,那么回到主函數中,就執行了,也就是定時中斷觸發了3次,但是主函數值執行了2次。這種情況比較極端了。需要重新設計主函數,重新設定定時時長。因此這種情況,我們要好好規划程序的框架。main函數執行一次的時間要短於定時到時長。因此在main函數中,盡量不要有死等的延時,盡量使用定時器來規划。這樣單片機效率高,同時可維護。
6、使用定時器實現單次延時
7、刪除標志位來實現定時,這兩種方式,可以參考魚鷹的文章,這里不細說了。這里僅做拋磚引玉。
8、合作時調度器,這個其實就是軟件定時器的方式來實現,網上有很多模板,其實這種方式是可維護性比較高,但是缺點比較多,限制條件比較多,具體參考安福萊的ucos教程,這里摘要如下
限制一、只有一個中斷的原則。
限制二、任務重疊的問題。
限制三、使用合作式調度器的應用程序有一個重要的要求:任務的運行時間 < 時標間隔,這個要求非常重要,而且實現起來額不容易,特別是程序中含有一些無法確定時間的函數,
其實對於裸機程度,沒有其他的隱蔽的東西,自己好好分析還是可以理清程序運行的細節及時序關系。遵守的原則:1、盡量不要在主程序中使用死等的延時,二、每個子程序(也可以叫任務吧)的查詢頻率要大於主程序運行的時間。比如:ad采樣,100ms采樣一次,那么,主程序一定要在100ms以內執行完畢。
想說的這么多了,裸機程序=定時器+狀態機。死等的延時可以是us級別的,時序性較高的地方,大的延時就使用定時器。