☰
ESP32模拟USB主机修复变砖FX2光谱仪固件
2026/10/15 16:47:40 网站建设 项目流程

1. 一台“变砖”的光谱仪到底卡在哪

手里这台USB4000光谱仪,是早年从某实验室淘汰设备里淘来的。它用的是USB2.0全速接口,内部一颗Cypress FX2系列USB控制器负责和上位机通信,光谱数据通过CCD线阵采集后经FX2打包上传。问题出在一次固件升级中断——上位机软件在写入FX2的EEPROM时断电,结果设备上电后PID/VID变成默认的未配置状态,电脑能识别到一个“未知USB设备”,但光谱仪软件死活连不上。这就是典型的“变砖”:硬件没坏,但固件引导链断了,设备无法进入正常工作模式。

我试过官方提供的固件恢复工具,但那个工具依赖特定版本的上位机软件,而且只在Windows XP/7下跑得稳,手头只有一台Linux笔记本和一块吃灰的ESP32开发板。于是决定用ESP32来模拟USB主机,直接和FX2的引导模式对话,把固件重新灌进去。这个思路的核心在于:FX2系列芯片在EEPROM为空或校验失败时,会枚举成一个默认的USB设备(VID 0x04B4,PID 0x8613),这个设备支持通过USB控制传输写入内部RAM,然后从RAM启动。只要能在ESP32上实现这套控制传输协议,就能把固件重新写进去,让设备复活。

这个项目适合谁看?如果你手头有类似基于FX2的旧设备(不限于光谱仪,很多老式数据采集卡、逻辑分析仪都用这个方案),或者你对ESP32的USB主机功能感兴趣,想找一个有实际产出的练手项目,那这篇内容应该能帮到你。我会把整个排查过程、协议分析、代码实现和踩过的坑都摊开讲,尽量让没有USB底层经验的人也能跟着做下来。

2. 为什么选ESP32做USB主机而不是树莓派

2.1 方案对比:ESP32、树莓派与PC直连

一开始我考虑过三个方案:用树莓派Zero做USB主机、用旧笔记本跑Linux直接操作、用ESP32-S2/S3做USB主机。树莓派方案最成熟,Linux内核自带usbfs,libusb库直接调用,写个Python脚本就能搞定。但手头没有多余的树莓派,而且树莓派功耗高,为了修一个设备专门跑一个Linux系统有点杀鸡用牛刀。旧笔记本方案类似,而且我试过在Linux下用libusb直接发控制传输,结果发现FX2的引导模式对控制传输的时序比较敏感,Linux的USB子系统调度延迟偶尔会导致超时,成功率大概只有七成。

ESP32-S2/S3自带USB OTG外设,可以配置成主机模式,而且ESP-IDF提供了usb_host库,能直接发控制传输和批量传输。最关键的是,ESP32的USB主机栈是裸机级别的,没有操作系统调度干扰,时序控制精确到微帧级别。我实测下来,用ESP32-S3发控制传输的成功率接近百分之百,而且整个设备可以做成一个巴掌大的小板子,插上就能用,比拖一台电脑方便得多。

注意:ESP32经典款(ESP32-WROOM)没有USB OTG外设,只有ESP32-S2、S3、S2 mini等带USB OTG的型号才能做USB主机。买板子的时候一定要看清型号,别买错了。

2.2 FX2引导模式的协议要点

FX2的引导模式协议其实不复杂,核心就是几条控制传输。设备枚举后,默认端点0支持标准USB控制传输。要往内部RAM写数据,用的是厂商自定义请求:bRequest = 0xA0,wValue = 目标地址高16位,wIndex = 目标地址低16位,然后通过bRequest = 0xA0的OUT传输把数据写进去。写完之后,再发一个bRequest = 0xA0的IN传输把数据读回来校验,确认写入无误。最后发一个bRequest = 0xA0的请求,让芯片从指定地址开始执行代码。

这里有个关键点:FX2的内部RAM只有8KB,而USB4000的固件大概有16KB左右。所以不能一次性把整个固件写进去,需要分块写入,每块不超过8KB,写一块执行一块,或者利用FX2的“外部RAM”模式。但USB4000的硬件设计里,FX2的EA引脚被拉高,意味着它从内部RAM启动。所以只能分块加载,先写一个小的“加载器”到RAM里,由这个加载器负责接收后续的固件数据并写入EEPROM。这个加载器的代码我是从网上找到的一个开源项目里提取的,大概几百字节,功能就是通过USB接收数据并写入I2C EEPROM。

2.3 硬件连接与供电

硬件连接很简单:ESP32-S3的USB D+和D-分别接到USB4000的D+和D-,VBUS和GND对应连接。但这里有个坑:USB4000的FX2芯片需要3.3V供电,而ESP32-S3的USB口默认是5V VBUS输出。我一开始直接把5V VBUS接到USB4000的VBUS引脚上,结果FX2的IO电压是3.3V,5V的VBUS虽然不直接进IO,但USB4000板上有一个LDO把VBUS降到3.3V给FX2供电,这个LDO的输入耐压是5.5V,所以5V没问题。但如果你用的板子LDO耐压不够,最好还是用外部3.3V供电,只把D+、D-和GND连过去。

另外,ESP32-S3的USB主机模式需要配置GPIO来使能VBUS输出,或者用外部电源给USB设备供电。我图省事,直接用了一个USB母座转接板,把ESP32-S3的USB口和USB4000的USB口对接,VBUS由ESP32-S3提供。实测下来,USB4000的工作电流大概在200mA左右,ESP32-S3的VBUS输出能力足够。

3. 固件提取与协议逆向的实操细节

3.1 从官方软件包里挖出固件

USB4000的固件藏在官方上位机软件的安装目录里,通常是一个.hex文件或者.iic文件。我用的是某版本的光谱仪软件,安装后在Program Files目录下找到一个USB4000.hex文件。这个文件是Intel HEX格式,里面包含了固件的二进制数据和地址信息。用objcopy或者Python的intelhex库可以把它转成纯二进制文件。

from intelhex import IntelHex ih = IntelHex("USB4000.hex") ih.tobinfile("USB4000.bin")

转出来的二进制文件大概16KB,开头几个字节是FX2的启动向量。这里要注意:FX2的固件通常有一个“启动头”,前几个字节是跳转指令,告诉芯片从哪里开始执行。如果你直接把二进制文件从地址0开始写,可能会覆盖掉这个启动头,导致芯片无法启动。正确的做法是先把启动头写到地址0,然后把剩余数据写到地址0x100之后,或者按照固件文件里的地址信息分段写入。

3.2 用逻辑分析仪抓取正常设备的枚举过程

为了确认FX2引导模式的协议细节,我用逻辑分析仪抓了一个正常工作的USB4000的枚举过程。逻辑分析仪接在USB D+和D-上,采样率设到24MHz,足够解析USB全速信号。抓下来的数据用USB协议分析软件解码后,可以看到设备枚举时的一系列标准请求:GET_DESCRIPTOR、SET_ADDRESS、SET_CONFIGURATION等等。这些请求的格式和标准USB协议一致,但FX2在引导模式下会返回一个特定的设备描述符,VID是0x04B4,PID是0x8613,bcdDevice是0x0000。

抓包还发现一个细节:FX2在引导模式下,端点0的最大包长度是64字节,但实际传输时,如果数据长度超过64字节,它会自动分包。所以我们在写控制传输的时候,每次传输的数据长度不要超过64字节,否则需要自己处理分包逻辑。ESP-IDF的usb_host库会自动处理分包,但如果你自己写底层驱动,就要注意这一点。

3.3 控制传输的代码实现

ESP-IDF的usb_host库提供了usb_host_transfer_submit_control函数,可以直接发控制传输。下面是我写的核心代码片段:

usb_host_transfer_t *transfer; usb_host_transfer_alloc(sizeof(usb_setup_packet_t) + data_len, 0, &transfer); usb_setup_packet_t *setup = (usb_setup_packet_t *)transfer->data_buffer; setup->bmRequestType = 0x40; // 主机到设备,厂商请求 setup->bRequest = 0xA0; setup->wValue = (addr >> 16) & 0xFFFF; setup->wIndex = addr & 0xFFFF; setup->wLength = data_len; memcpy(transfer->data_buffer + sizeof(usb_setup_packet_t), data, data_len); transfer->num_bytes = sizeof(usb_setup_packet_t) + data_len; usb_host_transfer_submit_control(usb_host_client_handle, transfer);

这段代码发的是一个OUT传输,把数据写到FX2的指定地址。写完之后,再发一个IN传输读回来校验:

setup->bmRequestType = 0xC0; // 设备到主机,厂商请求 setup->bRequest = 0xA0; setup->wValue = (addr >> 16) & 0xFFFF; setup->wIndex = addr & 0xFFFF; setup->wLength = data_len; // 然后提交传输,接收数据

校验通过后,发一个bRequest = 0xA0、wValue = 0x0000、wIndex = 0x0000、wLength = 0的控制传输,让FX2从地址0开始执行。这一步相当于按下了“运行”按钮。

实操心得:控制传输的超时时间要设得足够长,我一开始设了100ms,结果经常超时。后来改成1000ms,成功率就上去了。FX2在写入内部RAM的时候,如果地址对齐不好,可能会触发内部等待状态,导致响应变慢。

4. 分块加载与EEPROM写入的完整流程

4.1 第一阶段:加载引导加载器

FX2的内部RAM只有8KB,而我们要写入的固件有16KB,所以不能一次性全部写进去。我的做法是分两步:第一步,先写一个小的引导加载器到RAM里,这个加载器只有几百字节,功能是通过USB接收数据并写入I2C EEPROM。第二步,让FX2执行这个加载器,然后通过加载器把完整的固件写入EEPROM。EEPROM写完之后,重新上电,FX2就会从EEPROM加载固件,设备就复活了。

引导加载器的代码我是从一个开源项目里找到的,它用SDCC编译,生成的二进制文件大概512字节。写入的时候,从地址0开始写,写完发执行命令。这里有个细节:FX2的RAM地址0处是复位向量,所以引导加载器的开头必须是一条跳转指令,跳到实际的代码入口。我用的这个加载器已经处理好了这一点,直接写就行。

4.2 第二阶段:通过加载器写EEPROM

引导加载器运行起来后,它会枚举成一个新的USB设备,VID和PID可能和之前不同。这时候ESP32需要重新枚举设备,找到新的端点,然后通过批量传输把固件数据发给加载器。加载器收到数据后,通过I2C总线写入EEPROM。EEPROM的型号是24LC64,容量8KB,但USB4000的固件有16KB,所以实际上用的是24LC128或者更大的型号。我拆开设备看了一下,板上焊的是24LC256,32KB容量,足够放固件。

写EEPROM的过程比较慢,因为I2C的时钟频率只有100kHz,写一页(64字节)需要几毫秒。整个16KB的固件写下来,大概需要十几秒。这期间不能断电,否则又得重来。我写了一个简单的进度条,通过串口打印出来,方便观察进度。

for (int i = 0; i < firmware_size; i += 64) { send_data_to_loader(firmware + i, 64); vTaskDelay(pdMS_TO_TICKS(10)); // 等待EEPROM写入完成 printf("Progress: %d%%\n", (i * 100) / firmware_size); }

注意:EEPROM写入需要时间,每写一页之后要留出足够的延时。24LC256的页写周期典型值是5ms,最大10ms。我一开始延时设了5ms,结果偶尔会写失败,后来改成10ms就稳定了。如果你用的是更快的EEPROM,可以适当缩短延时,但最好查一下数据手册确认。

4.3 第三阶段:验证与重启

固件全部写入EEPROM后,发一个命令让加载器重启FX2,或者直接断电重新上电。重新上电后,FX2会从EEPROM加载固件,枚举成正常的USB4000设备。这时候用光谱仪软件连接,如果能正常识别并采集到光谱数据,就说明修复成功了。

我第一次操作的时候,写完之后设备没反应,重新上电还是未知设备。用逻辑分析仪抓包发现,FX2上电后确实读了EEPROM,但读出来的数据校验失败,又退回了引导模式。后来发现是EEPROM写入的时候,有一页数据写错了,导致整个固件的校验和不对。重新写了一遍,这次每写一页都读回来校验,确认无误后再写下一页,就成功了。

5. 常见问题与排查技巧实录

5.1 设备枚举失败或反复重连

这是最常见的问题,通常有几个原因:一是USB D+和D-接反了,虽然USB协议允许自动翻转,但有些FX2的固件不支持,接反了就会枚举失败;二是VBUS供电不足,FX2启动瞬间电流可能超过300mA,如果ESP32的VBUS输出能力不够,就会导致设备反复重连;三是ESP32的USB主机栈配置不对,比如没有使能VBUS输出,或者GPIO配置错了。

排查的时候,先用逻辑分析仪抓D+和D-的信号,看有没有USB复位和枚举的波形。如果没有波形,说明ESP32根本没发出USB信号,检查ESP-IDF的USB主机初始化代码。如果有波形但设备没反应,检查供电和接线。

5.2 控制传输超时或返回STALL

FX2在引导模式下,如果收到的控制传输请求格式不对,会返回STALL握手。常见的原因是bmRequestType的方向位设错了,或者wLength超过了端点0的最大包长度。另外,FX2对控制传输的数据阶段有严格的时序要求,如果主机在数据阶段之间插入太长的延时,FX2可能会超时复位。

我遇到过一次,发完OUT传输后,等了500ms才发IN传输,结果FX2已经复位了,IN传输返回STALL。后来把两个传输之间的延时缩短到10ms以内,就正常了。所以控制传输的各个阶段要紧凑,不要中间插入太长的延时。

5.3 EEPROM写入后设备不启动

写EEPROM的时候,最容易出错的地方是地址对齐和页边界。24LC256的页大小是64字节,写的时候不能跨页。如果一次写入的数据跨越了页边界,EEPROM内部会回卷到页首,覆盖掉之前的数据。所以写的时候要按64字节对齐,每页单独写。

另外,EEPROM的写保护引脚(WP)要拉低,否则写不进去。有些板子上WP引脚是悬空的,内部下拉,可以正常写;有些板子上WP引脚被拉高了,就需要手动改电路。我遇到的那块板子就是WP被拉高了,飞了一根线到GND才写进去。

5.4 固件校验和不匹配

固件文件里通常有一个校验和字段,写在固件的末尾或者开头。写完之后,要读回来计算校验和,和原始文件对比。如果不匹配,说明写入过程中有数据丢失或错误。常见的原因是I2C总线上的干扰,或者EEPROM写入延时不够。解决方法是降低I2C时钟频率,增加写入延时,或者在I2C总线上加一个4.7kΩ的上拉电阻。

下面是我整理的一个常见问题速查表:

现象可能原因排查方法解决方案
设备不枚举D+/D-接反检查接线交换D+和D-
设备反复重连VBUS供电不足测量VBUS电压外部供电或换用更大电流的LDO
控制传输超时传输间隔太长抓包看时序缩短传输间隔到10ms以内
控制传输STALL请求格式错误检查bmRequestType和wLength修正请求格式,确保wLength≤64
EEPROM写不进WP引脚拉高测量WP引脚电压将WP拉低到GND
写入后不启动页边界跨页检查写入地址按64字节对齐写入
校验和不匹配I2C干扰或延时不够读回数据对比降低I2C频率,增加延时,加上拉电阻

6. 几个容易被忽略的细节和我的实操体会

6.1 ESP32-S3的USB主机栈配置要点

ESP-IDF的USB主机栈默认是关闭的,需要在menuconfig里手动使能。具体路径是Component config -> USB Host,然后勾选Enable USB Host。另外,USB主机的任务优先级和栈大小也要调一下,默认的栈大小可能不够,我设成了4096字节。还有,USB主机的时钟源要选对,ESP32-S3的USB OTG外设需要48MHz时钟,如果时钟源配置不对,USB根本起不来。

idf.py menuconfig # Component config -> USB Host -> Enable USB Host # Component config -> USB Host -> USB Host Task Stack Size -> 4096

6.2 固件文件的地址映射

Intel HEX格式的固件文件里,地址信息是分散的,不是连续的。转成二进制文件的时候,要确保地址映射正确。有些固件文件里,地址0处是空的,实际代码从0x100开始。如果你直接从地址0开始写二进制文件,就会把代码写到错误的位置。我的做法是解析HEX文件,提取每个数据段的地址和长度,然后按地址分段写入。这样虽然麻烦一点,但能保证写入的位置和原始固件一致。

6.3 供电和接地的处理

USB全速信号的差分阻抗是90Ω,虽然ESP32-S3和USB4000之间的连线很短,但最好还是用双绞线或者屏蔽线。我一开始用杜邦线连的,结果枚举成功率只有一半,后来换成短的USB专用线,就稳定了。另外,ESP32-S3和USB4000的GND一定要共地,否则USB信号没有参考电平,根本无法通信。

6.4 备份原始EEPROM数据

在写EEPROM之前,一定要先把原始数据读出来备份。虽然设备已经变砖了,但EEPROM里可能还有部分有效数据,比如设备序列号、校准参数等。这些数据在修复后可能需要恢复。我读出来发现,EEPROM的前128字节是设备信息,包括序列号和校准日期。写固件的时候,我把这128字节保留了下来,只覆盖了固件区域。这样修复后,设备的序列号还是原来的,光谱仪软件不会因为序列号变化而重新校准。

实操心得:读EEPROM的时候,如果设备已经变砖,FX2可能无法正常响应I2C读取请求。这时候可以用ESP32的I2C外设直接连到EEPROM的I2C总线上,绕过FX2读取数据。EEPROM的I2C地址通常是0x50,读的时候注意页边界,不要跨页读。

6.5 修复后的验证方法

设备修复后,不要急着装回去,先做几个验证:一是用光谱仪软件连接,看能否正常识别设备型号和序列号;二是采集一条暗光谱和一条亮光谱,看数据是否正常;三是连续采集一段时间,看是否会出现掉线或数据错误。我修复后连续跑了两个小时,采集了上千条光谱,没有出现异常,才确认修复成功。

这个项目后续还可以扩展:比如把ESP32做成一个独立的修复工具,加上OLED屏幕和按键,不用电脑就能修复设备;或者把固件写入过程做成自动化脚本,一键完成从固件提取到EEPROM写入的全流程。如果你手头有类似的FX2设备,不妨试试这个方案,成本不到五十块钱,比买新的便宜多了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询