目录
概述
IAP(In Application Programming)即在应用中编程,允许在应用程序运行时更新或切换固件。STM32通过修改MSP(主堆栈指针)和PC(程序计数器)实现从不同地址启动,包括Flash或RAM地址。默认情况下,嵌入式程序以连续二进制形式烧录到STM32的可寻址Flash区域。若Flash容量足够存储多个完整程序,每个程序独立且完整,上电后可通过修改MSP值选择不同程序入口,从而实现多固件切换或升级。
BootLoader(引导加载程序)是嵌入式系统或计算机启动时运行的一段小型程序,负责初始化硬件、加载操作系统内核并将其控制权移交。它是系统从关机状态到操作系统完全运行之间的桥梁。
所以,固件升级的基本思路是将stm32的flash划分为若干个区域,其中包括BootLoader区域和APP区等,将各自的程序写到对应的flash区域里,本文将采用读取SD卡的文件来更新固件,只重实践不讲原理。
一、功能描述
使用STM32的SDIO总线驱动microSD卡实现SD卡升级程序。将FLASH分为2个部分。
分区介绍:
本文使用stm32f407zgt6,flash是1M,sector是不固定的(在读写擦除flash时需要特别注意),详细看下图STM32F4系列的FLASH结构。
BootLoader整个代码编译下来有36K左右,所以使用0x08000000~0x0000FFFF,剩下的FLASH全部用来存放APP,使用0x08010000~0x080F0000。
区域 | 起始地址 | 区域大小 | 功能 |
---|---|---|---|
BOOT | 0x08000000 | 0x00010000(64k) | 存放BootLoader程序 |
APP | 0x08010000 | 0x000F0000(960k) | 存放产品主程序 |
tips: 选择以上分区方式的好处是在上电瞬间可以触发升级,只要SD卡里面根目录有正确的程序都可以更新固件,所以就没必要用到SETTING区域在存更新触发标志位了。
1、BootLoader部分:
运行时首先会初始化SD卡,如果初始化成功并且检测到存在SD卡,就查找根目录是否有firmware.bin文件,如果有就升级,没有就直接进入APP,检测不到SD卡也会直接进入APP。
2、APP部分:
该部分需要在程序起始设置中断向量跳转指针,剩下的就是自己写的主程序。
二、BootLoader程序制作
主要包含了YMODEM协议的驱动代码。
1、分区定义
#define FLASH_SECTOR_SIZE 1024
#define FLASH_SECTOR_NUM 1024 // 1024K
#define FLASH_START_ADDR ((uint32_t)0x8000000)
#define FLASH_END_ADDR ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))
//flash sector addr
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) //sector0 addr, 16 Kbytes
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) //sector1 addr, 16 Kbytes
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) //sector2 addr, 16 Kbytes
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) //sector3 addr, 16 Kbytes
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) //sector4 addr, 64 Kbytes
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) //sector5 addr, 128 Kbytes
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) //sector6 addr, 128 Kbytes
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) //sector7 addr, 128 Kbytes
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) //sector8 addr, 128 Kbytes
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) //sector9 addr, 128 Kbytes
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) //sector10 addr,128 Kbytes
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) //sector11 addr,128 Kbytes
#define BOOT_SECTOR_ADDR 0x08000000
#define BOOT_SECTOR_SIZE 0x10000
#define APP_SECTOR_ADDR 0x08010000 // APP sector start address
#define APP_SECTOR_SIZE 0xF0000 // APP sector size
#define BOOT_ERASE_SECTORS_NUM 4 // 64k
#define APP_ERASE_SECTORS_NUM 8 // 64k+128k+128k+128k+128k+128k+128k+128k
typedef enum
{
NONE,
START_PROGRAM,
UPDATE_PROGRAM,
UPDATE_SUCCESS,
} process_status;
2、 主函数
这部分包含了升级的所有状态,该框架可以说对比上一篇文章是完全不变的,主要是SD卡初始化没问题就成功一大半了,具体看代码。
static void iap_process(void)
{
switch (bootupdate_process)
{
case NONE:
break;
case START_PROGRAM:
printf("start app...\r\n");
delay_ms(50);
if ((((*(vu32*)(APP_SECTOR_ADDR+4))&0xFF000000)==0x08000000)&&(!iap_load_app(APP_SECTOR_ADDR)))
{
printf("no program\r\n");
delay_ms(1000);
}
printf("start app failed\r\n");
break;
case UPDATE_PROGRAM:
if(SD_Init()==0)
{
printf("检测到SD卡\r\n");
show_sdcard_info(); //打印SD卡相关信息
sd_update();
}else
{
printf("没检测到SD卡\r\n");
}
bootupdate_process = UPDATE_SUCCESS;
break;
case UPDATE_SUCCESS:
bootupdate_process = START_PROGRAM;
// NVIC_SystemReset();
break;
default:
break;
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
bootupdate_process=UPDATE_PROGRAM;
while(1)
{
delay_ms(200);
LED0=!LED0;
iap_process();
}
}
3、SD卡升级文件检测和更新
关于SD卡和FATFS文件系统的移植我是参考正点原子的,主要是将malloc用全局变量代替,其它需要注意的是SD卡的扇区需要选择512字节。
本来是存放在堆区的,现改为存放在栈区
#define _USE_LFN 2 /* 0 to 3 */
挂载SD卡,判断sd卡根目录下是否有firmware.bin文件,有的话就获取一下该固件占用了多少个扇区,对应去擦除扇区,然后就可以复制固件到APP起始地址了。不存在该固件的话就直接退出进入APP。
void sd_update(void)
{
u8 file_buffer[512]={0};
uint32_t flash_addr = APP_SECTOR_ADDR;
unsigned long total_bytes_read = 0;
UINT bytes_read;
fres=f_mount(&fs[0],"0:",1); //挂载FLASH.
if(fres==FR_OK) //FLASH磁盘,FAT文件系统正常
{
printf("Flash disk OK!\r\n");
}
// 判断sd卡根目录下是否有firmware.bin文件
fres = f_stat("0:/firmware.bin", &fno);
if(fres == FR_OK)
{
printf("0:/firmware.bin”文件信息:\n");
printf("》文件大小: %ld(字节)\n", fno.fsize);
printf("sector_num=%d\r\n",get_mcu_flash_sector(flash_addr+(u32)fno.fsize)/8-4+1);
mcu_flash_erase(flash_addr, get_mcu_flash_sector(flash_addr+(u32)fno.fsize)/8-4+1);
}
else{
printf("firmware.bin not found!\r\n");
}
if(fres == FR_OK)
{
// 文件存在
printf("firmware.bin found, size: %lu bytes\r\n", fno.fsize);
printf("开始更新固件...\r\n");
// 打开固件文件
fres = f_open(&firmware_file, "0:/firmware.bin", FA_READ);
if(fres == FR_OK)
{
// 循环读取文件内容
while (total_bytes_read < fno.fsize)
{
// 读取数据块到缓冲区
fres = f_read(&firmware_file, file_buffer, 512, &bytes_read);
if (fres != FR_OK || bytes_read == 0)
{
// 读取出错或到达文件末尾
printf("读取文件失败或文件已结束,错误码: %d\r\n", fres);
break;
}
// 写入到FLASH
printf("正在写入地址 0x%08X,大小: %u 字节\r\n", flash_addr, bytes_read);
mcu_flash_write(flash_addr, file_buffer, bytes_read);
// 更新计数器和地址
total_bytes_read += bytes_read;
flash_addr += bytes_read;
// 显示进度
printf("更新进度: %lu/%lu bytes\r\n",
total_bytes_read, fno.fsize);
}
// 关闭文件
f_close(&firmware_file);
if (total_bytes_read == fno.fsize)
{
printf("固件更新完成! 共写入 %lu 字节\r\n", total_bytes_read);
} else {
printf("固件更新未完成! 已写入 %lu/%lu 字节\r\n", total_bytes_read, fno.fsize);
}
}
}
else
{
// 文件不存在
printf("firmware.bin not found!\r\n");
}
}
4、程序跳转
跳转这部分网上也很多,基本没什么区别,语句很少,建议每一步都去查一下大概意思。
tips:本文的开发环境有128k ram,所以下列语句需要判断跳转地址是否超出。
if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000)
详细参考以下文章:
关于STM32单片机IAP升级中if(((*(__IO uint32_t*)ulAddr_App) & 0x2FFE0000) == 0x20000000)语句的理解-CSDN博客
iapfun jump2app;
uint8_t iap_load_app(uint32_t app_addr)
{
uint32_t jump_addr;
if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000)
{
jump_addr = *(__IO uint32_t*) (app_addr + 4);
jump2app = (iapfun)jump_addr;
__set_MSP(*(__IO uint32_t*)app_addr);
jump2app();
return 1;
}
return 0;
}
三、APP程序制作
这部分只需要设置一下flash的偏移量。
NVIC_SetVectorTable (NVIC_VectTab_FLASH, 0x08010000); //向量重定向
四、工程配置(默认KEIL5)
BootLoader部分:0x08000000~0x0800FFFF
APP部分:0x08010000~0x080F0000
五、运行测试
(1)首先将firmware.bin固件存放到SD卡的根目录。
(2)没插上SD卡的情况,直接进入APP程序。
(3)上电前插入SD卡后启动的情况,可以看到检测到SD卡并且打印SD卡的基础信息,找到firmware.bin文件后就开始按512字节写入APP的起始地址,写完后进入新的APP程序。之后需要拔出SD卡,不然每次上电都会执行更新程序步骤。
结束语
以上SD卡升级stm32单片机功能已实现,思路和我前面写的文章一模一样,思路理解了就能一直套娃了,所以便一股脑移植SD卡了,建议大家在移植的时候对照着文档或者SDK直接移植过来,当然用HAL库更好。后面大家看到的也希望可以得到大家的指点或者互动,感谢各位亦菲彦祖们了。
完整代码下载地址:SD卡升级stm32固件资源-CSDN下载