[導讀]推薦語 本次推薦的是魚鷹寫的關于延時方面專題。文章列舉、分析了多個延時方法的優(yōu)缺點及改進方法等,同時也分享了一些使用經(jīng)驗,帶我們深刻理解單片機的各種延時功能。另外,魚鷹的這種鉆研、學習精神很值得我們學習 下轉原文(文章較長,可收藏下面慢慢讀~
推薦語
本次推薦的是魚鷹寫的關于延時方面專題。文章列舉、分析了多個延時方法的優(yōu)缺點及改進方法等,同時也分享了一些使用經(jīng)驗,帶我們深刻理解單片機的各種延時功能。另外,魚鷹的這種鉆研、學習精神很值得我們學習
下轉原文(文章較長,可收藏下面慢慢讀~):
最強干貨,不僅適用于單片機應用場合,其他任何需要延時的平臺都可以借鑒參考!
在這篇長達萬字的長文中,魚鷹將通過延時這種剛需功能聊聊
溢出、可重入、編程思想、共享變量保護等方面內容,以延時功能為載體,能更好的理解這些縹緲的知識點。
本篇長文將分成五篇陸續(xù)發(fā)布:概述、V1.0~V1.5、V1.7、V2.0~V2.3、V2.5~V2.7。版本V3.x留給對本公眾號發(fā)展有幫助的道友,在公眾號【魚鷹談單片機】的后臺回復關鍵字
延時即可獲取。
在生活中,時間與我們的生活息息相關,日出而作,日落而息,說的就是利用太陽來大概判斷時間,從而規(guī)劃自己的作息。而在單片機領域,同樣需要一個時間去控制你的代碼運行情況。
玩單片機的應該都了解過晶振,很多初學者可能會從前輩那里得到這樣一個比喻:單片機的心臟。從功能的角度來說確實如此,因為單片機代碼確實是依靠晶振執(zhí)行的,比如說晶振輸出一個脈沖,CPU執(zhí)行一條指令,就像心臟跳動一次,你做一個抬手動作。只不過你的心臟可能1秒只能跳動幾十次,而晶振1秒輸出脈沖幾十兆赫茲(1 MHz = 1,000 KHz = 1000,000 Hz),而一般單片機還會將晶振輸出的頻率進行倍頻,倍頻后的頻率才最終用于驅動CPU的運行。
如果說現(xiàn)實生活中,時間的最小單位是秒(應該沒人在生活和工作中去精確到毫秒吧,更多可能是分鐘),而在單片機領域,常用的時間單位應該是毫秒、微秒、納秒,而決定時間精度的就是晶振(更準確的說是經(jīng)過晶振分頻、倍頻后的系統(tǒng)時鐘,比如說 STM32 的 8 M 晶振分頻成 1 M,然后倍頻成 72 M 作為系統(tǒng)時鐘驅動 CPU,進而執(zhí)行存儲器中的代碼)。
對于應用開發(fā)來說,可能他不會去了解晶振頻率多少,系統(tǒng)頻率多少,一條指令運行時間又是多少?他們更多的需求是,在多少毫秒、多少秒(微秒很少用)之后這段代碼執(zhí)行一遍,這個界面刷新一遍,然后以這個時間為周期,循環(huán)執(zhí)行。這是利用延時功能去達到特定代碼周期執(zhí)行的目的。
延時可分為相對延時和絕對延時,相對延時與絕對延時的差別可看下圖理解:
(在后面介紹的幾種延時演進版本中,可自行思考采用何種方式延時)
本篇筆記將根據(jù)魚鷹多年編程經(jīng)驗介紹自己如何實現(xiàn)延時功能。說是進化論,不如說是魚鷹個人延時功能的使用演進過程。
說到延時函數(shù),51單片機過來的道友腦中應該會想起郭天祥老師視頻中的延時函數(shù),而使用原子例程的道友會想起例程中使用systick定時器實現(xiàn)的延時功能,盡管他們的實現(xiàn)方式有所不同,但他們都是采用死等的方式達到延時功能(所謂死等,有個比喻魚鷹覺得挺恰當?shù)模后H拉磨,讓CPU一直在一個地方打轉,時間到了就離開這個地方)。
死等方式確實很容易理解,也很容易實現(xiàn),但是它的弊病也很明顯,不說它極大的浪費了CPU的資源(在延時過程中,除了能處理中斷,后面的代碼無法處理),更重要的是影響的代碼的執(zhí)行效果,比如說你有一個功能時需要20毫秒執(zhí)行一次,而你的另一個功能卻需要30毫秒執(zhí)行,那你如何采用死等方式處理其中的矛盾呢?
既然是進化論,為了筆記的完整性,魚鷹將根據(jù)自己使用過的延時函數(shù)進行一一說明。
魚鷹首先使用的延時實現(xiàn)就是上面所說的死等方式:
類似上面這種CPU運行在一個循環(huán)中,直到達到條件后離開該函數(shù),從而達到延時功能。
這種實現(xiàn)方式只要對C語言有所了解,很容易理解,但是在使用過程中,你會發(fā)現(xiàn)延時并不準確,或者說可能在這個單片機里面,延時很準,移植到另一個單片機可能一點就不準了,所以為了達到準確的延時功能,必須借助示波器等儀器調整參數(shù)以確定真正的延時時間。
并且還有一種情況就是,如果這個函數(shù)被中斷后再執(zhí)行,那么你的延時將不再準確!這個問題留在V1.5版本討論。
當然如果你使用KEIL開發(fā),魚鷹還可以告訴你另一個確定時間的方法:
這個時間在參數(shù)設置正確的情況下還是非常準的(M3內核以上,在線或者模擬,右下角時間可清零用于重新計時),當然你還是得確定這個時間和現(xiàn)實時間的換算關系(當你把下面的參數(shù)根據(jù)實際情況設置正確后,它們的換算關系是1:1,這個可以通過示波器確定),當你了解了換算關系,那么你就可以脫離示波器,來知道某些代碼的運行時間(精度可達每條指令運行時間,這個時間實際上用的是DWT,這個后面說)。
與上述方式類似的實現(xiàn)是采用nop指令(空指令,即除了浪費CPU沒有任何功能的指令),但魚鷹
不建議使用
nop
指令實現(xiàn)微秒級別以上的延時,因為魚鷹不覺得nop指令能比上述實現(xiàn)方式優(yōu)勢更大:
1、都要采用某種方式確定實際延時時間(比如示波器,當然還有魚鷹上面的方式,通過對比示波器你會發(fā)現(xiàn),準確到讓你懷疑人生!);
2、當優(yōu)化級別提高時,有可能出現(xiàn)延時不一致的情況;
有人會說可以通過nop指令的執(zhí)行時間,進而確定延時時間,但是又引入以下問題:
1、去哪查找nop指令運行時間?上網(wǎng),沒錯,但好像這種資料比較難找,就算你找到了你怎么就能確定這個資料是對的,你還是需要通過示波器(還有上述方式,再次強調?。┲惖拇_定。
2、不同平臺下的nop指令執(zhí)行時間不一致,比如M3內核和M4內核nop指令執(zhí)行周期不一致,即使相同平臺下,如果哪天心血來潮,改變了系統(tǒng)時鐘頻率,那么參數(shù)你還是得重新確定。
3、nop匯編移植性不是很好,單片機中,大多數(shù)時候采用C語言編寫,你要在C中嵌入?yún)R編需要折騰一下。
以上就是魚鷹不建議采用nop指令的原因,既然能用C語言解決的,干嘛需要匯編指令,這種方式并沒有比上述代碼有更多優(yōu)勢,反而缺點不少。
那么真的如上面所說,nop指令沒有一點優(yōu)勢嗎?
有,精確延時(這里的精確延時指的是微小延時情況下的精確)!當芯片手冊告訴你某些功能需要延時多少個
系統(tǒng)時鐘周期時,采用nop指令無疑是最好的方式,因為這是能達到最小延時(指不會額外浪費CPU)的最佳方式,這樣你也不用考慮
進入、退出函數(shù)時額外消耗的時間了(當延時足夠長時,進入、退出函數(shù)消耗的時間可以忽略不計,而延時很短情況下需要考慮)。
前面介紹了版本1.0有一個很大的弊端,那就是在更改優(yōu)化級別的情況下,可能影響延時效果。所以有必要找到更好的方式去實現(xiàn)延時效果。
事實上,魚鷹在很長時間都是采用V1.0進行延時的,比如流水燈、按鍵消抖、數(shù)碼管顯示等。直到看到正點原子的延時函數(shù):
注釋很清晰,簡單來講就是設置一個初始值,然后由硬件負責遞減這個值,當減到零后設置標志位,循環(huán)中只要不停地查詢這個標志位是否置位即可,一旦置位,即代表延時時間達到了。
通過代碼和注釋可以知道,最大延時時間1864毫秒,1秒多點,對于單片機來說,時間很長了。
現(xiàn)在我們來分析這種實現(xiàn)方式的優(yōu)勢:
1、延時時間
相對精確,也就是說,只要配置正確,精度可達systick時鐘精度(當然如果延時在微秒級別時,誤差較大,除了進出函數(shù)消耗外,還有循環(huán)體外語句和判斷語句的執(zhí)行時間,這些很難避免)。
2、即使代碼采用最高級別進行優(yōu)化編譯,對于毫秒級別的延時影響也非常小。
3、即使在延時過程中被中斷了(裸機環(huán)境下被硬件中斷,系統(tǒng)環(huán)境下被硬件和其他任務中斷),延時時間在
絕大多數(shù)情況下是準確的,但是 V1.0版本采用純軟件的方式總是
將被中斷的時間包含在延時時間內。
雖說軟件版或硬件版都可能存在延時不準確的問題,但事實上在軟件中多延時幾個毫秒是沒有多大問題的(可通過關中斷確保延時準確),所以這種超過延時的情況不必太糾結,但是如果你的延時函數(shù)的延時時間可能
比需要延時時間更短,那么就要引起注意了!
1、就像上面所說的,延時時間最大1秒多點,對于有些需求來說,延時有點短了(有些人可能會說這是雞蛋里挑骨頭,誰沒事延時那么久,就算需要延時很久,多延時幾次就行了,嗯,算你過關)。
2、占用systick時鐘。用過嵌入式系統(tǒng)的都知道,大多數(shù)操作系統(tǒng)都會采用systick作為系統(tǒng)的心跳時鐘,也就是說,如果將來你的裸機代碼需要移植到系統(tǒng)中執(zhí)行,必須重新實現(xiàn)延時功能(可能你會說,我就在裸機上開發(fā),不上系統(tǒng)行不行,OK)。
3、函數(shù)
非可重入
!
這一點很多人可能都沒有意識到,在寫這段話之前,魚鷹也沒有意識到(延時這么簡單的功能,誰會想那么多,魚鷹亦是如此。但魚鷹在思考它的缺點時,也以為在裸機環(huán)境下不需要考慮可重入和不可重入問題,因為裸機就一個大循環(huán),肯定順序執(zhí)行,也就不需要考慮這種問題(
為什么順序執(zhí)行就不需要考慮可重入問題?
),但是卻突然想到了硬件中斷可打斷主循環(huán)的情況,而在中斷中執(zhí)行微秒級別的延時是有可能的)。
當你在主循環(huán)中延時
毫秒級別時,突然中斷來了,開始延時
微秒級別的代碼,那么必然修改systick寄存器,導致返回主循環(huán)時快速退出延時,最終達不到預期的延時效果!這是很可怕的事情,比如模擬I2C通信時,出現(xiàn)了這種情況……
通過上述分析,你應該知道使用這種實現(xiàn)方式有多大風險了吧!
(事實上以下實現(xiàn)方式應該是魚鷹在使用
V2.7
版本很久后才采用的方式,但因為內容的相關性,換個順序介紹)
這里不再使用systick,而是使用DWT(關于這個模塊,魚鷹后期可能專門寫一個小節(jié)介紹它,歡迎關注
魚鷹談
單片機),為什么使用它呢?
2、精度非常高,系統(tǒng)時鐘的精度,也就是說,即使你多執(zhí)行了一條指令,它也能發(fā)現(xiàn)!
3、延時足夠長,168 M頻率下可延時 25 秒多(0xFFFF FFFF / 168),這對于大多數(shù)需求都足夠了,只要你的執(zhí)行周期或者延時時間在此之內的都沒有問題。
4、屬于不用白不用的資源(cortex-m3、m4都有這個模塊,像魚鷹這么節(jié)約的人,肯定要用上的)。
2、函數(shù)內盡可能的減少不必要的語句執(zhí)行時間。
3、精度高,延時長(采用DWT的優(yōu)勢,而不是實現(xiàn)方式的優(yōu)勢)。
4、因為由
硬件修改寄存器,所以可用于
任何中斷中,即使這個中斷優(yōu)先級非常高(有些時候,如果時間寄存器由軟件修改,那么比它高的中斷就不能正常運行)。但不建議在中斷中延時毫秒級別以上的延時,微秒級別可以考慮。
現(xiàn)在我們來看看這段代碼如何實現(xiàn):
微秒和168相乘,是為了換算168M主頻下的
延時時鐘數(shù)(1 MHz = 1 us)。
接著獲取當前時鐘,作為開始計時的
時刻,最后
當前時間
(由硬件更改該值)與計時時刻比較,當發(fā)現(xiàn)時間增加到大于
延時時鐘數(shù)時,即可跳出循環(huán),此時即達到了延時目的。
以上代碼似乎不難理解,但是有經(jīng)驗的道友可能會問,你的變量大小是有限的(在這里是4字節(jié)),你不怕溢出嗎?溢出了之后,
計時時刻可能會比
當前時刻更大,那么使用減法會不會有問題?
在大一的時候魚鷹就在思考這個問題了,兩個
無符號的數(shù)相減,如果前者比后者
小
,會發(fā)生什么問題?這樣是否就達不到準確延時的目的了?是否需要考慮溢出的情況?
有人會說這只需要一個判斷語句就能輕松搞定了,當前者比后者小的時候特殊處理即可,比如4和5相減,特殊處理即可。
但魚鷹一直覺得應該有一種比較好的方式去解決,直到大四實習看到 FIFO 的源碼,魚鷹才豁然開朗,終于找到了(對 FIFO 感興趣的可以去看魚鷹的另一篇筆記,很詳細的介紹了一個非常有意思的公式,而魚鷹也在那篇筆記中說到可以利用這種方式去做延時,只是里面寫的有點bug,事實上不算bug,只是有種脫褲子放屁的感覺,各位道友可以去看看當時的實現(xiàn)方式,而對于 FIFO, 魚鷹目前也有了更多的經(jīng)驗,發(fā)現(xiàn)那篇筆記的實現(xiàn)方式采用 % 取余運算效率較低,還有一種更高效的方式,而且建議能用判斷語句,就別用 % 處理)!
那么目前這種
看似沒有處理溢出的方式是否真的能夠適應溢出的情況,答案是肯定的,那么原理何在?
我們經(jīng)??梢钥吹界姳恚ǚ菙?shù)字手表),指針從1一直轉到12,然后又從1開始,周而復始。當其從1轉回到1時,即經(jīng)歷了
12個小時,但是如果你在
超過
半
天時間
后來查看鐘表時,雖然你之前看到的是1,現(xiàn)在看到的還是1,但實際上已經(jīng)過去了
24小時了!
同理,計算機的世界亦是如此,如果說一個字節(jié)的最大值是255,那么這里的255就是鐘表里的12,字節(jié)溢出后變?yōu)?,而鐘表溢出后就是1,這種特性是由計算機和鐘表本身決定的,不隨外界變化而變化,當我們能夠利用這種特性是,你會發(fā)現(xiàn)能簡化很多東西(這個函數(shù)需要靠自己去悟,別人很難說清)。
DWT計數(shù)器變量大小為4個字節(jié),也就是說最大值為0xFFFF FFFF,那么我們來思考以下幾個問題:
2、這個函數(shù)的使用是否真的沒有一點隱患?
第一個問題,最大延時,前面魚鷹已經(jīng)計算過了,25秒多,那么精確的時間是多少呢?
(0xFFFF FFFF / 168) us,那么為什么不是((0xFFFF FFFF + 1)/ 168) us?這個留給道友去思考。
第二個問題,使用隱患問題,這個問題其實在說明鐘表例子時已經(jīng)說明了,如果你在超過它最大延時的時候再回來
查詢這個值,你會發(fā)現(xiàn)
最終延時遠遠超出了。事實上,你的延時函數(shù)不可能被打斷25秒多(如果真的打斷這么長時間,你就要好好考慮了),但是你不得不考慮這個問題,因為你在下次使用過這種方式時,你不能確定是否真的能使用DWT這種超級延時外設,有可能你的最大延時是1 毫秒
(比如一個定時器被你設定
1
毫秒溢出),那么你的延時函數(shù)被打斷 1 毫秒后再回來執(zhí)行是很可能的,所以你除了要考慮它的最大延時時間,還要考慮它
最大被打斷時間(DWT不用考慮這么多,因為25秒對于單片機來說實在是太長了)。
事實上,除了這個隱患還有另一個,這個留在V2.7版本討論。
第三、所謂可重入,簡單來說,就是這個函數(shù)能否當成兩個函數(shù)執(zhí)行,而不影響他們的功能,更實際一點的話就是,當在主函數(shù)和中斷函數(shù)同時
調用(注意用詞,執(zhí)行不恰當)這個函數(shù)時是否會造成功能紊亂(在這里表現(xiàn)為延時不準)。在這個函數(shù)中,這里的共享資源是DWT,按理說共享資源都需要進行保護,這樣才能可重入,但是因為這個函數(shù)只對DWT進行
讀取操作,而不進行寫操作(寫操作由硬件自動完成),所以不存在修改共享資源的情況,也就是它可重入的原因,那么為什么那些參數(shù)、局部變量也是可重入的?這個基礎扎實的話應該能懂,如果不懂就在評論區(qū)留言好了。
第四個問題,思想可適用于所有定時器,但注意只是思想,當你的定時器是
遞減的,比如systick,那么需要做一點點修改,謹記(當然168這個數(shù)也得正確設置,如果不知道怎么修改,可留言)!
事實上這種實現(xiàn)方式魚鷹最近(2020-01)在安富萊電子(強烈建議工作的道友參考安富萊例程和文檔,因為這些代碼應該都是由一位大佬寫的,非常專業(yè),而正點原子更適合初學者)和RT-Thread文檔中都有看到相關描述,只是在此之前魚鷹并沒有看到過類似代碼,而是由FIFO源碼深入思考受到啟發(fā),進而實現(xiàn)了以上代碼。而當看到安富萊延時實現(xiàn)時才發(fā)現(xiàn),實現(xiàn)代碼驚人相似(實際上在此之前魚鷹一直采用非死等方式實現(xiàn)延時的,但后來發(fā)現(xiàn)死等方式也很有必要,比如模擬I2C總線,所以利用非死等V2.7的實現(xiàn)思想實現(xiàn)了死等)。
前面V1.x版本的演進,很好的解決了延時問題,但有經(jīng)驗的你會發(fā)現(xiàn),上述實現(xiàn)方式有一個硬傷,那就是都是采用死等方式實現(xiàn),在等待延時內單片機除了
能響應中斷外,什么也干不了,如果說
延時時間很短(微秒級別)或者必須采用死等方式處理外,其他情況我們應該盡可能讓延時和其他任務同時處理。
假設我們有這樣一個需求,4個按鍵檢測、1個LCD屏幕顯示。按鍵濾波10毫秒,LCD屏幕
最低30毫秒刷新一次,執(zhí)行時間1毫秒(為啥不說按鍵執(zhí)行時間,因為執(zhí)行時間太短了,這里忽略不計了)。
如果在裸機環(huán)境下開發(fā),入門級的會這樣處理(大一時的魚鷹):
主循環(huán)不停執(zhí)行按鍵掃描,因為要濾波,所以使用郭天祥老師那種方式濾波10毫秒后等待按鍵電平穩(wěn)定,然后檢測,并且在主循環(huán)不停刷新LCD,反正這里說刷新周期為30毫秒,而我CPU任務不多,有時間就刷新唄。
按鍵濾波10毫秒,LCD屏幕執(zhí)行時間1毫秒,這樣在按鍵按下情況,LCD屏幕刷新周期可達到11毫秒,可達到要求(如果按鍵沒有按下,刷新周期1毫秒)。
后來隨著需求增加,比如增加串口通信處理,增加大量浮點數(shù)據(jù)處理,執(zhí)行時間20毫秒,要求這兩個任務能在100毫秒內執(zhí)行完畢,并且數(shù)據(jù)處理過程可被外部中斷而不影響。
假如把這個串口和數(shù)據(jù)處理繼續(xù)放在主函數(shù),先前最差情況下(按鍵按下),執(zhí)行周期為11毫秒,增加新任務后,最差情況下31毫秒可以完成串口和數(shù)據(jù)處理任務,小于100毫秒。
但是當你增加新任務后,你會發(fā)現(xiàn)LCD刷新要求達不到了(最少30毫秒),那該怎么辦?
通過分析你可以很容易的發(fā)現(xiàn),按下按鍵那一刻有10毫秒白白浪費了CPU,如果這段時間能用于數(shù)據(jù)處理就好了。
我們很容易想到把按鍵檢測功能放到中斷中去處理,為什么呢?因為它的執(zhí)行很短,只是濾波耗時,如果我們能把
按鍵檢測和濾波分開執(zhí)行就好了。
于是我們想到了使用兩個變量將按鍵和檢測分開(關于中斷下按鍵檢測可參考藍橋杯代碼,看似很難理解,但理解后你會發(fā)現(xiàn)這是一種非常高效的實現(xiàn)方式):
一個變量用于記錄按鍵電平狀態(tài),一個變量用于計時,定時器1ms中斷一次,當按鍵按下后(定時中斷中檢測是否按下),記錄電平狀態(tài),并且初始化計時變量為10,在定時中斷中發(fā)現(xiàn)計時變量不為0,遞減變量,直到為零后再次檢測按鍵狀態(tài)和當前狀態(tài)是否一致即可(關于按鍵檢測魚鷹可能會在后期分享一篇筆記進行深入分析,實現(xiàn)單擊、雙擊、長按等功能,關注魚鷹即可,在這里描述的這種實現(xiàn)方式魚鷹感覺還是復雜了點)。
因上述代碼執(zhí)行時間很短,所以在解決了按鍵問題后,就可以達到任務要求了。
由此我們可以得到V2.0版本基本實現(xiàn)思想:
第二:你需要在特定的地方遞增或遞減這個計時值,比如定時中斷函數(shù)。
以上第二點是一個很麻煩的事情,在
特定的地方修改值不是很大麻煩(也算一個麻煩,因為一旦你的計時任務增多,你的定時中斷任務復雜度必然會增加),最大問題在于修改計數(shù)值本身,修改變量會導致一些可重入問題,這個問題留待后面解決。
即使經(jīng)過上述方式優(yōu)化代碼結構后,如果在后期再增加新任務后,你可能會發(fā)現(xiàn)LCD屏幕刷新周期可能又不符合要求,每次增加任務后都可能會導致刷新頻率不符合要求(因為目前就是任務空閑即刷新,并沒有固定刷新時間),反反復復修改代碼誰也受不了,難道這就是所謂的碼農,難道就沒有更好的解決辦法了嗎?
借用上述V2.0的思想,我們可以將其擴展成V2.1,使得任務中對時間要求較高的功能進行精確控制。
前面介紹的任務可以細分好幾個小任務,按鍵檢測任務、LCD刷新任務、計算任務、串口任務。
上述任務中,LCD刷新任務有30 ms的硬性要求(保證屏幕不閃爍)。所以借用V2.0思想,使用計時變量對它精確控制,但是又不能像按鍵處理那樣將LCD刷新任務放到中斷任務中處理,因為LCD執(zhí)行時間長達1 ms。
很容易的,我們可以安排一個變量,在主循環(huán)中設置初始值,并執(zhí)行刷新任務,而中斷中檢測這個值是否大于零,大于零即遞減。
這樣一來,只要單片機不是滿負荷運行,應該就能達到刷新要求了(這里最終延時應該是31ms,可自行測試一下)。
但是有經(jīng)驗的你會發(fā)現(xiàn),計時變量在中斷和主函數(shù)都進行了修改操作,這就導致了一個問題:是否需要對這個變量保護?
通過認真思考,你會發(fā)現(xiàn)兩次修改都是有條件的:中斷下修改需要變量大于0,而主函數(shù)下修改需要變量等于零。這樣一來,你會發(fā)現(xiàn)變量修改是
互斥的,也就是不存在同時修改的可能?。?!
這也是為什么你沒有從別人類似的代碼中看到對共享資源保護的原因!
但是有些道友對共享變量不進行保護,在使用上存在顧慮,所以就會思考是否存在一種更好的方式去實現(xiàn)上述方法,由此V2.2現(xiàn)世。
V2.1存在的問題在于兩個地方同時修改了變量,導致變量成為了
偽共享變量(為什么是偽共享變量已經(jīng)在前面解釋了)。
有沒有一種方法,讓變量只在中斷被修改,而主函數(shù)可以根據(jù)變量當前的值判斷延時時間是否達到呢?
這里介紹一種常規(guī)的方法,就是讓變量的初始化賦值直接在中斷進行:
這樣主函數(shù)就只需要做判斷即可,而不需要修改變量。
但是這種處理有一個隱患,你必須在1ms內(假設中斷1ms)查詢到超時時間,一旦主函數(shù)有任務運行超過此時間,延時時間必然不準。
所以以上解決方式不建議采用。那么是否有更好一點的解決方式呢?
計時變量遞增,但不是30時清零,而是240時清零,即0~239循環(huán)(為什么不是增長到255后自然溢出?)。
這種實現(xiàn)方式好處就是減少了重新賦值計時變量的次數(shù),但不可避免的是,仍然存在1ms查詢頻率的限制,一旦超出,延時不再準確!
是否存在一種沒有查詢頻率限制的實現(xiàn)方法呢?換句話說,我們不是根據(jù)
時刻判定延時時間的到達,而是通過
時
長判斷呢?即1~2倍超時時間內都可以認為超時時間到,而不是剛好就是超時時間呢?
當然有,這里介紹一種有意思的公式(這個公式的妙處可看魚鷹FIFO相關筆記):
這個公式是用來計算隊列中的元素個數(shù),受它啟發(fā),我們可以利用這個公式計算時長:
Duration= (CurrentTime – Time + 256) % 256(注意不是255)
CurrentTime為隨時間不斷遞增的變量,Time為記錄的一個時刻點。
因為CurrentTime遞增到256后自動清零的特性,可簡化公式如下:
Duration= CurrentTime – Time
這里特別注意需要強制轉換成8位無符號整數(shù),這是因為相減的時候默認采用有符號計算,我們需要讓結果強制變成無符號8位整數(shù)(如果變量類型為無符號32位整數(shù)則不需要如此),還有判斷條件是大于29,而不是30。
我們關注的重點在于變量溢出后是否還能準確延時30 ms。
上述延時版本的實現(xiàn)是一種飛躍,極大的簡化延時代碼的實現(xiàn),但如果沒有接下來這個版本的演進,你可能會如此實現(xiàn)各種功能的延時:
每一個延時功能,都使用兩個變量按上述V2.5實現(xiàn)各種延時。
如果延時任務不多,確實沒有問題,但是如果延時任務很多,會帶來什么問題?
第一:中斷函數(shù)處理的延時變量很多,增加中斷負擔;
利用V2.5實現(xiàn)兩種以上延時功能后,你會發(fā)現(xiàn),中斷中的延時變量完全可以由一個變量承擔計時任務。
可以看到,通過上述代碼可以實現(xiàn)兩種延時任務,互不干擾。
而以上代碼才是魚鷹這篇長文的核心!也是魚鷹愿意花大量筆墨去寫這樣一個通用功能的原因所在,也是魚鷹特別希望各位道友掌握的一個延時技能(到此全文筆記已接近尾聲,V3.x版本更多的是用于提高關于延時使用的認知)。
那么現(xiàn)在來分析這樣的代碼實現(xiàn)有什么好處:
1、可重入,也就是說你在任何函數(shù)中采用這種延時方式,原則上不會影響延時的準確性(最終延時時間
大于等于需要延時時間)。
2、中斷執(zhí)行代碼極少,各個延時功能幾乎沒有耦合關系。
4、可實現(xiàn)超長延時,如果把計時變量改成32位整數(shù),延時時間超長(這里還是建議使用DWT,用于精確延時)。
5、變量少,一個延時任務只需要一個變量記錄上一次延時到達的時刻即可。
6、延時變量類型可根據(jù)延時長度自行選擇。比如說延時100 ms 使用8位整數(shù),延時1 s 使用16位整數(shù),延時 1天使用32位整數(shù)(當然判斷代碼需要根據(jù)情況修正才能準確延時,不懂的話可以留言討論)。
討論了這么多優(yōu)點,難道就沒有任何缺點嗎?
一番思考下,魚鷹想到了一個缺點,或者說另一個使用隱患。
我們想象這樣一個場景,8位的計時變量,延時250 ms,上一次記錄的超時時刻是0,原本應該在250這個時刻判斷為超時,但有一個任務執(zhí)行時間10 ms,導致在即將到達判斷條件前執(zhí)行了這個10 ms任務(假設在249這個時刻執(zhí)行了這個任務,249 + 10 = 259,溢出變成3,查詢時3 – 0 > 249不成立,但是250~255這些值是成立的),那么最終導致下一個250才是超時時刻,也就是說最終延時250 * 2 ms(如果不湊巧下一次又在249附近這里運行了這個任務,那么后果……),這肯定不是我們想看到的。
那么從這個例子中我們可以總結一個防止延時時間錯誤的方法:
在上面例子中,延時時間為250,查詢時間暫且認為是10(不包含其他任務情況下),最大可延時時間255。代入公式發(fā)現(xiàn)不滿足條件,這也就是為什么會出現(xiàn)延時錯誤(延時錯誤指的是超出延時時間兩倍以上)的原因。
因為魚鷹的需求一直是周期延時,就沒往單次延時方向考慮,后來將筆記發(fā)布到知乎之后,有網(wǎng)友由此受到啟發(fā),想改進他的延時功能(當時他使用的方法類似 V2.1)。
一開始魚鷹很不明白,為什么明知這個版本的兩大缺點,還選擇這種方式呢?
第一,該網(wǎng)友使用的場景是單次延時,而V2.7是周期延時,如果沒有好的策略處理是不能實現(xiàn)單次延時的;第二,雖然該網(wǎng)友的中斷變量數(shù)會增加,但他巧妙的避免了查詢頻率這一關鍵缺陷,所以還是很有參考價值的(希望公眾號的道友也能如這位網(wǎng)友一般,把自己的見解留言在文章下,這樣的交流對技術的提高是很有幫助的)。
相信很多道友在讀前面幾篇筆記時,有看到魚鷹重點強調了“
查詢頻率”,但又有多少道友理解了這一詞呢?查詢頻率,換句話說就是
代碼的執(zhí)行周期,因為是在延時判斷環(huán)境下,所以魚鷹稱之為“查詢頻率”。今天這篇筆記測試環(huán)境不再是裸機,而是 RT-Thread 操作系統(tǒng),通過修改線程延時,能讓各位道友更深刻理解“查詢頻率”一詞。
現(xiàn)在先來看看這位網(wǎng)友是如何解決查詢頻率限制的吧:
這是網(wǎng)友在魚鷹前面的思路下回復的內容,當時魚鷹那篇文章采用圖片形式,根本無法復制粘貼,所以這位網(wǎng)友能夠敲出這些代碼進行回復也算是有心了。
當時初看代碼時,還以為和魚鷹寫的版本一樣,所以一直在和這位
網(wǎng)友(
現(xiàn)在晉升為魚鷹的道友了,后面就以道友相稱了)強調查詢頻率限制,后面經(jīng)過不斷地討論后,他理解了魚鷹的查詢頻率的意思,魚鷹也理解了這段代碼和魚鷹寫的是不一樣的。
我們可以看一下中斷處理,發(fā)現(xiàn)它并
不是直接將變量遞減至零,而是留下了一個1,這個1就是用來做最后的延時超時時的處理工作,而一旦處理完成,完成清零。
也就是說,一個變量,被這位道友分成兩部分用,前部分用來延時,后一個1用來做超時時間達到的標志位,還有一點就是最終延時時間不需要再減1了。
因為時間遞減到1之后,中斷不再對其遞減,所以
這個
1
一直保留,直到判斷超時代碼執(zhí)行(查詢)完成,才完成最終的清零操作,輕松實現(xiàn)單次延時功能,而且也不存在查詢頻率的限制。
確實是相當不錯的策略,一個變量就解決一個延時問題,沒啥副作用。
如果延時不多,這個策略確實很不錯,但是一旦延時增多,中斷的負擔就會增加,遠不如V2.7版本的一條代碼高效(中斷代碼要盡可能的少,執(zhí)行時間盡可能的短)。
由此我們可以思考,是否能對 V2.7 版本進行改進,達到單次延時的效果呢。
一番思考下,魚鷹終于找到了一個很好的策略去實現(xiàn)它。
為了實現(xiàn)單次延時,魚鷹增加了一個延時變量,也就是說,在魚鷹這種策略下,如果要實現(xiàn)一次單次延時,必須使用兩個變量,這是這個方法的一點缺陷,所以對于內存不足的情況,可以使用那位道友的
V2.8.0(這個版本號是隨便編的,方便魚鷹說明)。
由于魚鷹懶得找systick處理函數(shù)在哪,也不想修改系統(tǒng)的代碼,就使用了一個線程增加時間(實際上該系統(tǒng)有一個函數(shù)rt_tick_get() 函數(shù)可以使用,但怕有些讀者不熟悉,所以自己弄了一個變量替代)。
關鍵性的東西已經(jīng)注釋好了,現(xiàn)在分析一下。
為了使時間變量準確增加,遞增變量的線程task優(yōu)先級必須比延時線程main更高(這里的main函數(shù)也是一個線程)。
首先分析一下為什么這個策略可以實現(xiàn)單次延時?關鍵點就在于延時時間delay被初始化為最大值,而每次延時完畢也會將其設置為最大值,而判斷條件是
大于號,也就是說只要delay設置為最大值,那么這個條件永遠也滿足不了,里面的代碼也永遠不會執(zhí)行,這樣一來就實現(xiàn)了單次延時的效果,而且這個延時時間也可以任意指定(也有限制條件,可回看V2.7)
設置兩個線程執(zhí)行頻率1 ms,在這里的超時代碼查詢頻率也可以認為是1 ms,但由于打印函數(shù)比較耗時,所以執(zhí)行時間較長,好在打印函數(shù)也是有條件限制的,影響不大。
這里用了一個flag來表示觸發(fā)條件,這個flag通過一個系統(tǒng)的shell命令led設置,
可以看到,延時時間非常準確,說延時20 ms,就延時20 ms,絕不含糊。
現(xiàn)在將main線程執(zhí)行頻率設置為 6 ms,task線程執(zhí)行頻率還是 1 ms。
可以看到,魚鷹只改變了main線程的執(zhí)行頻率,就導致延時時間超過了20毫秒,達到25毫秒,很不準確。這是因為雖然超時時間到了,但是因為代碼還沒有執(zhí)行到,所以導致執(zhí)行到代碼時,已經(jīng)超過延時時間很久了。
這里還有要注意的一點是,因為delay變量在多個地方調用,所以注意互斥保護,因為一旦上次延時沒有達到,你再次修改延時時間,那么必然影響上次延時效果,這是V2.8兩個版本都要考慮的問題,切記!
推薦閱讀:
為什么說你一定要掌握 KEIL 調試方法?
-THE END-
如果覺得文章對你有幫助,歡迎轉發(fā)、分享給朋友,感謝你的支持!
如果對本文有問題,歡迎留言!即使沒有問題也可以留下走心評論。
如需轉載請聯(lián)系我。
微信公眾號「魚鷹談單片機」
每周一更單片機知識
長按后識別圖中二維碼關注
免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。
關鍵字:
阿維塔
塞力斯
華為
加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...
關鍵字:
AWS
AN
BSP
數(shù)字化
倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...
關鍵字:
汽車
人工智能
智能驅動
BSP
北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...
關鍵字:
亞馬遜
解密
控制平面
BSP
8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。
關鍵字:
騰訊
編碼器
CPU
8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。
關鍵字:
華為
12nm
EDA
半導體
8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。
關鍵字:
華為
12nm
手機
衛(wèi)星通信
要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...
關鍵字:
通信
BSP
電信運營商
數(shù)字經(jīng)濟
北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...
關鍵字:
VI
傳輸協(xié)議
音頻
BSP
北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...
關鍵字:
BSP
信息技術
山海路引?嵐悅新程 三亞2024年8月27日 /美通社/ --?近日,海南地區(qū)六家凱悅系酒店與中國高端新能源車企嵐圖汽車(VOYAH)正式達成戰(zhàn)略合作協(xié)議。這一合作標志著兩大品牌在高端出行體驗和環(huán)保理念上的深度融合,將...
關鍵字:
新能源
BSP
PLAYER
ASIA
上海2024年8月28日 /美通社/ -- 8月26日至8月28日,AHN LAN安嵐與股神巴菲特的孫女妮可?巴菲特共同開啟了一場自然和藝術的療愈之旅。 妮可·巴菲特在療愈之旅活動現(xiàn)場合影 ...
關鍵字:
MIDDOT
BSP
LAN
SPI
8月29日消息,近日,華為董事、質量流程IT總裁陶景文在中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式上表示,中國科技企業(yè)不應怕美國對其封鎖。
關鍵字:
華為
12nm
EDA
半導體
上海2024年8月26日 /美通社/ -- 近日,全球領先的消費者研究與零售監(jiān)測公司尼爾森IQ(NielsenIQ)迎來進入中國市場四十周年的重要里程碑,正式翻開在華發(fā)展新篇章。自改革開放以來,中國市場不斷展現(xiàn)出前所未有...
關鍵字:
BSP
NI
SE
TRACE
上海2024年8月26日 /美通社/ -- 第二十二屆跨盈年度B2B營銷高管峰會(CC2025)將于2025年1月15-17日在上海舉辦,本次峰會早鳥票注冊通道開啟,截止時間10月11日。 了解更多會議信息:cc.co...
關鍵字:
BSP
COM
AI
INDEX
上海2024年8月26日 /美通社/ -- 今日,高端全合成潤滑油品牌美孚1號攜手品牌體驗官周冠宇,開啟全新旅程,助力廣大車主通過駕駛去探索更廣闊的世界。在全新發(fā)布的品牌視頻中,周冠宇及不同背景的消費者表達了對駕駛的熱愛...
關鍵字:
BSP
汽車制造
此次發(fā)布標志著Cision首次為亞太市場量身定制全方位的媒體監(jiān)測服務。 芝加哥2024年8月27日 /美通社/ -- 消費者和媒體情報、互動及傳播解決方案的全球領導者Cis...
關鍵字:
CIS
IO
SI
BSP
上海2024年8月27日 /美通社/ -- 近來,具有強大學習、理解和多模態(tài)處理能力的大模型迅猛發(fā)展,正在給人類的生產(chǎn)、生活帶來革命性的變化。在這一變革浪潮中,物聯(lián)網(wǎng)成為了大模型技術發(fā)揮作用的重要陣地。 作為全球領先的...
關鍵字:
模型
移遠通信
BSP
高通
北京2024年8月27日 /美通社/ -- 高途教育科技公司(紐約證券交易所股票代碼:GOTU)("高途"或"公司"),一家技術驅動的在線直播大班培訓機構,今日發(fā)布截至2024年6月30日第二季度未經(jīng)審計財務報告。 2...
關鍵字:
BSP
電話會議
COM
TE
8月26日消息,華為公司最近正式啟動了“華為AI百校計劃”,向國內高校提供基于昇騰云服務的AI計算資源。
關鍵字:
華為
12nm
EDA
半導體