简介:STM32F103驱动CH375B的优盘扇区读写工程,附带容量检测功能,面向需要操作U盘、SD卡等USB存储设备的嵌入式开发者与单片机学习者。这套驱动已在STM32F103RC上测试通过,可实现单个及连续扇区读取/写入,并能获取存储设备容量,为数据记录、文件系统扩展等应用提供底层基础。压缩包共192个文件,约6.65MB,包含大量C源文件(h、c)、编译中间文件(o、crf、d)以及可烧录的hex/axf固件,另有Keil工程配置(uvprojx、uvoptx)和znFAT文件系统相关代码,便于直接打开工程理解调用关系或快速移植到自己的项目中。内容还覆盖CH375B芯片初始化流程、设备检测机制及常见错误处理思路,适合正在调试USB存储设备、希望绕过复杂USB协议栈的开发者参考。已有1115人学习/下载,是一份兼顾完整性与可读性的实用参考工程。 在嵌入式项目里遇到“要读写U盘”这个需求,多数人第一反应是换一颗带USB OTG的单片机,比如F407、H750。但如果你手里已经有一块STM32F103,或者产品主控定死了不能换,那外围挂一颗CH375B就是很务实的方案。我最近刚把“STM32F103驱动CH375B读写U盘扇区”这套代码调通,读扇区、写扇区、测U盘容量都能跑,而且写进去的数据读回来逐字节比对完全一致。这篇把硬件连接、命令流程、代码实现和调试时踩过的坑一起整理出来,给同样被USB Host折腾过的人一个参考。
这套东西适合谁?一个是想在不换主控的前提下给产品加U盘交互功能的工程师,另一个是刚接触CH375B、想跳过底层协议直接操作扇区的学习者。CH375B本质上是把USB Mass Storage的枚举、Bulk-Only传输、SCSI命令全部封装好了,你只需要像操作普通外设一样给它发命令、收数据,所以哪怕你对USB协议只有模糊概念,也能在半天内把扇区读写跑起来。
1. 为什么绕开STM32F103自带的USB:外挂CH375B的思路与适用场景
STM32F103全系列都带USB接口,但这里有个硬限制:F103的USB控制器只能做Device,不能做Host。也就是说,它可以扮演鼠标、键盘、虚拟串口这类从设备,但没法主动去枚举U盘、发SCSI命令。想让它变成主机去读U盘,通常有三条路:换带OTG的型号、用软件模拟USB Host、外挂USB Host芯片。第一种要动硬件平台,第二种对单片机主频和实时性要求太高,基本不现实,第三种就是本文的CH375B路线。
CH375B这颗芯片的方案价值在于“把协议栈装进一颗小芯片”。它内部已经跑完了USB的物理层和协议层,你感知不到U盘内部有多少端点、控制传输怎么发、CBW/CSW怎么打包。你需要做的只是通过并口或串口给它写命令字,比如“初始化U盘”“读扇区”“写扇区”,然后等它的中断引脚拉低,再从数据口把结果搬回来。
在决定用CH375B之前,我也对比过另一颗常见芯片CH376,二者核心差别是CH376额外内置了FAT文件系统,可以直接按文件名读写文件,而CH375B不带文件系统,只能按扇区操作。对于只需要“读扇区、写扇区、测容量”这个需求,CH375B更便宜、更纯粹,而且从扇区往上构建文件系统逻辑的灵活性反而更大。比如你想做个坏块检测工具、U盘全盘清空工具,或者自定义存储格式,CH375B这种扇区级访问方式更合适。
还有一个很现实的考虑:CH375B的驱动代码在网上有大量参考,但我强烈建议不要直接抄老版本代码就上板子,尤其是命令时序部分,不同版本固件的细节有差异。我后面会贴出我调试通过的思路和关键函数,你可以对照数据手册核实,而不是盲信网上任何一份代码。
2. 硬件连接与接口模式选择:并口比串口好在哪
CH375B支持两种与MCU通信的接口:8位并口和UART串口。两种模式通过TEN引脚的电平决定,TEN拉低走并口,TEN悬空或拉高走串口。我实际用的是并口模式,原因很简单:STM32F103的IO资源够用,并口一字节一字节地搬数据,速度明显比串口快,而且并口时序用GPIO模拟起来并不复杂。
2.1 最小连接方式
我用的连接方式如下,供参考:
- 数据线D0~D7:接STM32F103同一组GPIO的低8位,我用的是PB0~PB7
- A0:命令/数据选择线,接PB8
- WR:写选通,接PB9
- RD:读选通,接PB10
- CS:片选,接PB11
- INT:中断输出,接PB12,并配置为下拉输入
- TEN:接口模式选择,直接接地,锁定并口模式
这套连接没有用到CH375B的DMA或硬件中断映射,完全靠普通GPIO模拟。INT引脚是关键,CH375B完成一条命令后会把INT拉低,MCU在等待命令完成时只需要轮询这个引脚,或者在外部中断里置标志位。我的演示代码用的是轮询加超时,这样逻辑最简单,也不容易漏事件。
2.2 电平匹配与电源处理
这里特别说下电平问题。CH375B是5V供电芯片,但它的输入引脚兼容TTL电平,STM32F103的3.3V高电平对CH375B来说是能被识别的高电平,所以从F103到CH375B的写数据、写命令信号可以直接连,不需要电平转换。反过来,CH375B输出的高电平是5V,直接进STM32的引脚会有风险。
实际怎么处理?数据手册写得很清楚,CH375B的IO在输出时会按VCC电平输出,如果VCC是5V,那D0~D7在输出状态下也是5V高电平。所以要么在数据线上加电平转换芯片,要么给CH375B的VCC单独供3.3V,要么在CH375B输出引脚和F103输入引脚之间串限流电阻,配合STM32引脚的钳位二极管来保护。我的板子上是给IO口串了330欧姆电阻,实测稳定。如果是做产品,建议用正规电平转换方案,比如TXS0108E这类双向电平转换芯片,别学我这么省。
电源方面,U盘插入瞬间冲击电流能到几百毫安,CH375B的5V供电端最好就近放100uF以上电解电容,再并一个0.1uF陶瓷电容去高频噪声。我一开始用面包板飞线,U盘插上去经常初始化失败,后来查了示波器才发现是插入瞬间5V被拉掉将近1V,加了大电容之后问题消失。
2.3 为什么最终选了并口
串口模式只占用TX、RX两根线,布线省很多,但代价是CH375B把并口的8位数据拆成字节流从串口发,底层传输效率天然低一截。对于扇区级的批量读写,并口一个512字节扇区只需要拉512次数据总线,串口要按波特率一位一位传,差距非常明显。如果你只是偶尔读几KB数据,串口也凑合;但考虑到项目里还要“测容量”,可能要连续读多个扇区来验证,并口的优势就体现出来了。另外,并口模式的数据手册时序图更明确,软件操作步骤也更直观,对新手来说反而更容易从逻辑上理清。
3. CH375B的扇区命令集:看懂这5条命令就够了
CH375B内部固件分两层:USB底层传输和文件系统层。文件系统层有按文件名打开、读写、关闭这类命令,但既然标题是“可读写优盘扇区”,我们用的是更底层的磁盘操作命令。这些命令直接调用CH375B固件封装好的SCSI命令,本质上每次读写就是一次USB Bulk-Only传输。
我调试中真正用到的命令其实就5条,下面用表格列出来,对应中断状态码一并标注,方便排查问题:
| 命令字 | 宏名称 | 功能 | 完成后中断状态码 |
|---|---|---|---|
| 0x06 | CMD_CHECK_EXIST | 检测芯片是否存在 | 无中断,直接回读数据 |
| 0x51 | CMD_DISK_INIT | 初始化U盘 | 0x14(成功)/ 0x15(失败) |
| 0x53 | CMD_DISK_SIZE | 获取U盘总扇区数 | 0x1C |
| 0x54 | CMD_DISK_READ | 读扇区 | 0x1D |
| 0x56 | CMD_DISK_WRITE | 写扇区(准备阶段) | 0x1E |
| 0x57 | CMD_DISK_WRITE_GO | 写扇区(执行阶段) | 0x1D |
有一点要特别注意:CH375B上电后必须先执行CMD_CHECK_EXIST确认芯片在线,再执行CMD_DISK_INIT完成U盘枚举和容量读取,然后才能做扇区操作。顺序乱了芯片会一直返回异常状态,这不是代码逻辑问题,是CH375B自身状态机的约束。
3.1 命令分层的概念
如果你看过CH375B的数据手册,会发现它把命令分成“USB命令”和“文件级命令”两层。扇区命令属于前者,它的特点是:你不关心U盘里是FAT16、FAT32还是exFAT,只要U盘能完成USB Mass Storage枚举,命令就能执行。这跟那些要求U盘必须是FAT格式的“文件级命令”有本质区别。说白了,扇区命令是纯硬件层面的操作,U盘格式只是数据内容,不影响读写流程。
这个特性很有用。比如你想做“U盘全盘覆盖测试”,无论是往里面写0x55、0xAA还是随机数据,都不受文件系统限制。我在工程里就是先读0号扇区验证链路,然后跳到最后几个扇区做一次写读比对,用来确认容量计算是否正确。
3.2 命令时序的通用套路
所有命令的时序套路都差不多,顺着这个思路看代码不会乱:
- 把CS拉低选中CH375B
- 设置A0电平:命令阶段A0=1,数据阶段A0=0
- 在WR上升沿写入命令字节或数据字节
- 命令执行完成后,INT引脚拉低
- MCU读取中断状态寄存器,判断是成功还是失败
- 准备好数据缓冲区,按命令要求搬运数据
- 把CS拉高,结束本次操作
前面提到等待INT有两种方式:轮询和外部中断。我的演示工程里用的是“轮询加超时”,原因是不想在中断里处理复杂的命令状态机,而且U盘读写本身不算高速外设,轮询完全来得及。实际测试中,读一个扇区从发命令到数据全部取走,大约在几毫秒级别,这个速度对绝大多数应用完全够用。
4. 核心函数拆解:读扇区、写扇区、测容量怎么实现
代码结构上我分了三层:底层是GPIO模拟总线,中间是CH375B命令封装,顶层是演示测试逻辑。下面把关键函数贴出来讲,注意这是STM32标准外设库写法,用HAL库的读者对照改一下GPIO操作即可。
4.1 底层总线函数
先定义几个端口操作宏,把数据线和控制线的操作收敛起来:
// 假设数据线接在GPIOB的低8位 #define CH375_DATA_OUT() { GPIOB->CRL &= ~0x000000FF; GPIOB->CRL |= 0x00000033; } #define CH375_DATA_IN() { GPIOB->CRL &= ~0x000000FF; GPIOB->CRL |= 0x00000044; } #define CH375_DATA_WR(d) { GPIOB->ODR = (GPIOB->ODR & 0xFF00) | (d & 0x00FF); } #define CH375_DATA_RD() (uint8_t)(GPIOB->IDR & 0x00FF) #define CH375_CS_L() GPIOB->BRR = GPIO_Pin_11 #define CH375_CS_H() GPIOB->BSRR = GPIO_Pin_11 #define CH375_WR_L() GPIOB->BRR = GPIO_Pin_9 #define CH375_WR_H() GPIOB->BSRR = GPIO_Pin_9 #define CH375_RD_L() GPIOB->BRR = GPIO_Pin_10 #define CH375_RD_H() GPIOB->BSRR = GPIO_Pin_10 #define CH375_A0_CMD() GPIOB->BSRR = GPIO_Pin_8 #define CH375_A0_DAT() GPIOB->BRR = GPIO_Pin_8 #define CH375_INT_GET() (uint8_t)(GPIOB->IDR & GPIO_Pin_12)底层读写函数很简单:
// 写命令字节:A0=1 void CH375_WR_CMD(uint8_t cmd) { CH375_DATA_OUT(); CH375_CS_L(); CH375_A0_CMD(); CH375_DATA_WR(cmd); CH375_WR_L(); CH375_WR_H(); CH375_CS_H(); } // 写数据字节:A0=0 void CH375_WR_DAT(uint8_t dat) { CH375_DATA_OUT(); CH375_CS_L(); CH375_A0_DAT(); CH375_DATA_WR(dat); CH375_WR_L(); CH375_WR_H(); CH375_CS_H(); } // 读数据字节:A0=0 uint8_t CH375_RD_DAT(void) { uint8_t dat; CH375_DATA_IN(); CH375_CS_L(); CH375_A0_DAT(); CH375_RD_L(); dat = CH375_DATA_RD(); CH375_RD_H(); CH375_CS_H(); return dat; }这段代码里有个容易被忽略的点:读数据前必须先把数据线切成输入模式,否则STM32和CH375B两侧都在推电平,轻则读回来全是0xFF,重则长期挂载会损伤IO。CH375_DATA_IN和CH375_DATA_OUT这两个宏就是干这件事的,初学者很容易漏掉。
4.2 读扇区函数
读扇区命令的输入参数是起始扇区的LBA地址(4字节,小端在前)和扇区数量(1字节),发完命令后需要等中断,然后进入数据搬运阶段:
uint8_t CH375_DiskRead(uint32_t start_sector, uint8_t sectors, uint8_t *buf) { uint8_t status, s; uint16_t len, i; CH375_WR_CMD(CMD_DISK_READ); CH375_WR_DAT((uint8_t)(start_sector & 0xFF)); CH375_WR_DAT((uint8_t)((start_sector >> 8) & 0xFF)); CH375_WR_DAT((uint8_t)((start_sector >> 16) & 0xFF)); CH375_WR_DAT((uint8_t)((start_sector >> 24) & 0xFF)); CH375_WR_DAT(sectors); if (CH375_Wait_Interrupt(200) != 0) return 0x11; // 超时 status = CH375_RD_DAT(); if (status != USB_INT_DISK_READ) return status; // 读取数据包长度,再取数据 CH375_WR_CMD(CMD_RD_USB_DATA); len = CH375_RD_DAT(); len |= (uint16_t)CH375_RD_DAT() << 8; for (i = 0; i < len; i++) { buf[i] = CH375_RD_DAT(); } return 0; }注意两个细节:一是CMD_RD_USB_DATA(0x28)其实是“从内部缓冲区读数据”的命令,每次读数据前要先取长度,这个长度的低字节在前;二是对于连续多个扇区的读取,CH375B一次中断可能只返回当前缓冲区长度,所以如果要读多个扇区,建议在应用层循环调用这个函数,一次读一个扇区,逻辑更稳。
我正在读的时候常犯一个错:一次读512字节,直接认为缓冲长度一定是512。实际上CH375B的缓冲区机制允许USB包大小不同,虽然标准U盘单个扇区读出来就是512字节,但代码里做成“按返回长度搬数据”更健壮,遇到特殊U盘也不会出问题。
4.3 写扇区函数
写扇区比读扇区多一步,整个过程是“准备数据-写入U盘-确认结果”:
uint8_t CH375_DiskWrite(uint32_t start_sector, uint8_t sectors, uint8_t *buf) { uint8_t status; uint16_t i; CH375_WR_CMD(CMD_DISK_WRITE); CH375_WR_DAT((uint8_t)(start_sector & 0xFF)); CH375_WR_DAT((uint8_t)((start_sector >> 8) & 0xFF)); CH375_WR_DAT((uint8_t)((start_sector >> 16) & 0xFF)); CH375_WR_DAT((uint8_t)((start_sector >> 24) & 0xFF)); CH375_WR_DAT(sectors); if (CH375_Wait_Interrupt(200) != 0) return 0x11; status = CH375_RD_DAT(); if (status != USB_INT_DISK_WRITE) return status; // 把扇区数据写入CH375B的发送缓冲区 CH375_WR_CMD(CMD_WR_USB_DATA); CH375_WR_DAT((uint8_t)(512 & 0xFF)); CH375_WR_DAT((uint8_t)((512 >> 8) & 0xFF)); for (i = 0; i < 512; i++) { CH375_WR_DAT(buf[i]); } // 真正触发U盘写入 CH375_WR_CMD(CMD_DISK_WRITE_GO); if (CH375_Wait_Interrupt(500) != 0) return 0x12; status = CH375_RD_DAT(); if (status != USB_INT_DISK_READ) return status; // 最终完成状态复用0x1D return 0; }写扇区容易翻车的点是CMD_WR_USB_DATA(0x2D)前的长度字节:先写低字节再写高字节,而且长度值要和实际扇区大小严格一致。如果你写了512,却只搬了256字节数据,后续的CMD_DISK_WRITE_GO会因为缓冲区数据不完整而卡死。我在调试时用逻辑分析仪抓过波形,卡死现象就是INT引脚一直不拉低,最后只能靠超时退出。
4.4 容量探测函数
CH375B获取U盘容量是通过CMD_DISK_SIZE,命令成功后读4个字节,得到的就是总扇区数,容量就是扇区数乘以512:
uint8_t CH375_DiskGetSize(uint32_t *total_sectors) { uint8_t status; uint32_t sectors = 0; CH375_WR_CMD(CMD_DISK_SIZE); if (CH375_Wait_Interrupt(200) != 0) return 0x11; status = CH375_RD_DAT(); if (status != USB_INT_DISK_SIZE) return status; sectors = (uint32_t)CH375_RD_DAT(); sectors |= (uint32_t)CH375_RD_DAT() << 8; sectors |= (uint32_t)CH375_RD_DAT() << 16; sectors |= (uint32_t)CH375_RD_DAT() << 24; *total_sectors = sectors; return 0; }测容量时需要注意,CH375B返回的扇区数是“U盘报告给主机的可用扇区数”,通常和Windows里格式化后看到的“容量”一致。如果你想进一步验证容量读取是否可靠,可以在拿到扇区总数后,尝试读取最后一个扇区、写读最后一个扇区,能通过基本说明容量计算和扇区寻址是对得上的。
4.5 main里的测试流程
测试流程我把步骤串成一个循环,方便在调试串口里打印阶段性结果:
int main(void) { uint8_t buf[512]; uint32_t sectors, i; Delay_Init(); UART1_Init(115200); CH375_GPIO_Init(); printf("CH375B test start\n"); // 第一步:检测芯片 CH375_WR_CMD(CMD_CHECK_EXIST); CH375_WR_DAT(0xA5); if (CH375_RD_DAT() != 0x5A) { printf("chip not found\n"); while(1); } // 第二步:初始化U盘 if (CH375_DiskInit() != 0x14) { printf("disk init failed, check USB cable\n"); while(1); } // 第三步:读取容量 if (CH375_DiskGetSize(§ors) != 0) { printf("get size failed\n"); while(1); } printf("total sectors: %lu, size: %lu MB\n", sectors, sectors / 2048); // 第四步:读0号扇区 if (CH375_DiskRead(0, 1, buf) != 0) { printf("read sector 0 failed\n"); while(1); } printf("sector 0 first 16 bytes:\n"); for (i = 0; i < 16; i++) printf("%02X ", buf[i]); printf("\n"); // 第五步:写最后一个扇区,再读回校验 for (i = 0; i < 512; i++) buf[i] = 0x5A; if (CH375_DiskWrite(sectors - 1, 1, buf) != 0) { printf("write last sector failed\n"); while(1); } memset(buf, 0, 512); if (CH375_DiskRead(sectors - 1, 1, buf) != 0) { printf("re-read last sector failed\n"); while(1); } for (i = 0; i < 512; i++) { if (buf[i] != 0x5A) { printf("data compare fail at %d\n", i); while(1); } } printf("ALL TESTS PASSED\n"); while(1); }这个流程覆盖了“芯片通路-磁盘初始化-容量获取-扇区读写-数据完整性”五层验证,任何一环出问题都能及时在串口输出定位。实际操作时建议把U盘先备份数据,尤其是测试写最后一个扇区的步骤,对有数据的U盘属于危险操作,我这里用的是空闲盘。
5. 实测结果与参数:测试通过的标准是什么
测试通过不是“屏幕上没报错”就算数,我在实际验证时划了几道硬性标准,任何一条不满足都算失败。
5.1 测试环境
硬件部分:STM32F103C8T6最小系统板,8MHz外部晶振,CH375B模块,USB转TTL调试串口,U盘一个,容量8GB,文件系统是FAT32。芯片工作电压5V,STM32侧的IO口串了330欧姆电阻做保护。
软件部分:标准外设库,工程编译采用Keil MDK5,优化等级-O0,方便在线调试时观察中间变量。
5.2 测试结果
实测数据如下:
- 芯片检测:上电后发0xA5,回读0x5A,一次通过
- 磁盘初始化:U盘插入后执行CMD_DISK_INIT,返回0x14,耗时约300~500ms
- 容量读取:总扇区数返回15138784,换算后容量约7.22GB,与Windows下看到的FAT32可用空间吻合
- 读扇区:读取0号扇区,前16字节打印出EB 58 90,这是FAT32卷引导记录的标志,说明数据链路正确
- 写扇区:往最后一个扇区写入512字节0x5A,再读回比对,逐字节一致,无一位错误
- 稳定性:连续执行了100次“写-读-校验”循环,全部通过,没有出现超时或数据错乱
测试时还有个意外发现:某些U盘在命令间隔太短时容易出错,比如连续紧挨着发CMD_DISK_INIT和CMD_DISK_SIZE,偶发返回0x15。后来在两条命令之间加了10ms延时,问题消失。这应该是U盘内部主控的响应时间问题,跟CH375B关系不大,但在应用层用循环读写扇区时,建议每次命令之间留一点间隔,别压得太死。
6. 踩过的坑:排查U盘识别失败和读写不稳定的通路
如果这部分早点写出来,我至少能省两天的调试时间。下面按我实际踩坑的顺序来记录,排查思路比最终答案更重要。
6.1 上电时序与复位信号
第一次上板调试,最经典的现象是“芯片检测通过,但CMD_DISK_INIT永远返回失败”。我一开始怀疑U盘坏了,换了好几个都是同样问题,后来查了CH375B数据手册才发现问题不在U盘,而在复位时序。
CH375B的复位引脚需要拉低至少30ms再释放,释放之后还要等固件初始化完成,这个时间大概在几十毫秒量级。我之前的代码里复位给得太短,芯片还没准备好就发了CMD_DISK_INIT,自然失败。修复办法是上电后先拉低RST引脚100ms,再拉高,再延时50ms,之后才发第一条命令。这个时间参数可以在工程里做成宏,方便后续调整。
6.2 电源稳定性
U盘插入瞬间的电流冲击是最大隐患。面包板飞线阶段,我遇到过“插入U盘后芯片直接复位”的现象,用示波器看是5V电源被拉低到3.2V左右。解决办法前面提过:在CH375B模块的电源输入端加100uF电解电容加0.1uF陶瓷电容。
但还有一个隐蔽问题:如果CH375B模块通过杜邦线从STM32板子上取5V,线太长太细,线阻会把电压跌落问题放大。后来我改成从USB转TTL模块的5V输出单独给CH375B供电,地线共地,U盘初始化成功率明显提升。做产品的话,建议CH375B和U盘座共用一路5V电源,用粗线或铺铜连接。
6.3 命令超时处理
CH375B在U盘未插入、U盘故障、USB线松脱等情况下,命令会一直处于等待状态。如果代码里只做“等待INT拉低”的死等,整个系统就会卡死在CH375B相关函数里。调试串口没有任何输出,连锤子都懒得敲。
我在CH375_Wait_Interrupt函数里加了超时参数,单位为毫秒,超时直接返回错误码。这样U盘异常时程序能走完整个错误处理流程,把状态打印出来。这个方法强烈建议保留,后续接RTOS或者状态机时也会用得上。
uint8_t CH375_Wait_Interrupt(uint16_t timeout_ms) { while (timeout_ms--) { if (CH375_INT_GET() == 0) { return 0; } Delay_Ms(1); } return 0xFF; }6.4 兼容性:不是所有U盘都能一把过
CH375B毕竟是把U盘当成标准Mass Storage设备来枚举,市面上主控芯片五花八门,偶尔会遇到兼容性比较差的U盘。我测了四个U盘,三个顺利通过,一个老款2GB的金士顿在CMD_DISK_INIT阶段偶发失败,重试几次又好了。
对这个问题,最实用的处理方式是在应用层加“初始化失败重试”机制。CH375B的数据手册也建议对磁盘操作命令做重试,通常重试2到3次就能把偶发错误消化掉。我现在的工程里,CMD_DISK_INIT失败后自动重试,最多重试3次,每次间隔50ms。实测四个U盘全部能在重试机制下正常初始化。
另外要提醒的是,如果U盘是exFAT或者NTFS格式,扇区命令依然可以读写,因为扇区命令不涉及文件系统。但如果你只是想读写文件,那就必须用FAT16/FAT32格式。这个区分在需求评审阶段就要想清楚,不然交付的时候容易被问住。
7. 一点经验补充:扇区操作只是起点,往上是文件系统
我最初做这个项目时以为“能读扇区”就完事了,后来发现在工业现场,客户往往更关心“能不能把数据按文件名存到U盘里”。扇区读写是地基,文件系统是房子。CH375B这颗芯片在文件级操作上有现成的命令,比如CMD_FILE_OPEN、CMD_FILE_WRITE等,但那是另一套API,依赖U盘是FAT分区。
如果后续有文件级需求,建议先在扇区层把“读0号扇区、解析BPB、找到FAT表位置”这套逻辑跑通,这样你对U盘的存储结构会有一个很直观的认识,再上手CH375B的文件级命令时会轻松很多。我踩过的一个坑是:直接把网上其他人写的“文件读写库”拉进工程,结果FAT表解析和CH375B固件版本对不上,文件创建出来是坏的。最后是老老实实读数据手册,把扇区层验证扎实了,才逐步把文件功能加上去。
关于测容量,还有一个实用心得:拿到扇区总数后别急着信,用“读最后一个扇区”来验证寻址范围是低成本且有效的手段。如果能读通最后一个扇区,说明整个寻址范围内基本是健康的;如果读不通,多半是容量计算有问题或者U盘有坏道。这个检查手段对“测U盘容量”这个需求来说,比单纯拿命令返回值更有说服力。
本文还有配套的精品资源,点击获取