色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁 > 設(shè)計應(yīng)用 > 嵌入式軟件架構(gòu)設(shè)計:建立抽象層

          嵌入式軟件架構(gòu)設(shè)計:建立抽象層

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

          這東西,眾說紛紜,各有觀點。什么是,我們能在網(wǎng)上找到無數(shù)種定義。比如,我們可以這樣定義:是軟件系統(tǒng)的基本結(jié)構(gòu),體現(xiàn)在其組件、組件之間的關(guān)系、組件設(shè)計與演進(jìn)的規(guī)則,以及體現(xiàn)這些規(guī)則的基礎(chǔ)設(shè)施。怎么定義一般來說,基本上不重要,我們不是在寫學(xué)術(shù)書籍,工程人員嘛,只關(guān)心軟件架構(gòu)能解決什么問題。

          本文引用地址:http://cafeforensic.com/article/202312/453918.htm

          軟件架構(gòu)不是制定出來的,而是產(chǎn)品和業(yè)務(wù)需求所決定的,架構(gòu)師所做的,只是忠于需求,并合理的表達(dá)了需求。軟件架構(gòu)也從來都不是一成不變的。在產(chǎn)品或者產(chǎn)品線的整個生命周期中,隨著業(yè)務(wù)和需求的變化,軟件架構(gòu)不斷發(fā)展和變化,以適應(yīng)新的需要。

          軟件架構(gòu)不是一個簡單的項目問題,而是產(chǎn)品或產(chǎn)品線的技術(shù)戰(zhàn)略問題。一個良好設(shè)計并推廣的軟件架構(gòu),能帶來如下好處。

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

          · 使軟件在宏觀層面建立規(guī)劃

          · 增強復(fù)用性,降低開發(fā)成本

          · 便于團隊內(nèi)部的技術(shù)培訓(xùn)

          · 使技術(shù)積累更加容易

          經(jīng)常看到的一個常見問題是,新手工程師,由于經(jīng)歷與知識不足,往往看不到項目全貌,很難深刻理解軟件架構(gòu),他們往往要經(jīng)過多年的專業(yè)訓(xùn)練,才能逐漸建立架構(gòu)意識。

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

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

          1. 隔離硬件相關(guān)代碼,建立

          2. 建立統(tǒng)一的軟件基礎(chǔ)設(shè)施

          3. 妥善識別和處理產(chǎn)品數(shù)據(jù)

          4. 功能分層與分解

          5. 組件及其接口設(shè)計

          6. 測試、調(diào)試與跨平臺開發(fā)的支持

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

          嵌入式軟件架構(gòu)之一 與硬件隔離

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

          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 ++];
          }

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

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

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

          耦合架構(gòu)的問題

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

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

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

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

          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 ++];
          }

          其次,耦合架構(gòu)會導(dǎo)致,在開發(fā)環(huán)境中(如Windows或者Linux,非目標(biāo)硬件),很難對應(yīng)用程序進(jìn)行單元測試。脫離目標(biāo)硬件,跨平臺開發(fā)嵌入式程序,是提升開發(fā)效率的重要措施。

          對耦合架構(gòu)來說,應(yīng)用程序代碼直接調(diào)用硬件,如果要進(jìn)行完整的測試工作,就要花費大量工作,因為測試程序也要去操作硬件,才能驗證正確與錯誤。或者,需要工程師在硬件上完成手動測試(實際上現(xiàn)在大家就這么干的,哈哈)。

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

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

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

          隔離架構(gòu)如何解決問題?

          到這里,我們架構(gòu)的第一步,呼之欲出,那就是:將軟件架構(gòu)分離為硬件相關(guān)和硬件無關(guān)兩個部分。這就要引入這個概念。何為抽象層?抽象層有很多種,比如硬件抽象層(HAL)、設(shè)備抽象層(DAL),操作系統(tǒng)抽象層(OSAL),網(wǎng)絡(luò)抽象層,文件系統(tǒng)抽象層,F(xiàn)lash抽象層(RT-Thread里就有這個)等等。

          對誰進(jìn)行抽象,就會建立這個東西的抽象層,無一定之規(guī)。本文中的抽象層,特指硬件抽象層,或者設(shè)備抽象層,或者二者兼?zhèn)?。具體是誰,取決于產(chǎn)品特性。

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

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

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

          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

          硬件相關(guān)的代碼,應(yīng)該改為如下的樣子。這尚且算不上真正的抽象層,只是抽象層最簡陋的替代實現(xiàn)方法,實際工程應(yīng)用中,抽象層還有很多細(xì)節(jié)需要闡述。

          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上創(chuàng)建硬件的替身程序(mock),也可以稱為假硬件。我們可以在假硬件上給出輸入數(shù)據(jù),并通過檢查假硬件給出的輸出數(shù)據(jù)會否符合預(yù)期,來對軟件進(jìn)行單元測試。在沒有硬件的情況,也可以對應(yīng)用層程序進(jìn)行開發(fā)。很多嵌入式程序員覺得不可能,但這時很多大公司開發(fā)軟件的方式。

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

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

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

          結(jié)論

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

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

          一個成功的軟件架構(gòu),從來不是一蹴而就,通常是通過迭代和演進(jìn)創(chuàng)建的。這需要技術(shù)負(fù)責(zé)人,或者架構(gòu)師,主動去推動軟件架構(gòu)的迭代,不斷推動軟件的優(yōu)化重構(gòu)。這就有點像明星的好身材,從來不是天生,都是后天自律的結(jié)果。

          但在嵌入式領(lǐng)域,無論搞什么產(chǎn)品,搞什么復(fù)雜的軟件架構(gòu),剝離硬件相關(guān),是第一步,也是最為關(guān)鍵的一步。連硬件相關(guān)代碼都剝不干凈,軟件架構(gòu)就猶如浮沙筑高臺,無從談起。

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



          評論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉