SmartTimer的開發思路

来源:http://www.cnblogs.com/elecsun/archive/2016/08/03/5733676.html
-Advertisement-
Play Games

今天來講一講我開發SmartTimer的思路。在上一篇介紹SmartTimer的文章 "《SmartTImer——一個基於STM32的時鐘管理器》" 中,我提到了要實現延遲XX毫秒執行XX函數的功能,比較好的方式是在定時器中斷中設置溢出標誌,而在程式主迴圈中檢測這個標誌,如果標誌置位則運行回調函數。 ...


今天來講一講我開發SmartTimer的思路。在上一篇介紹SmartTimer的文章《SmartTImer——一個基於STM32的時鐘管理器》中,我提到了要實現延遲XX毫秒執行XX函數的功能,比較好的方式是在定時器中斷中設置溢出標誌,而在程式主迴圈中檢測這個標誌,如果標誌置位則運行回調函數。這樣不僅可以實現非同步的操作,最大化利用MCU的計算資源,而且可以最小化定時器的中斷時間。只不過每設置一個定時器事件,就需要為這個事件設定全局標誌位,並且為其單獨計數、檢測標誌位。如果處理不好,將會使全局標誌散落在程式的各個角落,不僅使程式結構臃腫,而且會留下bug隱患。

基於以上原因,我明確了SmartTimer的開發目的,就是統一管理各個定時器事件,將定時計數、判斷定時溢出,執行回調函數都封裝起來。使用者在SmartTimer外部不必關心具體的內部實現,只需要用一行語句即可完成非同步延遲的操作,不必進行額外的操作。除了簡便外另一個好處是,即便定時器部分出現了程式上的bug,也使得問題更加集中,便於定位bug而進行修改。

在對外功能上,提供runlater,runloop,delay這三個功能。

SmartTimer的初步構想

明確了開發目的,就開始構建SmartTImer了。我考慮的SmartTimer架構其實很簡單,總體上分為三大塊,如下圖所示:

簡要說明一下這個系統框圖:

  1. 用戶調用runlater、runloop、delay後,會從記憶體池中拿出一個event對象並初始化,push進event鏈表中;
  2. 定時器中斷函數每隔Xms執行一次,遍歷整個event鏈表,給每個event事件定時+1,並判斷定時時間是否到達。如果到達則將該事件的溢出計數+1;如果定時迴圈次數不為0,則重新計數,否則將該事件對象從event鏈表中轉移到recycle數組中。
  3. 在主while迴圈中,查詢mark table,如某事件的溢出計數大於0則執行對應的回調函數,並將溢出計數-1;查詢recycle數組,如果有需要回收的event事件,則釋放資源到記憶體池中。

架構出來後,本著先完成再優化的原則,稍微考慮下優化方針,就可以動手實現了。由於SmartTimer沒有搶占機制,所以屬於非實時性工具。但理論上來說,如果以上描述的3大塊程式都執行的非常快,那麼就可以理解為接近“實時”了。所以為了提高實時性,需要提高程式的執行速度。在代碼優化上,我主要採用了空間換時間的方法,犧牲了一部分記憶體來換取執行速度。主要用在event pool上,以及用數組映射來設置溢出標誌和執行回調函數等地方。

SmartTimer的初步實現

首先抽象出定時器事件的定義:

    struct stim_event{
      uint16_t interval;   //定時事件的定時間隔 
      uint16_t now;        //定時事件的當前計數  
      uint16_t looptimes;  //loop次數 
      uint8_t addIndex;    //事件對象在記憶體池中的序號 
      uint8_t stat;        //定時事件的當前狀態
      struct stim_event *next;
      struct stim_event *prev;
    };

從結構體定義上可以看出,我準備用雙向鏈表來組織這些定時器事件。用鏈表而不是數組來構建事件list,是因為定時器事件的加入和移除是動態的,且有時會從list中間增減事件,所以用鏈表比數組更加合理。

在SmartTimer初始化時,會預先創建一個stim_event的數組作為event pool來使用。另外還會創建兩個元素數量與event pool相同的數組,一個是MarkArray,用來存儲event的溢出計數;一個是CallbackArray,用來存儲回調函數的函數指針。event、溢出計數、回調函數用stim_event中的addIndex聯繫起來。

event->addIndex為該event在event pool數組中的序號(數組下標),相應的MarkArray和CallbackArray數組中,序號為event->addIndex的元素,存儲著該event的溢出計數和回調函數指針。當某個event計時時間到,那麼MarkList中第event->addIndex個元素溢出計數+1,將CallbackArray數組中第event->addIndex個元素存儲的函數指針調用一次。

定時器中斷函數,主要是遍歷event鏈表,進行定時計數並判斷是否定時溢出。

    void stim_tick (void)
    {
      struct stim_event *tmp;
      if (event_list.count == 0)
        return;
    
      tmp = event_list.head;
        
      while(tmp != NULL){ //遍歷event鏈表
        if(tmp->stat != STIM_EVENT_ACTIVE){
                tmp = tmp->next;
          continue;
        }
        
        if(tmp->now == tmp->interval){
          mark_list[tmp->addIndex] += 1;  //時間到,溢出計數+1
                if((tmp->looptimes != STIM_LOOP_FOREVER) && 
                    (--tmp->looptimes == 0)){   // 迴圈次數為0,移除事件鏈表
                    tmp->stat = STIM_EVENT_RECYCLE;
                    recycle_list[recycle_count] = tmp;
                    recycle_count++;
                }
          tmp->now = 0;
        }
    
        tmp->now++;
        tmp = tmp->next;
      }
    }   

在主while迴圈中,主要是執行callback函數,以及回收event事件對象

    void stim_mainloop ( void )
    {
      uint8_t i;
      struct stim_event *tmp;
      
      for(i = 0; i < STIM_EVENT_MAX_SIZE; i++){  //檢查溢出標誌,並執行回調函數
        if((mark_list[i] != STIM_INVALID) && (mark_list[i] > 0)){
          if(callback_list[i] != NULL){
            callback_list[i]();
          }
          mark_list[i] -= 1;
        }
      }
    
      if(recycle_count > 0){
        for(i = 0; i < STIM_EVENT_MAX_SIZE; i++){  //如果有需要回收的event,則回收對象
          tmp = recycle_list[i];
          if(tmp != NULL && mark_list[tmp->addIndex] == 0){
            pop_event(tmp);
            recycle_list[i] = NULL;
            recycle_count--;
            break;
          }
        }
      }
    
    }   

以上是SmartTimer 1.0版本的核心代碼。至此,SmartTimer已經可以運行起來了,它大致上完成了我的設計目的,但是並沒有完成我的設計目標——它運行的效率並不高,這樣以來,實時性就會大打折扣。但是SmartTimer的設計架構我認為還是合理的,只需在這個框架內對調度演算法進一步優化,就可以更完善了。下一篇文章,我會介紹我的優化思路和實現方法,敬請期待!

SmartTimer的應用技巧

《SmartTImer——一個基於STM32的時鐘管理器》中,我已經詳細介紹過SmartTimer的一般使用方法,不再贅述,下麵來簡單談談高級用法。

我們先來看看傳統的單片機程式架構:

    void main (void)
     {
       system_init();    //系統初始化
       peripheral_init();//外設初始化
       while(1){
          switch(step){  //主程式狀態機
             case A: // do something
                     break;
             case B: //do something
                     break;
              ……
             default: //do something
                     break;
          }

          usart_driver();//串口驅動
          timer_event_handler();//定時器事件處理
          ……
       } 
     }

一般來說都是先進行系統各項初始化,然後就進入了主while迴圈中,在while迴圈中,主要執行系統狀態機,用來完成當發生xx事後,執行xx函數的功能。在狀態機之外,有一些輔助性的功能,例如系統Led呼吸燈驅動、串口驅動、定時事件回調函數等等。

我們單看主while迴圈內的部分,做一個最簡模型。如果假定switch狀態機部分的執行需要100ms,串口驅動執行需要10ms,定時器事件處理執行需要20ms,那麼while內的程式每迴圈一次使用130ms。我們其實可以換個角度來看這段程式,即switch狀態機每隔30ms執行一次,串口驅動每隔120ms執行一次,而定時事件處理函數每隔110ms執行一次。

是不是很神奇?如果基於時間的角度來考慮問題,那麼本來交織在一起的程式被輕易的模塊化了,模塊化的好處顯而易見,不僅程式架構簡單清晰,而且便於維護。利用SmartTimer的runloop功能可以輕鬆實現基於時間的模塊化程式架構。我們來看看將上面的例子進行基於時間的模塊化修改:

    static void state_machine(void)
    {
      switch(step){  //主程式狀態機
        case A: // do something
          break;
        case B: //do something
          break;
          ……
        default: //do something
            break;
      }
    }

    
    void main (void)
    {
      system_init();    //系統初始化
      peripheral_init();//外設初始化
    
      stim_loop(30,state_machine,STIM_LOOP_FOREVER);     
      stim_loop(120,usart_driver,STIM_LOOP_FOREVER);   
      stim_loop(110,timer_event_handler,STIM_LOOP_FOREVER);
      ……
      while(1){
        stim_mainloop();
      } 
    }

架構是不是瞬間變簡潔了?另外,照這個思路考慮問題,狀態機部分的程式也可以分解成若幹個模塊,使程式更加簡潔、優雅。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 成功安裝了Oracle 11g後,使用sqlplus登錄資料庫時遇到下麵錯誤: [oracle@DB-Server ~]$ sqlplus / as sysdba sqlplus: error while loading shared libraries: /u01/app/lib/libclnts... ...
  • 計算string所占的位元組長度:返回字元串的長度,單位是 計算string所占的字元長度:返回字元串的長度,單位是 eg: //去掉該欄位後面15位字元串 select t.depre_name, substr(t.depre_name, 0, (length(t.depre_name) 16)) ...
  • 一. 創建表的方法 語法:create table 表名( 屬性名數據類型完整約束條件, 屬性名數據類型條完整約束件, 。。。。。。。。。 屬性名數據類型 ); (1)舉例:1 create table example0( 2 id int, 3 name varchar(20), 4 sexboo ...
  • Kafka是目前非常流行的消息隊列中間件,常用於做普通的消息隊列、網站的活性數據分析(PV、流量、點擊量等)、日誌的搜集(對接大數據存儲引擎做離線分析)。 全部內容來自網路,可信度有待考證!如有問題,還請及時指正。 概念介紹 在Kafka中消息隊列分為三種角色: ,即生產者,負責產生日誌數據。 ,存 ...
  • 需求: 一篇文章里有很多評論,每個評論又有很多回覆評論,要求: 頁面將文章展示出來,且文章的主評論按照評論時間分頁展示,回覆評論的評論完全展示在每個主評論下麵,且按照回覆時間排序 最終查詢結果SQL查詢結果如下: Code: 評論編碼,ParentCode:回覆評論編碼,num:主評論序號,lvl: ...
  • 問題描述:在表列里有肉眼不可見字元,導致一些更新或插入失敗。 幾年前第一次碰見這種問題是在讀取考勤機人員信息時碰見的,折騰了一點時間,現在又碰到了還有點新發現就順便一起記錄下。 轉載註明出處:http://www.cnblogs.com/zzry/p/5729404.html 如下圖所示 golds ...
  • 背景 使用Exp命令在oracle 11g 以後不導出空表(rowcount=0),是最近在工作中遇到一個很坑的問題,甚至已經被坑了不止一次,所以這次痛定思痛,準備把這個問題徹底解決。之所以叫新方法,那一定有老方法了,這個方法是一位博友很早就提出了,以下是原文,其實也說明瞭問題的原因 Oracle1 ...
  • 網路進程間通信:socket API簡介 不同電腦(通過網路相連)上運行的進程相互通信機制稱為網路進程間通信(network IPC)。 在本地可以通過進程PID來唯一標識一個進程,但是在網路中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網路層的“ip地址”可以唯一標識網路中的主 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...