公司新闻

当前位置:主页 > 新闻中心 > 公司新闻 >

极海半导体APM32F407的USB MSD IAP应用(上)

作者: admin 时间:2023-02-28 来源:Admin5
摘要:01前言 极海半导体 推出的 APM32F407 系列IAP功能的实现有各种各样的方法,有些方法需要上位机,比如USART-IAP。有些则不需要上位机,也不需要专门驱动,而USB MSD IAP则是其中一种方法。...

01前言
极海半导体推出的APM32F407系列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工具可以清晰看到各个扇区的数据,方便分析
 

[向上]