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