1 概述
采用 LoRa 技術(shù)進(jìn)行無線通訊,考慮到產(chǎn)品的實(shí)際需求,增加了產(chǎn)品的 OTA 固件升級的功能。因?yàn)?LoRa 通訊速度較慢,合理的減小 APP 區(qū)域固件的大小加快固件升級的速度變的尤為重要,于是就開啟了優(yōu)化調(diào)整 APP 區(qū)域固件大小之旅。
代碼中使用到了 STM32_Cryptographic_Library、STM32_Std_Library 和 LoRa 驅(qū)動(dòng)庫,這些庫編譯之后的體積較大,猜想能不能將所有的這些庫文件放在 Bootload 進(jìn)行固化,然后封裝好接口供 APP 調(diào)用,順著這個(gè)思路開啟了優(yōu)化之路。
2 調(diào)試之路
2.1 想法
常見的固件升級是將片內(nèi) Flash 分為 Bootload 區(qū)域和 APP 區(qū)域(如下圖所示),由 APP 區(qū)域接收新固件存儲(chǔ)在片內(nèi)或者片外 Flash,然后置升級的標(biāo)志位并跳轉(zhuǎn)到 Bootload,在 Bootload 完成新固件的更新工作。這樣實(shí)現(xiàn)比較常規(guī),但是由于 APP 中包含了多種庫導(dǎo)致目標(biāo)文件比較大,LoRa 通訊速率又不高會(huì)使整個(gè)升級時(shí)間很長。
為了減小 APP 的大小,考慮將使用到庫文件都固化在 Bootload 內(nèi),將片內(nèi) Flash 分為三個(gè)區(qū)域(如下圖所示),增加一個(gè)共有函數(shù)區(qū)域,用于存放 Bootload 中封裝好的接口。在函數(shù)調(diào)用時(shí),如果 APP 調(diào)用的是共有函數(shù),那么首先去共有函數(shù)區(qū)域找到函數(shù)在 Flash 中的地址,然后到 Bootload 中的對應(yīng)位置執(zhí)行相應(yīng)的代碼,再講執(zhí)行結(jié)果返回給 APP 區(qū)域,整個(gè)調(diào)用過程如下所示。
2.2 函數(shù)和變量定義在絕對地址的實(shí)現(xiàn)
有了上面的想法,首先需要驗(yàn)證的是如何將函數(shù)和變量放置在 Flash 的固定位置處,這樣每次在調(diào)用固定位置處的接口就能找到 Bootload 中固化的代碼接口。查閱相關(guān)資料,了解到 IAR 中的具體實(shí)現(xiàn)如下:
2.2.1 IAR的擴(kuò)展關(guān)鍵字
@ 用于函數(shù)變量的絕對地址定位,將函數(shù)變量等放到指定的 section
__no_init禁止系統(tǒng)啟動(dòng)時(shí)初始化變量
__root 保證沒有使用的函數(shù)或者變量也能夠包含在目標(biāo)代碼中
2.2.2 函數(shù)的絕對定位
要將函數(shù)定義在絕對位置,需要在函數(shù)定義時(shí)后面加上 @".section_name",例如:
void fun1(int a, int b) @".MY_SECTION"
{
... // 函數(shù)內(nèi)容
}
然后在鏈接文件 .icf 中添加如下內(nèi)容。其中 0x08010000 表示在 Flash 中的地址,.MY_SECTION 必須與函數(shù) @ 后面雙引號中內(nèi)容一致
place at address mem:0x08010000 { readonly section .MY_SECTION};
2.2.3 變量的絕對定位
示例如下,變量絕對定位,無須修改 .icf 鏈接文件,直接指定具體位置即可。
__no_init char array1[100]@0x2000B000;
2.2.4 常量的絕對定位
常量的絕對定位示例如下:
__root const int str1[4]@".MYSEG" = {1, 2, 3, 4};
常量絕對定位,需要改.icf文件,示例如下:
place at address mem:0x08018500 { readonly section .MYSEG};
2.2.4 .c文件的絕對定位
要將 test.c 文件定位到 Flash 的絕對地址,那么在 .icf文件中應(yīng)該按照如下格式添加:
place at address mem:0x08018000 { section .text object test.o };
編譯完成后整個(gè) test.c 文件的所有函數(shù),都在 0x08018000 之后。
2.3 Bootload 共有函數(shù)的實(shí)現(xiàn)
考慮到在初期編寫代碼時(shí)共有函數(shù)是可能發(fā)生變化的,如果按照上述的方法一個(gè)一個(gè)將函數(shù)放在固定的位置不是很方便,因此采用數(shù)組的方式將所有的共有函數(shù)放置在一起,如下所示:
__root const uint32_t func_table[]@".COMMON_FUNC_SEG" = {
(uint32_t)&fun1, /** 00 /
(uint32_t)&fun2, / * 01 /
(uint32_t)&fun3, / * 02 */
}
按照上面數(shù)組的方式將所有共有函數(shù)集合在一起,然后再 .icf 鏈接文件中將該數(shù)組放置在固定位置處,這樣在 0x08010000 位置處依次就能找到定義的所有共有函數(shù)(每個(gè)成員是函數(shù)對象的地址,占 4 個(gè)字節(jié))。
/** 將數(shù)組放置在固定位置 */
place at address mem:0x08010000 { readonly section .COMMON_FUNC_SEG};
2.4 APP 共有函數(shù)的使用
按照上述的方法可以將所有的庫函數(shù)封裝好并固化在 Bootload 中,并且實(shí)現(xiàn)了將所有的共有函數(shù)接口放置在固定的位置,在 APP 區(qū)可以使用函數(shù)指針的方式進(jìn)行訪問,示例如下:
/** 1. 聲明 */
typedef int (*app_fun1)(int a, int b);
typedef void (app_fun2)(void);
typedef char (app_fun3)(char p);
/ 2. 定義函數(shù)指針類型的變量 /
app_fun1 fun1;
app_fun2 fun2;
app_fun1 fun3;
/ 3. 共有函數(shù)的重定義 /
#define FUNC_TABLE_ADDR (0x08010000) / 共有函數(shù)的首地址 */
void redefine_common_function(void)
{
uint32_t *func_table_addr = (uint32_t )FUNC_TABLE_ADDR;
fun1 = (app_fun1)func_table_addr[0]; / 00 /
fun2 = (app_fun2)func_table_addr[1]; / 01 /
fun3 = (app_fun3)func_table_addr[2]; / 02 */
}
通過上面的方式就能在 APP 區(qū)域調(diào)用 Bootload 中固化的接口了,不過要注意這種方式調(diào)試起來不是很方便,需要前期驗(yàn)證好 Bootload 中封裝的接口有沒有問題。
3 注意事項(xiàng)
按照上述的方法操作時(shí)有一些注意事項(xiàng)如下:
- 固件更新區(qū)的絕對定位的函數(shù),不能隨意調(diào)用其他庫函數(shù),那些被調(diào)用的函數(shù)也必須是絕對定位的。
- 絕對定位的函數(shù),如果要使用常量,那么被使用的常量也必須是絕對定位的。
- 絕對定位的函數(shù),如果要使用全局變量,那么被使用的常量也必須是絕對定位的,而局部變量則不受此限制。
4 調(diào)試坎坷之路
上面的想法很有新意,在調(diào)試時(shí)自己封裝的接口文件也經(jīng)過了驗(yàn)證,但是在 APP 調(diào)用共有函數(shù)時(shí)程序還是跑飛了,經(jīng)過不斷的分析現(xiàn)實(shí)線現(xiàn)象,找到了問題的根源所在。STM32 標(biāo)準(zhǔn)庫在進(jìn)行時(shí)鐘配置時(shí)定義了兩個(gè)全局的數(shù)組如下,由于開始沒有注意到這兩個(gè)全局?jǐn)?shù)組,而這兩個(gè)全局?jǐn)?shù)組是在 Bootload 區(qū)域定義的,跳轉(zhuǎn)到 APP 區(qū)域后會(huì)對棧空間重新初始化,原本放這兩個(gè)數(shù)組的位置就被初始化其他數(shù)值了,到時(shí)時(shí)鐘配置出錯(cuò)。
/** stm32f10x_rcc.c */
static __I uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};
static __I uint8_t ADCPrescTable[4] = {2, 4, 6, 8};
分析后的解決辦法如下,因?yàn)檫@兩個(gè)全局?jǐn)?shù)據(jù)需要在 Bootload 區(qū)域中使用,而 Bootload 需要進(jìn)行固化,所以需要將這兩個(gè)數(shù)組放置固定的位置,這樣每次使用到該數(shù)組時(shí)就回去固定的位置找,就不會(huì)出現(xiàn)被誤修改的情況了。修改方式如下:
__root const uint8_t APBAHBPrescTable[16]@".AHBAPB_PRESC_TABLE"={0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};
__root const uint8_t ADCPrescTable[4]@".ADC_PRESC_TABLE"={2, 4, 6, 8};
/** 對應(yīng)的修改 .icf 文件 */
place at address mem:0x08010000 { readonly section .AHBAPB_PRESC_TABLE};
place at address mem:0x08010010 { readonly section .ADC_PRESC_TABLE};
5 補(bǔ)充
上述講解了在 Bootload 和 APP 中共有函數(shù)的定義和使用,怎么驗(yàn)證是不是將其定義在絕對地址了呢?我們可以查看編譯后生成的 map 文件,如下所示,可以看到在 map 文件中可以找到定義的 section。
評論