一、背景知识
-
STM32启动流程(从内部flash启动)[1]
正常情况下,程序从Flash启动时的流程如下:(转载自)
https://blog.csdn.net/qq_42190402/article/details/139671333
-
程序从Flash启动,根据中断向量表找到复位中断处理函数的地址。(0x0800 0004处是中断向量表的起始地址,也是中断向量表的起始,记录了复位中断处理函数的地址)。
-
执行复位中断处理函数,初始化系统环境后跳转到main函数。
-
在main函数的死循环中运行,直到有中断发生。
-
中断发生时,跳转到中断向量表起始处,根据中断信号源跳找到相应的中断处理函数。
-
中断处理函数执行完后返回到main函数继续运行。
值得注意的是在bootloader中,CM4内核上电后从地址 0x0000 0000 处取出堆栈指针 MSP 的初始值,该值就是栈顶地址。而我们通过内部flash中启动,因此这个地址被映射到了:0X0800000
以上我们得到一个重要的信息,也就是,STM32上电后,是从0X08000000 flash地址启动的。因此我们的boot程序需要烧录至0X08000000,在boot中完成app代码搬运后,跳转至APP程序中,app程序的flash地址需要我们自己指定。
-
STM32程序烧录方式
-
通过J-link/ST-link等烧录器将hex文件(hex文件中包含flash起始地址)烧录至指定位置。
例如在Keil中指定的on-chip 起始flash地址为:
那么这种方式会将hex文件烧录至0X8000000地址中。
-
ISP(In System Program)方式:
-
ISP(In-System Programming,在系统可编程)是一种技术,它允许用户直接在电路板上对空白或已编程的器件进行编程、擦除或更新,无需将器件从电路板上取下。这一过程依赖于bootloader(自举程序),它存储在微控制器如STM32的内部ROM(系统存储器)中,主要负责通过串行外设(如USART、CAN、USB、I2C等)接收应用程序代码,并将其写入Flash内存。不同的串行接口定义了各自的通信协议,包括命令集和数据传输序列。
2.3 IAP(In Application Program)
IAP(In Application Programming)即在应用编程, IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。
通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两部分代码,第一部分程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、 USART)接收程序或数据,执行对第二部分代码的更新;第二部分代码才是真正的功能代码。
两部分代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一部分代码开始运行,它作如下操作:
-
检查是否需要对第二部分代码进行更新;
-
如果不需要更新则转到第二部分代码执行;
-
执行更新操作;
-
跳转到第二部分代码执行;
二、IAP实现OTA升级
由以上得知,想要实现在APP程序里实现在线升级我们可以用以下方式实现:
程序还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数。
在执行完 IAP 以后(即将新的 APP 代码写入 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,但在不同位置上,共有两个中断向量表。
-
编写一个APP程序,烧录地址为0x802000000
在该程序中,需要将中断向量表偏移至0x80200000,
SCB->VTOR = 0x8020000;
通过串口/SPI等通讯接口,接收来自上位机的升级数据包(bin)文件,保存到一个大缓存中
int arm_mcu_upgrade_data_set(ArmSpiQueue_str *UpgradeData)
{int RecvIndex = UpgradeBuffIndex;char CheckSum = 0;if(MCU_UpgradeStartFlag == 1){os_printf_log("UpgradeData->len = %d\n", UpgradeData->len);for(int i = 1; i < UpgradeData->len; i++){CheckSum += UpgradeData->data[i];MCU_UpgradeBuff[RecvIndex+i-1] = UpgradeData->data[i];}if(CheckSum == UpgradeData->data[0]){UpgradeBuffIndex += UpgradeData->len-1;MCU_UpgradeACK = 1;}else{MCU_UpgradeACK = 0;}os_printf_log("CheckSum = %x, UpgradeData->data[0] = %x\r\n", CheckSum, UpgradeData->data[0]);}else{MCU_UpgradeACK = 0;}//fw pack upgrade finshif(UpgradeBuffIndex >= MCU_FWSumSize){os_printf_log("UpgradeBuffIndex = %d, MCU_FWSumSize = %d\r\n", UpgradeBuffIndex, MCU_FWSumSize);aw911c_led_control(led_null);}return 0;
}
接收完数据包后,将升级包数据、升级标志写入在一个内部flash区域(注意不要和启动位置冲突),例如((uint32_t)0x08040000)地址;
int mcu_upgrade_start(uint8_t *UpgradeData, uint32_t size)
{int err = 0;uint32_t addr = 0;uint8_t sector = 0;uint32_t UpgradeFlag = 0, FW_Size = 0;flash_unlock();//check upgrade prevent flagUpgradeFlag = flash_read_u32(MCU_USER_UPGRADE_FLAG_ADDR);os_printf_log("UpgradeFlag = %x\n", UpgradeFlag);if(UpgradeFlag != MCU_UPGRADE_PREV_FLAG){flash_lock();os_printf_log("UpgradeFlag = %x, != MCU_UPGRADE_PREV_FLAG\r\n", UpgradeFlag);return 1;}//start write flash//erase flashaddr = MCU_UPGRADE_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){flash_lock();os_printf_log("flash_addr_erase is fail\n");return err;}//write flashos_printf_log("flash_write_u8 size = %d, %x-%x-%x-%x\n", size, UpgradeData[0], UpgradeData[1], UpgradeData[2], UpgradeData[3]);err = flash_write_u8(addr, UpgradeData, size);if(err){flash_lock();os_printf_log("flash_write_u8 is fail err = %d\r\n", err);return err;}//erase flashaddr = MCU_USER_PARAM_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){flash_lock();os_printf_log("flash_addr_erase is fail\n");return err;}//UpgradeFlag = MCU_UPGRADE_FINSH_FLAG;flash_write_u32(MCU_USER_UPGRADE_FLAG_ADDR, &UpgradeFlag, 1);FW_Size = size;flash_write_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR, &FW_Size, 1);flash_lock();return 0;
}
-
编写一个boot程序,烧录在0X08000000 flash地址,
上电后,先执行的是该程序,因此,需要去flash保存升级标志的区域读回升级标志,如果需要升级,则先将0x802000000地址数据擦除,并将0x08040000地址数据写入到0x802000000。
int mcu_fw_update(void)
{int err = 0;uint32_t addr = 0;uint8_t sector = 0;uint32_t UpgradeFlag = 0, FW_Size = 0;uint8_t *upgradeAddr = 0;UpgradeFlag = flash_read_u32(MCU_USER_UPGRADE_FLAG_ADDR);FW_Size = flash_read_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR);os_printf_log("UpgradeFlag = %x, FW_Size = %d\r\n", UpgradeFlag, FW_Size);flash_unlock();if(UpgradeFlag == MCU_UPGRADE_FINSH_FLAG){if(FW_Size == 0x00000000 || FW_Size == 0xFFFFFFFF){os_printf_log("flash_read_u32 FW_Size is err, FW_Size = %d\r\n", FW_Size);flash_lock();return 1;}//start write flashupgradeAddr = (uint8_t *)MCU_UPGRADE_BASE_ADDR;os_printf_log("MCU_UPGRADE_BASE_ADDR = %x-%x-%x-%x\n", *(upgradeAddr), *(upgradeAddr+1), *(upgradeAddr+2), *(upgradeAddr+3));memcpy(UpgradeData, upgradeAddr, FW_Size);//erase flashaddr = MCU_APP1_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){os_printf_log("flash_addr_erase is fail\n");flash_lock();return 1;}//write flasherr = flash_write_u8(addr, UpgradeData, FW_Size);if(err){os_printf_log("flash_write_u8 is fail err = %d\r\n", err);flash_lock();return 1;}//erase flashaddr = MCU_USER_PARAM_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){os_printf_log("flash_addr_erase is fail\n");flash_lock();return 1;}UpgradeFlag = MCU_UPGRADE_PREV_FLAG;flash_write_u32(MCU_USER_UPGRADE_FLAG_ADDR, &UpgradeFlag, 1);FW_Size = 0;flash_write_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR, &FW_Size, 1);}else if(UpgradeFlag == MCU_UPGRADE_FINSH_FLAG){flash_lock();return 0;}else{//erase flashaddr = MCU_USER_PARAM_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){os_printf_log("flash_addr_erase is fail\n");flash_lock();return 1;}UpgradeFlag = MCU_UPGRADE_PREV_FLAG;flash_write_u32(MCU_USER_UPGRADE_FLAG_ADDR, &UpgradeFlag, 1);FW_Size = 0;flash_write_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR, &FW_Size, 1);}flash_lock();return 0;
}
写入完成后,跳转至0x802000000地址启动,跳转方式:
__asm void SET_MSP (uint32_t ulAddr)
{MSR MSP, r0 //??Main Stack??BX r14 //
}/* ?????? */
typedef void (*Jump_Fun)(void); //????
void IAP_jumpApp (uint32_t App_Addr)
{Jump_Fun JumpToApp; //if (((*( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //{os_printf_log("IAP_jumpApp \n");JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //SET_MSP( * ( __IO uint32_t * ) App_Addr ); //JumpToApp(); //}else{os_printf_log("*( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) != 0x20000000, = %x\r\n", ((*( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ));}
}
三、烧录
两个程序编写完成后,需要将两个程序合并为一个hex文件烧录,主要是需要先将boot程序烧录进去,后续就可以进行IAP升级了,合并方式如下:
-
打开J-flash工具,选择目标期间,例如STM32F407VG
-
打开boot程序的HEX文件
-
Merge APP程序的HEX文件,并将文件另存。得到合并的HEX文件。
可以发现,hex文件是带FLASH存储信息的,APP程序被放在了08020000目标位置了