[RISC-V MCU 创新应用比赛] 沁恒 CH32V103 USB Host FATFS 读取U盘文件

[复制链接]
 楼主| gtbestom 发表于 2021-9-16 11:43 | 显示全部楼层 |阅读模式
#申请原创#  @21小跑堂

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

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

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

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

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

93116142bcea883ff.png


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

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

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

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

  咋办,想想办法呗  

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

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

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

  1. typedef struct __PACKED _USB_DEVICE_DESCR {
  2.     UINT8 bLength;
  3.     UINT8 bDescriptorType;
  4.     UINT16 bcdUSB;
  5.     UINT8 bDeviceClass;
  6.     UINT8 bDeviceSubClass;
  7.     UINT8 bDeviceProtocol;
  8.     UINT8 bMaxPacketSize0;
  9.     UINT16 idVendor;
  10.     UINT16 idProduct;
  11.     UINT16 bcdDevice;
  12.     UINT8 iManufacturer;
  13.     UINT8 iProduct;
  14.     UINT8 iSerialNumber;
  15.     UINT8 bNumConfigurations;
  16. } USB_DEV_DESCR;
  
  至此主机就知道设备的 PID、VID、使用的USB协议版本、端点情况等

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

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

  1. typedef struct __PACKED _USB_CONFIG_DESCR {
  2.     UINT8 bLength;
  3.     UINT8 bDescriptorType;
  4.     UINT16 wTotalLength;
  5.     UINT8 bNumInterfaces;
  6.     UINT8 bConfigurationValue;
  7.     UINT8 iConfiguration;
  8.     UINT8 bmAttributes;
  9.     UINT8 MaxPower;
  10. } USB_CFG_DESCR;


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

  1. typedef struct __PACKED _USB_INTERF_DESCR {
  2.     UINT8 bLength;
  3.     UINT8 bDescriptorType;
  4.     UINT8 bInterfaceNumber;
  5.     UINT8 bAlternateSetting;
  6.     UINT8 bNumEndpoints;
  7.     UINT8 bInterfaceClass;
  8.     UINT8 bInterfaceSubClass;
  9.     UINT8 bInterfaceProtocol;
  10.     UINT8 iInterface;
  11. } USB_ITF_DESCR;

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

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

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

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

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

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

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

  1. // 写扇区
  2. // pdrv:磁盘编号0~9,*buff:发送数据首地址,sector:扇区地址,count:需要写入的扇区数
  3. #if _USE_WRITE
  4. DRESULT disk_write (
  5.     BYTE pdrv,          /* Physical drive nmuber (0..) */
  6.     const BYTE *buff,   /* Data to be written */
  7.     DWORD sector,       /* Sector address (LBA) */
  8.     UINT count          /* Number of sectors to write (1..128) */
  9. )
  10. {
  11.     u8 res=0;
  12.     if (!count) return RES_PARERR;
  13.     switch(pdrv)
  14.     {
  15.         case SD_CARD:
  16.             res = SD_WriteDisk((u8*)buff,sector,count);
  17.             break;
  18.         case EX_FLASH:
  19.             res=0;
  20.             break;
  21.         case USB_DISK:
  22.             CHRV3vLbaCurrent = sector;
  23.             res = CHRV3WriteSector(count, (u8*)buff);
  24.             break;
  25.         default:
  26.             res=1;
  27.     }
  28.     if(res == 0x00) return RES_OK;
  29.     else return RES_ERROR;
  30. }
  
  #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

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

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


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

  1. FoundNewDev = 0;
  2.         ret = InitRootDevice(Com_Buffer);
  3.         if (ret == ERR_SUCCESS)
  4.         {
  5.           CHRV3DiskStatus = DISK_USB_ADDR;
  6.           printf( "Device Enum Succeed\r\n" );
  7.         }
  8.         else
  9.         {
  10.           printf("Device Enum Failed\r\n");
  11.         }


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

  1. res_sd = f_mount(&usbfs, u"2:", 1);
  2.         if(res_sd == FR_OK)
  3.         {
  4.           printf("Mount OK\r\n");
  5.           ListIndex = 0;
  6.           ListCount = File_Scan(u"2:", "MP3", ListIndex, fname);
  7.           printf("%d\r\n", ListCount);
  8.         }
  9.         else if(res_sd == FR_NO_FILESYSTEM)
  10.         {
  11.           printf("No File System\r\n");
  12.         }
  13.         else
  14.         {
  15.           printf("Mount Fail: %d\r\n", res_sd);
  16.         }


  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盘呢?              
jkl21 发表于 2022-11-2 20:41 | 显示全部楼层
FATFS 不是直接操作SD卡的吗
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

114

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部

19

主题

114

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部