[ESP8266/ESP32]ESP32 S2 做一个假U盘 精华

8843浏览
查看: 8843|回复: 1

[ESP8266/ESP32] ESP32 S2 做一个假U盘

[复制链接]
ESP32 S2 支持 USB Device (实际上是 OTG ,但是厂家一直没有放出对于 USB Host 的支持,所以一般用户只能按照 Device 来使用)。对于  USB Device 来说,MassStorageDevice(缩写为 MSD)是我们通常能接触到的传输最快的设备。之前介绍过Arduino TinyUSB 库的使用,其中提供了一个 RAM Disk 的例子,是使用 PSRAM(2MBytes) 将自身模拟为一个1.5Mbytes大小FAT12格式的U盘。美中不足的是空间过小没有太多实用性。
这次介绍如何来将自身模拟为一个更大的U盘。从原理上来说,设备需要正确响应读取命令READ10,需要返回正确的内容。而对于 Windows来说,写入之后系统内部有缓存机制,设备无需真正存储写入的内容,用户也可以看到刚刚写入的内容。因此,我们可以将一个U盘中的内容写到 SPI ROM 中。只要U盘内容没有超过ESP32S2SPI 的大小,都是有机会来实现的。
第一步,制作U盘内容文件。
1.     磁盘管理器中创建一个 VHD

ESP32 S2 做一个假U盘图1
2.     我们创建一个 32MB 的虚拟磁盘,这里使用32MB的原因是我们希望格式化之后一个扇区512Bytes,如果创建为32MB那么Windows格式化最小的扇区是1024Bytes。
ESP32 S2 做一个假U盘图2
3.     创建之后这个盘是没有初始化,没有分区的
ESP32 S2 做一个假U盘图3
我们需要初始化之
ESP32 S2 做一个假U盘图4
推荐使用GPT分区
ESP32 S2 做一个假U盘图5
再创建一个 Simple Volume
ESP32 S2 做一个假U盘图6
推荐使用 FAT格式:
ESP32 S2 做一个假U盘图7
4.     上述操作之后系统中就多出来一个盘符:
ESP32 S2 做一个假U盘图8
5.     接下来需要使用工具来制作这个盘的镜像。我是用 HXD (HexEditor and Disk Editor )这个工具是免费的。需要以管理员权限启动之
ESP32 S2 做一个假U盘图9
需要选择 Physical Disks 中的64MB 的盘
ESP32 S2 做一个假U盘图10
使用 Save As 保存为文件
ESP32 S2 做一个假U盘图11
6.上述操作结束后,我们就有一个32MB大小的文件。之后在磁盘管理器中 Detach VHD 刚才创建的虚拟盘。
ESP32 S2 做一个假U盘图12
这个时候,我们可以看到2个文件,比如下面的 VHD 是我们刚才的虚拟硬盘文件,64MBDisk是上面硬盘的镜像。两者大部分的内容是相同,前者比后者在文件尾部稍微多了一些格式数据。
ESP32 S2 做一个假U盘图13

第二步,上面我们有了合适的磁盘镜像,接下来我们需要将这个数据转化为给 Arduino 使用的定义文件。为此编写一个 C# 程序,对文件以512Bytes为单位进行扫描。如果某一个512Bytes内出现不为0 的字节,那么就将整个512Bytes保存下来。
软件使用方法很简单,使用 open打开你要制作镜像的文件,然后就开始自动处理,处理完成后会将结果保存为你选中文件名+.h的文件中。比如,下面就是我处理前面提到的 32MB 磁盘的结果。对于一个空的磁盘,只有6144Bytes的有效信息(当前其中仍然有大量的0x00字节)。
ESP32 S2 做一个假U盘图14
生成的 .h 有两部分:

1.磁盘扇区定义,这里是有内容(不全为0x00)的全部扇区;
  1. const unsigned char Sector[13][512]  = {
  2. {
  3. //No. 0 Sector: 0x0
  4. 0x33,0xC0,0x8E,0xD0,0xBC,0x00,0x7C,0x8E,0xC0,0x8E,0xD8,0xBE,0x00,0x7C,0xBF,0x00,
  5. 0x06,0xB9,0x00,0x02,0xFC,0xF3,0xA4,0x50,0x68,0x1C,0x06,0xCB,0xFB,0xB9,0x04,0x00,
  6. 0xBD,0xBE,0x07,0x80,0x7E,0x00,0x00,0x7C,0x0B,0x0F,0x85,0x0E,0x01,0x83,0xC5,0x10,
  7. 0xE2,0xF1,0xCD,0x18,0x88,0x56,0x00,0x55,0xC6,0x46,0x11,0x05,0xC6,0x46,0x10,0x00,
复制代码

2.扇区索引,用于索引上面的内容。
  1. int Index[12]= {0x0,0x1,0x2,0x80,0x82,0x171,0x260,0x280,0x281,0x282,0xFFDF,0xFFFF,};
复制代码



举例,当设备收到访问 n号扇区的命令后,需要在 Index[] 中查找,如果无法找到,那么可以直接返回这个删除内容为全 0x00;如果能找到,那么再从获得的索引值中查找具体内容。举例如下:收到访问 0x3扇区的请求,然后就比较 Index[] 中每一项的值,结果发现没有等于 0x03 的,那么就直接返回全0.使用十六进制工具可以看到,0x600-0x7FF是全0.
ESP32 S2 做一个假U盘图15
另外一个例子,设备收到对 0x280 扇区的访问,我们可以在Index[]中第八项找到这个值,那么我们接下来就需要访问 0x280*512=0x50000
  1. //No. 7 Sector: 0x280
  2. 0x2E,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x00,0xAD,0x79,0x68,
  3. 0xE2,0x52,0xE2,0x52,0x00,0x00,0x7A,0x68,0xE2,0x52,0x02,0x00,0x00,0x00,0x00,0x00,
  4. 0x2E,0x2E,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x00,0xAD,0x79,0x68,
  5. 0xE2,0x52,0xE2,0x52,0x00,0x00,0x7A,0x68,0xE2,0x52,0x00,0x00,0x00,0x00,0x00,0x00,
  6. 0x42,0x74,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0x00,0xCE,0xFF,0xFF,
  7. 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,
  8. 0x01,0x57,0x00,0x50,0x00,0x53,0x00,0x65,0x00,0x74,0x00,0x0F,0x00,0xCE,0x74,0x00,
  9. 0x69,0x00,0x6E,0x00,0x67,0x00,0x73,0x00,0x2E,0x00,0x00,0x00,0x64,0x00,0x61,0x00,
  10. 0x57,0x50,0x53,0x45,0x54,0x54,0x7E,0x31,0x44,0x41,0x54,0x20,0x00,0xAD,0x79,0x68,
复制代码

实际查看到的内容如下,可以看出是和上面相同的。
ESP32 S2 做一个假U盘图16
第三步,编写代码。我们可以使用 TinyUSB RamDisk代码,稍加修改就可以得到我们需要的程序。关键代码有两个地方:

1.返回USB MSD基本信息,一个是扇区个数,一个是扇区大小。比如,下面扇区个数为DISK_BLOCK_NUM每个扇区大小为DISK_BLOCK_SIZE,同样使用这两个信息我们也能得知磁盘大小(二者相乘即可)

  1. // Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
  2. // Application update block count and block size
  3. void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)
  4. {
  5.   (void) lun;
  6.   *block_count = DISK_BLOCK_NUM;
  7.   *block_size  = DISK_BLOCK_SIZE;
  8.   log_v("block count: %d, block size: %d", *block_count, *block_size);
  9. }
复制代码

2.处理READ10命令,在tud_msc_read10_cb()这个函数。Windows会发送请求,要求返回lba 扇区 offset 处长度为 bufsize 的内容(实际上 offset 一直为0),bufsize是我们需要填充的 Buffer 大小(实际上一直是一个扇区的大小)

  1. // Callback invoked when received READ10 command.
  2. // Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
  3. int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
  4. {
  5.   (void) lun;
  6.   //uint8_t* addr = &msc_disk[lba * 512] + offset;
  7.   //memcpy(buffer, addr, bufsize);
  8.   log_v("read lba %X, offs: %d, bufs %d", lba, offset,bufsize);
  9.   uint32_t SectorIndex = IsInRomSector(lba);
  10.   if (SectorIndex != 0xFFFFFFFF) {
  11.      memcpy(buffer, &Sector[SectorIndex][0], bufsize);
  12.      log_v("reply %d", SectorIndex);
  13.   } else {
  14.      memset(buffer,0, bufsize);
  15.      log_v("reply all 0");
  16.   }
  17.   return bufsize;
  18. }
复制代码
实验使用的是一款带有2个 TypeC 口的 ESP32S2 开发板(特别注意,这个开发板2个USB口 VCC 是相互连接的,因此如果是在同一台电脑上使用不会有什么问题,但是如果在2台不同的电脑上同时使用USB可能出现电压不匹配的问题)
ESP32 S2 做一个假U盘图19
之后就能在ESP32S2 USB口上看到多出来一个U盘了:
ESP32 S2 做一个假U盘图17

测试一下写入速度(当然,因为我们没有实际的存储设备,所有写入的数据实际上都被丢掉了),最快能达到800K/S
ESP32 S2 做一个假U盘图18



zoologist  高级技匠
 楼主|

发表于 2021-7-2 15:13:21

C#编写的磁盘镜像到.h 可执行文件
下载附件FileToh.zip

上述可执行文件的源代码
下载附件WindowsFormsApplication9.zip

ROMDisk 的源代码
下载附件RomDisk.zip

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail