Files
ch340-webusb/docs/ch34x-registers.md
T
jiangchen 929bf061d0
test / test (push) Canceled after 0s
docs: 补上先例调研与安卓 Web Serial 的真实进展
- 新增"先例与定位":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 补"与其它实现的关系"
2026-09-23 00:48:32 +08:00

116 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 会失败——驱动里把这种失败翻译成了中文提示。