今天為大家分享一款開源的,專為單片機開發的輕量級 OTA 組件,挺有參考和學習意義的。
正文介紹部分:
一、簡介
本開源工程是一款專為 32 位 MCU 開發的 OTA 組件,組件包含了bootloader、固件打包器(Firmware_Packager)、固件發送器三部分,并提供了一個基于 STM32F103 和 YModem-1K 協議的案例,因此本案例的固件發送器名為YModem_Sender。
mOTA 中的 m 可意為 mini 、 micro 、 MCU ( Microcontroller Unit ),而 OTA ( Over-the-Air Technology ),即空中下載技術,根據維基百科的定義, OTA 是一種為設備分發新軟件、配置,乃至更新加密密鑰(為例如移動電話、數字視頻轉換盒或安全語音通信設備——加密的雙向無線電)的方法。OTA 的一項重要特征是,一個中心位置可以向所有用戶發送更新,其不能拒絕、破壞或改變該更新,并且該更新為立即應用到頻道上的每個人。用戶有可能“拒絕” OTA 更新,但頻道管理者也可以將其踢出頻道。由此可得出 OTA 技術幾個主要的特性:
一個中心可向多個設備分發更新資料(固件);
更新資料一旦發送便不可被更改;
設備可以拒絕更新;
中心可以排除指定的設備,使其不會接收到更新資料。
本工程僅實現 OTA 更新資料的部分技術,即上文列出的 OTA 技術幾個主要的特性,而不關心中心分發資料中間采用何種傳輸技術。(本工程的 example 使用 UART 作為 MCU 和外部的傳輸媒介)
?
二、實現的功能
MCU 設備上的 OTA 升級可理解為 IAP (In Application Programming) 技術, MCU 通過外設接口(如 UART 、 IIC 、 SPI 、 CAN 、 USB 等接口),連接具備聯網能力的模塊、器件、設備(以下統稱上位機)。上位機從服務器上拉取固件包,再將固件包以約定的通訊協議,經由通訊接口發送至 MCU ,由 MCU 負責固件的解析、解密、存儲、更新等操作,以完成設備固件更新的功能。需要注意的是,example提供的示例不基于文件系統,而是通過對 Flash 劃分為不同的功能區域完成固件的更新。
本組件實現了以下功能:
固件包完整性檢查:自動檢測固件 CRC 值,保證固件數據的可靠性。
固件加密:支持 AES256 加密算法,提高固件的安全性。
APP 完整性檢查:支持 APP 運行前進行完整性檢查,以確認運行的固件無數據缺陷。
斷電保護:當固件更新過程中(含下載、解密、更新等過程),任何一個環節斷電,設備再次上電時,依然能確保有可用的固件。(需配置為至少雙分區)
固件水印檢查:可檢測固件包是否攜帶了特殊的水印,確認非第三方或非匹配的固件包。
固件自動更新:當 download 或 factory 分區有可用的固件,且 APP 分區為空或 APP 分區不是最新版本的固件時,可配置為自動開始更新。
恢復出廠設置:factory 分區存放穩定版的固件,當設備需要恢復出廠設置時,該固件會被更新至 APP 分區。
無須 deinit :我們知道,固件更新完畢后從 bootloader 跳轉至 APP 前需要對所用的外設進行 deinit ,恢復至上電時的初始狀態。本組件的 bootloader 包含了下載器的功能,當使用復雜的外設收取固件包時, deinit 也將變得復雜,甚至很難排除對 APP 的影響。為此,本組件采用了再入 bootloader 的方式,給 APP 提供一個相當于剛上電的外設環境,免去了 deinit 的代碼。
功能可裁剪:本組件通過功能裁剪可實現單分區、雙分區、三分區的方案切換、是否配置解密組件、是否自動更新 APP 、是否檢查 APP 完整性、是否使用 SPI Flash (待實現)。
固件存放至 SPI flash :本組件可通過user_config.h配置 download 分區和 factory 分區的所在位置為片內 flash 或 SPI flash ,使用了 SFUD (Serial Flash Universal Driver)作為 SPI flash 的底層驅動庫。若使用的 SPI flash 支持 SFDP (Serial Flash Discovable Parameters),則可在不修改任何源代碼的情況下更換其它品牌型號的 SPI flash 。若不支持 SFDP ,SFUD 中已有對應 SPI flash 參數表的話,也可做到在不修改任何源代碼的情況下更換其它品牌型號的 SPI flash 。
?
三、 bootloader 架構
(一)軟件架構
硬件層描述的是運算器件和邏輯器件,如CPU、ADC、TIMER、各類IC等,是所有軟件組件的硬件基礎,是軟件邏輯的最終底層實現。
硬件抽象層是位于驅動與硬件電路之間的接口層,將硬件抽象化。它隱藏了特定平臺的硬件接口細節,為驅動層提供抽象化的硬件接口,使其具有硬件無關性。
驅動層通過調用硬件抽象層的開放接口,實現一定的邏輯功能后封裝,提供給上層軟件調用。
數據傳輸層負責收發數據,對外開放的是數據發送與接收相關的接口,屏蔽了通訊接口的邏輯代碼,使其易于修改為其他類型的通訊接口。
協議析構層將調用數據傳輸層的數據收發接口進行封包發送與收包解析,通過實現用戶的自定義協議,完成對數據的構造和解析。
應用層負責業務邏輯代碼的實現,通過調用其他層封裝的接口,完成頂層邏輯功能。
?
(二)文件架構
文件 | 功能描述 |
---|---|
main.c | 由 STM32CubeMX 自動生成,負責外設的初始化 |
user_config.h | 用戶配置文件,用于裁剪 OTA 組件的功能 |
app_config.h | 應用配置文件,配置代碼工程的一些運行選項 |
app.c | 應用層,負責業務邏輯代碼的實現 |
firmware_manage.c | 固件的管理接口層,提供了固件的所有操作接口 |
protocol_parser.c | 協議析構層,實現協議的解包和封包 |
data_transfer.c | 數據傳輸層,對外提供數據發送和接收的接口 |
data_transfer_port.c | 數據傳輸層的移植位置,便于修改為其它通訊接口 |
utils.c | 工具庫,實現了一些需要全局調用的工具性質函數 |
bsp_config.h | BSP 層的配置文件 |
bsp_common.h | BSP 層的公共頭文件 |
bsp_board.c | 實現板卡的一些自定義的初始化代碼 |
bsp_uart.c | 通用的 UART 驅動庫 |
bsp_uart_port.c | UART 的接口移植文件 |
bsp_uart_config.h | UART 的配置文件 |
bsp_timer.c | 通用的 timer 驅動庫 |
bsp_flash.c | flash 的分區操作抽象接口 |
fal_stm32f1_flash.c | STM32F1 片內 flash 的寫入、讀取和擦除的抽象接口 |
?
四、 bootloader 的設計思路
整個 bootloader 設計思路的內容較多,本設計思路也以 PDF 文檔的形式提供,詳見《bootloader程序設計思路》。
?
五、固件更新流程
根據配置的分區方案不同,固件的更新流程會有些不同,此處僅展示簡要的更新流程,便于快速理解固件更新的流程,因此屏蔽了很多細節,更詳細的內容,請閱讀《bootloader程序設計思路》文檔和源代碼。
本組件的目的是最大程序的減少 APP 的改動量以實現 OTA 的功能,從下圖可知, bootloader 便完成了固件的下載、存放、校驗、解密、更新等所有操作, APP 部分所需要做的有以下三件事。
根據 bootloader 占用的大小和 flash 的最小擦除單位,重新設置 APP 的起始位置和中斷向量表。
增加觸發進入 bootloader 以開始固件更新的方式。(如:接收來自上位機的更新指令)
設置一個更新標志位,且這個標志位在 APP 軟復位進入 bootloader 時仍能被讀取到。(當固件更新的方式為上位機指令控制時,可以不執行此步驟)
??一般來說,通知 bootloader 需要進行固件更新的方式有以下兩種:
采用上位機指令控制的方式,優點是 APP 無須設置更新標志位,即便設備在收到更新指令后斷電,也可以照常更新。缺點是設備在上電后, bootloader 需要等待幾秒的時間(時間長短由通訊協議和上位機決定),以確認是否有來自上位機的更新指令,從而決定進入固件更新模式亦或跳轉至 APP 。
APP 在軟復位進入 bootloader 之前設置一個特殊的標志位,可以放置在 RAM 、備份寄存器或者外部的非易失性存儲介質中(如:EEPROM)。此方式的優點是設備上電時 bootloader 無須等待和驗證是否有固件更新的指令,通過標志位便可決定是否進入固件更新模式亦或跳轉至 APP ,且利用再入 bootloader 的機制,可以給 APP 提供一個干凈的外設環境。缺點則是 APP 和 bootloader 都要記錄標志位所在的地址空間,且該地址空間不能被挪作他用,不能被意外修改,更不能被編譯器初始化。相較于上個方案多了要專門指定該變量的地址并且不被初始化的步驟。若使用的是 RAM 作為記錄標志位的介質,則還有斷電后更新標志信息丟失的問題。
綜上所述,沒有完美的方案。本組件支持上述方案二選一,根據實際需求進行選擇和取舍即可。
由于案例采用了 YModem-1K 協議,而本組件開始固件更新的方式是通過上位機發送指令開始的,因此測試時若設備正在運行 APP ,需要有個進入 bootloader 的條件,為了便于展示,案例使用了板卡上的功能按鍵作為觸發條件,模擬上位機向設備發送了更新指令,詳見案例的使用說明。
?
六、固件檢測與處理機制
之所以單獨列出固件的檢測與處理機制,是為了方便理解代碼邏輯,此部分也以 PDF 文檔的形式提供,詳見《固件檢測與處理機制》。
?
七、所需的工具
Firmware_Packager 此工具是必選項,負責打包 bin 固件,并為 bin 固件添加一個 96 byte 的表頭,最終生成為 fpk(Firmware Package) 固件包。關于 96 byte 表頭的具體內容,詳見《fpk固件包表頭信息》。由于 YModem-1K 協議的每包的數據大小是 1 Kbyte ,為了便于 bootloader 解包,本工具也將固件表頭擴大至了 1 Kbyte ,若自定義的協議支持可變包長,可將表頭長度恢復為 96 byte 。
需要注意的是,本案例選擇了 YModem-1K 協議,因此若直接采用或測試example目錄中的案例,固件打包器的表頭尺寸需要選擇 1024 byte 。
YModem_Sender 本工程的 example 采用廣泛使用且公開的 YModem-1K 通訊協議,因此也提供了一個基于 YModem-1K 協議的發送器。由于固件發送器和通訊協議是綁定的,實際使用時,不必綁定此工具,本工程僅為了方便測試而提供。
注:以上的工具是基于 Qt6 開發的,YModem_Sender 依賴 Qt 的 serial_port 庫,需要自行添加。以上工具作為 OTA 組件的一部分,自然也是開源的。運行平臺是 windows ,目前僅在 win10 和 win11 上測試過。若需要修改和編譯工程,需要自行安裝 Qt ,請自行搜索安裝教程。
?
八、組件占用的空間
本組件的案例是基于 YModem-1K 協議及 UART 作為 MCU 與外部的數據傳輸媒介,因此不是僅計算核心代碼部分的占用空間情況,而是整個可用工程。此數據才更有參考意義。以下是幾種方案配置占用的 flash 和 RAM 的大小。
最小占用(單分區 )
Program Size: Code=7796 RO-data=464 RW-data=116 ZI-data=9316
flash: 8376 byte (8.18 kB)
ram: 9432 byte (9.21 kB)
一般占用(單分區 + 解密組件)
Program Size: Code=8574 RO-data=1010 RW-data=116 ZI-data=9572
flash: 9700 byte (9.47 kB)
ram: 9688 byte (9.46 kB)
一般占用(三分區 )
Program Size: Code=8846 RO-data=614 RW-data=116 ZI-data=9348
flash: 9576 byte (9.35 kB)
ram: 9464 byte (9.24 kB)
最大占用(三分區 + 解密組件)
Program Size: Code=9706 RO-data=1158 RW-data=116 ZI-data=9604
flash: 10980 byte (10.72 kB)
ram: 9720 byte (9.49 kB)
因雙分區和三分區的占用尺寸相差較小,因此不單獨列出雙分區的占用情況。本組件已盡量壓縮尺寸,但由于固件數據的處理需要一定的 RAM 開銷,此部分除非修改每個分包的最小處理單位(目前是 4096 byte ),否則已經很難進一步壓縮,請按實際需求選擇。
?
九、移植說明
由于寫教程工作量較大,本開源工程暫不提供詳細的移植說明文檔。代碼已分層設計,具備一定的移植性,有經驗的工程師看example中的示例代碼和本說明基本都能自行移植到別的芯片平臺。這里僅做幾點說明。
挖個坑,后續有時間再錄個移植視頻。
bootloader 部分的核心代碼都在source目錄下,是移植的必需文件。
source/Component和source/BSP目錄下的組件庫非移植的必選項,根據功能需要進行裁剪。
因固件包含表頭,固件寫入的 flash 分區的方式與通訊協議是強相關的。若自定義的協議支持可變長度,那么建議傳輸第一個分包時就是固件表頭的大?。藴时眍^大小是 96 byte ,本工程因采用 YModem-1K 協議,固件打包器將表頭擴大到了 1 Kbyte,自行修改即可),從而方便 bootloader 解包。
除開表頭部分,固件的每個切包不能超過 4096 byte ,且 4096 除以每個切包大小后必須是整數(如常見的128、256、512、1024、2048等),否則就得修改源碼。
單分區方案雖然節省了 flash 空間,但本組件的很多功能和安全特性都無法使用,除非 flash 實在受限,否則建議至少使用雙分區的方案。
??以下是移植的基本步驟
(一)bootloader 部分
建議參考example中的案例進行移植。
創建一個代碼工程,并確保這個工程可以正常運行。(如:控制一個 LED 閃爍)
將source目錄下的文件放到工程目錄下,可隨意放置和命名。
將source目錄下的文件按需添加進代碼工程中(source/Component目錄下的組件庫非移植的必選項),并包含對應的頭文件目錄。
實現data_transfer_port.c和fal_stm32f1_flash.c內的函數,若與案例一致,則無需修改。
若使用了自定義的通訊協議,則修改protocol_parser.c和protocol_parser.h,若與案例一致,則無需修改。
將app.c文件內的函數移植進你的應用代碼,記得包含app.h,必要時可修改。
嘗試編譯并解決編譯器報錯的問題。(若提示缺失部分文件,可在example目錄中尋找并添加進工程,后續再行修改也是可以的 )
按需求設置好user_config.h文件,請仔細閱讀說明。
查看app_config.h是否有需要修改的配置項。
若選擇了“使用標志位作為固件更新的依據USING_APP_SET_FLAG_UPDATE”(否則忽略此步驟),且標志位放置在 RAM ,則需要配置標志位update_flag所在的 RAM 地址,并且配置 IDE 或分散加載文件不對其進行初始化。IDE 配置的方式參考如下圖所示。( 需包含common.h) 其中,宏FIRMWARE_UPDATE_VAR_ADDR在user_config.h中配置,本案例是0x20000000。注意,提供給update_flag的 RAM 區域一定要勾選NoInit。
/* 固件更新的標志位,該標志位不能被清零 */ #if (USING_IS_NEED_UPDATE_PROJECT == USING_APP_SET_FLAG_UPDATE) #if defined(__IS_COMPILER_ARM_COMPILER_5__) volatile uint64_t update_flag __attribute__((at(FIRMWARE_UPDATE_VAR_ADDR), zero_init)); #elif defined(__IS_COMPILER_ARM_COMPILER_6__) #define __INT_TO_STR(x) #x #define INT_TO_STR(x) __INT_TO_STR(x) volatile uint64_t update_flag __attribute__((section(".bss.ARM.__at_" INT_TO_STR(FIRMWARE_UPDATE_VAR_ADDR)))); #else #error "variable placement not supported for this compiler." #endif #endif
?
工程配置建議選擇 AC6 (雖然本組件也支持 AC5 ,除非不得已,否則建議使用 AC6),選擇C99(如果使用了perf_counter,則需要選擇gnu99) ,優化根據需要選擇即可,建議按下圖所示配置。
嘗試再次編譯并解決編譯器提示的問題。
若選擇了“使用標志位作為固件更新的依據USING_APP_SET_FLAG_UPDATE”(否則忽略此步驟),編譯通過后,查看 map 文件是否新增了一個 Region ,并且地址正確,Type為Zero,該區域為UNINIT,若全部符合,則移植成功。
(二)APP 部分
APP 部分的移植相對簡單,可直接參考example中的案例。
創建一個代碼工程,并確保這個工程可以正常運行。(如:控制一個 LED 閃爍)
確定 APP 在 flash 中的地址,需考慮 bootloader 的大?。ú荒芎?bootloader 有沖突),中斷向量表對地址的要求(如必須是 0x200 的整數倍), flash 的擦除粒度(因 flash 擦除時是以扇區為單位的)。需要注意的是, APP 的地址一定要和 bootloader 的user_config.h中配置的一致,否則無法運行。
在外設初始化前修改中斷向量表, keil 可采用下圖的方式修改,一勞永逸。
/* 設置中斷向量表后,開啟總中斷 */ extern int Image$$ER_IROM1$$Base; BSP_INT_DIS(); SCB->VTOR = (uint32_t)&Image$$ER_IROM1$$Base; BSP_INT_EN();
?
同 bootloader 部分的步驟 10 一致。
增加固件更新時進入 bootloader 的代碼,如上位機發送固件更新的指令。(測試時可通過按鍵模擬上位機發送固件更新)
在執行固件更新的指令的代碼處,添加設置update_flag標志位的值和系統復位的代碼,如下圖所示。其中,FIRMWARE_UPDATE_MAGIC_WORD的值是0xA5A5A5A5,注意此值要和 bootloader 保持一致 。
若需要通過標志位使用“恢復出廠固件”的功能,也是同理,對應的宏則是FIRMWARE_RECOVERY_MAGIC_WORD,值為0x5A5A5A5A。
update_flag = FIRMWARE_UPDATE_MAGIC_WORD; HAL_NVIC_SystemReset();
嘗試再次編譯并解決編譯器提示的問題。
同 bootloader 部分的步驟 13 一致。
?
十、一些問題的解答
為什么不使用 RTOS ?
為了最大程度的減少 bootloader 占用的 flash 空間,體積越小,組件的適用范圍就越廣。當然,本組件是開源的,想在 bootloader 里增加 RTOS 或者其它代碼也是可以的。
?
為什么要將 bootloader 設計在 flash 的首地址?
我們知道, bootloader 的運行環境最理想的情況是未經使用任何外設的。有些設計會將 APP 放置在 flash 首地址, bootloader 放置在其它地址,優點是 APP 無須設置 APP 的起始位置和中斷向量表,改動量最少,缺點是這種方式很難做到通用,需要在 bootloader 或 APP 中 deinit 所使用的外設,否則固件更新時可能會出現各式各樣的異常。實際上,每個設備每個產品所使用的外設都是不確定的,為了做到通用,本組件選擇了 bootloader 設計在 flash 的首地址的方案。
?
為什么要設計成單分區、雙分區和三分區?
現實情況是,并非所有設備的 flash 空間都有比較大的富余。有些設備,無法使用多個分區, bootloader + APP 分區已經是極限。而 bootloader 分區方案不同時,其占用的 flash 大小也不同,為了盡可能的減小 bootloader 的體積,而將分區設計成可配置的方式。
?
什么是 fpk ?
fpk 是 mOTA 組件的固件打包器生成的一種文件,基于 bin 文件,在其頭部增加了一個 96 byte 表頭后合成的一個新文件,后綴是.fpk。fpk 取自英文詞語 Firmware Package 的縮寫,意為固件程序包,本組件統稱為固件包,而提及固件時,一般指的是 bin 文件。
?
我可以不用固件打包器(Firmware_Packager),直接用 bin 文件進行更新嗎?
目前是不能的。本組件提供的功能和安全特性是基于 fpk 表頭和多分區的方式實現的,因此需要固件打包器打包固件,生成 fpk 固件包。為了最大程度的使用這些功能和安全特性, bootloader 的更新流程是基于含有表頭的固件包開發的,暫時不考慮增加不含表頭的更新流程。當然,不排除因為要求的人多了,我就開搞了。
?
十一、引用的第三方庫
??本開源工程使用了或將使用以下的第三方庫,感謝以下優秀的代碼庫(排名不分先后)。
fal(Flash Abstraction Layer) ,RT-Thread 團隊的開發的庫,是對 Flash 及基于 Flash 的分區進行管理、操作的抽象庫。
SFUD(Serial Flash Universal Driver) 一款使用 JEDEC SFDP 標準的串行 (SPI) Flash 通用驅動庫。
crc-lib-c為本工程的 CRC32 驗算提供了基礎。
tinyAES這是一個用 C 編寫的 AES 、 ECB 、 CTR 和 CBC 加密算法的小型可移植的庫。
SEGGER RTTSEGGER's Real Time Transfer (RTT) is the proven technology for system monitoring and interactive user I/O in embedded applications. It combines the advantages of SWO and semihosting at very high performance.
perf_counter該庫利用 SysTick 實現了代碼的運行時間測量和通用的 ms 及 us 的延時函數,且不影響原有的 SysTick 功能和邏輯,若使用的是 AC5 或 AC6 ,可以做到無感使用,即只需將 perf_counter 庫添加進代碼工程后即可直接使用,無需調用 init 之類的任何函數。
審核編輯:湯梓紅
-
單片機
+關注
關注
6037文章
44558瀏覽量
635298 -
mcu
+關注
關注
146文章
17148瀏覽量
351197 -
開源
+關注
關注
3文章
3349瀏覽量
42499 -
OTA
+關注
關注
7文章
580瀏覽量
35225 -
組件
+關注
關注
1文章
512瀏覽量
17826
原文標題:一款專為32位MCU開發的OTA組件-mOTA
文章出處:【微信號:玩轉單片機與嵌入式,微信公眾號:玩轉單片機與嵌入式】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論