打印
[APM32F4]

基于APM32F407的USB MSD IAP应用

[复制链接]
1860|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 susutata 于 2023-2-20 16:53 编辑

#申请原创#@21小跑堂
基于APM32F407的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的整体架构。
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区及文件和目录数据区三个部分构成。



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

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

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

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之后,用来记录文件系统中空闲簇的数量以及下一可用簇的簇号等信息,以供操作系统作为参考。其数据结构如下:

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)文件分配表单组成。表项数值对应的含义如下:

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

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

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工具分析得到:
Boot扇区 = 0x0000 ~ 0x0C00

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

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

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

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

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

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

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

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


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空间划分如下所示。


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

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


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

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

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

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


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

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

参考资料
01 FAT Filesystem (elm-chan.org)
02 MSD_BOOTLOADER

使用特权

评论回复
沙发
luobeihai| | 2023-2-20 21:23 | 只看该作者
点赞

使用特权

评论回复
板凳
cashrwood| | 2023-3-4 20:47 | 只看该作者
进入USB IAP下载需要注意什么?

使用特权

评论回复
地板
maudlu| | 2023-3-4 21:39 | 只看该作者
就是所谓的 DFU?              

使用特权

评论回复
5
backlugin| | 2023-3-4 21:48 | 只看该作者
在开发 IAP驱动程序时,需要避免 PC指针跳到用户程序区域。

使用特权

评论回复
6
jimmhu| | 2023-3-5 10:41 | 只看该作者
在开发 IAP驱动程序时,需要避免 PC指针跳到用户程序区域。

使用特权

评论回复
7
benjaminka| | 2023-3-5 11:48 | 只看该作者
官方提供的USB IAP例程了吗?

使用特权

评论回复
8
pixhw| | 2023-3-7 21:53 | 只看该作者
到官网找一个USB IAP的例程。

使用特权

评论回复
9
adolphcocker| | 2023-3-9 13:12 | 只看该作者
最近做了STM32通过USB程序升级功能测试,效果不错。

使用特权

评论回复
10
beacherblack| | 2023-3-9 13:29 | 只看该作者
进入USB IAP下载需要注意什么?

使用特权

评论回复
11
phoenixwhite| | 2023-3-10 11:01 | 只看该作者
就是所谓的 DFU?              

使用特权

评论回复
12
tabmone| | 2023-3-10 14:28 | 只看该作者
怎么用usb进行iap更新               

使用特权

评论回复
13
caizhiwei| | 2023-6-7 21:48 | 只看该作者
源码能分享一下吗?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

32

帖子

4

粉丝