STM32 W5500 OTA功能 - bootloader及app的设计和实现

Leonie ·
更新时间:2024-11-11
· 966 次阅读

通过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 #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的操作不当,导致用户数据记录区数据串位。


作者:Mr_Johhny



w5500 ota bootloader stm32 app

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章