關於軟體定時器的一些討論
簡介
這裡先介紹下軟體定時器和硬體定時器的區別
硬體定時器:
CPU內部自帶的定時器模組,通過初始化、配置可以實現定時,定時時間到以後就會執行相應的定時器中斷處理函式。硬體定時器一般都帶有其它功能,比如PWM輸出、輸入捕獲等等功能。但是缺點是硬體定時器數量少!!
軟體定時器:
軟體定時器允許設定一段時間,當設定的時間到達之後就執行指定的功能函式,被定時器呼叫的這個功能函式叫做定時器的回撥函式。回撥函式的兩次執行間隔叫做定時器的定時週期,簡而言之,當定時器的定時週期到了以後就會執行回撥函式。
在FreeRTOS中有專門的軟體定時器功能,我們可以在MCU中簡單的是實現“軟體定時器”如下:
void timer_1000ms(void) { printf("timer_1000ms\r\n"); } int main(void) { static timer_tick = 0; timer_tick = systick_ms; while() { if((systick_ms-timer_tick)>1000) { timer_tick = systick_ms; timer_1000ms(); } } }
這就是簡單的軟體定時器,是的,這就是特別簡潔版本的軟體定時器。當然它是有缺點的,比如systick_ms每1ms加1,所以軟體定時器的精度是ms為單位的,並且如果while(1)中有其他程式碼阻塞,軟體定時器也會跟著阻塞的。
這個簡單的軟體定時器畢竟很"簡陋",大家可以自行封裝豐富一下,或者參考已經有的開源方案:MultiTimer,一款可無限擴充套件的軟體定時器。
MultiTimer 是一個軟體定時器擴充套件模組,可無限擴充套件你所需的定時器任務,取代傳統的標誌位判斷方式, 更優雅更便捷地管理程式的時間觸發時序。
開源地址: http://github.com/0x1abin/MultiTimer。
MultiTimer
MultiTimer的設計比較簡潔,只有2個檔案,並且只有4個函式,總共82行程式碼,稍微花一點功夫就可以理解明白。
移植步驟
- 配置系統時間基準介面,安裝定時器驅動
uint64_t PlatformTicksGetFunc(void) { /* Platform implementation */ } MultiTimerInstall(PlatformTicksGetFunc);
- 例項化一個定時器物件
MultiTimer timer1;
- 設定定時時間,超時回撥處理函式, 使用者上下指標,啟動定時器
int MultiTimerStart(&timer1, uint64_t timing, MultiTimerCallback_t callback, void* userData);
- 在主迴圈呼叫定時器後臺處理函式
int main(int argc, char *argv[]) { ... while (1) { ... MultiTimerYield(); } }
具體就不做手把手教程如何移植了,在STM32F207移植好的工程開源地址:
開源地址: http://github.com/strongercjd/STM32F207VCT6/tree/master/23-Timer-MultiTimer。
下面分析一下MultiTimer。
在移植的第一步,配置系統時間基準介面,安裝定時器驅動。
uint64_t PlatformTicksGetFunc(void) { /* Platform implementation */ } MultiTimerInstall(PlatformTicksGetFunc);
看一下MultiTimerInstall函式原型。
typedef uint64_t (*PlatformTicksFunction_t)(void); static PlatformTicksFunction_t platformTicksFunction = NULL; int MultiTimerInstall(PlatformTicksFunction_t ticksFunc) { platformTicksFunction = ticksFunc; return 0; }
這其實就是函式指標實現的回撥函式,其實就是給MultiTimer提供一個計數器。
除去回撥函式,該開源專案還是單鏈表的很好的示例,學習資料結構是比較乏味的,這個開源專案是單鏈表很好的應用落地,不太懂的同學可以學習下。下面摘取一下部分程式碼
連結串列的刪除:
for (; *nextTimer; nextTimer = &(*nextTimer)->next) { if (timer == *nextTimer) { *nextTimer = timer->next; /* remove from list */ break; } }
插入連結串列:
for (nextTimer = &timerList;; nextTimer = &(*nextTimer)->next) { if (!*nextTimer) { timer->next = NULL; *nextTimer = timer; break; } if (timer->deadline < (*nextTimer)->deadline) { timer->next = *nextTimer; *nextTimer = timer; break; } }
遍歷連結串列:
MultiTimer* entry = timerList; for (; entry; entry = entry->next) { /* Sorted list, just process with the front part. */ if (platformTicksFunction() < entry->deadline) { return (int)(entry->deadline - platformTicksFunction()); } /* remove expired timer from list */ timerList = entry->next; /* call callback */ if (entry->callback) { entry->callback(entry, entry->userData); } }
當然MultiTimer也是有缺點的,比如一個定時器是1000ms,另一個定時器是500ms,排程時就會衝突,也沒有定時器排程搶佔,會隨著其他程式碼的阻塞而阻塞。這種類似的問題不再詳述,大家使用的時候多測測就好。
任務排程
看了上面的操作,如果我們不叫軟體定時器,叫它“任務”,是不是和FreeRTOS任務類似,感覺高階一些,甚至這篇文章標題可以修改為《一篇文章教你實現作業系統》,開個歡笑,不做標題黨。
有些專案實時性要求高,需要任務搶佔,所以需要使用FreeRTOS這樣的作業系統,但它資源佔用比例過大,不利於專案開發,在一般的小專案中也用不到RTOS的太多功能,使用上面的思路,你可以把每個任務設定不同的間隔時間週期性呼叫,如果有實時性要求很高的事件,就通過中斷處理。
當然也可以使用開頭的粗糙方法:
if((systick_ms-timer_tick)>1000) { timer_tick = systick_ms; timer_1000ms(); }
這樣功能是可以實現的,但沒有模組化,不利於程式碼的維護。我們可以借鑑MultiTimer思路封裝一下軟體介面。
並且,如果你的專案中,任務的個數是固定不變的,可以將MultiTimer中的連結串列拿掉,直接使用全域性變數就可以,如果有額外的時間模仿FreeRTOS實現一些訊號量,對列等,這就是自己的OS(無搶佔)啊。(當然這屬於重複造輪子,但對一些公司來講,有適合自己業務的,最精簡的程式碼框架是很有必要的)。
本文轉載自微信公眾號「知曉程式設計」,可以通過以下二維碼關注。轉載本文請聯絡知曉程式設計公眾號。
- Spring中實現非同步呼叫的方式有哪些?
- 帶引數的全型別 Python 裝飾器
- 整理了幾個Python正則表示式,拿走就能用!
- SOLID:開閉原則Go程式碼實戰
- React中如何引入CSS呢
- 一個新視角:前端框架們都卷錯方向了?
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 手寫程式語言-遞迴函式是如何實現的?
- 一文搞懂模糊匹配:定義、過程與技術
- 新來個阿里 P7,僅花 2 小時,做出一個多執行緒永動任務,看完直接跪了
- Puzzlescript,一種開發H5益智遊戲的引擎
- @Autowired和@Resource到底什麼區別,你明白了嗎?
- CSS transition 小技巧!如何保留 hover 的狀態?
- React如此受歡迎離不開這4個主要原則
- LeCun再炮轟Marcus: 他是心理學家,不是搞AI的
- Java保證執行緒安全的方式有哪些?
- 19個殺手級 JavaScript 單行程式碼,讓你看起來像專業人士
- Python 的"self"引數是什麼?
- 別整一坨 CSS 程式碼了,試試這幾個實用函式
- 再有人問你什麼是MVCC,就把這篇文章發給他!