韌體更新設計實例 (四) Flash API 寫入實驗

遠端韌體更新是嵌入式系統開發者不可或缺的方便工具之一,筆者使用TI 的TMS320F280025C 並搭配 Raspberry Pi 作為傳送更新的系統,透過 I2C 介面將編譯好的 .hex 韌體檔案由 Raspberry Pi傳送到 MCU ,並且正確執行更新後的應用程式。這篇文章說明接收到韌體封包之後,如何利用 MCU本身的功能將資料寫入到 Flash中,並且介紹 Flash API的使用方法,最後量測 Flash讀寫時的效能。{alertInfo}

目錄

    前言

    上一篇文章:微處理機韌體更新設計實例 (三) Image檔製作與通訊設計 介紹韌體的格式,還有如設計通訊協議,讓他符合檔案格式標準,方便我們讀取與寫入。仔細看程式碼的讀者應該有發現裡面埋了兩個程式碼,分別是 erase_flash_sector以及 program_flash_data。
    如同字面上的意思,這兩個 Function分別是清除 Flash資料以及寫入 Flash資料,這篇文章會來說明實際上是如何操作的,以及 C2000處理器在操作 Flash的時候需要注意的事項。

    MCU 記憶體介紹

    一般來說,一顆 MCU中有兩種記憶體,第一種是 RAM,隨機存取記憶體,他是揮發性記憶體的一種。他速度快、耗電低的優勢讓他適合用來存放程式碼所需要的變數資料。但是既然是揮發性記憶體,代表的就是只要斷電資料就會消失,使得他沒辦法存放斷電後仍需要被保留的重要資料,如:我們判韌體更新的版本編號,或者機器型號等等。
    另一種記憶體 Flash是非揮發性記憶體的一種,他的速度相較於 RAM大約慢了兩倍的時間,而且讀寫的時也比較耗電。聽起來很遜,為甚麼還要用他呢?因為他的大優勢在於斷電後仍然能保持資料,這讓我們可以把 MCU要執行的代碼安心的放在 Flash內,不必擔心斷電後程式碼就消失。此外,他也可以模擬成 EEPROM,存放我們想要在斷電後也可被讀取的資料,如先前提到的版本編號等等。
    這篇文章:谈程序在RAM,FLASH 和外部RAM中运行的速度 比較程式碼在 RAM與 Flash下執行的速度差異,以一顆時脈 150MHz的 MCU來說,他們的執行速度接近兩倍。

    Flash API

    就 C2000 微控制器來說,Flash 與 RAM不同,平常在開發韌體要修改變數的時候,只需要簡單的用如:
    i = 3;
    這樣的描述句就可以定義好變數。然而 Flash的寫入需要透過" Flash API"的工具才能控制 Flash模組,並且寫入數據。
    除此之外,Flash API是 TI的閉源 API,官方只提供編譯好的 library檔案。換句話說我們沒辦法得知他內部真正的內容到底是什麼,只能靠 Call API的方式,依照官方文件的指示進行操作。
    而 Flash API也很特別,他實際的程式碼是內建在 MCU ROM的內部!我們在使用的時候編譯好 Falsh API, MCU要執行的時候會去內部的 ROM調用他的機器碼對 Falsh進行操作,非常有趣。
    Flash 指令在 ROM中的位置可以參考 F280025C TRM中,Memory Maps的說明:

    此外,這張表也可以發現除了 Flash API之外,ROM內部還有很多不同的東西可以調用,如IQMath,一種把浮點數轉換成固定位元小數的計算方法、CRC Table,循環冗於校驗預先計算好的結果表,以及各種加速運算工具。關於其他加速表格的使用說明。
    回到正題,使用 Flash API的方法可以參考:[FAQ] FAQ on Flash API usage for C2000 devices一文,筆者整理使用流程如下:

    1. 初始化 MCU PLL,設定周邊模組時脈
    2. 將 Flash API的初始設定與其程式碼從 ROM與 Flash複製到 RAM中,方便更快速的執行
    3. 初始化 Flash的工作模式(如:省電模式、校驗模式等等)以及 Flash API的有限狀態機
    4. 接下來就可以依照狀態機模式進行操作,如:清除特定 Section、寫入資料等等

    erase_flash_sector()

    我們分析一下前一篇文章用到的erase_flash_sector,他內部的程式碼如下:
    /* @brief   erase specific flash sector to 0xFF
     * @param   uint32_t    sector: flash sector to be erase
     * @return  uint16_t    status: return flash API execute status
     * @disc    because the limitation of flashAPI, the minimal area to erase is one sector (4096 byte)
     *          if error happened, send error message back to host
     */
    #pragma CODE_SECTION(erase_flash_sector, ".TI.ramfunc");
    uint16_t erase_flash_sector(uint32_t sector, flash_status_t flash_status){
        Fapi_StatusType  oReturnCheck;
        Fapi_FlashStatusType  oFlashStatus;
        Fapi_FlashStatusWordType  oFlashStatusWord;
    
        // Erase the sector that is programmed in the above example
        oReturnCheck = Fapi_issueAsyncCommandWithAddress(Fapi_EraseSector,(uint32 *)sector);
    
        // Wait until FSM is done with erase sector operation
        while (Fapi_checkFsmForReady() != Fapi_Status_FsmReady){}
    
        if(oReturnCheck != Fapi_Status_Success){
            i2c_send_error();
            flash_status = ERASE_FAILED;
        }
        // Read FMSTAT register contents to know the status of FSM after
        // erase command to see if there are any erase operation related errors
        oFlashStatus = Fapi_getFsmStatus();
        if(oFlashStatus != 0){
            i2c_send_error();
            flash_status = ERASE_FAILED;
        }
        // Verify that Sector6 is erased
        oReturnCheck = Fapi_doBlankCheck((uint32 *)sector, Sector8KB_u32length, &oFlashStatusWord);
    
        if(oReturnCheck != Fapi_Status_Success){
            i2c_send_error();
            flash_status = ERASE_FAILED;
        }
        else
            flash_status = ERASE_DONE;
        return flash_status;
    }
    首先前幾行先將 Flash的軟體狀態機依照範例程式設定好,並且定義好如:Fapi_StatusType,Fapi_FlashStatusType以及 Fapi_FlashStatusWordType的變數。
    主程式先確保 Flash API的狀態是 Ready後,再擦除特定的位址。但筆者將其擦除的位址包裝成變數,使用的時候就可以傳入要擦除的位址。實際猜除的指令是Fapi_issueAsyncCommandWithAddress(),但是這個指令只會單方向的進行擦除,不管有沒有成功。因此在擦除後要利用 Fapi_doBlankCheck 來檢查是否擦除成功,並且檢查 Flash API的狀態。
    TMS320F28002x Flash API Version 1.57.00.00 Reference Guide 中,有建議的擦除與寫入流程,筆者都是基於官方提供的範例程式進行改寫,流程也會跟隨文件的建議。下圖是擦除 Flash 記憶體的建議流程:

    program_flash_data()

    筆者在Flash 的寫入是使用program_flash_data,他的原始碼如下:
    /* @brief   write data into specific flash address
     * @param   uint32_t    sector: flash sector to be erase
     * @return  uint16_t    status: return flash API execute status
     * @disc    because the limitation of flashAPI, the minimal area to erase is one sector (4096 byte)
     *          if error happened, send error message back to host
     */
    #pragma CODE_SECTION(program_flash_data, ".TI.ramfunc");
    uint16_t program_flash_data(uint32_t start_addr, uint16_t *data, uint16_t size, flash_status_t flash_status)
    {
        uint32_t  write_addr = 0;
        uint16_t  i = 0;
        uint32_t *Buffer32;
        Fapi_StatusType  oReturnCheck;
        Fapi_FlashStatusType  oFlashStatus;
        Fapi_FlashStatusWordType  oFlashStatusWord;
    
        for(i=0, write_addr = start_addr; (write_addr < (start_addr + size)); i+= 8, write_addr+= 8)
        {
            oReturnCheck = Fapi_issueProgrammingCommand((uint32 *)write_addr ,data+i,
                                                   8, 0, 0, Fapi_AutoEccGeneration);
    
            //
            // Wait until the Flash program operation is over
            //
            while(Fapi_checkFsmForReady() == Fapi_Status_FsmBusy);
    
            if(oReturnCheck != Fapi_Status_Success){
                i2c_send_error();
                flash_status = WRITE_FAILED;
            }
    
            //
            // Read FMSTAT register contents to know the status of FSM after
            // program command to see if there are any program operation related
            // errors
            //
            oFlashStatus = Fapi_getFsmStatus();
            if(oFlashStatus != 0){
                i2c_send_error();
                flash_status = WRITE_FAILED;
            }
    
            Buffer32 = (uint32 *)data;
    
            //
            // Verify the programmed values.  Check for any ECC errors.
            //
            oReturnCheck = Fapi_doVerify((uint32 *)write_addr, 4, Buffer32+(i/2), &oFlashStatusWord);
    
            if(oReturnCheck != Fapi_Status_Success){
                i2c_send_error();
                flash_status = WRITE_FAILED;
            }
            else
                flash_status = WRITE_DONE;
        }
        return flash_status;
    }
    與 erase_flash_sector 相同,使用的時候需要宣告 Flash API會用到的狀態機變數,接著由於寫入的資料長度與內容可能會不相同,我設計了一個 for迴圈,將要寫入的位址進行更新,每一次只寫入8個byte。這也是 Flash API的限制,一次要以一個 page的長度進行寫入。即使我只要寫一個 byte,一次寫入還是要湊滿8 個byte才能正確寫入。
    使用 Fapi_issueProgrammingCommand 將資料寫入到指定位址後,再次檢查寫入是否成功,同時更新下一次要寫入的資料位址,進行下一次寫入。 下方是 Reference guide的建議寫入流程:

    Flash 擦除與寫入測試

    實際使用 F280025C進行 Flash的擦除,可以參考下面的 Gif短片。
    畫面右方是 Microchip的 Serial Analyser監控視窗,用來模擬 I2C的訊號傳送;左上角是 CCS的變數監控視窗,主要觀察 system_status以及 ADC中斷量測 Iout的數值變化。
    當 MCU接收到任體更新指令時,會停止 ADC中斷並將所有MCU資源留給任體更新使用,因此右方點擊 fwupdate_start按鈕時,Iout會停止更新。
    左下角的畫面是 CCS Memory browser的視窗,他可以監控特定位址的記憶體內容,包含 RAM與 Flash。這邊我們觀察 0x8F0000中的資料變化。
    當 erase_flash按鈕被按下後,這其中的記憶體就清除為 0xFF,表示清除成功。
    除了實際測試功能之外,我們也對 Flash API的執行效能進行量測。利用 GPIO與示波器,我們量測實際清除 7個 sector記憶體的時間大約是 400mS左右。但實際的時間長度會依據實際資料中,有多少 bit需要從 0變成 1而定。
    我們觀察收到清除指令的 I2C SDA總線,以及 I2C RX FIFO的執行時間作為依據,結果如下:

    對於 Flash的寫入我們也一起測試功能,並且量測效能。首先是功能,與擦除相同,右方是 Serial Analyser的視窗,左邊是 CCS。
    但寫入的步驟比較多,首先需要擦除,跟上面一樣。但之後我把資料分成 D1、D2、D3等等的按鈕,每個按鈕傳送的資料容可以參考右上角的矩陣視窗,D1 的資料內容是模擬 hex檔案第一行位址擴充指令,他設定寫入位址的偏移量。
    所以看起來點選 D1的時候沒有反應是正常的。接著 D2得資料內容是 0x48 0xEF,D3 則是比較長的字串0x22 0x05 0xA4....。
    當指令傳送後點選 CCS中的更新按鈕就可以發現傳送的資料被完整的寫入到 Flash記憶體內。
    我也對 Flash 寫入的動作進行效能量測。以一個 39 byte的資料加上 check sum校驗以及寫入,大概需要花費 3mS。實際上花費的時間也會因為資料中有幾個 bit需要從 1 變成 0而定。
    觀察 I2C的 SDA總線,以及 RX FIFO的時間,量測出寫入時間。SDA前面有好幾個脈衝就是傳送資料的過程,但這不在 Flash API的討論範圍所以我們不對其進行探討。

    小結

    MCU 內建的記憶體有兩種,一種是速度快且省電的 RAM,但他斷電後資料就會消失;另一種是速度慢耗電,但可以斷電後維持資料的 Flash。在不需要額外增加記憶體的情況下,靈活的使用內建的 Flash就成了一個必要的手段。
    然而 C2000處理器規定要操作 Flash需要透過 Flash API,Flash API是 TI提供的閉源軟體包,它的功能是操作 C2000系列 MCU的 Flash模組。它在每一顆 MCU出廠時就被寫入內建的 ROM之中,以利開發者可以快速調用,除此之外,我們也需要在編譯時就將這個函式庫引入到自定義的程式 之中。
    如此一來,我們可以靈活的使用 Flash記憶體,讓著它儲存韌體更新過程中會用到的變數,如:產品型號、版本編號等等,還有最主要的韌體包也得以被寫入到 MCU內。如此一來才能完成韌體更新的功能。

    參考資料

  1. [FAQ] FAQ on Flash API usage for C2000 devices
  2. 2 留言

    留個言吧

    1. 補充一下,另一種大家比較常用的MCU STM32 有把Flash的寫入操作包裝成HAL,所以不用特別引入像TI的 Flash API這種東西

      回覆刪除
      回覆
      1. https://stackoverflow.com/questions/54159802/how-to-write-to-stm32-flash

        刪除

    張貼留言

    留個言吧

    較新的 較舊