我是靠谱客的博主 贤惠书本,最近开发中收集的这篇文章主要介绍STM32的USB MSD IAP应用,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

01 前言

IAP功能的实现有各种各样的方法,有些方法需要上位机,比如USART-IAP。有些则不需要上位机,也不需要专门驱动,而USB MSD IAP则是其中一种方法。其优点有:

  • 不需要专门上位机和驱动,直接将设备接入Windows系统即可方便使用,文件传输进度也能够显示
  • USB传输速度较快(FS:12Mbit/s,HS:480Mbit/s)且可靠

随着国内各MCU厂商产品线的扩展,已经有很多带USB相关外设的MCU系列可以选择,比如L072、F072、F103、F107、F4xx等。

下图是利用PC电脑实现MSD IAP的整体架构。
USB MSD Bootloader-总体架构.png

02 MSD IAP需要解决的问题

要实现USB MSD IAP功能,需要解决以下几个问题以改善体验:

  • 没有文件系统导致每次MCU掉电重新枚举都需要格式化的问题
    可以使用伪文件系统的方法解决。当MSD枚举时,MCU将伪文件系统的相关信息回应给USB Host,从而使USB Host认为MSD已存在文件系统,不需要格式化。

  • IAP状态的提示问题
    可以使用文件系统种的文件目录和读写地址信息来作状态判断,以作出提示。

下面是上述问题的解决,及MSD IAP的实现过程。

03 USB MSD枚举

使用官方MSC例程即可实现。要注意的是,需要将block地址转换成数据字节地址,以进行后续伪FAT32系统读写操作及IAP的操作。

#define MEMORY_BLOCK_SIZE          512

// 读数据
USBD_STA_T USBD_FS_MemoryReadData(uint8_t lun, uint8_t* buffer, uint32_t blockAddr, 
                                 uint16_t blockLength)
{
    USBD_STA_T usbStatus = USBD_OK;

    uint32_t index;
    uint8_t *bufferTemp = (uint8_t*)buffer;
    uint64_t readAddr = blockAddr * MEMORY_BLOCK_SIZE;

    for(index = 0; index < blockLength; index++)
    {
        IAP_FAT32_Read(bufferTemp, (uint32_t)readAddr);
        readAddr += MEMORY_BLOCK_SIZE;
        bufferTemp += MEMORY_BLOCK_SIZE;
    }

    return usbStatus;
}
// 写数据
USBD_STA_T USBD_FS_MemoryWriteData(uint8_t lun, uint8_t* buffer, uint32_t blockAddr, 
                                 uint16_t blockLength)
{
    USBD_STA_T usbStatus = USBD_OK;

    uint32_t index;
    uint8_t *bufferTemp = (uint8_t*)buffer;
    uint64_t writeAddr = blockAddr * MEMORY_BLOCK_SIZE;

    for(index = 0; index < blockLength; index++)
    {
        IAP_FAT32_Write(bufferTemp, (uint32_t)writeAddr);
        writeAddr += MEMORY_BLOCK_SIZE;
        bufferTemp += MEMORY_BLOCK_SIZE;
    }

    return usbStatus;
}

04 伪FAT32文件系统实现

在windows系统下,一般FAT32文件系统由保留区、FAT区及文件和目录数据区三个部分构成。

FAT文件系统构成.png

当USB枚举成MSD设备后,主机将会尝试读取该MSD设备的DBR、FAT及设备数据等内容。那么如果在主机读写操作的过程,正确应答文件系统的内容,则可以伪造一个文件系统,如下图所示。

那么接下来看来FAT32文件系统的具体内容。

启动扇区(DBR)

启动扇区从第一扇区开始,它保存了每个扇区的字节数,一个簇的扇区数,FAT表的起始位置,FAT表的个数以及FAT表的扇区数等信息。记录这些信息的数据结构是 BPB(BIOS Parameter Block),其数据结构如下。

字段偏移量字节数含义
BS_JmpBoot03跳转指令
BS_OEMName38OEM名称
BPB_BytsPerSec112每扇区字数
BPB_SecPerClus131每簇扇区数
BPB_RsvdSecCnt142保留扇区数
BPB_NumFATs161FAT个数
BPB_RootEntCnt172根目录项数(FAT32已无该限制)
BPB_TotSec16192扇区总数(小于32M使用)
BPB_Media211存储介质描述
BPB_FATSz16222每FAT表占用扇区数 (小于32M使用)
BPB_SecPerTrk242逻辑每磁道扇区数
BPB_NumHeads262逻辑磁头数
BPB_HiddSec284系统隐含扇区数
BPB_TotSec32324扇区总数(大于32M使用)
BPB_FATSz32364每FAT表扇区数(大于32M使用)
BPB_ExtFlags402标记
BPB_FSVer422版本 (通常为0)
BPB_RootClus444根目录起始簇
BPB_FSInfo482Boot占用扇区数
BPB_BkBootSec502备份引导扇区位置
BPB_Reserved5212保留
BS_DrvNum641
BS_Reserved651保留
BS_BootSig661扩展引导标记
BS_VolID674序列号
BS_VolLab7111卷标
BS_FilSysType828文件系统

C语言中可以用结构体来表示。

/**
  * @brief  BIOS paramter block
  */
typedef struct 
{
    uint8_t     BS_JmpBoot[3];
    uint8_t     BS_OEMName[8];
    uint16_t    BPB_BytsPerSec;
    uint8_t     BPB_SecPerClus;
    uint16_t    BPB_RsvdSecCnt;
    uint8_t     BPB_NumFATs;
    uint16_t    BPB_RootEntCnt;
    uint16_t    BPB_TotSec16;
    uint8_t     BPB_Media;
    uint16_t    BPB_FATSz16;
    uint16_t    BPB_SecPerTrk;
    uint16_t    BPB_NumHeads;
    uint32_t    BPB_HiddSec;
    uint32_t    BPB_TotSec32;

    /* FAT32 Structure */
    uint32_t    BPB_FATSz32;
    uint16_t    BPB_ExtFlags;
    uint16_t    BPB_FSVer;
    uint32_t    BPB_RootClus;
    uint16_t    BPB_FSInfo;
    uint16_t    BPB_BkBootSec;
    uint8_t     BS_Reserved1[12];
    uint8_t     BS_DrvNum;
    uint8_t     BS_Reserved2;
    uint8_t     BS_BootSig;
    uint32_t    BS_VolID;
    uint8_t     BS_VolLab[11];
    uint8_t     BS_FilSysType[8];
} FAT32_PBP_T;

其具体实现如下,可以用数组或结构体形式定义:

static const uint8_t FAT32_BPB[] = {
    0xEB,                               /*00 - BS_jmpBoot */
    0xFE,                               /*01 - BS_jmpBoot */
    0x90,                               /*02 - BS_jmpBoot */
    'M','S','D','O','S','5','.','0',    /* 03-10 - BS_OEMName */
    0x00, 0x02,                         /*11 - BPB_BytesPerSec = 512 */
    0x01,                               /*13 - BPB_Sec_PerClus = 2K*1 = 2K*/
    0x7C, 0x11,                         /*14 - BPB_RsvdSecCnt = 0x117C * 512 / 1024 == 2238KB */
    0x02,                               /*16 - BPB_NumFATs = 2 */
    0x00, 0x00,                         /*17 - BPB_RootEntCnt = 512 */
    0x00, 0x00,                         /*19 - BPB_TotSec16 = 0 */
    0xF8,                               /*21 - BPB_Media = 0xF8 */
    0x00, 0x00,                         /*22 - BPBFATSz16 = 0x0000 */
    0x3F, 0x00,                         /*24 - BPB_SecPerTrk = 0x003F */
    0xFF, 0x00,                         /*26 - BPB_NumHeads = 0x00FF */
    0x3F, 0x00, 0x00, 0x00,             /*28 - BPB_HiddSec = 0x0000003F */
    0xC1, 0xC0, 0x03, 0x00,             /*32 - BPB_TotSec32 = 0x0003C0C1 120MB */
    0x42, 0x07, 0x00, 0x00,             /*36 - BPB_FATSz32  = 0x00000742 */
    0x00, 0x00,                         /*40 - BPB_ExtFlags  = 0x0000 */
    0x00, 0x00,                         /*42 - BPB_FSVer  = 0x0000 */
    0x02, 0x00, 0x00, 0x00,             /*44 - BPB_RootClus  = 0x00000002 */
    0x01, 0x00,                         /*48 - BPB_FSInfo  = 0x0001 */
    0x06, 0x00,                         /*50 - BS_Reserved */
    0x00, 0x00, 0x00, 0x00,             /*52 - BS_Reserved */
    0x00, 0x00, 0x00, 0x00,             /*56 - BS_Reserved */
    0x00, 0x00, 0x00, 0x00,             /*60 - BS_Reserved */
    0x80,                               /*64 - BS_DrvNum = 0x80 */
    0x00,                               /*65 - BS_Reserved1 = 0 , dirty bit = 0*/
    0x29,                               /*66 - BS_BootSig = 0x29 */
    0xB0, 0x49, 0x90, 0x02,             /*67 - BS_VolID = 0x029049B0 */
    'N','O',' ','N','A','M','E',' ',' ',' ',' ',  /*71 - BS_VolLab */
    'F','A','T','3','2',' ',' ',' '     /*82 - BS_FilSysType */
};

信息扇区(FSINFO)

FSINFO扇区一般位于保留区的启动扇区与BPB之后,用来记录文件系统中空闲簇的数量以及下一可用簇的簇号等信息,以供操作系统作为参考。其数据结构如下:

字段偏移量字节数含义
FSI_LeadSig04值为0x41615252,这个标记用来表示该扇区为FSInfo扇区
FSI_Reserved14480保留,FAT32格式化时应将此区域全部设置为0
FSI_StrucSig4844值为0x61417272,这个标记用来表明扇区已被使用
FSI_Free_Count4884保存最新的剩余簇数量,如果为0xFFFFFFFF表示剩余簇未知,需要重新计算
FSI_Nxt_Free4924该值指示从哪里开始寻找剩余簇,通常设定为驱动程序最后分配出去的簇号
FSI_Reserved249612保留
FSI_TrailSig5084值为0xAA550000,标记扇区结束

C语言中可以用结构体来表示。

/**
  * @brief  FS info
  */
typedef struct 
{
    uint32_t  FSI_LeadSig;
    uint8_t   FSI_Reserved1[480];
    uint32_t  FSI_StrucSig;
    uint32_t  FSI_Free_Count;
    uint32_t  FSI_Nxt_Free;
    uint8_t   FSI_Reserved2[12];
    uint32_t  FSI_TrailSig;
} FAT32_FSINFO_T;

文件分配表(FAT1和FAT2)

FAT扇区是一组与数据簇号对应的列表,每个表项占用四个字节,其由两个完全相同的FAT(File Allocation Table)文件分配表单组成。表项数值对应的含义如下:

表项数值描述
0x00000000空闲簇,即表示可用
0x00000001保留簇
0x00000002 - 0x0FFFFFEF被占用的簇,其值指向下一个簇号
0x0FFFFFF0 - 0x0FFFFFF6保留值
0x0FFFFFF7坏簇
0x0FFFFFF8 - 0x0FFFFFFF文件最后一个簇

保留前5号表项为固定值。

表项含义
0号0x0FFFFFF8FAT表起始固定标识
1号0x0FFFFFFF不使用,默认值
2号0x0FFFFFFF标识文件结束
3号0x0FFFFFFF标识文件结束
4号0x0FFFFFFF标识文件结束

数据区

数据区是存放用户数据的区域,位于FAT2之后。其中短文件的数据结构如下:

字段偏移量字节数含义
DIR_Name011短文件名
DIR_Attr111文件属性
0x01:ATTR_READ_ONLY(只读)
0x02:ATTR_HIDDEN(隐藏)
0x04:ATTR_SYSTEM(系统)
0x08:ATTR_VOLUME_ID(卷标)
0x10:ATTR_DIRECTORY(目录)
0x20:ATTR_ARCHIVE(存档)
DIR_NTRes121SFN信息
0x18,文件名和扩展名都小写
0x10,文件名大写而扩展名小写
0x08,文件名小写而扩展名大写
0x00,文件名和扩展名都大写
DIR_CrtTimeTenth131文件创建时间,精确到10ms的值
DIR_CrtTime142文件创建时间
DIR_CrtDate162文件创建日期
DIR_LstAccDate182最后访问日期
DIR_FstClusHI202文件起始簇号的高16位
DIR_WrtTime222文件最近修改时间
DIR_WrtDate242文件最近修改日期
DIR_FstClusLO262文件起始簇号的低16位
DIR_FileSize284文件大小(单位:字节)

C语言中可以用结构体来表示。

/**
  * @brief  Director entry information
  */
typedef struct 
{
    uint8_t DIR_Name[11];
    uint8_t DIR_Attr;
    uint8_t DIR_NTRes;
    uint8_t DIR_CrtTimeTenth;

    uint16_t DIR_CrtTime;
    uint16_t DIR_CrtDate;
    uint16_t DIR_LstAccDate;
    uint16_t DIR_FstClusHI;
    uint16_t DIR_WrtTime;
    uint16_t DIR_WrtDate;
    uint16_t DIR_FstClusLO;
    uint32_t DIR_FileSize;
} FAT32_DIR_ENTRY_T;

其具体实现如下,定义卷标名字为BOOTLOADER 。创建第一个文件,文件名用指针传递,方便动态修改:

#define FAT32_VOLUME_LABEL          "BOOTLOADER "

uint8_t FAT32_StatusFileName[FAT32_FILE_NAME_SIZE] = {
    'S','T','A','T','U','S',' ',' ','T','X','T'
};

/**
  * @brief  fat32 read root director
  * @param  buffer: read buffer
  * @param  fileName: file name
  * @retval FAT32 status
  */
USER_STATUS_T FAT32_ReadDirEntry(uint8_t *buffer, uint8_t *fileName)
{
    USER_STATUS_T status = USER_OK;
    
    fat32Info.dir = (FAT32_DIR_ENTRY_T*)buffer;
    memset(buffer, 0, FAT32_SECTOR_SIZE);
    
    memcpy(fat32Info.dir->DIR_Name, FAT32_VOLUME_LABEL, 11);
    fat32Info.dir->DIR_Attr = FAT32_ATTR_VOLUME_ID;
    fat32Info.dir->DIR_NTRes = 0x00;
    fat32Info.dir->DIR_CrtTimeTenth = 0x00;
    fat32Info.dir->DIR_CrtTime = 0x0000;
    fat32Info.dir->DIR_CrtDate = 0x0000;
    fat32Info.dir->DIR_LstAccDate = 0x0000;
    fat32Info.dir->DIR_FstClusHI = 0x0000;
    fat32Info.dir->DIR_WrtTime = FAT32_MAKE_TIME(0,0);
    fat32Info.dir->DIR_WrtDate = FAT32_MAKE_DATE(10,02,2023);
    fat32Info.dir->DIR_FstClusLO = 0x0000;
    fat32Info.dir->DIR_FileSize = 0x00000000;
    
    ++fat32Info.dir;
  
    memcpy(fat32Info.dir->DIR_Name, fileName, 11);
    fat32Info.dir->DIR_Attr = FAT32_ATTR_ARCHIVE;
    fat32Info.dir->DIR_NTRes = 0x10;
    fat32Info.dir->DIR_CrtTimeTenth = 0x00;
    fat32Info.dir->DIR_CrtTime = FAT32_MAKE_TIME(0,0);
    fat32Info.dir->DIR_CrtDate = FAT32_MAKE_DATE(10,02,2023);
    fat32Info.dir->DIR_LstAccDate = FAT32_MAKE_DATE(10,02,2023);
    fat32Info.dir->DIR_FstClusHI = (uint16_t)(FAT32_FST_CLUS >> 16);
    fat32Info.dir->DIR_WrtTime = FAT32_MAKE_TIME(0,0);
    fat32Info.dir->DIR_WrtDate = FAT32_MAKE_DATE(10,02,2023);
    fat32Info.dir->DIR_FstClusLO = (uint16_t)(FAT32_FST_CLUS);
    fat32Info.dir->DIR_FileSize = 0x00000000;

    return status;
}

各扇区地址的计算

各扇区地址计算如下,也可以通过winhex工具分析得到:

[!note] Boot扇区
Boot扇区 = 0x0000 ~ 0x0C00

[!note] FAT1表头地址
FAT1表头地址 = 保留扇区数 × 每扇区字节数 = BPB_RsvdSecCnt x BPB_BytsPerSec

[!note] FAT2表头地址
FAT2表头地址 =(保留扇区数 + FAT1表扇区数)× 每扇区字节数

[!note] 数据区根目录地址
数据区根目录地址 = (保留扇区数 + FAT表扇区数 × FAT表个数 +(根目录首簇号 - 2)× 每簇扇区数)× 每扇区字节数
= (BPB_RsvdSecCnt + BPB_FATSz32 x 2 + ( BPB_RootClus- 2) x BPB_SecPerClus) x 512

[!note] 文件起始偏移地址
文件起始地址偏移 =(保留扇区数 + FAT表扇区数 × FAT表个数 +(文件起始簇号 - 2)× 每簇扇区数)× 每扇区字节数
= (BPB_RsvdSecCnt + BPB_FATSz32 x 2 + ( (DIR_FstClusHI | DIR_FstClusLO) - 2) x BPB_SecPerClus) x 512

APP固件文件起始地址的计算

FAT文件系统写入文件时会访问数据区根目录地址,根据上述扇区地址的计算可以知道根目录的地址。

当FAT文件系统访问根目录地址时会将文件目录信息写入,然后再在偏移地址之后写入文件内容,这时获取数据并转换成数据区文件结构即可根据文件起始偏移地址的公式来计算。

[!note] APP固件起始地址范围
APP固件起始地址范围 -> BIN文件起始偏移地址 ~ DIR_FileSize(BIN),最大为USER_APP_SIZE。
其中USER_APP_SIZE = FLASH_SIZE - BOOTLOADER_SIZE

使用winhex分析

使用winhex工具可以清晰看到各个扇区的数据,方便分析。至于winhex工具的使用,网上有很多文章介绍,这里不赘述。
image.png

05 Flash操作的实现

Flash的操作和普遍的IAP程序一样,包括Flash的解锁、上锁、擦除和写入等操作。网上有很多例子,这些不再赘述。

06 MSD Bootloader实现

Bootloader架构

MSD Bootloader的程序结构如下,由USB、Flash驱动、伪FAT32系统、MSC类处理及Bootloader应用程序代码构成。

资源使用和分配情况

使用AC6的image size优化等级后,MSD Bootloader约使用15KB Flash资源,如果使用F4xx系列,刚好使用第一个扇区存放bootload程序即可。

//==============================================================================

    Total RO  Size (Code + RO Data)                14728 (  14.38kB)
    Total RW  Size (RW Data + ZI Data)             11936 (  11.66kB)
    Total ROM Size (Code + RO Data + RW Data)      14936 (  14.59kB)

//==============================================================================

所以APP和Bootloader空间划分如下所示。
USB MSD Bootloader-Flash划分.png

程序流程

程序运行流程如下图所示,首先枚举MSD设备,接着以伪FAT32文件系统应答和处理主机的操作和数据,最后更新状态文件或者直接跳转到APP程序中。

这些流程中,是以USB Host访问的地址及数据来判断属于哪个过程的,这些地址的计算可以参考上述伪文件系统章节的扇区和APP固件起始地址的计算。

USB MSD Bootloader-Bootloader.png

状态判断和切换

Bootloader程序初始化时会创建第一个文件,用于指示Bootloader状态。该文件的文件名变化和IAP过程的关系如下图所示。

USB MSD Bootloader-状态指示.png

07 MSD IAP的使用过程

将USB口接入电脑,然后按住KEY1按键并复位MCU,那么程序就会自动进入MSD BOOTLOADER模式。同时在电脑上枚举出以BOOTLOADER命名的U盘,而MINI Board上的LED3会持续闪烁。
image.png

image.png

初始化完成后可以看到U盘中的状态文件名变为READY.txt
image.png

然后拷贝或者发送APP的BIN文件到U盘。注意该BIN文件的偏移地址大小需要根据实际来设定,这里设定为0x4000。
image.png

image.png

等待文件传输完成后,程序将自动重新枚举。如果IAP成功则状态文件名变更为SUCCESS.txt,如果失败则为ERROR.txt
image.png

然后复位(释放KEY1)即可进入APP程序中,也可以设置IAP完成后自动跳转到APP。

参考资料

FAT Filesystem (elm-chan.org)
STM32F103_MSD_BOOTLOADER

最后

以上就是贤惠书本为你收集整理的STM32的USB MSD IAP应用的全部内容,希望文章能够帮你解决STM32的USB MSD IAP应用所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部