高柱榮,蔣昌茂,劉洪林
(1.桂林市利通電子科技有限責任公司,廣西 桂林 541004;2.桂林師范高等??茖W校,廣西 桂林 541001;3.桂林航天工業學院計算機科學與工程學院,廣西 桂林 541004)
智能燃氣表已成為居民生活用氣的一種主要計量儀表,被廣泛應用并分散地安裝在各戶居民家中,其設計缺陷修補、功能改進或性能提升等需求,在物聯網時代將變得簡單可行。本文基于窄帶物聯網(narrow band-intemet of things,NB-IoT)燃氣表項目,研究設計了一種安全、可靠的遠程升級方案。
物聯網燃氣表由主控微控制器(micro controller,MCU)、窄帶(narrow band,NB)通信模組、計量單元、閥控單元、液晶顯示器(liquid crystal display,LCD)、蜂鳴器、按鍵、實時時鐘、供電單元等組成,主要實現燃氣使用量的采集計量、連網上報數據和接收服務器下發的命令數據等功能。而遠程升級功能則是利用物聯網燃氣表自身具有的網絡連接特性,通過遠程操控實現對燃氣表固件程序的更新:即更新主控MCU內的程序,以達到改進和提升的目的。本文的升級系統由業務服務器、燃氣表終端和升級服務器三部分組成。
STM32L0 MCU是意法半導體公司推出的、基于ARM Cortex-M0+內核的系列微控制器[1],具有集成外設單元豐富、高性能與超低能耗完美平衡等特性,同時具有低成本的優勢,非常適合由電池供電或由能量收集供電的物聯網應用。本文選擇STM32L073RZ作為物聯網燃氣表的主控MCU。
應用程序編程(in application programming,IAP)技術是指MCU允許正在運行的程序對片內Flash的部分區域進行編程更新,更新后可以執行新的程序,從而實現程序的升級[2-4]。STM32L0芯片有三種啟動模式可選,需要配置為:從Flash主存儲器啟動,才能啟用IAP功能。
升級系統組成如圖1所示。

圖1 升級系統組成圖
IAP的具體應用可通過多種不同的方式實現。文獻[5]~文獻[6]把Flash分成一個Bootloader程序區和一個APP程序區。在APP程序中接收升級數據包,臨時存儲在EEPROM中,接收完成后重啟系統。在Bootloader程序中,將數據從EEPROM拷貝到Flash的APP程序區完成升級[5-6]。該方案的缺點是需要額外的存儲器硬件、增加了成本,而且升級固件存儲在EEPROM中容易被非法訪問和復制。對比文獻[5],文獻[7]~文獻[8]在Flash中多劃分出了一個文件存儲區,代替EEPROM存儲升級數據包[7-8],較好地解決了額外增加硬件成本和固件數據文件安全的問題。但該方法仍然需要在Bootloader程序中更新APP程序,增加了Bootloader程序的復雜性和升級失敗機率。
針對上述方案的不足,本文作了進一步的改進,程序設計成Bootloader和APP兩個獨立的工程。APP工程在編譯前通過設置不同的起始地址,編譯生成兩個固件:APP-A和APP-B。升級時,如果MCU正在運行的是APP-A,則僅更新最新版本APP-B到其對應的Flash地址中,不更改原版APP-A固件程序,反之亦然。該設計可徹底解決升級過程中因意外失敗,導致不可回退到原版本程序的問題。
STM32L073RZ芯片屬于大容量產品,具有192 KB的片內Flash存儲器和20 KB的 RAM。Flash的起止地址為0x08000000~0x0802FFFF,共分為1 536頁。每頁大小為128 B。而每扇區則由32頁4 KB組成。
為實現IAP升級,需要把Flash人為地分成幾個部分:一部分存儲引導程序,即Bootloader程序區;另一部分存儲用戶應用程序,即APP程序區;還有一部分存儲升級標志或用戶程序標志,簡稱配置信息區。本設計把Flash分成Bootloader程序區、配置信息區、APP-A程序區和APP-B程序區四個部分。Flash存儲器分區如表1所示。

表1 Flash存儲器分區
在Keil MDK-ARM開發環境中,編譯BootLoader程序工程時,其起始地址需設置為0x8000000,空間大小為0x3000。
APP程序工程則需要編譯兩次,得到兩個固件程序。APP-A程序的起始地址設置為0x8004000,空間大小為0x16000。APP-B程序的起始地址設置為0x801A000,空間大小為0x16000。另外,還需要將APP程序工程編譯生成的Axf文件,轉換成升級固件程序Bin文件,用于實際燒入MCU。設置User選項卡下的After Build/Rebuildr的Run #1選項為:C:Keil_v5ARMARMCCinfromelf.exe-bin-o.ReleaseTG134_SRC.bin.ReleaseTG134_SRC.axf。編譯后自動轉換生成一個TG134_SRC.bin二進制文件。
Bootloader程序工程編譯生成的Bootloader.hex文件,需要與APP程序工程生成的APP-A.hex文件合并,生成一個新的HEX文件,用于產品生產時燒錄到MCU中。推薦使用優秀的合并工具SEGGER J-Flash進行合并。經轉換生成的APP-A.bin和APP-B.bin文件,需要再經過加密才能用于升級,而且加密算法最好使用3DES或SM4以上級別的算法,安全才有保障。
在通常的升級方案中,Bootloader程序是整個方案中的關鍵部分,需完成初始化程序、升級文件檢測、固件更新以及程序跳轉等工作。特別是固件更新,一般耗時較長,容易受到電源不穩或外部干擾的影響,造成不可回退的升級失敗。本方案中,Bootloader程序僅從配置信息區讀取最新版APP程序的存儲地址,然后把程序跳轉到對應的地址執行APP程序;其他的升級功能操作放到APP程序中實現。
以下是程序完整的Bootloader代碼。
Typedef void(*pFunction)(void);
//定義函數指針類型
pFunction JumpToApplication;
//定義函數指針
uint32_t u32JumpAddress;
//32位跳轉地址
#define CONFIG_ADDRESS(uint32_t)0x08003000
#define APP_A_ADDRESS (uint32_t)0x08004000
#define APP_B_ADDRESS (uint32_t)0x0801A000
void IAP_StartUserApp(uint32_t appAddr)
{
//STM32L073RZ的棧頂地址值必須在此范圍內
if((*(__IO uint32_t *)appAddr >=0x20000000)&&\(*(__IO uint32_t *)appAddr<=0x20004FFF))
{
//讀取程序跳轉地址
u32JumpAddress =*(__IO uint32_t *)(appAddr+4);
//初始化函數指針
JumpToApplication=(pFunction)u32JumpAddress;
//初始化棧頂指針
_set_MSP(*(__IO uint32_t *)appAddr);JumpToApplication();
//跳轉到用戶應用程序
}
}
int main(void)
{
第四,從上述政治與文學的關系研究進一步拓展,則體現為東方主義視域下的明治文學家/文人(如:夏目漱石、內藤湖南、依田學海)的中國觀(包括中國形象)或戰爭觀研究。如:李雁南、泊功、宋剛等學者的研究。[56-58]
uint32_t u32AppAddress = 0;
HAL_Init();
SystemClock_Config();
//讀取最新版本APP程序的存儲地址
u32AppAddress=*(__IO uint32_t *)CONFIG_ADDRESS;
if(APP_B_ADDRESS == u32AppAddress)
//跳轉到APP-B程序執行
IAP_StartUserApp(APP_B_ADDRESS);else
//跳轉到APP-A程序執行
IAP_StartUserApp(APP_A_ADDRESS);while(1){}
}
3.3.1 重定向中斷向量表
STM32芯片內部通過中斷向量表響應中斷。中斷向量表的起始地址是0x08000004。在程序正常運行過程中,如果有中斷發生,芯片內部硬件機制會將PC指針強制指向中斷向量表,并根據中斷源取出對應中斷向量執行中斷服務程序[9]。然而,APP程序是一套完整獨立的程序,自有另外一張中斷向量表。其起始地址是APP程序首地址+4,與原Flash中存儲的中斷向量表已發生了偏移。在APP程序執行過程中,如果有中斷請求發生,PC指針仍然被強制跳轉到0x08000004地址的中斷向量表,而不是APP程序自己的中斷向量表。因此,需要對中斷向量表進行重定向,即配置向量表偏移量寄存器,使之指向APP程序的向量表[10]。
在APP程序的main()函數中,第一件要做的事情就是重定向中斷向量表,從配置信息區讀取當前APP程序自身的存儲地址,把地址值設置為APP程序的中斷向量表偏移量。
程序代碼如下。
int main(void)
{
sIAP.CurrAppAddr = *(__IO uint32_t *)CONFIG_ADDRESS;
//讀取當前APP程序的存儲地址
if((APP_A_ADDRESS != sIAP.CurrAppAddr)&& \(APP_B_ADDRESS != sIAP.CurrAppAddr))
{
//未設置則默認為APP_A
sIAP.CurrAppAddr = APP_A_ADDRESS;IAP_WriteConfig(CONFIG_ADDRESS,sIAP.CurrAppAddr);
//把APP-A的址址寫入配置信息區
}
//設置中斷向量表偏移量
SCB->VTOR = sIAP.CurrAppAddr;HAL_Init();
SystemClock_Config();
//以下是應用業務和升級包處理程序
}
3.3.2 固件安全傳輸與更新
升級流程如圖2所示。

圖2 升級流程圖
固件升級服務的安全直接關系到整個系統的安全,因為此服務能徹底更改終端的運行程序。首先,必須保證新版本固件程序本身經過嚴格測試才能發布到升級服務器中。特別是新固件程序自身的再次升級的功能,必須確保升級后的程序,即使有錯誤也有繼續升級的能力。另一個重點是對內部和外部惡意攻擊的提防,如嗅探攻擊、癱瘓升級服務器攻擊和提供假冒升級固件程序攻擊等。
因此,升級服務由業務系統對特定的終端實施單獨開放,僅有業務系統發送升級命令,并為該終端標記升級允許標志后,才允許該終端進行升級。升級服務器的IP、端口等信息也由業務系統伴隨升級命令一起發送給終端,終端內不固定存儲升級服務器的IP及端口。固件程序必須經過加密和驗證后,才能存入升級服務器:即服務器內不存儲固件程序Bin文件,僅存儲加密后的Bin文件,保障服務器端不泄露程序的Bin文件。
燃氣表終端主動與業務服務器連接,在完成正常的抄表、充值等業務之后,如果有新版本固件需要升級到終端,則開啟升級。
①由業務服務器發送允許升級命令給終端,通知終端在一定的時間內允許升級。
②收到終端的應答之后,業務服務器再通知升級服務器在一定的時間內允許某個終端來申請升級。
③收到升級服務器的應答之后,業務服務器給終端發送升級命令,同時把升級服務器的IP和端口號通知終端。
④終端主動斷開與業務服務器的連接,重新連接到升級服務器,并發送自己當前的固件版本信息給升級服務器,申請升級。
⑤升級服務器根據終端當前版本信息,選擇一個新版APP固件程序。如果終端當前版本為APP-A,則選擇新版APP-B;否則,選擇新版APP-A,把選擇的新版固件程序文件大小等信息反饋給終端。
⑥終端從邏輯地址0開始,按合適的長度請求下載第一包固件數據,升級服務器按請求地址和長度取出固件數據返回給終端。
⑦終端把接收到的固件數據,經過校驗和解密之后,寫入Flash中對應的APP程序區內,繼續請求下載下一包固件數據。
⑧當終端接收到最后一包固件數據并寫入Flash之后,校驗整個新版APP固件程序的合法性。最后,更新配置信息區的最新版APP程序存儲地址選項的值。這是整個升級過程最為關鍵的一步。但只更新一個字的內容,失敗的機率可以忽略不計。即使失敗了,終端還可以繼續運行舊版的程序。
⑨終端主動斷開與升級服務器的連接,復位系統。
⑩終端重啟后,連接到業務服務器,通知業務服務器該終端已成功完成升級。
升級過程中,由于網絡信號質量或其他的原因,終端有可能會收不到來自服務器的應答,或收到不完整的數據。因此,終端需要做很多的容錯措施,如最多3次請求下載同一個數據包等。如果本次升級沒有成功,則可等待到下一次連接時再進行升級。
本課題項目已在國內某市安裝的NB-IoT物聯網燃氣表上進行了驗證測試,一共測試100臺。一次連接升級就成功的有97臺,其中有92臺的升級耗時在4 min之內,另外5臺耗時在4~6 min之間。升級失敗的3臺,再次啟動連接進行第2次升級測試,在6 min內全部成功完成升級。最后,查看服務器后臺數據,發現未能在4 min內完成升級的燃氣表有8臺。其信號強度值都沒有超過10(0~31,值越大表示信號越好),并且都有多次請求下載同一數據包的情況;同時,且其安裝位置環境都是較為封閉、屏蔽物較多。由此可知,物聯網燃氣表的網絡信號質量對升級的成功與否影響較大。
本文主要根據安全和可靠的要求,充分考慮了終端設備的安全、固件程序的安全、升級服務器的安全和升級意外失敗的回退等多個方面的需求,開展整個升級方案的設計工作。對Flash進行了科學的分區,在程序設計上把不可更新的Bootloader程序極簡化,把可重復升級更新的APP程序精準化。對終端的升級操作引入了時效性,而升級服務器IP地址則隱藏化。該方案已在實際應用中得到了驗證,對其他物聯網終端的遠程升級設計有一定的參考價值。