對於每個微控制器愛好者及工程開發設計人員,在剛接觸微控制器的那最初的青蔥歲月裡,都有過點亮跑馬燈的經歷。從看到那一排排小燈按著我們的想法在跳動時激動心情。到隨著經驗越多,越來又會感覺到這個小燈是個好東西,尤其是在除錯資源有限的環境中,有時會幫上大忙。
但對於絕大多數人,我們在最最初讓燈閃爍起來時大約都會用到阻塞延時實現,會像如下**的樣子:
while
(1)
然後,在我們接觸到定時器,我們會發現,原來用定時中斷來處理會更好。比如我們可以500ms中斷一次,讓燈亮或滅,其餘的時間系統還可以做非常之多的事情,效率一下提公升了很多。
這時我們就會慢慢意識到,第一種(阻塞延時)方法效率很低,讓晶元在那兒空執行幾百公釐,什麼也不做,真是莫大的浪費,尤其在晶元頻率較高,任務又很多時,這樣做就像在平坦寬闊的高速公路上挖了一大坑,出現事故可想而知。
但乙個微控制器中的定時器畢竟有限,如果我需要幾十個或者更多不同時間的定時中斷,每乙個時間到都完成不同的處理動作,如何去做呢。一般我們會想到在乙個定時中斷函式中再定義static 變數繼續定時,到了所需時間,做不同的動作。而這樣又會導致在乙個中斷裡做了很多不同的事情,會搶占主輪詢更多時間,有時甚至喧賓奪主,並也不是很如的思維邏輯。
那麼有沒有更好的方法來實現呢,答案是肯定的。下面介紹我在乙個專案中偶遇,乙個精妙設計的非阻塞定時延時軟體的設計(此設計主要針對於無作業系統的裸機程式)。
在上篇文章中有對
systick的介紹,比如我要設定其10ms中斷一次,如何實現呢?
也很簡單,只需呼叫
core_cm3.h檔案中 systick_config 函式 ,當系統時鐘為72mhz,則設定成如下
即可systick_config(720000 ); (遞減計數720000次後中斷一次) 。此時systick_handler中斷函式就會10ms進入一次;
任務定時用軟體是如何設計的呢 ?
且先看其資料結構,這也是精妙所在之處,在此作自頂向下的介紹:
其定義結構體型別如:
typedef struct
timer_struct;
其中char_field 為一聯合體,設計如下:
typedef union
char_field
而它內部的timer_bit是乙個可按位訪問的結構體:
typedef struct
timer_bit
此聯合體的這樣設計的目的將在後面的**中體現出來。
如此結構體的設計就完成了。
然後我們定義的一全域性變數,timer_struct gtimer;
並在標頭檔案中巨集定義如下:
#define bsystem10msec gtimer.status.field.bit0
#define bsystem50msec gtimer.status.field.bit1
#define bsystem100msec gtimer.status.field.bit2
#define bsystem1sec gtimer.status.field.bit3
#define btemp10msec gtimer.status.field.bit4
#define btemp50msec gtimer.status.field.bit5
#define btemp100msec gtimer.status.field.bit6
#define btemp1sec gtimer.status.field.bit
另外為了後面程式清晰,再定義一狀態指示:
typedef enum
timerstatus;
至此,準備工作就完成了。下面我們就開始大顯神通了!
首先,10ms定時中斷處理函式如,可以看出,每到達10ms 將把
btemp10msec
置1,每50ms 將把
btemp50msec
置1,每100ms 將把
btemp100msec
置1,每1s 將把
btemp1sec
置1,void systick_handler(void)
if(0 =
=(gtimer.tick10msec % 10))if
(100 =
= gtimer.tick10msec)}
而這又有什麼用呢 ?
這時,我們需在主輪詢while(1)內最開始呼叫乙個定時處理函式如下:
void systimer _process(void)
if(btemp50msec)
if(btemp100msec)
if(btemp1sec)
gtimer.status.byte &
= 0x0f;}
此函式開頭與結尾兩句
gtimer.status.byte &
= 0xf0;
gtimer.status.byte &
= 0x0f
就分別巧妙的實現了bsystem*** (低4位) 和 btemp***
(高4位)
的清零工作,不用再等定時到達後還需手動把計數值
清零。此處
清零工作
用到了聯合體中的變數共用乙個起始儲存空間的特性。
但要保證while(1)輪詢時間要遠小於10ms,否則將導致定時延時不準確。這樣,在每
輪詢一次,就先把
bsystem*** ,再根據
btemp***判斷是否時間到達,並把
對應的bsystem*** 置1,而後面所有的任務就都可以通過bsystem*** 來進行定時延時,在最後函式退出時,又會把
btemp***清零,為下一次時間到達後查詢判斷作好了準備。
說了這麼多,舉例說明一下如何應用:
void task_a_processing(void)
}void task_b_processing(void)
}void task_c_processing(void)
if(5 =
= ticks)
}void task_d_processing(void)}
以上示例四個任務程序,
在主輪詢裡可進行如下處理:
int main(void)}
這樣,就可以輕鬆且清晰實現了多個任務,不同時間內處理不同事件。(但注意,每個任務處理中不要有阻塞延時,也不要處理過多的事情,以致處理時間較長。可設計成狀態機來處理不同任務。)
精妙的微控制器非阻塞延時程式設計
文章出處 對於每個微控制器愛好者及工程開發設計人員,在剛接觸微控制器的那最初的青蔥歲月裡,都有過點亮跑馬燈的經歷。從看到那一排排小燈按著我們的想法在跳動時激動心情。到隨著經驗越多,越來又會感覺到這個小燈是個好東西,尤其是在除錯資源有限的環境中,有時會幫上大忙。但對於絕大多數人,我們在最最初讓燈閃爍起...
微控制器延時函式
精確的微控制器常用延時函式 c 誤差0us 12m 並未驗證 1 延時0.5ms void delay0.5ms void 誤差0us 2 延時1ms void delay1ms void 誤差0us 3 延時2ms void delay2ms void 誤差0us 4 延時3ms void del...
51微控制器延時函式
c程式中可使用不同型別的變數來進行延時設計。經實驗測試,使用unsigned char型別具有比unsigned int更優化的 在使用時應該使用unsigned char作為延時變數。以某晶振為12mhz的微控制器為例,晶振為12mhz即乙個機器週期為1us。一.500ms延時子程式 程式 voi...