一步一步實現STM32-FOTA系列教程之Bootloader編寫
一步一步實現STM32-FOTA系列教程之Bootloader編寫
文章系列連結
《一步一步實現STM32-FOTA系列教程之bin檔案生成》
《一步一步實現STM32-FOTA系列教程之STM32-FLASH分割槽說明》
《一步一步實現STM32-FOTA系列教程之FLASH靜態區讀寫》
前言
上一篇文章《一步一步實現STM32-FOTA系列教程之FLASH靜態區讀寫》實現了對FLASH靜態區讀寫的操作,有了這部分功能之後,就可以實現一個非常簡單的Bootloader程式碼了。
轉載請註明出處
Bootloader 功能說明
這裡提供的Bootloader功能就非常的簡單了,就是在Bootloader啟動之後,讀取FLASH靜態區的引數資訊,然後判斷啟動分割槽標誌位的值,然後進入相應的分割槽,執行該分割槽的程式。
注意這裡的教程中沒有在 Bootloader 中編寫聯網獲取新版本的程式碼,這部分的實現會放到主分割槽和備份分割槽的程式碼中實現。
Bootloader 啟動流程
這個啟動流程之前已經說過了,這裡貼出來,方便對比程式碼。
函式實現
FLASH 分割槽巨集定義
// FLASH 分割槽 配置 #define FLASH_BASE_ADDR ((uint32_t)0x08000000) #define NLEDBOOTLOADER_SIZE (64*1024) // Bootloader 大小為 64KB #define FIRMWAR_ONE_SIZE (80*1024) // 韌體1 大小為 80KB #define FIRMWAR_TWO_SIZE (80*1024) // 韌體2 大小為 80KB #define NLED_CONFIG_PARAM_SIZE (224*1024) #define BOOTLOADER_START_ADDR (FLASH_BASE_ADDR) //Bootloader 啟動地址 #define FIRMWAR_ONE_START_ADDR (FLASH_BASE_ADDR + NLEDBOOTLOADER_SIZE ) // 韌體 1 啟動地址 #define FIRMWAR_TWO_START_ADDR (FLASH_BASE_ADDR + NLEDBOOTLOADER_SIZE +FIRMWAR_ONE_SIZE) // 韌體 2 啟動地址 #define CONFIG_PARAM_START_ADDR (FLASH_BASE_ADDR + NLED_CONFIG_PARAM_SIZE) // FLASH 靜態區引數 起始地址
程式跳轉程式碼
在之前的 FLASH靜態區引數讀寫的基礎上。增加了對啟動分割槽的判斷還有載入啟動分割槽的程式碼。
//跳轉到應用程式段 //appxaddr:使用者程式碼起始地址. void iap_load_app(u32 appxaddr) { if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //檢查棧頂地址是否合法. { jump2app=(iapfun)*(vu32*)(appxaddr+4); //使用者程式碼區第二個字為程式開始地址(復位地址) MSR_MSP(*(vu32*)appxaddr); //初始化APP堆疊指標(使用者程式碼區的第一個字用於存放棧頂地址) for(int i = 0; i < 8; i++) { NVIC->ICER[i] = 0xFFFFFFFF; /* 關閉中斷*/ NVIC->ICPR[i] = 0xFFFFFFFF; /* 清除中斷標誌位 */ } jump2app(); //跳轉到APP. } } //判斷進行韌體升級還是跳轉APP void LoadingFirmware(void) { if(bootflag ==1) { printf("Jump To Firmware First 0x%08X\r\n",FIRMWAR_ONE_START_ADDR); iap_load_app(FIRMWAR_ONE_START_ADDR); } else if(bootflag ==2) { printf("Jump To Firmware First 0x%08X\r\n",FIRMWAR_TWO_START_ADDR); iap_load_app(FIRMWAR_TWO_START_ADDR); } else { printf("boot error\r\n"); } LED0 = 0; }
主函式實現
int main()
{
ledInit();
uart1_init(9600);
delay_init();
printf("-----------------------------------\r\n");
printf("------------Bootloader-------------\r\n");
printf("-----------------------------------\r\n");
LOG_COMPILE();
printf("Version: V1.0\r\n");
LED0_Blink(100);
GetDeviceInfo();
LoadingFirmware(); /*判斷載入啟動分割槽*/
}
至此,如此簡單的一個Bootloader 就編寫完成,下面開始測試驗證一下吧。
注意事項
1、由於Bootloader所佔用的FLASH空間大小為64KB,如果在MDK中進行除錯模擬時,注意修改MDK工程的ROM大小,如下圖所示。
2、在使用JLINK模擬器下載程式碼的時候,注意下載時擦除 FLASH 扇區的方式不要選擇全片擦除,這裡選擇擦除部分塊,選擇方式如下所示。
測試驗證
驗證說明
本次測試驗證主要是驗證該 Bootloader 能否載入指定分割槽中的程式,由於還沒有編寫遠端獲取韌體的程式碼,因此這裡採用除錯編譯模擬的方式將主分割槽和備份分割槽的程式碼燒寫到 FLASH 中。
為了測試方便,這裡僅僅提供兩個最簡單的分割槽程式碼,其中主分割槽的程式碼會在串列埠1迴圈列印Firmware1 running…,迴圈間隔為1秒;備份分割槽的程式碼則會在串列埠1迴圈列印Firmware2 running…,迴圈間隔為2.5秒。
Firmware1韌體main檔案實現
#include <string.h>
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
#include "logdebug.h"
#include "fotaprotocol.h"
//執行指示燈
void LED0_Blink(int xms)
{
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
}
int main()
{
NVIC_SetVectorTable(FIRMWAR_ONE_START_ADDR,0);
delay_init();
ledInit();
uart1_init(9600);
printf("-----------------------------------\r\n");
printf("------------Firmware1--------------\r\n");
printf("-----------------------------------\r\n");
LOG_COMPILE();
printf("Firmware-1-Version: V1.0\r\n");
while(1)
{
printf("Firmware1 running...\r\n");
LED0_Blink(200);
}
}
注意,程式碼編寫完之後,還要修改MDK工程中ROM區域的設定,Firmware1韌體的設定如下。
Firmware2韌體main檔案實現
#include <string.h>
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
#include "logdebug.h"
#include "fotaprotocol.h"
//執行指示燈
void LED0_Blink(int xms)
{
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
LED0 =1;
delay_ms(xms);
LED0 =0;
delay_ms(xms);
}
int main()
{
NVIC_SetVectorTable(FIRMWAR_TWO_START_ADDR,0);
delay_init();
ledInit();
uart1_init(9600);
printf("-----------------------------------\r\n");
printf("------------Firmware2--------------\r\n");
printf("-----------------------------------\r\n");
LOG_COMPILE();
printf("Firmware-2-Version: V1.0\r\n");
while(1)
{
printf("Firmware2 running...\r\n");
LED0_Blink(500);
}
}
注意,程式碼編寫完之後,還要修改MDK工程中ROM區域的設定,Firmware2韌體的設定如下。
驗證步驟
- 首先將 Bootloader 利用 Jlink 燒寫到 FLASH 中。
- 然後將 Firmware1 和 Firmware2 韌體分別按照同樣的方法燒寫到FLASH中。
- 燒寫完成後,按復位按鈕檢視啟動日誌。
燒寫完成後,正常的測試現象是,首先啟動 Bootloader 列印 Bootloader 相關資訊,然後根據啟動分割槽標誌位載入不同的韌體。
注意,由於在 Bootloader中加入了交替修改啟動分割槽標誌位的程式碼,因此每次重啟微控制器,Bootloader就會從不同的分割槽執行程式碼。這樣也達到了測試Bootloader載入啟動分割槽的效果了。
測試日誌記錄
下面附上測試驗證的日誌,注意微控制器重啟是手動硬體復位的,日誌如下。
-----------------------------------
------------Bootloader-------------
-----------------------------------
Compile Time: Nov 13 2018,19:24:50
Version: V1.0
Static Params Address :0x08038000
start to boot firmware one
Testing...
set updateflag 2
Jump To Firmware First 0x08010000
-----------------------------------
------------Firmware1--------------
-----------------------------------
Compile Time: Nov 13 2018,19:39:26
Firmware-1-Version: V1.0
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
Firmware1 running...
-----------------------------------
------------Bootloader-------------
-----------------------------------
Compile Time: Nov 13 2018,19:24:50
Version: V1.0
Static Params Address :0x08038000
start to boot firmware two
Testing...
set updateflag 1
Jump To Firmware First 0x08024000
-----------------------------------
------------Firmware2--------------
-----------------------------------
Compile Time: Nov 13 2018,19:33:58
Firmware-2-Version: V1.0
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
Firmware2 running...
測試原始碼
測試原始碼中包含有Bootloader原始碼,主分割槽原始碼和備份分割槽原始碼,已經打包到一個壓縮包中了,下面附上下載連結。
測試驗證程式碼下載連結
參考文件
正點原子 STM32 系列開發板IAP實驗