# CH34x 寄存器与初始化序列(逆向 + 内核对照) 本驱动没有用到任何私有 SDK:全部控制传输都对齐 Linux 内核 `drivers/usb/serial/ch341.c` 以及 bbamorachi 站点(tools.bbamorachi.us)前端 bundle 中的实现(两者一致)。 ## 设备标识 | 芯片 | VID | PID | 接口类 | 浏览器端驱动 | |---|---|---|---|---| | CH340 / CH340G / CH340C / CH340B | `0x1A86` | `0x7523` | `0xFF` vendor-specific | `ch34x` | | CH341 / CH341A(串口模式) | `0x1A86` | `0x5523` | `0xFF` | `ch34x` | | 部分 CH341A 克隆 | `0x1A86` | `0x5512` / `0x7522` | `0xFF` | `ch34x`(默认接受) | | CH343 / CH9102 / CH347(CDC 模式) | `0x1A86` | `0x55D2…0x55E7` | CDC-ACM | `cdcAcm` | CH340 只有 **一个接口、两个 bulk 端点**(OUT `0x02`、IN `0x82`,wMaxPacketSize = 32)。 端点号不写死,由 `layout()` 从配置描述符里找:"第一个同时带 bulk IN 和 bulk OUT 的接口"。 ## 控制传输(vendor / device) | 请求 | 名称 | 作用 | |---|---|---| | `0x5F` | READ_VERSION | 读 2 字节版本,第 2 字节必须为 0,否则芯片型号/固件不对 | | `0x95` | READ_REG | 读寄存器:`value` = 寄存器地址,长度 2 | | `0x9A` | WRITE_REG | 写寄存器:`value` = 寄存器地址,`index` = 数据 | | `0xA1` | SERIAL_INIT | 串口初始化 / modem 控制(`0x501F,0xD90A` = 关流控) | | `0xA4` | MODEM_CTRL | DTR/RTS,**位取反写入**:`~((rts?0x40:0)|(dtr?0x20:0))` | 关键寄存器: | 寄存器 | 含义 | |---|---| | `0x1312` | 波特率分频高位(含预分频标志位) | | `0x0F2C` | 波特率分频低位 | | `0x2518` | LCR:`0xC3` = 8 数据位 + 1 停止位 + 无校验 + bit7 允许写分频 | | `0x0706` | 状态寄存器(初始化时读一下做同步) | | `0xFFFF` / `0xFF9F`(作为 `0xA4` 的 value) | modem 控制字,见上 | ## 波特率算法 ```js factor = Math.floor(1532620800 / baud); ps = 3; while (factor > 65520 && ps > 0) { factor >>= 3; ps -= 1; } if (factor > 65520) throw new Error('Unsupported CH34x baud rate'); factor = 65536 - factor; ps |= 0x80; // 预分频标志 write(0x9A, 0x1312, (factor & 0xFF00) | ps); // 高位 write(0x9A, 0x0F2C, factor & 0xFF); // 低位 ``` 常用值的计算结果(已由 `test/driver.test.mjs` 断言): | 波特率 | 0x1312 | 0x0F2C | |---|---|---| | 9600 | `0xB282` | `0x0C` | | 115200 | `0xCC83` | `0x08` | | 460800 | `0xF383` | `0x02` | 基准时钟 1532620800 Hz = 48 MHz × 32(`CH341_BAUDBASE_FACTOR`),与内核一致。 ## 完整初始化序列(本驱动实现) ``` 1 0x5F 读版本(2B) → [1] 必须为 0 2 0xA1 0000 0000 → 清 modem 控制 3 0x9A 1312 <9600 factor> → 先按 9600 配置(芯片上电默认收发格式) 4 0x9A 0F2C <9600 divisor> 5 0x95 2518 (读 2B) → 读回 LCR 做同步 6 0x9A 2518 00C3 → 写 LCR = 8N1 7 0x95 0706 (读 2B) 8 0xA1 501F D90A → 关硬件流控 9 0x9A 1312 <目标波特率 factor> 10 0x9A 0F2C <目标波特率 divisor> 11 0xA4 FFFF 0000 → DTR/RTS 复位 12 0x95 0706 (读 2B) 13 0x9A 1312 <目标波特率 factor> ← 与 9/10 重复一次,确保切换生效 14 0x9A 0F2C <目标波特率 divisor> 15 0x9A 2518 00C3 16 0xA4 FF9F 0000 → DTR+RTS 有效(`~0x0060`) ``` 共 16 次控制传输,测试里逐条比对。 ## CDC-ACM(CH343 / CH9102 / CH347 CDC 模式) 标准 USB CDC 请求,不用厂商私有寄存器: | 请求 | 名称 | 数据 | |---|---|---| | `0x20` (class/interface) | SET_LINE_CODING | 7 字节:波特率 uint32 小端 + 停止位 + 校验 + 数据位(`00 00 08` = 8N1) | | `0x22` (class/interface) | SET_CONTROL_LINE_STATE | `0x0003` = DTR+RTS,`0x0000` = 全部释放 | ## 数据通道 - **读**:`transferIn(epIn, packetSize)`,默认预排队 **32** 个请求(`readQueueDepth`), 通过 `ReadableStream` 暴露;每个 transfer 完成即补一个,安卓上不容易丢数据。 - **写**:`transferOut(epOut, chunk)`,按 **4096 字节**分片,检查 `bytesWritten` 保证有进展。 - open 时如果 `device.configuration` 为空,先 `selectConfiguration(configurations[0])`; 然后 `claimInterface`,非 0 的 alt setting 需要 `selectAlternateInterface`。 ## 与其它实现的关系 本文件描述的序列与下列实现一致,可互相印证: - Linux 内核 drivers/usb/serial/ch341.c(原始出处) - selevo/WebUsbSerialTerminal 的 serial.js(WebUSB 版移植,README 声明 work on android browser) - tools.bbamorachi.us 前端驱动(五芯片,含 CH340) - Google 的 web-serial-polyfill(Serial API over WebUSB,但不含 CH340 厂商初始化) ## 为什么安卓不用装驱动 CH340 的接口类标志是 `0xFF`(vendor-specific),安卓/Linux 内核**不会**给它绑定 `cdc_acm` / `usbserial`,所以设备是"空闲"的,浏览器(用户态)可以 `claimInterface` 并直接收发。CDC-ACM 类设备就没这么幸运:个别 ROM 上内核会先抢走,此时 `claimInterface` 会失败——驱动里把这种失败翻译成了中文提示。