遠端韌體更新是嵌入式系統開發者不可或缺的方便工具之一,筆者使用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一文,筆者整理使用流程如下:
- 初始化 MCU PLL,設定周邊模組時脈
- 將 Flash API的初始設定與其程式碼從 ROM與 Flash複製到 RAM中,方便更快速的執行
- 初始化 Flash的工作模式(如:省電模式、校驗模式等等)以及 Flash API的有限狀態機
- 接下來就可以依照狀態機模式進行操作,如:清除特定 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內。如此一來才能完成韌體更新的功能。
補充一下,另一種大家比較常用的MCU STM32 有把Flash的寫入操作包裝成HAL,所以不用特別引入像TI的 Flash API這種東西
回覆刪除https://stackoverflow.com/questions/54159802/how-to-write-to-stm32-flash
刪除張貼留言
留個言吧