<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 設計應用 > 嵌入式軟件架構設計:建立抽象層

          嵌入式軟件架構設計:建立抽象層

          作者:時間:2023-12-14來源:收藏

          這東西,眾說紛紜,各有觀點。什么是,我們能在網上找到無數種定義。比如,我們可以這樣定義:是軟件系統的基本結構,體現在其組件、組件之間的關系、組件設計與演進的規則,以及體現這些規則的基礎設施。怎么定義一般來說,基本上不重要,我們不是在寫學術書籍,工程人員嘛,只關心軟件架構能解決什么問題。

          本文引用地址:http://www.ex-cimer.com/article/202312/453918.htm

          軟件架構不是制定出來的,而是產品和業務需求所決定的,架構師所做的,只是忠于需求,并合理的表達了需求。軟件架構也從來都不是一成不變的。在產品或者產品線的整個生命周期中,隨著業務和需求的變化,軟件架構不斷發展和變化,以適應新的需要。

          軟件架構不是一個簡單的項目問題,而是產品或產品線的技術戰略問題。一個良好設計并推廣的軟件架構,能帶來如下好處。

          · 最大限度地減少不必要的返工

          · 使軟件在宏觀層面建立規劃

          · 增強復用性,降低開發成本

          · 便于團隊內部的技術培訓

          · 使技術積累更加容易

          經??吹降囊粋€常見問題是,新手工程師,由于經歷與知識不足,往往看不到項目全貌,很難深刻理解軟件架構,他們往往要經過多年的專業訓練,才能逐漸建立架構意識。

          但軟件架構真的只是資深工程師和架構師的專利嗎?這個也不見得。古人作文,講究立意為先。

          今天工程師做項目和產品,也應該先立意。這個意,就是指要有高度。工程師入門能從軟件架構的高度出發,看待軟件問題,相信對軟件的理解,會更加深刻一些。因此,總結了軟件架構的六個步驟,供工程師參考。

          1. 隔離硬件相關代碼,建立

          2. 建立統一的軟件基礎設施

          3. 妥善識別和處理產品數據

          4. 功能分層與分解

          5. 組件及其接口設計

          6. 測試、調試與跨平臺開發的支持

          需要注意的是,這些并不足以保證工程師學會軟件架構。嵌入式軟件架構師,是不可培養的。但至少,嵌入式工程師們,可以了解到什么是正確的努力方向,很多時候,選擇比努力更加重要。

          嵌入式軟件架構之一 與硬件隔離

          許多新手乃至老手嵌入式工程師,在未了解軟件架構之前,把應用層功能和硬件相關的代碼,不由自主的攪和在一起寫。這種做法非常普遍。比如下面的代碼:

          void modbus_rtu_write_reply(uint8_t add, uint8_t func_code, uint16_t reg, uint16_t data)
          {
              rs485.buff_tx[0] = add;
              rs485.buff_tx[1] = func_code;
              rs485.buff_tx[2] = (uint8_t)(reg >> 8);
              rs485.buff_tx[3] = (uint8_t)(reg);
              rs485.buff_tx[4] = (uint8_t)(data >> 8);
              rs485.buff_tx[5] = (uint8_t)(data);
              uint16_t crc16 = mb_crc16(rs485.buff_tx, 6);
              rs485.buff_tx[6] = (uint8_t)(crc16);
              rs485.buff_tx[7] = (uint8_t)(crc16 >> 8);
              rs485.tx_total = 8;
              rs485.tx_num = 0;
              /* Send data from the uart port. The hardware related program. */
              LL_USART_ClearFlag_TC(USART1);
              LL_USART_EnableIT_TC(USART1);
              USART1->DR = rs485.buff_tx[rs485.tx_num ++];
          }

          上面的這一段代碼,不是一個好例子。從函數LL_USART_ClearFlag_TC開始的一句,也就意味著,這個Modbus的代碼,和MCU提供出的固件庫耦合在一起寫了。

          著名的SOLID原則中,有個依賴倒置原則,高層模塊不應該依賴于底層模塊,它們應該共同依賴于抽象。此處的代碼,顯然違反了這一原則。Modbus作為高層模塊,此處對MCU固件庫的API進行了依賴。

          對于這種將硬件相關的代碼與功能耦合在一起的軟件架構,在本文中,我們姑且稱之為“耦合架構”;而我們要追求的,是將隔離硬件相關的軟件架構,我們稱之為“隔離架構”。接下來,我們將詳細對比,耦合架構和隔離架構各自的特征。

          耦合架構的問題

          雖然從原則上來說,耦合架構是不對的,但萬事皆有因,存在即合理。一般而言,大部分嵌入式軟件工程師,都出自硬件相關的專業(比如電子、自動化等),來自于軟件工程和計算機專業的嵌入式工程師不多(他們都去互聯網行業了),因此從他們的知識結構和習慣思維出發,一般從硬件視角看待嵌入式系統,而不是站在軟件抽象的視角。

          但理解歸理解,道理歸道理,既然已經從事嵌入式軟件,哪怕是硬件專業出身的,我也建議他一定拋棄既有思維,學會抽象這一強大的軟件思維工具,否則他的職業天花板將非常低。

          耦合架構帶來的問題,也是顯而易見的,那就是,實實在在的難以移植。因為一旦硬件發生變化,比如MCU停產,芯片短缺等等(在當前形勢下太過常見),嵌入式軟件就要大把修改。如果軟件規模較大,嘗試移植耦合架構的代碼到在新MCU上,是一項艱巨的工作,沒人愿意干這事。因此產品開發完成,更新架構并推倒重來,幾乎是不可能。

          別說工程師不愿意,你問問老板答應嗎?于是工程師們只能檢查所有代碼,把與硬件交互的每一行代碼改掉,遇到硬件交互方式大不相同的,就更糟心,還要大篇幅的改,邊改邊罵娘。比如上面的代碼,如果換一片芯片,可能要改為以下代碼。

          void modbus_rtu_write_reply(uint8_t add, uint8_t func_code, uint16_t reg, uint16_t data)
          {
              rs485.buff_tx[0] = add;
              rs485.buff_tx[1] = func_code;
              rs485.buff_tx[2] = (uint8_t)(reg >> 8);
              rs485.buff_tx[3] = (uint8_t)(reg);
              rs485.buff_tx[4] = (uint8_t)(data >> 8);
              rs485.buff_tx[5] = (uint8_t)(data);
              uint16_t crc16 = mb_crc16(rs485.buff_tx, 6);
              rs485.buff_tx[6] = (uint8_t)(crc16);
              rs485.buff_tx[7] = (uint8_t)(crc16 >> 8);
              rs485.tx_total = 8;
              rs485.tx_num = 0;
           
             /* Send data from the uart port. The hardware related program. */
              MCU_NEW_USART_ClearFlag_TC(NEW_USART1);
              MCU_NEW_USART_EnableIT_TC(NEW_USART1);
              NEW_USART1->DR = rs485
          .buff_tx[rs485.tx_num ++];
          }

          其次,耦合架構會導致,在開發環境中(如Windows或者Linux,非目標硬件),很難對應用程序進行單元測試。脫離目標硬件,跨平臺開發嵌入式程序,是提升開發效率的重要措施。

          對耦合架構來說,應用程序代碼直接調用硬件,如果要進行完整的測試工作,就要花費大量工作,因為測試程序也要去操作硬件,才能驗證正確與錯誤?;蛘?,需要工程師在硬件上完成手動測試(實際上現在大家就這么干的,哈哈)。

          手動測試很繁瑣,往往讓人煩躁,工程師的主觀感受,會影響測試質量。很多時候,為了趕進度,或者規避繁瑣的測試工作,軟件并沒有經過很好的測試,整體系統質量受到影響。另外,手動測試,交付軟件可能需要更長的時間。而自動測試,往往只需要一瞬間,清楚明了。

          第三,耦合架構將存在不易擴展的問題。耦合架構,往往是共享數據的,也就是所謂的全局變量滿天飛。隨著軟件系統的擴大,每個新功能的添加,變得更加困難,而且是越來越困難,出現BUG的機會急劇增加。屎山就是這么煉成的。

          但需要說明的是,數據問題,不是說隔離了硬件,就能完全解決掉。數據問題,是嵌入式軟件乃至任何軟件的核心問題,它需要在架構六部曲之二和之三中,通過軟件基礎設施的合理構建,和數據機制的合理制定,共同得到解決。

          隔離架構如何解決問題?

          到這里,我們架構的第一步,呼之欲出,那就是:將軟件架構分離為硬件相關和硬件無關兩個部分。這就要引入這個概念。何為抽象層?抽象層有很多種,比如硬件抽象層(HAL)、設備抽象層(DAL),操作系統抽象層(OSAL),網絡抽象層,文件系統抽象層,Flash抽象層(RT-Thread里就有這個)等等。

          對誰進行抽象,就會建立這個東西的抽象層,無一定之規。本文中的抽象層,特指硬件抽象層,或者設備抽象層,或者二者兼備。具體是誰,取決于產品特性。

          在硬件相關代碼和硬件獨立代碼之間創建抽象層,這是軟件移植的要求,實際上也是依賴倒置原則需求。在這里,我們有必要對依賴倒置原則進行強調:高層模塊不應該依賴于底層模塊,它們應該共同依賴于抽象。也就是說,應用層代碼(硬件無關),不應該依賴于硬件相關的代碼(驅動代碼),他們應該依賴于抽象層代碼。

          抽象層的創建,將允許將應用代碼從一個微控制器移動到下一個微控制器,或者一套硬件遷移到另一套硬件,應用層代碼不必更換。抽象層打破了硬件依賴關系;換句話說,應用程序根本不必知道,也不必關心,當前運行的是什么硬件,應用程序只需要關心抽象層的API是什么樣的。

          新的硬件驅動程序要做的,僅僅是滿足接口的要求而已。這意味著如果我們更改硬件,則只會更改硬件相關的模塊,而不是整個代碼庫。

          void modbus_rtu_write_reply(uint8_t add, uint8_t func_code, uint16_t reg, uint16_t data)
          {
              rs485.buff_tx[0] = add;
              rs485.buff_tx[1] = func_code;
              rs485.buff_tx[2] = (uint8_t)(reg >> 8);
              rs485.buff_tx[3] = (uint8_t)(reg);
              rs485.buff_tx[4] = (uint8_t)(data >> 8);
              rs485.buff_tx[5] = (uint8_t)(data);
              uint16_t crc16 = mb_crc16(rs485.buff_tx, 6);
              rs485.buff_tx[6] = (uint8_t)(crc16);
              rs485.buff_tx[7] = (uint8_t)(crc16 >> 8);
              rs485.tx_total = 8;
              rs485.tx_num = 0;
              /* Send data from the uart port. The hardware related program. */
              hal_uart_send(HAL_UART_ID_1, rs485.buff_tx, rs485.tx_total);
          }
          void hal_uart_send

          硬件相關的代碼,應該改為如下的樣子。這尚且算不上真正的抽象層,只是抽象層最簡陋的替代實現方法,實際工程應用中,抽象層還有很多細節需要闡述。

          void hal_uart_send(uint8_t uart_id, void *buffer, uint32_t size)
          {
              /* Start the uart sending process, the remaning data will be send in UART ISR 
                 function. */

              MCU_NEW_USART_ClearFlag_TC(NEW_USART1);
              MCU_NEW_USART_EnableIT_TC(NEW_USART1);
              NEW_USART1->DR = rs485.buff_tx[rs485.tx_num ++];
          }

          抽象層還可以解決單元測試的許多問題。有了抽象層,我們可以在Windows或者Linux上創建硬件的替身程序(mock),也可以稱為假硬件。我們可以在假硬件上給出輸入數據,并通過檢查假硬件給出的輸出數據會否符合預期,來對軟件進行單元測試。在沒有硬件的情況,也可以對應用層程序進行開發。很多嵌入式程序員覺得不可能,但這時很多大公司開發軟件的方式。

          抽象層的建立,還有一個好處。軟件不必等著硬件就緒才開始開發,而在硬件可用之前,就開始專注于開發和交付應用程序。

          這樣做的好處是,可以在項目早期就對客戶提供試用服務,并根據客戶反饋進行功能調整。如今,太多的團隊專注于首先準備好硬件,而核心應用程序是事后才想到的。這樣并不利于對嵌入式軟件進行良好的設計和實現。

          那么如何建立抽象層呢?抽象層的建立,涉及到幾個關鍵的因素:抽象的程度、抽象的手段以及抽象的對象。這些問題,非常復雜,非三言兩語就能說清。

          結論

          嵌入式軟件與其他軟件領域都不一樣,因為沒有一個軟件領域,和嵌入式軟件一樣,會和硬件進行直接交互(請注意此處直接二字)。

          為了應對可能出現的硬件變化(無論是MCU,PCBA,還是連接PCBA的設備),嵌入式軟件架構師應該將硬件相關的代碼獨立出去,并壓縮在一個最小的范圍內。否則,一旦使用耦合架構,不對硬件相關代碼進行剝離,屎山式的代碼,幾乎是注定的結局。

          一個成功的軟件架構,從來不是一蹴而就,通常是通過迭代和演進創建的。這需要技術負責人,或者架構師,主動去推動軟件架構的迭代,不斷推動軟件的優化重構。這就有點像明星的好身材,從來不是天生,都是后天自律的結果。

          但在嵌入式領域,無論搞什么產品,搞什么復雜的軟件架構,剝離硬件相關,是第一步,也是最為關鍵的一步。連硬件相關代碼都剝不干凈,軟件架構就猶如浮沙筑高臺,無從談起。

          合抱之木,生于毫末,有志于提升技術水平的工程師們,先從隔離硬件開始吧。



          評論


          相關推薦

          技術專區

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();