<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      嵌入式軟件架構漫談

      軟件架構的意義在于提高開發效率和代碼可維護性、可擴展性。

      剛好最近需要用到裸機開發,在此自我總結一下經驗和見解。如有錯誤,歡迎評論區指出。

      架構需要做到兩個維度的解耦:
      縱向的分層;
      橫向的模塊化

      分層好理解,可以看一下一個基于RTOS的軟件架構:
      image

      其作用在于后期的移植和排查只需要關注某一層級即可,比如更換芯片,那只需要修改驅動層即可;更換RTOS,那只需修改OS抽象層即可;按鍵任務控制LED任務功能失效,那就按照層級逐層排查(OS層的消息同步是否有問題?LED的亮滅驅動層是否有問題?)即可。
      軟件上的實現其實就是把函數盡量封裝成抽象的接口。以存儲功能為例子:

      • 對于上層應用調用者來說,需求很簡單,能存能讀即可;
      • 不同存儲類型有不同的存儲步驟,如flash需要按頁擦寫,EEPROM可以按字節寫入還不需要擦操作;
      • 如果存儲數據量小、存儲頻繁,那還要考慮擦寫均衡;
      • 不同存儲芯片有不同廠商不同型號,那寄存器的定義自然不同;
      • 芯片的通信方式也有不同,SPI、IIC等等;

      一個小小的存儲需求,在實現細節上卻五花八門,我們可以簡單分出三個層級:
      image

      這樣,后續的項目甚至可以像搭積木一樣,開發者專心適配積木接口,可以極大提高開發效率,且由于其復用得到反復驗證,穩定性也會不斷提高。

      難點在于如何做好橫向的模塊化。因為本身模塊之間就需要交互,層級也不是都只在應用層,那天然就會和追求的模塊獨立性相矛盾。
      設計模塊化會涉及到三個問題:

      1.怎么分模塊?

      模塊的劃分可以參考“功能”單一性、通用性和分層來劃分。
      以AT指令AT+LED控制LED亮滅功能為例,整個程序的實現鏈路如下:uart串口接收指令->指令解析->控制LED。那就可以分成三個模塊:
      1)驅動模塊:負責接收指令;
      2)協議模塊:負責解析AT指令;
      3)LED模塊:負責控制LED燈亮滅;
      每個模塊只負責單一功能,這樣任一模塊的變動都不會影響到其它模塊。同時可以復用模塊,協議模塊不僅可以解析串口收到的指令,也能同時解析如usb收到的指令。

      2.模塊怎么運行?

      a.時間片線性輪詢

      這是裸機最常用的架構,固定時間片對所有程序走一遍。注意這個時間片的長度設計,太長會導致系統響應慢,太短會導致輪詢一遍的時間大于時間片。
      優點是結構簡單明了。
      缺點也很明顯,實時性會比較查。

      int g_10ms_flag = 0;
      
      void systick_irq_handle(void)
      {
          g_10ms_flag = 1;
      }
      
      int main(void)
      {
          sysytick_init(); // 10ms
      
          while(1) {
              if (g_10ms_flag == 1) {
                  g_10ms_flag = 0;
      
                  key_process();
                  led_process();
              }
          }
      }
      

      b.調度表驅動

      這種方式是時間片線性輪詢的進階版,上面提到其缺點是實時性較差且時間片不能設置太短。因為所有任務不是都必須在同一周期進行輪詢,那我們可以把任務劃分,比如10ms輪詢、200ms輪詢等。而同一周期的還可以進一步作起點偏移,比如A偏移0ms,B偏移3ms,那就會在0ms執行A,3ms執行B,10ms執行A,13ms執行B......如此盡管AB任務周期都是10ms,但避免了同一時刻觸發有效分散系統負載。
      缺點是所有模塊都是在調度表中靜態配置,嚴格周期執行的,靈活性稍有欠缺。

      #define TASK_NUM 2
      
      typedef struct {
          int offset;    // 相對于周期起點的偏移
          int period;    // 周期
          void (*task_func)(void);  // 任務函數指針
      } schedule_entry_t;
      
      int g_system_tick = 0;
      
      void key_process(void);
      int led_process(void);
      
      schedule_entry_t schedule_table[TASK_NUM] = {
          { .offset = 0,   .period = 20, .task_func = key_process },
          { .offset = 3,   .period = 10, .task_func = led_process },
      };
      
      void systick_irq_handle(void)
      {
          g_system_tick++;
      }
      
      // 調度器函數
      void scheduler_tick(void) {
          for (int i = 0; i < TASK_NUM; i++) {
              schedule_entry_t *task = &schedule_table[i];
      
              if (((g_system_tick - task->offset) % task->period) == 0) {
                  task->task_func();
              }
          }
      }
      
      int main(void)
      {
          sysytick_init(); // 1ms
      
          while (1) {
              scheduler_tick();
          }
      
          return 0;
      }
      

      c.事件驅動

      有的模塊并不需要去周期輪詢,而只有當某個事件觸發后才去執行。比如,只有按鍵按下才去亮燈。
      好處是響應會更快,不需要等下一個時間片才執行;解耦性也好,每個模塊只要定義好什么事件觸發,需要執行其它模塊執行也只要發出事件即可。
      缺點事件如果一多,那管理和邏輯也會變得復雜,而且解耦性越好,那時序的控制力會越弱。
      為方便演示,先寫一個簡單的示例:

      #define PIN_KEY_1  P1_0
      #define EVENT_TICK (1 << 0)
      #define EVENT_KEY  (1 << 1)
      
      int g_event = 0;
      
      void systick_irq_handle(void)
      {
          g_event |= EVENT_TICK;
      }
      
      void key_process(void)
      {
          if (PIN_KEY_1 == 0) {
              g_event |= EVENT_KEY;
          }
      }
      
      int main(void)
      {
          sysytick_init(); // 10ms
      
          while(1) {
              if (g_event & EVENT_TICK) {
                  g_event ^= EVENT_TICK;
                  key_process();
              }
              if (g_event & EVENT_KEY) {
                  g_event ^= EVENT_KEY;
                  led_process();
              }
          }
      }
      

      大致如上例,通過事件來觸發執行,現在我們再進一步封裝一下,寫一個事件調度器:

      #define PIN_KEY_1  P1_0
      #define MAX_EVENTS       10
      #define MAX_HANDLERS     5
      
      // 事件類型
      typedef enum {
          EVENT_NONE = 0,
          EVENT_TICK,
          EVENT_KEY,
          EVENT_MAX
      } event_type_e;
      
      // 定義事件回調函數指針類型
      typedef void (*event_handler)(void);
      
      // 事件注冊表
      event_handler handlers[EVENT_MAX][MAX_HANDLERS];
      
      // 簡單環形隊列,用來存儲觸發過的事件
      typedef struct {
          event_type_e queue[MAX_EVENTS];
          int head;
          int tail;
      } event_queue_t;
      
      event_queue_t g_event = {0};
      
      // 注冊事件處理器
      void register_event_handler(event_type_e type, event_handler handler)
      {
          for (int i = 0; i < MAX_HANDLERS; ++i) {
              if (handlers[type][i] == NULL) {
                  handlers[type][i] = handler;
                  break;
              }
          }
      }
      
      // 觸發事件
      void trigger_event(event_type_e type)
      {
          g_event.queue[g_event.tail] = type;
          g_event.tail = (g_event.tail + 1) % MAX_EVENTS;
      }
      
      // 事件調度器
      void process_events(void)
      {
          while (g_event.head != g_event.tail) {
              event_type_e type = g_event.queue[g_event.head];
              g_event.head = (g_event.head + 1) % MAX_EVENTS;
      
              for (int i = 0; i < MAX_HANDLERS; ++i) {
                  if (handlers[type][i] != NULL) {
                      handlers[type][i]();
                  }
              }
          }
      }
      
      void systick_irq_handle(void)
      {
          trigger_event(EVENT_TICK);
      }
      
      void key_process(void)
      {
          if (PIN_KEY_1 == 0) {
              trigger_event(EVENT_KEY);
          }
      }
      
      void led_process(void)
      {
      
      }
      
      int main(void)
      {
          sysytick_init(); // 10ms
      
          // 注冊事件回調
          register_event_handler(EVENT_TICK, key_process);
          register_event_handler(EVENT_KEY, led_process);
      
          while (1) {
              process_events();
          }
      
          return 0;
      }
      

      事件調度器封裝起來后,使用就更加簡單了。開發者只需三步:1)增加事件類型;2)注冊事件回調函數;3)在需要的地方調用觸發事件接口函數。
      還有消息驅動模型、發布-訂閱模型,都是和事件驅動類似,改造一下調度器就可以實現了。原理一致就不展開了,請自行查閱。

      d.狀態機

      狀態機是嵌入式非常常見的設計模式,軟件架構也是能參考使用的。
      優點是狀態流程明確,易于維護。
      缺點是狀態一多復雜度就會暴漲,特別是狀態中還有子狀態時。所以一般不會只用狀態機,會搭配其它架構一起使用,狀態機只用來管理子模塊。

      #define PIN_KEY_1  P1_0
      
      // 定義狀態枚舉
      typedef enum {
          STATE_IDLE,
          STATE_RUNNING,
          STATE_WORKING,
          STATE_ERROR
      } state_e;
      
      // 當前狀態變量
      state_e g_current_state = STATE_IDLE;
      
      void systick_irq_handle(void)
      {
          if (g_current_state == STATE_IDLE) {
              g_current_state = STATE_RUNNING;
          }
      }
      
      void key_process(void)
      {
          if (PIN_KEY_1 == 0) {
              g_current_state = STATE_WORKING;
          } else {
              g_current_state = STATE_IDLE;
          }
      }
      
      int led_process(void)
      {
      
      }
      
      void error_process(void)
      {
      
      }
      
      // 狀態機處理函數
      void state_handle(void)
      {
          switch (g_current_state)
          {
              case STATE_IDLE:
                  break;
              case STATE_RUNNING:
                  key_process();
                  break;
              case STATE_WORKING:
                  key_process();
                  if (led_process() == 0) {
                      g_current_state = STATE_ERROR;
                  }
                  break;
              case STATE_ERROR:
                  error_process();
                  g_current_state = STATE_IDLE;
                  break;
              default:
                  break;
          }
      }
      
      int main(void)
      {
          sysytick_init(); // 10ms
      
          while (1) {
              state_handle();
          }
      
          return 0;
      }
      

      e.基于任務RTOS

      這里就是直接上系統調度器了,缺點是開銷大,復雜度高,需要注意死鎖、溢出等問題,一旦出了問題排查難度較大。這里主要討論裸機情形,請自行學習,不過多描述了。RTOS也是嵌入式軟件必學技能,等學習實踐后再回頭看本文會有不同體會的。

      實際項目中,是很少使用單一模型的,一般會視情況組合使用,比如事件驅動+狀態機、事件驅動+消息驅動、時間片周期輪詢+狀態機等等,取長補短。

      3.模塊間怎么交互?

      模塊雖然解耦,當本質運行仍存在著時序依賴、數據依賴、狀態依賴等依賴關系,所以就需要有同步機制,來輔助我們實現“異步”編程。

      a.函數調用

      這是最簡單直觀的方式,開銷最小,缺點就是強耦合,復用性低。上文AT指令控制LED的例子,AT協議解析完如果直接調用LED控制函數,便是該方法。

      b.接口回調

      模塊A提供一個函數指針,在模塊B中注冊,由模塊 B 在需要時去調用。上文中事件驅動框架中的事件調度器就是該方法。

      c.信號/事件/消息/廣播機制

      模塊間通過隊列、郵箱、事件等各種異步通信方式來傳遞信息,這是RTOS都會提供同步機制,會帶來一定的開銷,不同機制的開銷不同,局限性也不同,需要根據需求場景選用夠用的機制即可。例如,systick中斷,需要同步到key按鍵去檢測,那使用信號即可,就不需要使用消息的方式,節省資源開銷。(嵌入式軟件其實就是性能和資源的平衡藝術)

      d.共享內存

      多個模塊訪問同一個數據空間,往往需要配合互斥鎖、臨界區保護。優點是簡單高效,但是并發控制復雜,會增加出錯概率。是裸機最常用的同步方式了。

      總結一下:

      架構 = 模塊化 + 分層
      分層 = 清晰職責 + 接口抽象
      模塊化 = 清晰職責 + 解耦通信 + 合理調度模型

      posted @ 2025-08-06 19:27  HughWu  閱讀(155)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 欧美日韩在线亚洲二区综二| 国产最大成人亚洲精品| 国产睡熟迷奷系列网站| 精品熟女日韩中文十区| 亚洲人成网网址在线看| 高级艳妇交换俱乐部小说| 欧美在线精品一区二区三区| 国产二区三区视频在线 | 精品国产美女福到在线不卡| 国产精品自拍午夜福利| 日韩欧美一中文字暮专区| 国产互换人妻xxxx69| 丰满少妇被猛烈进出69影院| 国产69精品久久久久人妻刘玥| 精品偷拍一区二区三区| 国产第一页浮力影院入口| 边添小泬边狠狠躁视频| 办公室强奷漂亮少妇视频| 天堂V亚洲国产V第一次| 少妇人妻偷人精品免费| 久草热在线视频免费播放| 在线看无码的免费网站| 肉大捧一进一出免费视频| 国产边打电话边被躁视频| 又大又长粗又爽又黄少妇毛片| 亚洲国产午夜精品福利| 久久久精品2019中文字幕之3| 无码AV中文字幕久久专区| 日韩精品三区二区三区| 在线视频中文字幕二区| 精品日韩亚洲av无码| 久久精品道一区二区三区| 日韩成人无码影院| 久久精品熟妇丰满人妻久久| 国产中文字幕精品免费| 亚洲乱人伦中文字幕无码| 狠狠亚洲超碰狼人久久| 日韩女同在线二区三区| 欧美乱大交aaaa片if| 亚洲天堂av在线一区| 人人人澡人人肉久久精品|