zoologist 发表于 2021-7-2 15:11:21

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

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

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

{

//No. 0 Sector: 0x0

0x33,0xC0,0x8E,0xD0,0xBC,0x00,0x7C,0x8E,0xC0,0x8E,0xD8,0xBE,0x00,0x7C,0xBF,0x00,

0x06,0xB9,0x00,0x02,0xFC,0xF3,0xA4,0x50,0x68,0x1C,0x06,0xCB,0xFB,0xB9,0x04,0x00,

0xBD,0xBE,0x07,0x80,0x7E,0x00,0x00,0x7C,0x0B,0x0F,0x85,0x0E,0x01,0x83,0xC5,0x10,

0xE2,0xF1,0xCD,0x18,0x88,0x56,0x00,0x55,0xC6,0x46,0x11,0x05,0xC6,0x46,0x10,0x00,
2.扇区索引,用于索引上面的内容。int Index= {0x0,0x1,0x2,0x80,0x82,0x171,0x260,0x280,0x281,0x282,0xFFDF,0xFFFF,};


举例,当设备收到访问 n号扇区的命令后,需要在 Index[] 中查找,如果无法找到,那么可以直接返回这个删除内容为全 0x00;如果能找到,那么再从获得的索引值中查找具体内容。举例如下:收到访问 0x3扇区的请求,然后就比较 Index[] 中每一项的值,结果发现没有等于 0x03 的,那么就直接返回全0.使用十六进制工具可以看到,0x600-0x7FF是全0.
另外一个例子,设备收到对 0x280 扇区的访问,我们可以在Index[]中第八项找到这个值,那么我们接下来就需要访问 0x280*512=0x50000 //No. 7 Sector: 0x280

0x2E,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x00,0xAD,0x79,0x68,

0xE2,0x52,0xE2,0x52,0x00,0x00,0x7A,0x68,0xE2,0x52,0x02,0x00,0x00,0x00,0x00,0x00,

0x2E,0x2E,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x00,0xAD,0x79,0x68,

0xE2,0x52,0xE2,0x52,0x00,0x00,0x7A,0x68,0xE2,0x52,0x00,0x00,0x00,0x00,0x00,0x00,

0x42,0x74,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0x00,0xCE,0xFF,0xFF,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,

0x01,0x57,0x00,0x50,0x00,0x53,0x00,0x65,0x00,0x74,0x00,0x0F,0x00,0xCE,0x74,0x00,

0x69,0x00,0x6E,0x00,0x67,0x00,0x73,0x00,0x2E,0x00,0x00,0x00,0x64,0x00,0x61,0x00,

0x57,0x50,0x53,0x45,0x54,0x54,0x7E,0x31,0x44,0x41,0x54,0x20,0x00,0xAD,0x79,0x68,
实际查看到的内容如下,可以看出是和上面相同的。
第三步,编写代码。我们可以使用 TinyUSB 的RamDisk代码,稍加修改就可以得到我们需要的程序。关键代码有两个地方:
1.返回USB MSD基本信息,一个是扇区个数,一个是扇区大小。比如,下面扇区个数为DISK_BLOCK_NUM每个扇区大小为DISK_BLOCK_SIZE,同样使用这两个信息我们也能得知磁盘大小(二者相乘即可)
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size

// Application update block count and block size

void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)

{

(void) lun;



*block_count = DISK_BLOCK_NUM;

*block_size= DISK_BLOCK_SIZE;

log_v("block count: %d, block size: %d", *block_count, *block_size);

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

// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.

int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)

{

(void) lun;

//uint8_t* addr = &msc_disk + offset;

//memcpy(buffer, addr, bufsize);

log_v("read lba %X, offs: %d, bufs %d", lba, offset,bufsize);

uint32_t SectorIndex = IsInRomSector(lba);

if (SectorIndex != 0xFFFFFFFF) {

   memcpy(buffer, &Sector, bufsize);

   log_v("reply %d", SectorIndex);

} else {

   memset(buffer,0, bufsize);

   log_v("reply all 0");

}



return bufsize;

}实验使用的是一款带有2个 TypeC 口的 ESP32S2 开发板(特别注意,这个开发板2个USB口 VCC 是相互连接的,因此如果是在同一台电脑上使用不会有什么问题,但是如果在2台不同的电脑上同时使用USB可能出现电压不匹配的问题)之后就能在ESP32S2 的 USB口上看到多出来一个U盘了:

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



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

C#编写的磁盘镜像到.h 可执行文件


上述可执行文件的源代码


ROMDisk 的源代码


页: [1]
查看完整版本: ESP32 S2 做一个假U盘