通過W5500的網絡功能,到文件服務器下載STM32要更新的固件(可執行bin文件),存儲到STM32片內FLASH的APP備份區中,以待bootloader拷貝到APP代碼執行區,以實現OTA在線升級的功能。
我使用的芯片是STM32F103RCT6,48Kbyte的RAM內存和256Kbyte的片內FLASH。
片內FLASH區域劃分
對於片內FLASH應用的劃分,包括4個區域:bootloader區(36Kbyte),App代碼執行區(108Kbyte),App代碼備份區(108Kbyte),用戶數據記錄區(4Kbyte),共256Kbyte,如圖所示:
那麼片內FLASH各個區域對應的索引地址爲:
Bootloader功能設計與實現
bootloader和普通的app程序類似,只不過它在STM32上電後開始執行的,它負責檢測用戶數據數據記錄區是否有已經下載好的固件要更新。
如果用戶數據數據記錄區沒有下載好的固件數據要更新,則直接跳轉到App代碼執行區;
如果用戶數據數據記錄區有下載好的固件數據要更新,那麼根據用戶數據記錄區的下載固件文件長度,將App代碼備份區的數據拷貝到App代碼執行區,如果拷貝完成,則將用戶數據記錄區和App代碼備份區數據擦除,再跳轉到App代碼執行區去執行更新好的新程序。
在Keil工程中,bootloader的配置
bootloader的代碼實現:
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif
#ifndef __Z_HARDWARE_LED_H
#define __Z_HARDWARE_LED_H
#include "z_hardware_led.h"
#endif
#ifndef __Z_HARDWARE_FLASH_H
#define __Z_HARDWARE_FLASH_H
#include "z_hardware_flash.h"
#endif
#include <string.h>
#define APP_FLASH_ADDRESS (0x8009000)
#define BACKUP_FLASH_ADDRESS (0x8024000)
#define RECORD_FLASH_ADDRESS (0x803F000)
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
void func_jump2app(void)
{
if (((*(__IO uint32_t*)APP_FLASH_ADDRESS) & 0x2FFC0000 ) == 0x20000000)
{
__disable_irq();
JumpAddress = *(__IO uint32_t*) (APP_FLASH_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APP_FLASH_ADDRESS);
Jump_To_Application();
}
}
void func_led_onoff_cross(void);
u16 flash_buf[1024];
int main()
{
u8 i;
u32 len_backup;
init_led();
func_flash_read_datas(RECORD_FLASH_ADDRESS, flash_buf, 128);
delay_ms(10);
if(flash_buf[0] == 0x0002)// to update
{
u8 i, page, suc;
len_backup = flash_buf[2]*65536 + flash_buf[3];
page = len_backup / SECTOR_SIZE + 1;
if(page <= 54)//108kbyte
{
for(i = 0; i < page; i++)
{
memset(flash_buf, 0xFFFF, sizeof(flash_buf));
func_flash_read_datas(BACKUP_FLASH_ADDRESS + i*2048, flash_buf, 1024);
suc = func_flash_write_datas(APP_FLASH_ADDRESS + i*2048, flash_buf, 1024);
if(suc != 0)
{
break;
}
}
if(suc == 0)//copy success
{
//clear backup
for(i = 0; i < page; i++)
{
func_flash_erase_page(BACKUP_FLASH_ADDRESS + i*2048);
}
func_flash_erase_page(RECORD_FLASH_ADDRESS);
}
}
}
for(i = 0; i < 5; i++)
{
func_led_onoff_cross();
}
func_jump2app();
}
void func_led_onoff_cross()
{
func_led1_on();
func_led2_off();
delay_ms(200);
func_led2_on();
func_led1_off();
delay_ms(200);
}
App功能設計與實現
App的功能,業務邏輯部分不作爲本文描述重點。關於網絡或其他方式下載固件,通過一個外部觸發去執行,我這裏使用了一個撥碼開關來觸發下載固件的事件。
在App代碼執行開始的地方,做一箇中斷向量的配置
NVIC_SetVectorTable(NVIC_VectTab_FLASH, (FLASH_APPCODE_ADDR - FLASH_BASE_ADDR));
然後初始化各個GPIO的操作,由於我是通過W5500的Http Get方式下載可執行文件bin,初始化一下網絡的配置,DHCP動態獲取IP等。還有一個地方,是在網絡下載數據的過程,通過一個鉤子函數進行註冊存儲函數,以實現下載的方法和業務分離。
//hook
init_hooks_http_download_file_save(func_custom_http_downloading_file_save);
文件下載的內容,存儲到App代碼備份區,下載完成後會存儲升級固件的標記以及下載了的固件文件的長度。
至於 STM32 W5500 Http Get方式下載固件的思路和實現,可參考 《STM32 W5500 Http Client Get請求 下載bin文件思路和實現》。
下載完成後,程序跳轉到bootloader去執行。
關於App的工程配置
App的測試代碼:
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif
#ifndef __Z_HARDWARE_LED_H
#define __Z_HARDWARE_LED_H
#include "z_hardware_led.h"
#endif
#ifndef __Z_HARDWARE_USART2_H
#define __Z_HARDWARE_USART2_H
#include "z_hardware_usart2.h"
#endif
#ifndef __Z_HARDWARE_FLASH_H
#define __Z_HARDWARE_FLASH_H
#include "z_hardware_flash.h"
#endif
#ifndef __Z_HARDWARE_SPI_H
#define __Z_HARDWARE_SPI_H
#include "z_hardware_spi.h"
#endif
#ifndef __Z_HARDWARE_SWITCHKEYS_H
#define __Z_HARDWARE_SWITCHKEYS_H
#include "z_hardware_switchkeys.h"
#endif
#ifndef __W5500_H
#define __W5500_H
#include "w5500.h"
#endif
#ifndef __SOCKET_H
#define __SOCKET_H
#include "socket.h"
#endif
#ifndef __W5500_CONF_H
#define __W5500_CONF_H
#include "w5500_conf.h"
#endif
#ifndef __DHCP_H
#define __DHCP_H
#include "dhcp.h"
#endif
#ifndef __HTTPC_H
#define __HTTPC_H
#include "httpc.h"
#endif
typedef void (*pFunction)(void);
pFunction Jump_To_Bootloader;
uint32_t JumpAddress;
void func_jump2bootloader(void)
{
if (((*(__IO uint32_t*)FLASH_BASE_ADDR) & 0x2FFC0000 ) == 0x20000000)
{
__disable_irq();
JumpAddress = *(__IO uint32_t*) (FLASH_BASE_ADDR + 4);
Jump_To_Bootloader = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) FLASH_BASE_ADDR);
Jump_To_Bootloader();
}
}
void func_led_onoff_cross(void);
void func_led_onoff_sametime(void);
void func_custom_http_downloading_file_save(u8 state, u8 pageno, u8* cache, u32 len_cache_cont)//pageno start from 1
{
// func_usart2_dma_send_bytes(cache, len_cache_cont);
if(state == 0)//downloading
{
//write FLASH Backup file content
func_flash_write_datas(FLASH_APPBACKUP_ADDR + (pageno-1)*2048, (u16*)cache, SIZE_DOWNLOAD_CACHE/2);
}
else if(state == 1)//done
{
//write FLASH Backup file content
func_flash_write_datas(FLASH_APPBACKUP_ADDR + (pageno-1)*2048, (u16*)cache, (len_cache_cont%2) == 0 ? (len_cache_cont/2) : (len_cache_cont/2+1));
//write FLASH Record
{
u16 arrs[4];
u32 downloadsize = (pageno-1)*SIZE_DOWNLOAD_CACHE + len_cache_cont;
arrs[0] = 2;
arrs[1] = 0xFFFF;
arrs[2] = downloadsize >> 16;
arrs[3] = (u16)downloadsize;
func_flash_write_datas(FLASH_RECORD_ADDR, arrs, 4);
}
}
}
u8 buf[2048];
int main()
{
u8 state_k1_init;
u8 mac[6]={0, };
DHCP_Get dhcp_get;
//FIXME your file server ip
u8 srv_ip[] = {192, 168, 1, 109};
u16 srv_port = 8888;
NVIC_SetVectorTable(NVIC_VectTab_FLASH, (FLASH_APPCODE_ADDR - FLASH_BASE_ADDR));
init_led();
init_switchkeys();
init_system_spi();
func_w5500_reset();
// init_hardware_usart2_dma(115200);
getMacByLockCode(mac);
setSHAR(mac);
sysinit(txsize, rxsize);
setRTR(2000);
setRCR(3);
state_k1_init = func_get_switchkey1();
//USART DMA problem: 2 bytes missing
// func_usart2_dma_send_bytes(mac, 2);
// delay_ms(100);
//hook
init_hooks_http_download_file_save(func_custom_http_downloading_file_save);
//DHCP
for(;func_dhcp_get_ip_sub_gw(buf, sizeof(buf), 1, mac, &dhcp_get, 500) != 0;);
if(func_dhcp_get_ip_sub_gw(buf, sizeof(buf), 1, mac, &dhcp_get, 500) == 0)
{
setSUBR(dhcp_get.sub);
setGAR(dhcp_get.gw);
setSIPR(dhcp_get.lip);
close(1);
}
for(;;)
{
if(state_k1_init != func_get_switchkey1())
{
u8 res;
//downlaod bin file and reboot
memset(buf, 0 , sizeof(buf));
res = func_http_get_download_file(0, srv_ip, srv_port, "/file/APP_Download.bin_1.1.8", 5000, buf, sizeof(buf));
if(res == 0)
{
//jump to bootloader
func_jump2bootloader();
}
state_k1_init = func_get_switchkey1();
}
// func_led_onoff_cross();
func_led_onoff_sametime();
}
}
void func_led_onoff_cross()
{
func_led1_on();
func_led2_off();
delay_ms(500);
func_led2_on();
func_led1_off();
delay_ms(500);
}
void func_led_onoff_sametime()
{
func_led1_on();
func_led2_on();
delay_ms(500);
func_led2_off();
func_led1_off();
delay_ms(500);
}
測試與結果
將bootloader編譯並生成bin文件,通過STM32 ST-LINK Utility工具,將bootloader下載到0x08000000的位置上,如圖:
將App編譯一版LED1和LED2交叉亮滅的固件,通過STM32 ST-LINK Utility工具,將App下載到0x08009000的位置上,如圖:
再編譯一版LED1和LED2同時亮滅的固件,上傳到文件服務器。
復位板子後,程序先執行bootloader的LED交叉快速閃爍,隨後進入到原始App的LED慢速交叉閃爍。扳動撥碼開關,隨後幾秒,LED交叉快速閃爍,然後兩個LED同時亮,同時滅。App固件升級完成!
總結
bootloader和App的思路比較簡單,但是實現的過程中,可能會遇到一些坑。說說我遇到過的坑:
1、bootloader工程target的FLASH大小配置
2、bootloader工程的startup_stm32f10x_hd.s文件的堆棧大小配置過小,導致Debug不好用,配置大一些就好了。
3、bootloader工程操作FLASH的代碼問題,導致bin文件內容串位-通過STM32 ST-LINK Utility工具檢查 FLASH中實際的內容是什麼,從而排查解決了問題。
4、App工程也是因爲FLASH的操作不當,導致用戶數據記錄區數據串位。