固件烧写到Flash的实现
前言
实验硬件:STM32F407ZGT6,sd卡,USB线
本文目标:在STM32中实现IAP本地升级,通过USB MSC连接电脑放置bin文件包
软件:Keil、CubeMX
实现功能:前两篇文章中,我们已经实现了STM32 Bootloader的地址跳转功能,并介绍了如何通过SD卡管理升级固件文件及升级标志位的设置。本文将进入Bootloader升级流程的核心环节——将SD卡中的bin固件文件安全、可靠地烧写到STM32的内部Flash指定区域。
传送门:
IAP(一)bootloader地址跳转实现
IAP(二)SD卡固件升级流程与文件管理
OTA升级也可借鉴本文
一、获取固件大小与扇区擦除
在进行固件升级前,首先需要获取待升级固件的实际大小,并据此计算出需要擦除的Flash扇区范围。这样可以确保只擦除App区域,避免误擦Bootloader或其他重要数据。我们使用f_stat函数获取文件信息结构体中的fsize字段。(先要挂载sd卡)
uint32_t SD_Get_File_Size(const char* filename)
{
FILINFO fileInfo;
FRESULT res;
res = f_stat(filename, &fileInfo);
if (res != FR_OK) {
return 1;
}
return fileInfo.fsize;
}
STM32的Flash通常以扇区(Sector)为单位擦除,不同芯片的扇区大小和数量不同。我们需要根据App区的起始地址、bin文件大小,计算出涉及的所有扇区编号。
本文使用的是STM32F407ZGT6芯片,其他型号芯片需根据其各自分区做相应调整。
static uint32_t GetFlashSector(uint32_t addr)
{
if (addr < 0x08000000) {
return 0xFFFFFFFF;
}
if (addr < 0x08010000) {
return (addr - 0x08000000) / 0x4000;
}
else if (addr < 0x08020000) {
return 4;
}
else {
return 5 + (addr - 0x08020000) / 0x20000;
}
}
uint32_t CalEraseSectors(uint32_t startAddr, uint32_t size,
uint32_t *startSector, uint32_t *endSector)
{
uint32_t endAddr = startAddr + size;
if (startAddr < 0x08000000 || endAddr > 0x08100000) {
return 1;
}
*startSector = GetFlashSector(startAddr);
if (*startSector == 0xFFFFFFFF) {
return 1;
}
*endSector = GetFlashSector(endAddr - 1);
if (*endSector == 0xFFFFFFFF) {
return 1;
}
return 0;
}
进行对应扇区擦除
HAL_StatusTypeDef Erase_Sectors(uint32_t start_sector, uint32_t end_sector)
{
HAL_StatusTypeDef status = HAL_OK;
FLASH_EraseInitTypeDef eraseInit;
uint32_t sectorError = 0;
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
for (uint32_t i = 0; i < total; i++) {
eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
eraseInit.Sector = start_sector + i;
eraseInit.NbSectors = 1;
eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
status = HAL_FLASHEx_Erase(&eraseInit, §orError);
if (status != HAL_OK) {
break;
}
}
HAL_FLASH_Lock();
return status;
}
Erase_Sectors 用于批量擦除STM32 Flash的指定范围内的扇区。其主要流程如下:
- 解锁Flash:HAL_FLASH_Unlock() 允许对Flash进行写/擦除操作
- 清除Flash相关标志位:__HAL_FLASH_CLEAR_FLAG(…) 清除之前可能遗留的Flash操作标志,防止影响本次擦除。
- 循环擦除每个扇区
- 锁定Flash:HAL_FLASH_Lock() 防止后续误操作
- 返回擦除状态
二、固件写入
在完成Flash扇区的擦除后,接下来需要将SD卡中的bin固件文件分块读取,并逐步写入到STM32的App区Flash。
由于STM32的Flash写入通常以4字节(32位)为单位进行,因此我们需要将bin文件按块读取,并按字对齐方式写入目标地址。
整个写入过程需要注意以下几点:
- 分块读取:为节省RAM资源,通常采用固定大小的缓冲区(如1024字节)分批读取文件内容
- 对齐写入:Flash写入必须以4字节对齐,若遇到不足4字节的数据需特殊处理
- 异常处理:写入过程中要检测文件读取和Flash写入的返回值,确保每一步都能正确完成
下面给出固件写入的核心实现代码
uint32_t OTA_WriteFirmware(const char* firmwarename, uint32_t flash_start_addr, uint32_t file_size)
{
FIL file;
FRESULT res;
UINT br;
uint8_t read_buf[BIN_BUF_SIZE];
uint32_t flash_addr = flash_start_addr;
int ret_status = 0;
HAL_StatusTypeDef flash_status = HAL_OK;
//打开固件文件
res = f_open(&file, firmwarename, FA_READ);
if (res != FR_OK) {
return 1; // 固件打开失败
}
//解锁Flash
HAL_FLASH_Unlock();
//循环读取文件并写入Flash
while(1) {
//读取文件数据到缓冲区
res = f_read(&file, read_buf, BIN_BUF_SIZE, &br);
if (res != FR_OK) {
ret_status = -2;
break; // 退出循环
}
//读到0字节,表示文件读取完毕
if (br == 0) break;
//将读取到的数据按4字节写入Flash
for (uint32_t i = 0; i < br; i += 4) {
if (i + 4 > br) {
break;
}
uint32_t data = *(uint32_t*)(read_buf + i);
flash_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, flash_addr, data);
if (flash_status != HAL_OK) {
ret_status = -3; // 写入失败
break;
}
flash_addr += 4;
}
if (ret_status != 0) break;
}
//锁定Flash
HAL_FLASH_Lock();
//关闭文件
f_close(&file);
return ret_status;
}
完成如上工作,STM32的IAP核心功能已经基本完成,后续章节将对IAP进行进一步完善,包括USB MSC在bootloader中访问sd卡,版本备份异常回退等功能提升系统的健壮性与用户体验