关于SYSTICK延时函数的两个小疑问
我們知道,STM32庫函數里通常使用來自內核的系統定時器SYSTICK作為時基,實現計數延時。一般來講,ST公司提供的庫函數里將SYSTICK定時器配置為1ms的定時器中斷,每產生1ms中斷則相關中斷事件計數變量加一。具體應用中我們經常會調用那個Delay()函數以實現計數定時,做延時或超時管理。
有人在閱讀ST提供的LL庫里的這個延時函數時,發現代碼里對延時參數總是做了個加1操作,代碼如下:
上圖中紅色代碼,程序進來后就對給定的延時參數做了個加1操作,這不將1ms延時變成2ms了嗎?
其實,這個地方已經有做了注釋,就是為了保證有1個最小的延時等待,函數參數給定1ms的延時,經過這樣加1操作后就能保證至少1ms的實際延時,極限情況的確可能達到2ms,但不會超過2ms。但如果這里不做加1操作,函數參數給定1ms的延時,實際延時最短的情況的極限是0,最長的極限是1ms.
我們不妨借助下面圖形一起看看,可能更直觀點。
假設調用Delay函數時給定延時參數為3,首先讀到的滴答中斷計數值為5,顯然在上圖的綠色區除了兩個邊界點外的任一時刻讀到的數據都是5。如果先不對延時參數加1,那么實際延時就落在(2ms,3ms)之間;如果對延時參數先加1,那么實際延時就落在(3ms,4ms)之間。換言之,擬定延時n個ms,若做加一操作,實際延時落在(n,n+1)ms開區間;若不做加1操作,實際延時落在(n-1,n)ms開區間。
那這個的加1操作是必須的嗎?我不這么認為,加不加1沒有原則性問題。當我們把該定時器的溢出中斷時間定好后,精度就定了,以1ms中斷為例,我們就得接受1ms內的誤差。這個地方知道怎么回事就好,畢竟中斷代碼的編寫、延時參數的擬定都是我們自己定的。
上面代碼是ST提供的LL庫里關于那個延時函數的寫法,看看HAL庫的寫法是不是一樣的,不妨順便看看。【下面截圖便是Hal_Delay()函數代碼】
顯然,在HAL庫里的Delay函數里進來后也首先做了加1操作,跟LL庫的做法一樣。
上面HAL庫延時函數代碼里的Tickstart是調用Delay()函數時首次讀到的滴答中斷計數變量的值,wait是延時值,Hal_GetTick()函數動態讀取到的滴答中斷計數變量的值【uwTick】。對于上面while語句里面的uwTick與Tickstart的比較的寫法,有人可能會產生疑問。當Hal_GetTick()函數讀取到的滴答中斷累加值uwTick小于或等于Tickstart時還能得出正確的比較結果嗎?
比方像下面這種情況,某無符號整型變量循環累加計數,現在需求得Val2與Val1的差值以測試信號寬度。
假定上面數據的計數周期為T,且Val2與Val1的間隔不超過1T。此時二者的間隔用數學表達式就是:Val2+T-Val1。這里的代碼似乎沒有考慮這個溢出周期的問題?可是那個uwTick完全可能出現小于tickstart的情況啊。
這是怎么回事呢?我也一度很懷疑這個寫法,當時也做了些簡單測試。對于測試結果,現在回想起來當時也沒做冷靜的分析而做出了錯誤的判斷。
回過頭來想,按理說這個代碼不該有問題。畢竟是老代碼了,何況之前也沒有人反映這個地方有問題。難道哪里誤會了?
因為當前代碼用到的幾個變量都是32位無符號數,剛開始測試時也是基于32位數做的,結果動不動就很大,不容易判斷正誤。我干脆將相關數據全部改為8位無符號數,這樣測試起來就方便些。
我先準備了下面的0~255的循環計數表格。顯然計數溢出周期為256。
現在要在任意一個不大于1個計數周期內求取任意2個數據之間隔,即求這兩個無符號數之差。假設計算上圖中第1行數據5與第2行數據3的間隔值。這里用StartValue表示5,NowValue表示3,Result表示結果,都定義為8位無符號類型。
參照庫代碼寫法,代碼該這樣寫:Result1= NowValue – StartValue;
按照我的想法,代碼該這樣寫: Result2= NowValue + 256 – StartValue;
經過測試,結果是一樣的,也不難驗證是正確的。
對于這個結果,意外,但也意料之中。因為我在添加那個256周期值時心里就在犯嘀咕,有所警覺了。我現在是兩個8位數的加減,加個256按理是不會影響結果的,這樣推理下來,兩行代碼的結果就本該一樣。
我們再回頭看看上面HAL庫函數中延時函數:
這里tickstart和wait以及函數HAL_GetTick()讀到的變量是uwTick值都是32位無符號變量,顯然,uwTick與tickstart的延時間隔也不會超過0xfffffff。
如果uwTick小于或等于tickstart,通常我們需要加上溢出周期,這個周期值等于32位數據模值,用16進制表示就是1后面跟8個0。經過測試發現下面代碼的兩種寫法在微處理器的計算結果也是一樣的。【uwTick對應下面截圖代碼中的NowValue, uwTick對應StartValue】
因為都定義成32位數據寬度了,結果很大,但是正確的。很明顯上圖中A行代碼更簡潔。看來,庫代碼這里這樣寫是沒問題的。
上面兩次測試除了位寬差別外,其它都一樣。怎么感覺求算不超過1個計量周期寬度內任意兩個數據的間隔值不用考慮那個溢出周期呢?
其實,只要是任意兩個相同無符號數據類型數據做減法,且數據所能計量的最大值為其滿量程值【比如8位對應255,16位對應65535】,求算任意一個周期內兩個數據的間隔值時,使用上面A行代碼寫法是沒有問題的,但這并不等于說不用考慮溢出周期的問題。
以現在測試為例。A行程序代碼在微處理里完成的就是數學表達式NowValue?加上?溢出周期值?減去?StartValue的結果,我們不妨將A行代碼的計算過程做個翻譯解讀就看得出來。只是B行代碼里再多加1個溢出周期值也不影響計算結果。【注:前面兩個測試的溢出周期值剛好等于各自所用數據寬度的模值。】
但實際應用中,數據變量的計數周期往往并不等于所用數據寬度的模值。此時前面A行代碼的寫法就行不通了,而要用B行代碼寫法。即A行代碼寫法只能用在特定場合,B行代碼寫法具有普適性。
我們不妨看看下面表格。這個表格跟前面0~255的表格類似,只是換成由一系列0~99的數據周期性排列而成。
同樣,我們求第一行的數據5與第2行的數據3之間的間隔數,這里的數據都定義成uint8類型。顯然這里計數周期為100,而非256,此時的代碼就不能采用前面A行代碼寫法。
我們可以看看驗證結果:【顯然第一種寫法的結果是錯的,不難判斷正確結果是98】
我們繼續看一個實戰性的例子,看一個有關使用TIMER測量脈沖寬度的例子。
下圖斜線表示計數器計數方向,現在利用TIMER的捕獲功能測量下面一段脈沖的高電平寬度。
假設TIMER為16位,計數器向上單向計數。于脈沖的上沿和下沿事件分別捕捉到兩個計數值Val1和Val2,二者都定義成16位無符號整型變量。
結合圖形可以看出,脈沖雖然跨越了溢出點,但脈沖寬度沒有超過1個計數周期,Val2的值小于Val1。此時我們求算t1的寬度,正常都會這樣寫:
t1 = Val2+ ARR+1 - Val1;
當然,如果說你把ARR剛好設置為0xfffff, 此時程序代碼就可以簡化成 ?t1 = Val2 - Val1。
OK,關于STM32片內Systick定時器延時中斷應用的兩個小疑問就聊到這里。問題雖小,但可以牽扯出不少東西供探究。。
????????中秋快樂!
***********************************
往期話題閱讀鏈接:
1、如何在CubeIDE環境下查看或生成匯編文件
2、兩份基于STM32做FDCAN開發的資料
3、STM32CubeIDE幾個調試工具使用演示
4、基于STM32G4芯片的DAC應用示例
5、STM32L0芯片FLASH編程示例及提醒
總結
以上是生活随笔為你收集整理的关于SYSTICK延时函数的两个小疑问的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 显式锁Lock的集大成之作,最细节教程
- 下一篇: 016-JLE JNG(小于等于)