IAP(二)SD卡固件升级流程与FatFS文件管理

前言

  实验硬件:STM32F407ZGT6,sd卡,USB线
  本文目标:在STM32中实现IAP本地升级,通过USB MSC连接电脑放置bin文件包
  软件:Keil、CubeMX
  实现功能:本节将详细介绍如何在Bootloader中初始化SD卡、挂载FATFS文件系统、查找并校验升级固件包(bin文件),以及如何通过USB MSC模式将SD卡与PC连接,实现固件的便捷传输和管理。
  传送门:
    IAP(一)bootloader地址跳转实现
  OTA升级也可借鉴本文

一、SD卡初始化与FatFS文件系统挂载

  FATFS 是一个专为嵌入式系统设计的通用 FAT 文件系统模块,它支持 FAT12、FAT16 和 FAT32 文件系统,广泛应用于 SD 卡、U盘等存储介质的读写操作,尤其适合 STM32 等微控制器平台。
  FatFS源码官网地址:http://elm-chan.org/fsw/ff/00index_e.html
  典型应用场景: SD卡、TF卡、U盘等外部存储的文件读写,数据记录、日志存储、固件升级等功能。

  我的sd卡接线如下,淘宝上任意买一个sd卡模块都可以
在这里插入图片描述
  我们这边直接用CubeMX自动生成带FatFS的工程配合SDIO接口用于SD卡的管理。
  初始化SD卡硬件接口,我这边用的是SDIO单线
在这里插入图片描述
  勾选FatFS文件系统
在这里插入图片描述
  这些设置完后点击生成代码

二、固件文件查找

  bin文件(Binary File,简称bin)是指以二进制格式存储的数据文件。在嵌入式开发中,bin文件通常指的是编译后生成的、可以直接烧录到芯片Flash中的固件镜像文件。keil中如何生成bin文件这边不讲了,我们生成app的bin文件之后放到sd卡中,比如说我这边在sd卡根目录新建了一个fimware的文件夹,将bin文件命名为ota_1234.bin然后放在sd卡,1234作为版本号供单片机进行识别。
  我们要做的是使用f_mount挂载sd卡并查找sd卡中的固件bin文件,然后打开对应文件夹并找到我们放进去的bin文件,挂载与查找函数如下:

 if (f_mount(&SDFatFS, SDPath, 1) == FR_OK) {
        printf("SD f_mount success\r\n");
    } else {
        printf("SD f_mount failed\r\n");
    }
int SD_find_ota_file(const char *dirPath, char *foundFilePath, size_t maxLen)
{
    FRESULT res;
    DIR dir;
    FILINFO fno;
 
    res = f_opendir(&dir, dirPath);
    if (res == FR_OK) {
        while (1) {
            res = f_readdir(&dir, &fno);
            if (res != FR_OK || fno.fname[0] == 0) break;
            if (!(fno.fattrib & AM_DIR)) {
                if (strcasestr(fno.fname, "ota") != NULL) {
                    snprintf(foundFilePath, maxLen, "%s/%s", dirPath, fno.fname);
                    f_closedir(&dir);
                    f_mount(NULL, SDPath, 0);  // ÏÈÐ¶ÔØ
                    f_mount(&SDFatFS, SDPath, 1);  // ÔÙ¹ÒÔØ
                    return 0; // ÕÒµ½
                }
            }
        }
        f_closedir(&dir);
    }
    printf("cannot open sd\r\n");
    return -1; // δÕÒµ½»ò³ö´í
}

char otaFilePath[64] = {0};
if (SD_find_ota_file("/firmware", otaFilePath, sizeof(otaFilePath)) == 0) {
   printf("find ota: %s\r\n", otaFilePath); 
} else {
   printf("cannot find ota\r\n");
}

  SD_find_ota_file 用于在指定目录下查找包含"ota"字样的文件,并将找到的第一个文件的完整路径保存到 foundFilePath,用于后续升级操作。查找函数主要流程为:

  1. 打开目录:使用 f_opendir 打开指定的目录(dirPath)。
  2. 遍历目录下的文件:用 f_readdir 逐个读取目录下的文件和子目录信息。
  3. 判断是否为普通文件:跳过子目录,只处理普通文件(!(fno.fattrib & AM_DIR))。
  4. 查找文件名中包含"ota"的文件:用 strcasestr 判断文件名中是否包含"ota"(不区分大小写)。
  5. 保存文件路径并重新挂载文件系统:找到后,用 snprintf 拼接完整路径到 foundFilePath,然后关闭目录,并重新挂载SD卡(有些情况下需要这样做以防止文件系统异常)。
  6. 返回结果:找到文件返回0,没找到或出错返回-1

三、升级标志的管理

  升级标志位用于指示当前是否需要进行OTA升级,防止因升级中断或异常导致系统无法启动。Bootloader每次启动时先读取标志位,判断是否需要进入升级流程。
  常见的作法是把升级标志位放在一般放在Bootloader区域末尾、或Flash最后一页。但是放在内部flash中会占用大量空间,以stm32f407为例,最小扇区是16kb,为了存储一个标志位就要浪费16kb的空间,内部flash空间本身就珍贵,这在实际工程里是极度浪费的,因此,工程中一般将标志位放在外部flash如W25Q64上。
  本文由于笔者的开发板没有挂载外部flash,因此放在了内部flash中,放在外部flash稍加修改即可。

#define FLASH_SIZE          ((uint32_t)0x100000U)
#define UPGRADE_FLAG_ADDR           (0x080E0000U)     // FLAG位置(Sector 11)
#define UPGRADE_FLAG_NO_UPGRADE     (0xFFFFFFFFFFFFFFFFULL) //不升级标志(擦除后默认值)
#define UPGRADE_FLAG_PENDING        (0xAAAAAAAAAAAAAAAAULL) //升级标志
uint64_t FLASH_Read_U64(uint32_t address)
{
    // 检查地址是否在 Flash 内存范围
    if (address >= FLASH_BASE && address < (FLASH_BASE + FLASH_SIZE))
    {
        // 检查地址是否是 8 字节对齐
        if ((address % 8) == 0)
        {
            return *(volatile uint64_t *)address;
        }
    }
    return UPGRADE_FLAG_NO_UPGRADE;
}

uint64_t Upgrade_Flag = FLASH_Read_U64(UPGRADE_FLAG_ADDR);

  FLASH_Read_U64 函数是从指定的Flash地址读取一个64位(8字节)数据。函数主要流程为:

  1. 检查地址合法性:判断传入的 address 是否在有效的Flash范围内,这样可以防止非法地址访问,避免MCU异常。
  2. 检查8字节对齐:只有当 address 是8字节对齐时(address % 8 == 0),才进行读取。
  3. 读取数据:如果上述条件都满足,直接将该地址处的8字节内容以 uint64_t 形式返回。
  4. 异常返回。

  有了读取标志位,肯定也要有写入标志位,写入标志位一般放在app中,在满足特定条件后将标志位写入,重启后即可进行固件更新,若标志位不为需升级,则直接跳转进入app,写标志位函数如下:

void SetUpgradeFlag(uint64_t flag)
{
    FLASH_EraseInitTypeDef EraseInitStruct;
    uint32_t SectorError = 0;

    HAL_FLASH_Unlock();

    // 擦除sector11扇区
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
    EraseInitStruct.Sector = FLASH_SECTOR_11;
    EraseInitStruct.NbSectors = 1;
    EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;

    if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK)
    {
        //擦除失败
        printf("Erase fail\r\n");
        HAL_FLASH_Lock();
        return;
    }
 // 写入低4字节
    uint32_t flag_low = (uint32_t)(flag & 0xFFFFFFFF);
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, UPGRADE_FLAG_ADDR, flag_low) != HAL_OK)
    {
        printf("Write low fail, error: 0x%lX\r\n", HAL_FLASH_GetError());
        HAL_FLASH_Lock();
        return;
    }
    // 写入高4字节
    uint32_t flag_high = (uint32_t)((flag >> 32) & 0xFFFFFFFF);
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, UPGRADE_FLAG_ADDR + 4, flag_high) != HAL_OK)
    {
        printf("Write high fail, error: 0x%lX\r\n", HAL_FLASH_GetError());
        HAL_FLASH_Lock();
        return;
    }
    HAL_FLASH_Lock();
}

  SetUpgradeFlag 函数用于在Flash指定地址写入一个64位(8字节)的升级标志位。函数主要流程为:

  1. 准备擦除参数:使用 FLASH_EraseInitTypeDef 结构体配置擦除参数。
  2. 解锁Flash:调用 HAL_FLASH_Unlock() 允许对Flash进行写操作。
  3. 擦除目标扇区:调用 HAL_FLASHEx_Erase() 擦除目标扇区。
  4. 写入低4字节、写入高4字节。
  5. 锁定Flash:写入完成后,调用 HAL_FLASH_Lock() 锁定Flash,防止误操作。

四、验证与其他

  在bootloader中读取sd卡bin文件并打印前32位与实际bin文件比较判断固件文件查找功能是否正确。
  在bootloader中读取标志位,若不需要升级则继续运行代码设置需要升级标志位并打印boot,若需要升级则跳转至app设置不需要升级标志位并打印app进行校验。看到的程序现象是第一次复位进入boot,第二次复位进入app。
  另外,升级标志位也可创建一个结构体进行管理,结构体可由升级标志位、版本号、日期组成进行方便后续工程。sd卡读取文件名可顺便读取版本号,或者另一种方式是在升级固件开头写入版本信息的头部信息。本文只是供IAP入门,这些进阶功能的实现不在本文列出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值