硬件定時(shí)器
區(qū)別于 rt-thread 內(nèi)核實(shí)現(xiàn)的兩種定時(shí)器,這種定時(shí)器依賴芯片內(nèi)置的定時(shí)器外設(shè),依靠穩(wěn)定高速的晶振實(shí)現(xiàn)精確定時(shí),可以實(shí)現(xiàn) rt_timer 無法達(dá)到的定時(shí)精度。硬件定時(shí)器最重要的兩個(gè)參數(shù)是定時(shí)器時(shí)鐘和定時(shí)器重載值。
定時(shí)器時(shí)鐘越高,定時(shí)器精度越高;重載值越大,實(shí)現(xiàn)的定時(shí)時(shí)間越長。
在定時(shí)器時(shí)鐘一定的前提下,重載值就決定了定時(shí)器定時(shí)時(shí)間的準(zhǔn)確性。
兩種計(jì)算重載值算法
hwtimer.c 文件 `timeout_calc` 函數(shù)實(shí)現(xiàn)
float overflow;
float timeout;
rt_uint32_t counter;
int i, index = 0;
float tv_sec;
float devi_min = 1;
float devi;
/* changed to second */
overflow = timer->maxcnt/(float)timer->freq;
tv_sec = tv->sec + tv->usec/(float)1000000;
if (tv_sec < (1/(float)timer->freq))
{
/* little timeout */
i = 0;
timeout = 1/(float)timer->freq;
}
else
{
for (i = 1; i > 0; i ++)
{
timeout = tv_sec/i;
if (timeout <= overflow)
{
counter = timeout*timer->freq;
devi = tv_sec - (counter/(float)timer->freq)*i;
/* Minimum calculation error */
if (devi > devi_min)
{
i = index;
timeout = tv_sec/i;
break;
}
else if (devi == 0)
{
break;
}
else if (devi < devi_min)
{
devi_min = devi;
index = i;
}
}
}
}
timer->cycles = i;
timer->reload = i;
timer->period_sec = timeout;
counter = timeout*timer->freq;
return counter;
第二種實(shí)現(xiàn),
rt_uint32_t counter, reload;
rt_uint32_t timer_cnt;
int i, index = 0, n0, n1;
float tv_sec;
rt_uint32_t dev, dev_min;
/* changed to second */
tv_sec = tv->sec + tv->usec/(float)1000000.0;
timer_cnt = tv_sec * timer->freq;
if (timer_cnt == 0) {
timer_cnt = 1;
}
if (timer_cnt < timer->maxcnt) {
timer->cycles = timer->reload = 1;
timer->period_sec = tv_sec;
counter = timer_cnt;
return counter;
}
if (timer_cnt % timer->maxcnt == 0) {
timer->cycles = timer->reload = timer_cnt / timer->maxcnt;
timer->period_sec = tv_sec;
counter = timer_cnt;
return counter;
}
n0 = timer_cnt / timer->maxcnt + 1;
n1 = timer_cnt / 2;
dev_min = n0;
for (i = n0; i < n1; i++) {
reload = (rt_uint32_t)(timer_cnt / i);
dev = timer_cnt - reload * i;
if (dev == 0) {
// end
index = i;
break;
} else if (dev < dev_min) {
dev_min = dev;
index = i;
}
}
timer->cycles = timer->reload = index;
timer->period_sec = index / timer->freq;
counter = timer_cnt / index;
return counter;
測試環(huán)境
定時(shí)器頻率設(shè)定 1M。定時(shí)器最大重載值 65535。
系統(tǒng):win10
IDE:Qt Creator
最大定時(shí)范圍
兩種算法,最主要的差別在于前一種用 float 運(yùn)算,因?yàn)?float 可以表達(dá)的值范圍更大,定時(shí)時(shí)間可以更長。
而在 1M 定時(shí)器時(shí)鐘前提下,用 32 位無符號整型 timer_cnt,最大可以處理時(shí)間僅有 4294.967295s。
精度 PK
這里不支持嵌入 html 表格,只好貼圖了
分別選各個(gè)量級的時(shí)間,用兩種算法計(jì)算,第二種算法可以把誤差降低到0,但是也暴露出一些問題,在某些時(shí)間,例如 3.230970s、12.230970s、14.230970s... 誤差是很小,定時(shí)器重載值也很小,這是我們不愿意看到的。
第一種算法,在計(jì)算大于 1000 的數(shù)時(shí),誤差也隨之增大。比如 1000s 誤差為 3.236ms;4293.0s 誤差為 64.080ms。
運(yùn)算速度 PK
測試方法:抽取某幾個(gè)時(shí)間值,循環(huán) 1M 次運(yùn)算,計(jì)量 1M 次運(yùn)算總耗時(shí)時(shí)間。
time | float | uint32 |
3.317s | 98.736ms | 3000+s |
7.537s | 178.545ms | 21.921ms |
7.000537s | 168.549ms | 175.530ms |
999.999s | 17407.468ms | 30866.978ms |
999.000999s | 17458.347ms | 337.047ms |
從抽取的幾個(gè)值測試結(jié)果看,第一種算法耗時(shí)比較穩(wěn)定,第二種算法對不同值的運(yùn)算時(shí)間差異很大。特別的,3.317s 這個(gè)值用第二種算法,1M 次運(yùn)算總耗時(shí)時(shí)間可能達(dá)到 3000s。
從上一小節(jié)的精度比對可以看出,第二種算法對精度要求太高了。下面降低第二種算法的精度,達(dá)到和第一種一樣的精度再重復(fù)一次。修改代碼如下
if (dev == 0) {
// end
index = i;
break;
} else if (dev > dev_min) {
break;
} else if (dev < dev_min) {
dev_min = dev;
index = i;
}
再次測試結(jié)果:
time | float | uint32 |
3.317s | 104.720ms | 20.945ms |
3.000317s | 91.728ms | 21.941ms |
7.537s | 179.519ms | 21.941ms |
7.000537s | 168.549ms | 20.944ms |
999.999s | 17480.734ms | 27.927ms |
999.000999s | 17366.539ms | 20.944ms |
我們可以看出來,在相同精度條件下,第二種算法的運(yùn)算速度比第一種快很多,而且耗時(shí)反而變得更集中。
其實(shí),對結(jié)束條件再次修正,將 `dev == 0` 的嚴(yán)苛誤差條件換成 `dev <= 1` 也不會出現(xiàn)上面 3000+s 慢速。
if (dev <= 1) {
// end
index = i;
break;
} else if (dev > dev_min) {
break;
} else if (dev < dev_min) {
dev_min = dev;
index = i;
}
超過 4295s 的超長定時(shí)
需要修改 `rt_uint64_t timer_cnt` 的定義為 64 位無符號整型 `rt_uint64_t timer_cnt` 。
又因?yàn)槎〞r(shí)時(shí)間很長很長,對誤差要求可以降低一些,對第二種算法做的第二處修改:
if (dev <= 500) {
// end
index = i;
break;
} else if (dev < dev_min) {
dev_min = dev;
index = i;
}
超長時(shí)間,第二種算法的表現(xiàn)也很優(yōu)秀。第三組數(shù)據(jù)第一種方法竟然出錯(cuò)了,沒算出結(jié)果。
下面是 10k 次(沒有進(jìn)行 1W 次是因?yàn)橛行r(shí)間太長了)運(yùn)算時(shí)間統(tǒng)計(jì)
time | float | uint32 |
9999.537s | 1741.341ms | 5.010ms |
19999.999s | 3481.173ms | 27.926ms |
1999999.999s | - | 2616.001ms |
返璞歸真
以上是對兩種算法從不同角度進(jìn)行的比對測驗(yàn)。看似用 float 可以計(jì)算更大的定時(shí)數(shù),但是,測試結(jié)果并不那么理想。使用 64位整型數(shù)計(jì)算,可能得到比用 float 更精確的結(jié)果。
使用 32 位無符號整型數(shù)運(yùn)算雖然最大定時(shí)時(shí)間只有 4294.9s 。但是我們也看到了,第一種方法有可能出現(xiàn)計(jì)算誤差的,當(dāng)誤差超過 1ms 我們用 rt_thread_mdelay 或者 rt-thread 的軟/硬定時(shí)器,可能結(jié)果比硬件定時(shí)器更精確了,反而失去了精確定時(shí)器的意義。在這個(gè)前提下,使用 32 位無符號整型數(shù)已經(jīng)足夠了。
算法及測試源碼見: https://gitee.com/thewon/rt_thread_repo/tree/master/user
審核編輯:湯梓紅
-
算法
+關(guān)注
關(guān)注
23文章
4600瀏覽量
92646 -
定時(shí)器
+關(guān)注
關(guān)注
23文章
3237瀏覽量
114475 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1273瀏覽量
39924
發(fā)布評論請先 登錄
相關(guān)推薦
評論