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

677浏览
查看: 677|回复: 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

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

第二步,上面我们有了合适的磁盘镜像,接下来我们需要将这个数据转化为给 Arduino 使用的定义文件。为此编写一个 C# 程序,对文件以512Bytes为单位进行扫描。如果某一个512Bytes内出现不为0 的字节,那么就将整个512Bytes保存下来。
软件使用方法很简单,使用 open打开你要制作镜像的文件,然后就开始自动处理,处理完成后会将结果保存为你选中文件名+.h的文件中。比如,下面就是我处理前面提到的 32MB 磁盘的结果。对于一个空的磁盘,只有6144Bytes的有效信息(当前其中仍然有大量的0x00字节)。
image028.png
生成的 .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.
image030.png
另外一个例子,设备收到对 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,
复制代码

实际查看到的内容如下,可以看出是和上面相同的。
image032.png
第三步,编写代码。我们可以使用 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可能出现电压不匹配的问题)
O1CN01VogXGW27lqyED9uul_!!2200640407838.jpg
之后就能在ESP32S2 USB口上看到多出来一个U盘了:
image034.png

测试一下写入速度(当然,因为我们没有实际的存储设备,所有写入的数据实际上都被丢掉了),最快能达到800K/S
image036.png



zoologist  高级技师
 楼主|

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

C#编写的磁盘镜像到.h 可执行文件
FileToh.zip (5.12 KB, 下载次数: 8)
回复

使用道具 举报

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

本版积分规则

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

硬件清单

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

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

mail