test / test (push) Canceled after 0s
- 新增"先例与定位":Linux ch341.c / selevo / 3fffff / calcite / web-serial-polyfill / bbamorachi 站点 / SchoepsLabs 等,明确本库是工程化而非首创 - 修正"安卓没有 Web Serial"的表述:Chrome Android 138+ 已支持蓝牙 RFCOMM, USB 串口依赖 Android Serial API(2026 Q2 起、部分机型,预计 M149) - docs 补"与其它实现的关系"
116 lines
5.1 KiB
Markdown
116 lines
5.1 KiB
Markdown
# 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` 会失败——驱动里把这种失败翻译成了中文提示。
|