前文指出了基于系統(tǒng)滴答計數(shù)實(shí)現(xiàn)的毫秒級延時的問題。
uint32_t comm_get_ms(void)
{
return sys_tick_get();
}
void comm_delay(uint32_t ms)
{
uint32_t timeout = comm_get_ms() + ms;
while(comm_get_ms() 《 timeout);
}
comm_get_ms返回當(dāng)前系統(tǒng)時間(系統(tǒng)滴答計數(shù)),即系統(tǒng)從啟動到現(xiàn)在經(jīng)過了多少毫秒。comm_delay先獲取當(dāng)前時間,加上延時時間以計算出到期時間timeout,之后循環(huán)等待當(dāng)前時間超過timeout以完成延時。
系統(tǒng)時間使用uint32_t變量來記錄,經(jīng)過49.71天后將達(dá)到最大值UINT32_MAX(0xffffffff),溢出后回到0重新累加。不僅是當(dāng)前時間會溢出,在接近49.71天時,計算的timeout將會更先一步溢出,從而使延時判斷失效。
前文在結(jié)尾給出了解決方案:
void comm_delay(uint32_t ms)
{
uint32_t timeout = comm_get_ms() + ms;
while(comm_get_ms() - timeout 》 UINT32_MAX / 2);
}
其實(shí)改動很小,僅僅修改了判斷超時的條件。為什么要用兩個時間差去與UINT32_MAX / 2比較?判斷條件為什么是大于?
了解其中的原理是有必要的。因?yàn)檠訒r的條件如上,而如果想實(shí)現(xiàn)定時的話,條件就會倒過來。知其所以然,方能靈活運(yùn)用。
定時任務(wù):
uint32_t timeout = 0;
while (1)
{
if (comm_get_ms() - timeout 《 UINT32_MAX / 2)
{
printf(“hello
”);
timeout = comm_get_ms() + 1000;
}
}
主要矛盾
無論是延時還是定時,我們都是在進(jìn)行時間的比較。先根據(jù)延時或定時時長計算出到期時間timeout,之后不停的判斷當(dāng)時時間有沒有超過這個timeout。
所有的時間變量都是uint32_t,由于它的最大值非常大,為了方便講解,我們假設(shè)所有的變量都是uint8_t,即8位無符號整型,取值范圍為0-255。同樣為方便敘述,以cur_time表示當(dāng)前時間,以timeout表示目標(biāo)到期時間。
現(xiàn)在的任務(wù)也非常清楚了,在各種場景下比較cur_time是否超過了timeout。比如:
起始cur_time為10,延時目標(biāo)為5,則timeout為10 + 5 = 15。判斷依據(jù)非常簡單,cur_time 《 15時視為未超過timeout,或者說cur_time 《 timeout視為未超過timeout。
起始cur_time為250,延時目標(biāo)為10,則timeout為250 + 10 = 260 = 4。此時cur_time 《 timeout不再適用。
張三和李四誰跑的快
既然時間溢出問題讓我們頭疼,那我們先來看一個簡單的問題,一個任何人都可以不假思索得出答案的問題:判斷跑道上的張三和李四誰跑的快,或者說誰跑在前面。
如下圖,張三(A)和李四(B)在跑道上跑步,沿逆時針方向跑。藍(lán)色是起跑線,不過他們并不只跑一圈,假設(shè)跑三圈。并且我們知道,張三和李四的水平相差不大,短短的三圈不足以讓他們拉快過長的距離,更不可能出現(xiàn)套圈。
假設(shè)這個跑道長256米,從起點(diǎn)開始沿逆時針方向(即跑步的方向)標(biāo)注坐標(biāo)。那么A和B在坐標(biāo)軸的位置大致如下:

假設(shè)A為10,B為240,A 《 B,但是從跑道的圖中大家不假思索就得出A跑在前面。這是為什么呢?
大家在判斷誰在前面時,其實(shí)根本沒去管那根藍(lán)色的線(起點(diǎn)或終點(diǎn))。因?yàn)榕艿朗孜蚕噙B,而且張三和李四要跑好幾圈,必將多次經(jīng)過起終點(diǎn),所以起終點(diǎn)沒有任何判斷價值。
人腦是怎么判斷的
筆者反復(fù)自我剖析,覺得可能是這樣判斷的:
人腦會做兩種假設(shè),張三(A)快,或者李四(B)快。最終選擇一個最合理的假設(shè)。
假設(shè)張三(A)快,那么A沿順時針跑回B(逆時針是前進(jìn)方向,往回跑就是順時針)的距離即為A超前B的距離,如下圖的紅色箭頭,相對于一圈的長度而言是一個較小的距離。假設(shè)李四(B)快,則B沿順時針方向需要跑大半圈才能遇到張三(A)。如果李四確實(shí)比張三快的話,那么快了不只一點(diǎn)點(diǎn),而是超前大半圈。先前說過,張三和李四的水平相差不大,短短的三圈不足以讓他們拉快過長的距離。所以我們更愿意相信第一種假設(shè)成立,即張三(A)比李四(B)跑的快。
人腦做上述判斷的時候,并沒有給跑道建立坐標(biāo)系,也不是判斷張三和李四的坐標(biāo)值哪個大,而是判斷張三和李四的距離。這個距離是有方向性的。
假設(shè)張三(A)快,則目測A跑回B的距離L(A-B)。這個距離比較小,所以判斷成立,A確實(shí)在B前面。
假設(shè)李四(B)快,則目測B跑回A的距離L(B-A)。這個距離比較大,所以判斷不成立,B其實(shí)在A的后面。
其實(shí)根本不需要驗(yàn)證兩種假設(shè),只需要驗(yàn)證一個就行了,因?yàn)樗鼈兪菍α⒌摹?/p>
回歸代碼
人腦通過視覺來估測張三與李四的距離,但是計算機(jī)不行,它需要一個明確的方法,還是需要坐標(biāo)系的。
還是假設(shè)這個跑道長256米,從起點(diǎn)開始沿逆時針方向(即跑步的方向)標(biāo)注坐標(biāo)。
簡單情況
先看簡單的情況,即A和B在起點(diǎn)的同側(cè)。對應(yīng)到坐標(biāo)系上為:

A在40米處(記為Xa),B在20米處(記為Xb)。A返回到B的距離為
L = Xa - Xb = 40 - 20 = 20
這個距離遠(yuǎn)小于256,所以A在B的前面。
溢出情況
再來看看復(fù)雜的溢出情況,即A和B在起點(diǎn)兩側(cè)。
對應(yīng)在坐標(biāo)系上時,為方便繪制,將A、B與起終點(diǎn)的距離拉遠(yuǎn)一點(diǎn)。Xa=30,Xb=220。A返回到B的距離為:
L = L1 + L2 = (Xa - 0) + (256 - Xb) = 30 + (256 - 220) = 66
66也是遠(yuǎn)小于256的,所以A還是在B的前面。
歸一
有沒有發(fā)現(xiàn)什么不對?剛才討論區(qū)分簡單情況和溢出情況,在計算L時的公式是不同的,這可有點(diǎn)小麻煩。如果有統(tǒng)一的公式就好了。
讓我們再看一眼溢出情況的公式:
L = L1 + L2
= (Xa - 0) + (256 - Xb)
= Xa - Xb + 256 = Xa - Xb
這么一調(diào)整就和簡單情況一樣了吧。為什么把加256給去掉了?因?yàn)槲覀冇懻揦a和Xb是uint8_t,加256和沒加是一樣的。
驗(yàn)證
還是上一個例子的場景,我們來假設(shè)B在A前面。B返回到A的距離為:
L = Xb - Xa = 220 - 30 = 190
190比較接近256,所以假設(shè)不成立,B并不在A前面,而是A在B前面。
我們在判斷距離時,用了兩種標(biāo)準(zhǔn):
L遠(yuǎn)小于256
L比較接近256
對于計算機(jī)而言,這是無法實(shí)現(xiàn)的,它需要一個明確的標(biāo)準(zhǔn)。那是什么呢?就一刀切吧,以256 / 2為閾值。
L 《 256 / 2:假設(shè)成立
L 》 256 / 2:假設(shè)失敗至于L == 256 / 2的情況,隨便歸入哪個都行。
再看時間判斷
void comm_delay(uint32_t ms)
{
uint32_t timeout = comm_get_ms() + ms;
while(comm_get_ms() - timeout 》 UINT32_MAX / 2);
}
再看這時間判斷,有沒有豁然開朗呢?comm_get_ms()是張三,timeout是李四,變量范圍由uint8_t變成了uint32_t,僅此而已。
后記
這種超時判斷方法并非由筆者想出,是筆者在閱讀RT-Thead操作系統(tǒng)的timer源碼時發(fā)現(xiàn)的。rt_timer是RT-Thread的定時器模塊,提供基于系統(tǒng)滴答計數(shù)的定時功能,其計數(shù)值就是32位無符號整型uint32_t,時間久了必然溢出。
筆者之前也為溢出問題感到頭疼,而RT-Thread號稱不懼溢出,于是筆者懷著好奇的心態(tài)挖掘了其解決方法。在rt_timer中,有多處這樣的判斷,現(xiàn)在看起來是不是感覺很親切呢?
/*
* It supposes that the new tick shall less than the half duration of
* tick max.
*/if ((current_tick - t-》timeout_tick) 《 RT_TICK_MAX / 2)
編輯:jq
-
延時
+關(guān)注
關(guān)注
0文章
110瀏覽量
26360
原文標(biāo)題:從rtthread timer模塊中找到裸機(jī)定時問題的解決方案
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
智能數(shù)字毫秒表的應(yīng)用場景介紹、數(shù)字毫秒儀 智能毫秒表
人形機(jī)器人開發(fā)觀察:如何搞定全身40+關(guān)節(jié)的亞毫秒級同步控制?
AI服務(wù)器機(jī)柜BBU的毫秒級瞬態(tài)功率缺口:為什么“混合型超級電容(LIC)+BBU”更合適?
Analog Devices PM139系列:太空級電壓比較器的性能剖析
超級電容黑科技:毫秒級快充,賦能設(shè)備瞬時動力爆發(fā)
使用系統(tǒng)定時器SysTick來實(shí)現(xiàn)精確延時微秒和毫秒函數(shù)
數(shù)據(jù)采集 “延遲高”?5G + 實(shí)時采集,毫秒級同步無卡頓
感知再進(jìn)化,新增“觸發(fā)延時”功能!
如何利用蜂鳥HbirdV2-SoC自帶外設(shè)PWM進(jìn)行毫秒級的延時和計時
三防手持機(jī)是什么?工業(yè)級三防手持機(jī)抗摔能力怎么樣?
探頭的延時介紹
毫秒級響應(yīng)!配網(wǎng)行波故障預(yù)警與定位裝置煥新效率
認(rèn)識探頭的延時
中科曙光讓氣象數(shù)據(jù)解碼邁入毫秒級時代
智能電網(wǎng)第6期 毫秒級時延如何實(shí)現(xiàn)?電力設(shè)備狀態(tài)實(shí)時監(jiān)控優(yōu)化
剖析毫秒級延時防溢出的原理
評論