韌體更新設計實例 (一) 流程設計

遠端韌體更新是嵌入式系統開發者不可或缺的方便工具之一,筆者使用TI 的TMS320F280025C 並搭配 Raspberry Pi 作為傳送更新的系統,透過 I2C 介面將編譯好的 .hex 韌體檔案由 Raspberry Pi傳送到 MCU ,並且正確執行更新後的應用程式。想知道怎麼做到的讀者請一定要閱讀這系列的文章!
{alertInfo}

目錄

    前言

    數位電源一直是未來電源產業的一個趨勢,不僅可以實現傳統類比電源無法達成的可編程性之外,可拓展性以及可維護性都比以往更佳。其中最具潛力的就是透過韌體更就能為原有的產品進行加值,不僅可以修補漏洞之外,也能使得產品壽命更長。
    但並不是每一種微處理器都有內建的韌體更新功能,目前只有高階產品才有比較大的記憶體空間來存放兩個不同版本的韌體,為了講求低成本的電源產業,勢必無法採用更為而貴的 MCU,除此之外,高階微處理器也增加許多與電源無關的功能,導致無謂的成本浪費。
    講到數位電源,另一個強大的功能就是他的通訊。PMBus (Power Management Bus) 功率管理匯流排是電源產業的一套通訊標準,透過預先定義好的指令以及資料格式,讓不同供應商的 IC 可以互相控制與通訊,消弭電源與系統之間通訊的高牆,讓越來越聰明的系統可以監測到電源變化進而降低碳排放來保護我們的地球。
    筆者使用的專案採用初階 MCU TMS320F280025C,這是一顆瞄準工業控制領域的產品,沒有太多花俏的周邊,是實實在在的一顆 MCU。也因此需要從頭手動撰寫出一套韌體更新的流程,讓我們的產品可以透過軟體更新來達成增加功能的目的。 這一系列文章會分成五個部分:
  1. 本篇會說明韌體更新的主要需求,更新步驟流程圖以及選用 MCU 的限制
  2. 第二篇說明 MCU 啟動程序的原理還有切換韌體的方法
  3. 第三篇說明韌體包格式選擇,並依照檔案格式設計對應到接收程式
  4. 第四篇說明收到封包後如何寫入到 MCU 內部的方法,以及他的效能量測
  5. 最後一篇則是演示韌體更新的過程,以及開發過程中的實驗與Bug


  6. F280025C 的硬體參數與限制

    TMS320F280025C 是一顆由德州儀器生產的實時微處理機(Real time MCU),被廣泛應用在工業控制領域。從馬達控制,機械手臂,逆變器以及數位電源都有他的身影,眾所周知的電動車大廠 特斯拉 Tesla 也使用他們家的高階處理器 TMS320F2611P8KO來滿足嚴苛的環境與需求。

    然而,我們使用的 F280025C 是整個系列最低階的處理器。最大的優點是省錢,一顆零售價只要 2.26美金;缺點的話就是快閃記憶體容量小,系統運作時脈慢,且能用的周邊模組不多。考量到我們的應用也只會用到基本的 ADC 量測還有基本的 UART, I2C 通訊,說實話這麼多模組也不太能憑藉一己之力讓它們物盡其用。只能期許自己在未來可以成為精通這方面的工程師,讓自己可以一個打三個。更多關於這系列 MCU 的開發經驗,歡迎閱讀有C2000 標記的其他文章

    言歸正傳,這顆 MCU 重點規格如下:
    CPU C28x
    Frequency 100 MHz
    RAM 24 KB
    Flash 128 KB
    ADC Resolution 12 bit
    Total processing 100 MPIS
    UART 3
    CAN 1
    PWM(ch) 14
    ADC Channels 14, 16
    SPI 2

    挑選 MCU 的核心指標包含主頻率、快取記憶體大小、快閃記憶體大小,以及應用所需要使用到的周邊設備有哪些。針對訊號量測的應用,也可以依照預算挑選高解析度的類比數位轉換器。
    在這系列處理器的天梯圖中我們就已經知道這顆 MCU 的快閃記憶體是最小的,在這有限的空間中所有程式碼都是寸土寸金,這也是我們想在這顆低成本 MCU 上實作韌體更新的最大挑戰。

    韌體更新方法設計與流程圖

    生活中不管是用手機或者用電腦,常常會遇到更新。當更新的時候螢幕也會提醒你不可以關閉電源,或者手機一定要在電池 50% 以上才能更新。這背後除了要避免安裝中斷之外,也跟各個廠商採用的更新方式有關。
    常見的更新方式會像這樣
    1. 主機將新的韌體透過網路傳送到客戶端
    2. 客戶端下載好完整檔案並進行檢查
    3. 確認檔案完整性後,進行安裝程序
    4. 系統重新起動,套用更新設定值
    上述流程在電腦、手機或較大型的微處理機中非常常見,他們有足夠的資源去跑網路協議,也有足夠的空間存放完整更新檔。但在低成本系統中,如信義區的地價般的快閃記憶體不一定有足夠的空間容納一個完整的更新包!且一般而言也不會特地安裝一個作業系統在裡面,上述的方式需要在針對記憶體空間的調配下更多功夫。
    要節省空間又要同時下載檔案,我們還有一塊記憶體 RAM 可以使用,雖然只是杯水車薪的量但也不無小補。筆者設計的 MCU 更新流程則會變成這種方式:
    1. 遠端主機先把韌體檔案切分成許多片段,透過通訊端口傳送到 MCU
    2. MCU 收到資料封包先暫存在 RAM 中,等待完整封包傳送進行資料校驗
    3. 確認封包正確後,寫入 Flash 中
    4. 重複這段過程直到封包完整傳送
    5. 系統重新起動,跳轉並執行新的韌體
    兩者最大的區別就在新韌體的傳輸方式從網路變成通訊端口,以及寫入方式從一次寫入變成分段寫入。
    分段寫入除了可以節省記憶體空間之外,還有另外一個好處: 常見的通訊端口有UART, I2C 或 SPI,這些協議並沒有內建資料校驗在協議中。當傳輸過程中遇到干擾的時候,分段寫入的方式就可以重新傳送錯誤段落,而不需要整個檔案重新傳輸。
    綜合上述的主要觀念,在加入許多實作細節,我畫出下面的流程圖。左邊 Host 是傳送資料更新的主機端,右邊 Client 則是接收資料的客戶端。他們之間透過 I2C 進行通訊,而且在每一步驟中都會檢查資料的正確性。
    在這裡我列出幾點比較細節的部分,讓大家可以一起討論與精進:
    1. 在接收更新的過程中,我設計一個機制將其他不必要的中斷關閉,確保更新過程不會被外部影響
    2. 我使用 ASCII 碼直接用字元比對韌體的版本編號,以及韌體型號。只有型號相同且版本編號大於目前版本的時候才會執行更新
    3. Host 如果超過 5mS 沒有收到來自 Client 的回應,會自動重新傳送當前資料。但這部分目前沒有實作出來,概念上應該是可行的
    4. 寫入資料到 Flash 之前,會先進行清除動作,避免殘留資料造成錯誤
    5. 檔案接收並寫入完成後,將 Client 的版本編號更新,並且修改程式碼的 Entry point

    實現韌體更新最大的重點就是程式碼跳轉,如何從舊版本的程式跳轉到新版本的程式,是實現韌體更新最重要且不可或缺的環節。
    TI 的 MCU 中有兩個 GPIO 被作為啟動引導程序( Boot lader )選擇腳位,又被稱作 Boot mode select pin。當 MCU 上電後,會先讀取這兩隻腳位的狀態,在決定從哪一個通訊協議去接收韌體封包啟動。F280025C 有四種選擇,分別是 Parallel IO、SCI(UART)、CAN與Flash。這是 MCU 內建的第一階段引導程序( First stage Bootloader),主要角色就是設定啟動任體的來源。
    這裡我們設定成 Flash ,原因是 MCU 斷電後也要讓系統從內建的非揮發性記憶體啟動,而不是等待其他周邊資料輸入。
    值得釐清的觀念是:看起來我們可以直接用 UART 讓新的韌體從這邊傳入順便執行,這樣不就解決了嗎?
    答案是不一定。也許這是一個非常方便的方法,但考量到實際產品在生產後,這兩個 Boot mode select pin 的電性狀態(高電位或低電位)需要是是固定的。我們需要確保每一次 MCU 都從自己內部 Flash 啟動,才有辦法完全脫離除錯裝置( Debugger )或依賴其他啟動設備。所以在這裡的先決條件就是不使用 MCU 本身的 Boot Mode Select 功能,全部在 Flash 中刻出一個韌體更新介面。
    構想如下,當第一階段引導程序被設定成 Flash Boot 後,我們要手動建立一個" 第二階段引導程序",也就是 Second stage bootloader。這為我們的啟動程序如虎添翼,大大提升設計彈性。
    如下方流程圖所示,當 MCU 執行到 Second stage bootloader 後,會去判斷現在是否需要跳轉到另一個 Entry Point。這樣就能夠從舊的韌體跳轉到新的韌體!
    註: Entry Point (進入點)是在程式中執行第一條指令的地方,和程式訪問命令列參數的地方。要開始一個程式的執行,裝載器或作業系統會將控制權傳遞到它的入口點。(在引導期間,作業系統自身就是這個程式)。這標誌著從裝載時(和動態連接時,如果存在的話)到執行時的轉變。- wikipedia

    Linker command file

    要修改 Entry Point 的位置 — 也就是程式碼開始執行的位址,需要從了解 MCU Flash 的燒錄方式開始。 TI 使用 "Linked Command File" 作為設定記憶體地圖的檔案。編譯器在經過編譯,組合與連結的步驟後,最後會依照這個檔案的內容將程式碼寫入到對應的 Flash 位址。
    筆者會在這邊簡介這個檔案的結構,想要詳細了解的朋友可以參考德州儀器的官方說明:TI Linker Command File Primer
    下方程式碼是筆者專案所用到的 f28002x_headers_nonbios.cmd 檔案:
    
    MEMORY
    {
     PAGE 0:    /* Program Memory */
     PAGE 1:    /* Data Memory */
       ACCESSPROTECTION           : origin = 0x0005F500, length = 0x0000003E
       ADCA                       : origin = 0x00007400, length = 0x00000080
       ADCC                       : origin = 0x00007500, length = 0x00000080
       ADCARESULT                 : origin = 0x00000B00, length = 0x00000018
       ...
       SPIB                       : origin = 0x00006110, length = 0x00000010
       SYNCSOC                    : origin = 0x00007940, length = 0x00000006
       TESTERROR                  : origin = 0x0005F590, length = 0x00000010
       WD                         : origin = 0x00007000, length = 0x0000002C
       XBAR                       : origin = 0x00007920, length = 0x00000020
       XINT                       : origin = 0x00007070, length = 0x0000000C
    
    }
    
    
    SECTIONS
    {
    /*** PIE Vect Table and Boot ROM Variables Structures ***/
    UNION run = PIEVECTTABLE
    {
        PieVectTableFile
        GROUP
        {
            EmuKeyVar
            EmuBModeVar
            EmuBootPinsVar
            FlashCallbackVar
            FlashScalingVar
        }
    }
    
       AccessProtectionRegsFile   : > ACCESSPROTECTION, type=NOINIT
       AdcaRegsFile               : > ADCA, type=NOINIT
       AdccRegsFile               : > ADCC, type=NOINIT
       AdcaResultRegsFile         : > ADCARESULT, type=NOINIT
       AdccResultRegsFile         : > ADCCRESULT, type=NOINIT
       ...
       SyncSocRegsFile            : > SYNCSOC, type=NOINIT
       TestErrorRegsFile          : > TESTERROR, type=NOINIT
       WdRegsFile                 : > WD, type=NOINIT
       XbarRegsFile               : > XBAR, type=NOINIT
       XintRegsFile               : > XINT, type=NOINIT
    }

    首先可以看到這個檔案分成兩個主要段落,MEMORYSECTIONSMEMORY這邊負責將快閃記憶體設定好長度與分頁名稱,第一行
       ACCESSPROTECTION           : origin = 0x0005F500, length = 0x0000003E
    的意思就是將從 0x0005F500 開始,長度為 0x0000003E 的記憶體,命名為 ACCESSPROTECTION。這麼作可以在後續使用與設定時增加易讀性。而 SECTIONS 則是將程式碼產出物件檔( object file )放置到指定分頁名稱,以及設定分頁名稱的群組。如這行程式碼:
    AccessProtectionRegsFile   : > ACCESSPROTECTION, type=NOINIT
    將 AccessProtectionRegsFile 放置到先前設定的 ACCESSPROTECTION 中,並且增加一個型態是 NOINIT。除了 NOINIT 型態之外,section 還也其他種類,如:NOLOAD, COPY, DSECT等等。這部分可以額外參考Linker Special Section Types有更多對於輸出執行檔的說明。

    I2C 的優點與缺點

    常見的通訊協議以及筆者使用過的通訊協議有UART, I2C, 以及SPI。他們各自有適合的應用場域以及情境,之前的文章: 搞懂通訊協議|深入解析SPI應用在EEPROM的注意事項與實作以及輪詢與中斷的通訊處理比較|解決I2C裝置通訊穩定性的問題講解SPI以及I2C在實作上會遇到的問題,下方表格是UART, I2C與SPI的比較表格:

    UART I2C SPI
    特色 - 全雙工
    - 非同步通訊
    - 點對點
    - 半雙工
    - 支援多主多從
    - 有地址編碼
    - 全雙工
    - 高速同步通訊
    - 主從架構
    總線數量 2 條 (Tx, Rx,不包含 GND) 2 條 (SCL, SDA,不包含 GND) 最少 4 條 (SCLK, MOSI, MISO, CS)
    優勢 - 簡單易用,無需時鐘同步
    - 常用於長距離通訊
    - 廣泛支援
    - 器件數量多時,總線需求不變
    - 器件互相辨識透過地址
    - 節省接腳
    - 高速 (可達數十 Mbps)
    - 傳輸穩定
    - 通訊協定簡單
    劣勢 - 僅能點對點通訊
    - 資料傳輸速率受限制 (1 Mbps)
    - 資料傳輸速率較慢 (HS mode 最高 3.4 Mbps)
    - 容易受雜訊影響
    - 器件數量多時,需更多 CS 接腳
    - 線材需求較多

    I2C最大的優點就是即使總線上有配置多個裝置,只需要兩條線即可完成通訊,不像UART 僅能點對點傳輸,也不像SPI 需要額外的CS 線占用更多硬體資源。但缺點也非常明顯,容易受到干擾之外,當SCL 訊號受到干擾後,整個I2C 總線便會癱瘓。
    但UART 的優勢也很明顯,不需要額外的Clock 訊號就能夠驅動,因為彼此已經事先知道通訊Baud rate, 內部通訊模組就會產生對應的clock 來跟資料對齊,且UART 的工控應用,如: RS-232, RS-485等等的協議與UART 相同,唯獨電氣特性不同而已。但缺點就是目前僅能單點傳輸,當然,真有必要還是可以透過軟體通訊協議的方式,定義一個基於通訊封包的Address ,如 Microchip的技術文件: Basic Operation of UART with Protocol Support - Address Mode 可以利用前一個byte 定義9-bit的Address,但就會需要額外的功夫去設計。
    至於SPI,多數用在高速通訊上,如: 需要快速取樣的外置ADC,外部Flash 儲存裝置,或者乙太網路晶片等等。他最大的缺點: 需要額外CS 線,其實也能夠使用多工器來降低總線數量,如8條CS 線可以使用一個三對八多工器來降低空間。
    這次筆者的韌體更新應用在硬體上通訊距離較短,會被放置在同一個外殼內,且被安裝在隔絕雜訊的環境,但除了MCU之外又有很多裝置掛在同一條匯流排上,因此最後選用I2C作為通訊界面。

    小結

    科技的發展轉瞬即逝,當前最新的硬體設備可能很快就會過時,透過軟體更新可以賦予硬體一個全新的生命。且在數位電源越來越成熟的情境下,替電源產品更新韌體已經是未來的趨勢。首先要更新韌體不僅需要理解伺服器與客戶端的角色之外,也要熟悉MCU 上如何切換韌體以及他的流程圖,也要詳細的設計流程的指令還有行為。流程不僅要包含常規的資料處理,也要有例外狀況,如:超時處理,資料錯誤處理等等。理解MCU 啟動程序以及 Linker command file 所代表的意涵後,依照應用情境選用適當的通訊協議來實現這項功能。

    參考資料

    1. 第1讲.DSP C2000 Bootloader开发之启动流程讲解 - 暗星归来
    2. 第2讲.DSP C2000 Bootloader开发之OTA更新思路 - 暗星归来
    3. 第3讲.DSP C2000 Bootloader开发之程序设计分析和下载验证 - 暗星归来
    4. 第4讲.DSP C2000 Bootloader开发之程序设计分析下 - 暗星归来
    5. 第5讲.DSP C2000 Bootloader开发之输出格式讲解及提取数据 - 暗星归来
    6. 第6讲.DSP C2000 Bootloader开发之实战演示及2803x更新思路 - 暗星归来

    Post a Comment

    留個言吧

    較新的 較舊