打印
[RISC-V MCU 创新应用比赛]

沁恒 CH32V103 USB Host FATFS 读取U盘文件

[复制链接]
5044|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创#  @21小跑堂

  沁恒 CH32V103 在引脚和功能上几乎和 STM32F103 系列通用,在保持通用的情况下,还创新的增加了 USB Host 功能

  曾经用 STM32F103 做过仪表,需要把仪表数据导出到U盘,但 STM32F103 不支持 Host,需要外接沁恒的 CH375、CH376 进行读写U盘

  现在使用单片 CH32V103 就能替代完成这些功能,省钱了

  最近使用沁恒 CH32V103 做了个 MP3,支持 SD 卡歌曲播放,既然有 USB Host,那也能支持U盘歌曲播放

  Duang,开整(U盘用的少,就用读卡器+SD卡替代了)



  沁恒官方也提供了一份适用于 CH32V103 的U盘读写库 libRV3UFI.a,提供了常用的文件读写操作

extern        UINT8        CHRV3GetVer( void );                 /* 获取当前子程序库的版本号 */
extern        void        CHRV3DirtyBuffer( void );            /* 清除磁盘缓冲区 */
extern        UINT8        CHRV3BulkOnlyCmd( PUINT8 DataBuf );  /* 执行基于BulkOnly协议的命令 */
extern        UINT8        CHRV3DiskReady( void );              /* 查询磁盘是否准备好 */
extern        UINT8        CHRV3AnalyzeError( UINT8 iMode );    /* USB操作失败分析CHRV3IntStatus返回错误状态 */
extern        UINT8        CHRV3FileOpen( void );               /* 打开文件或者枚举文件 */
extern        UINT8        CHRV3FileClose( void );              /* 关闭当前文件 */
extern        UINT8        CHRV3FileErase( void );              /* 删除文件并关闭 */
extern        UINT8        CHRV3FileCreate( void );             /* 新建文件并打开,如果文件已经存在则先删除后再新建 */
extern        UINT8        CHRV3FileAlloc( void );              /* 根据文件长度调整为文件分配的磁盘空间 */
extern        UINT8        CHRV3FileModify( void );             /* 查询或者修改当前文件的信息 */
extern        UINT8        CHRV3FileQuery( void );              /* 查询当前文件的信息 */
extern        UINT8        CHRV3FileLocate( void );             /* 移动当前文件指针 */
extern        UINT8        CHRV3FileRead( void );               /* 从当前文件读取数据到指定缓冲区 */
extern        UINT8        CHRV3FileWrite( void );              /* 向当前文件写入指定缓冲区的数据 */
extern        UINT8        CHRV3ByteLocate( void );             /* 以字节为单位移动当前文件指针 */
extern        UINT8        CHRV3ByteRead( void );               /* 以字节为单位从当前位置读取数据块 */
extern        UINT8        CHRV3ByteWrite( void );              /* 以字节为单位向当前位置写入数据块 */
extern        UINT8        CHRV3DiskQuery( void );              /* 查询磁盘信息 */
extern        void        CHRV3SaveVariable( void );           /* 备份/保存/恢复子程序库的变量,用于子程序库在多个芯片或者U盘之间进行切换 */
  
  这函数看着眼熟,看起来就像是 CH375 上面的那套

  直接使用,就能遍历、读写文件了

  官方例程也提供了创建长文件名的示例,但是没找到读取长文件名的示例,肯定是偷懒了  

  咋办,想想办法呗  

  USB协议相对复杂,可以参考相关文档,这里就不展开了

  简单来说,U盘插入后,引起USB总线电平变化触发中断,根据D+D-的电平信号,USB主机就知道设备的速度类型,低速或者全速

  接着USB主机复位USB总线(ResetRootHubPort),然后读取设备描述符(CtrlGetDeviceDescr),获取以下设备信息

typedef struct __PACKED _USB_DEVICE_DESCR {
    UINT8 bLength;
    UINT8 bDescriptorType;
    UINT16 bcdUSB;
    UINT8 bDeviceClass;
    UINT8 bDeviceSubClass;
    UINT8 bDeviceProtocol;
    UINT8 bMaxPacketSize0;
    UINT16 idVendor;
    UINT16 idProduct;
    UINT16 bcdDevice;
    UINT8 iManufacturer;
    UINT8 iProduct;
    UINT8 iSerialNumber;
    UINT8 bNumConfigurations;
} USB_DEV_DESCR;
  
  至此主机就知道设备的 PID、VID、使用的USB协议版本、端点情况等

  接着主机发出设置地址指令(CtrlSetUsbAddress),重设USB设备的地址(这之前USB设备都是以默认的0地址与主机通信)

  接下去都以新地址与USB设备通信,首先读取配置描述符(CtrlGetConfigDescr),获取以下配置信息

typedef struct __PACKED _USB_CONFIG_DESCR {
    UINT8 bLength;
    UINT8 bDescriptorType;
    UINT16 wTotalLength;
    UINT8 bNumInterfaces;
    UINT8 bConfigurationValue;
    UINT8 iConfiguration;
    UINT8 bmAttributes;
    UINT8 MaxPower;
} USB_CFG_DESCR;


  接口描述符和端点描述符也可以在这个过程返回给主机

typedef struct __PACKED _USB_INTERF_DESCR {
    UINT8 bLength;
    UINT8 bDescriptorType;
    UINT8 bInterfaceNumber;
    UINT8 bAlternateSetting;
    UINT8 bNumEndpoints;
    UINT8 bInterfaceClass;
    UINT8 bInterfaceSubClass;
    UINT8 bInterfaceProtocol;
    UINT8 iInterface;
} USB_ITF_DESCR;

typedef struct __PACKED _USB_ENDPOINT_DESCR {
    UINT8 bLength;
    UINT8 bDescriptorType;
    UINT8 bEndpointAddress;
    UINT8 bmAttributes;
    UINT16 wMaxPacketSize;
    UINT8 bInterval;
} USB_ENDP_DESCR;
  
  至此主机已经获取到足够的信息,可以根据 bInterfaceClass 判断设备是 鼠标、键盘、U盘等类型

  主机设置配置,如果是U盘,可以使用 SCSI 指令集或者 UFI 指令集中的一部分指令操作U盘

  当然,这些指令还需要进行 CBW 命令块封包,通过批量传输 BulkOnly 协议发送

  U盘数据读写也是通过 CBW 命令设置读取的,能读写U盘后,剩下的文件系统就由 FATFS 接管了

  修改 FATFS 里 diskio.c 文件里的 disk_initialize 函数,增加U盘初始化函数

DSTATUS disk_initialize (
    BYTE pdrv               /* Physical drive nmuber (0..) */
)
{
    u8 res=0;
    switch(pdrv)
    {
        case SD_CARD:
            res = SD_Initialize();
            if(res)                         // STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常
                SD_SPI_ReadWriteByte(0xff); // 错误,提供额外的8个时钟
            SD_Card_SPI_SetSpeed(0);
            break;
        case EX_FLASH:
            break;
        case USB_DISK:
            res = CHRV3DiskReady();
            break;
        default:
            res = 1;
    }
    if(res) return STA_NOINIT;
    else return 0;                          //初始化成功
}
  
  修改读写扇区函数

// 读扇区
// pdrv:磁盘编号0~9,*buff:数据接收缓冲首地址,sector:扇区地址,count:需要读取的扇区数
DRESULT disk_read (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE *buff,     /* Data buffer to store read data */
    DWORD sector,   /* Sector address (LBA) */
    UINT count      /* Number of sectors to read (1..128) */
)
{
    u8 res=0;
    if (!count) return RES_PARERR;                  // count不能等于0,否则返回参数错误
    switch(pdrv)
    {
        case SD_CARD:
            res = SD_ReadDisk(buff, sector, count);
            if(res)                                 // STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常
                SD_SPI_ReadWriteByte(0xff);         // 提供额外的8个时钟
            break;
        case EX_FLASH:
            break;
        case USB_DISK:
            CHRV3vLbaCurrent = sector;
            res = CHRV3ReadSector(count, buff);
            break;
        default:
            res=1;
    }
    if(res==0x00) return RES_OK;
    else return RES_ERROR;
}

// 写扇区
// pdrv:磁盘编号0~9,*buff:发送数据首地址,sector:扇区地址,count:需要写入的扇区数
#if _USE_WRITE
DRESULT disk_write (
    BYTE pdrv,          /* Physical drive nmuber (0..) */
    const BYTE *buff,   /* Data to be written */
    DWORD sector,       /* Sector address (LBA) */
    UINT count          /* Number of sectors to write (1..128) */
)
{
    u8 res=0;
    if (!count) return RES_PARERR;
    switch(pdrv)
    {
        case SD_CARD:
            res = SD_WriteDisk((u8*)buff,sector,count);
            break;
        case EX_FLASH:
            res=0;
            break;
        case USB_DISK:
            CHRV3vLbaCurrent = sector;
            res = CHRV3WriteSector(count, (u8*)buff);
            break;
        default:
            res=1;
    }
    if(res == 0x00) return RES_OK;
    else return RES_ERROR;
}
  
  #define SD_CARD  0  // SD Card
  #define EX_FLASH 1  // Flash Chip
  #define USB_DISK 2  // USB Disk


  就可以了,记得把 ffconf.h 里的 #define _VOLUMES    3,改掉,支持几个盘,就改成几个

  好了,再总结一下:

  先初始化USB Host

pHOST_RX_RAM_Addr = RxBuffer;
    pHOST_TX_RAM_Addr = TxBuffer;
    USB_Host_Config(ENABLE);
    CHRV3LibInit();
    SetUsbSpeed(1);                 // 全速
  
  然后循环检测U盘是否插入

if(R8_USB_INT_FG & RB_UIF_DETECT)       // RB_UIF_DETECT:USB主机模式下USB设备连接或断开事件中断标志位
        {
          R8_USB_INT_FG = RB_UIF_DETECT ;       // 检测到USB设备连接或断开触发
          ret = AnalyzeRootHub();               // 分析跟集线器状态
          if(ret == ERR_USB_CONNECT)
          {
            FoundNewDev = 1;
            printf("Insert Device\r\n");
          }
          else if(ret == ERR_USB_DISCON)
          {
            printf( "Remove Device\r\n" );
          }
        }


  检测到插入后,初始化设备,即获取描述符,设置地址等

FoundNewDev = 0;
        ret = InitRootDevice(Com_Buffer);
        if (ret == ERR_SUCCESS)
        {
          CHRV3DiskStatus = DISK_USB_ADDR;
          printf( "Device Enum Succeed\r\n" );
        }
        else
        {
          printf("Device Enum Failed\r\n");
        }


  然后就可以挂载U盘,进行读写了

res_sd = f_mount(&usbfs, u"2:", 1);
        if(res_sd == FR_OK)
        {
          printf("Mount OK\r\n");
          ListIndex = 0;
          ListCount = File_Scan(u"2:", "MP3", ListIndex, fname);
          printf("%d\r\n", ListCount);
        }
        else if(res_sd == FR_NO_FILESYSTEM)
        {
          printf("No File System\r\n");
        }
        else
        {
          printf("Mount Fail: %d\r\n", res_sd);
        }


  USB全速设备速度不会超过12Mbps,即每秒1.5M Byte

使用特权

评论回复

相关帖子

沙发
xinmeng_wit| | 2021-10-7 11:05 | 只看该作者
调用f_write,然后再调用f_close,好像会出错,就是close不了

使用特权

评论回复
板凳
lzbf| | 2022-11-2 19:55 | 只看该作者
能不能实现CH32V103 直接在电脑上显示sd卡?

使用特权

评论回复
地板
tabmone| | 2022-11-2 20:22 | 只看该作者
这个最大支持多大的U盘呢?              

使用特权

评论回复
5
jkl21| | 2022-11-2 20:41 | 只看该作者
FATFS 不是直接操作SD卡的吗

使用特权

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

本版积分规则

19

主题

109

帖子

0

粉丝