1. 程式人生 > >精妙的微控制器非阻塞延時程式設計

精妙的微控制器非阻塞延時程式設計

http://blog.chinaunix.net/uid-29673749-id-4425603.html

  對於每個微控制器愛好者及工程開發設計人員,在剛接觸微控制器的那最初的青蔥歲月裡,都有過點亮跑馬燈的經歷。從看到那一排排小燈按著我們的想法在跳動時激動心情。到隨著經驗越多,越來又會感覺到這個小燈是個好東西,尤其是在除錯資源有限的環境中,有時會幫上大忙。


但對於絕大多數人,我們在最最初讓燈閃爍起來時大約都會用到阻塞延時實現,會像如下程式碼的樣子:
  1. while(1)
  2. {
  3.     LED =OFF;
  4.     Delay_ms(500);
  5.     LED = ON;
  6.     Delay_ms(500)
    ;
  7. }
然後,在我們接觸到定時器,我們會發現,原來用定時中斷來處理會更好。比如我們可以500ms中斷一次,讓燈亮或滅,其餘的時間系統還可以做非常之多的事情,效率一下提升了很多。

這時我們就會慢慢意識到,第一種(阻塞延時)方法效率很低,讓晶片在那兒空執行幾百毫米,什麼也不做,真是莫大的浪費,尤其在晶片頻率較高,任務又很多時,這樣做就像在平坦寬闊的高速公路上挖了一大坑,出現事故可想而知。

但一個微控制器中的定時器畢竟有限,如果我需要幾十個或者更多不同時間的定時中斷,每一個時間到都完成不同的處理動作,如何去做呢。一般我們會想到在一個定時中斷函式中再定義static 變數繼續定時,到了所需時間,做不同的動作。而這樣又會導致在一箇中斷裡做了很多不同的事情,會搶佔主輪詢更多時間,有時甚至喧賓奪主,並也不是很如的思維邏輯。


那麼有沒有更好的方法來實現呢,答案是肯定的。下面介紹我在一個專案中偶遇,一個精妙設計的非阻塞定時延時軟體的設計(此設計主要針對於無作業系統的裸機程式)。

在上篇文章中有對systick的介紹,比如我要設定其10ms中斷一次,如何實現呢?
也很簡單,只需呼叫 core_cm3.h檔案中 SysTick_Config 函式 ,當系統時鐘為72MHZ,則設定成如下即可SysTick_Config(720000 ); (遞減計數720000次後中斷一次) 。此時SysTick_Handler中斷函式就會10ms進入一次;

任務定時用軟體是如何設計的呢 ? 
且先看其資料結構,這也是精妙所在之處,在此作自頂向下的介紹:

其定義結構體型別如:
  1. typedef struct
  2. {
  3.     uint8_t Tick10Msec;
  4.     Char_Field Status;
  5. } Timer_Struct;
其中Char_Field 為一聯合體,設計如下:
  1. typedef union
  2. {
  3.     unsigned char byte;
  4.     Timer_Bit field;
  5. } Char_Field
而它內部的Timer_Bit是一個可按位訪問的結構體:
  1. typedef struct
  2. {
  3.     unsigned char bit0: 1;
  4.     unsigned char bit1: 1;
  5.     unsigned char bit2: 1;
  6.     unsigned char bit3: 1;
  7.     unsigned char bit4: 1;
  8.     unsigned char bit5: 1;
  9.     unsigned char bit6: 1;
  10.     unsigned char bit7: 1;
  11. } Timer_Bit
此聯合體的這樣設計的目的將在後面的程式碼中體現出來。
如此結構體的設計就完成了。

然後我們定義的一全域性變數,Timer_Struct  gTimer;

並在標頭檔案中巨集定義如下:
  1. #define bSystem10Msec        gTimer.Status.field.bit0
  2. #define bSystem50Msec        gTimer.Status.field.bit1
  3. #define bSystem100Msec       gTimer.Status.field.bit2
  4. #define bSystem1Sec          gTimer.Status.field.bit3
  5. #define bTemp10Msec          gTimer.Status.field.bit4
  6. #define bTemp50Msec          gTimer.Status.field.bit5
  7. #define bTemp100Msec         gTimer.Status.field.bit6
  8. #define bTemp1Sec            gTimer.Status.field.bit
另外為了後面程式清晰,再定義一狀態指示:
  1. typedef enum
  2. {
  3.     TIMER_RESET = 0,
  4.     TIMER_SET = 1,
  5. } TimerStatus;
至此,準備工作就完成了。下面我們就開始大顯神通了!

首先,10ms定時中斷處理函式如,可以看出,每到達10ms 將把bTemp10Msec置1,每50ms 將把bTemp50Msec 置1,每100ms 將把bTemp100Msec 置1,每1s 將把bTemp1Sec 置1,
  1. void SysTick_Handler(void)
  2. {
  3.         bTemp10Msec = TIMER_SET;
  4.         ++gTimer.Tick10Msec;
  5.         if (== (gTimer.Tick10Msec % 5))
  6.         {
  7.             bTemp50Msec = TIMER_SET;
  8.         }
  9.         if (== (gTimer.Tick10Msec % 10))
  10.         {
  11.             bTemp100Msec = TIMER_SET;
  12.         }
  13.         if (100 == gTimer.Tick10Msec)
  14.         {
  15.             gTimer.Tick10Msec = 0;
  16.             bTemp1Sec = TIMER_SET;
  17.         }
  18. }
而這又有什麼用呢 ?

這時,我們需在主輪詢while(1)內最開始呼叫一個定時處理函式如下:
  1. void SysTimer _Process(void)
  2. {
  3.     gTimer.Status.byte &= 0xF0;
  4.     if (bTemp10Msec)
  5.     {
  6.         bSystem10Msec = TIMER_SET;
  7.     }
  8.     if (bTemp50Msec)
  9.     {
  10.         bSystem50Msec = TIMER_SET;
  11.     }
  12.     if (bTemp100Msec)
  13.     {
  14.         bSystem100Msec = TIMER_SET;
  15.     }
  16.     if (bTemp1Sec)
  17.     {
  18.         bSystem1Sec = TIMER_SET;
  19.     }
  20.     gTimer.Status.byte &= 0x0F;
  21. }
此函式開頭與結尾兩句
  1. gTimer.Status.byte &= 0xF0;
  2. gTimer.Status.byte &= 0x0F
就分別巧妙的實現了bSystemXXX (低4位) 和 bTempXXX(高4位)的清零工作,不用再等定時到達後還需手動把計數值清零。此處清零工作用到了聯合體中的變數共用一個起始儲存空間的特性。

但要保證while(1)輪詢時間要遠小於10ms,否則將導致定時延時不準確。這樣,在每輪詢一次,就先把bSystemXXX ,再根據bTempXXX判斷是否時間到達,並把對應的bSystemXXX 置1,而後面所有的任務就都可以通過bSystemXXX 來進行定時延時,在最後函式退出時,又會把bTempXXX清零,為下一次時間到達後查詢判斷作好了準備。

說了這麼多,舉例說明一下如何應用:
  1. void Task_A_Processing(void)
  2. {
  3.     if(TIMER_SET == bSystem50Msec){
  4.         //do something
  5.     }
  6. }
  7. void Task_B_Processing(void)
  8. {
  9.     if(TIMER_SET == bSystem100Msec){
  10.         //do something
  11.     }
  12. }
  13. void Task_C_Processing(void)
  14. {
  15.     static uint8_t ticks = 0;
  16.     if(TIMER_SET == bSystem100Msec){
  17.        ticks ++ ;
  18.     }
  19.     if(== ticks){
  20.         ticks = 0;
  21.          //do something
  22.     }
  23. }
  24. void Task_D_Processing(void)
  25. {
  26.     if(TIMER_SET == bSystem1Sec){
  27.         //do something
  28.     }
  29. }
以上示例四個任務程序,

在主輪詢裡可進行如下處理:
  1. int main(void)
  2. {
  3.     while(1)
  4.     {
  5.         SysTimer _Process();
  6.         Task_A_Processing();
  7.         Task_B_Processing();
  8.         Task_C_Processing();
  9.         Task_D_Processing();
  10.     }
  11. }
這樣,就可以輕鬆且清晰實現了多個任務,不同時間內處理不同事件。(但注意,每個任務處理中不要有阻塞延時,也不要處理過多的事情,以致處理時間較長。可設計成狀態機來處理不同任務。)

相關推薦

精妙微控制器阻塞程式設計

http://blog.chinaunix.net/uid-29673749-id-4425603.html   對於每個微控制器愛好者及工程開發設計人員,在剛接觸微控制器的那最初的青蔥歲月裡,都有過點亮跑馬燈的經歷。從看到那一排排小燈按著我們的想法在跳動時激動心情。到

Qt阻塞時與阻塞(四種方法之個人筆記)

/***************************************************************************************** *  功能描述: 毫秒級非阻塞延時函式 *  引數: 延時毫秒數 *  其他說明: 已試驗,

Qt 阻塞阻塞

ecs ati ddms cat 應該 oid 名稱 set 情況 一般情況下,延時大概分為兩類,一個是非阻塞延時,一個是阻塞延時,但從名稱上應該都可以看出來具體的含義,下面針對這兩類延時方法,做一個具體的說明和代碼實現:一、關於Qt實現非阻塞延時的方法:void QSle

使用FreeRTOS在SD卡驅動使用系統導致上電重啟不工作的情況

new i開啟 ask 函數 fault 思想 初始化 font 是否 一、問題描述在一個使用FreeRTOS的工程中,只做了SD卡的驅動,由於RTOS使用了Systick,故非系統延時函數使用的是 DWT中的時鐘周期(CYCCNT)計數功能,但是在SD卡驅動中使用了這個非

51微控制器 Keil C 程式的簡單(晶振12MHz,一個機器週期1us.)

一. 500ms延時子程式 void delay500ms(void) { unsigned char i,j,k; for(i=15;i>0;i--) for(j=202;j>0;j--) for(k=81;k>0;k--); } 產生的彙

對MSP430微控制器__delay_cycles精確的說明及改正

在這裡, 我來討論一下關於MSP430微控制器使用__delay_cycles延時的問題. IAR for MSP430編譯器提供了一個編譯器內聯的精確延時函式(並非真正的 函式)以提供使用者精確延時使用, 該函式原型是: __intrinsic void __delay_cy

微控制器兩大方法總結

實現延時通常有兩種方法:一種是硬體延時,要用到定時器/計數器,這種方法可以提高CPU的工作效率,也能做到精確延時;另一種是軟體延時,這種方法主要採用迴圈體進行。 1 使用定時器/計數器實現精確延時 微控制器系統一般常選用11.059 2 MHz、12 MHz

【MPI學習4】MPI並行程式設計模式:阻塞通訊MPI程式設計

這一章講了MPI非阻塞通訊的原理和一些函式介面,最後再用非阻塞通訊方式實現Jacobi迭代,記錄學習中的一些知識。 (1)阻塞通訊與非阻塞通訊 阻塞通訊呼叫時,整個程式只能執行通訊相關的內容,而無法執行計算相關的內容; 非阻塞呼叫的初衷是儘量讓通訊和計算重疊進行,提高程式整體執行效率。 整體對比見下圖:

python:阻塞或非同步程式設計

例如,對於一個聊天室來說,因為有多個連線需要同時被處理,所以很顯然,阻塞或同步的方法是不合適的,這就像買票只開了一個視窗,佷多人排隊等一樣。那麼我們如何解決這個問題呢?主要有三種方法:forking、threading、非同步I/O。 Forking和threading的方

阻塞模式WinSock程式設計入門 使用 WSAAsyncSelect模型

非阻塞模式WinSock程式設計入門 介紹 WinSock是Windows提供的包含了一系列網路程式設計介面的套接字程式庫。在這篇文章中,我們將介紹如何把它的非阻塞模式引入到應用程式中。文章中所討論的通訊均為面向連線的通訊(TCP),為清晰起見,文章對程式碼中的一些細枝末

51微控制器C語言函式

     C程式中可使用不同型別的變數來進行延時設計。經實驗測試,使用unsigned char型別具有比unsigned int更優化的程式碼,在使用時應該使用unsigned char作為延時變數。 以某晶振為12MHz的微控制器為例,晶振為12MHz即一個機器週期為1us。 一. 500ms延時子程

iOS開發——Swift字串替換 + HTML標籤正則過濾 + 主執行緒阻斷

一.字串替換 單獨替換:   //原始字串 let str1 = "LCL中金公司iOS" //替換後的字串 let str2 = str1.replacingOccurrences

阻塞賦值的內部和外部

學習verilog有一段時間了,從字面上理解,阻塞和非阻塞的區別很直白。 前者是序列,主要用於描述組合邏輯,和軟體中的賦值類似;後者是並行,主要用於描述時序邏輯。 但是和內部延時、外部延時混用在一起的時候,各種意想不到的情況就會發生。 下面將介紹,對於非阻塞賦值,內部延遲和

FIFO 阻塞寫+阻塞讀+迴圈讀的一種方法

用mkfifo在當前目錄下建立一個myfifo的有名管道 只執行非阻塞寫的程式 open引數為O_WRONLY | O_NONBLOCK write失敗,這是man手冊裡面說明了的情況 如果open引數為O_RDWR | O_NONBLOCK 寫程式則可以立即返回 但是當執

C/C++網路程式設計在windows和linux中將socket設定為阻塞阻塞

C/C++網路程式設計在windows和linux中將socket設定為阻塞和非阻塞              在 socket程式設計中,對於socket的讀寫預設都是阻塞的,但有的情況我們需要將其設定為非阻塞,比如做多

Python併發程式設計之同步\非同步and阻塞\非阻塞

一、什麼是程序 程序: 正在進行的一個過程或者說一個任務。而負責執行任務則是cpu。 程序和程式的區別: 程式僅僅只是一堆程式碼而已,而程序指的是程式的執行過程。 需要強調的是:同一個程式執行兩次,那也是兩個程序,比如開啟暴風影音,雖然都是同一個軟體,但是一個可以播郭德綱,一個可以播高曉鬆。 二、並行

微控制器 軟體時間控制

微控制器 軟體延時時間控制 一、簡述    記--通過程式碼方式實現軟體延時(不精確延時)。 二、指令週期          微控制器需要一個時鐘訊號送給內部各個電路,才能使它們有節拍地協同工作。時鐘訊號的頻率是由

併發程式設計學習筆記之原子變數與阻塞同步機制(十二)

概述 java.util.concurrent包中的許多類,比如Semaphore和ConcurrentLinkedQueue,都提供了比使用Synchronized更好的效能和可伸縮性.這是因為它們的內部實現使用了原子變數和非阻塞的同步機制. 近年來很多關於併發演算法的研究都聚焦在非阻塞演算法(nonb

Windows網路程式設計(八):阻塞模式(非同步模式)

前面幾篇文章介紹的無論是TCP通訊還是UDP通訊都是阻塞式的,它們在執行recv或recvfrom時會線上程中等待,直到接收到資訊為止,所以在應用的時候一般都需要開闢子執行緒,在子執行緒裡專門做這類事情,不然它會影響主執行緒的執行。  系統提供三種網路模型

Qt:Qt實現Winsock網路程式設計阻塞模式下的簡單遠端控制的開發(WSAAsyncSelect)

Qt實現Winsock網路程式設計—非阻塞模式下的簡單遠端控制的開發(WSAAsyncSelect) 前言 這邊部落格應該是 Qt實現Winsock網路程式設計—TCP服務端和客戶端通訊(多執行緒) 的姐妹篇,上篇部落格中的socket通訊中所用的Windows api函式 都是