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

5.1 KiB
Raw Blame History

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)

关键寄存器:

寄存器 含义
0x1312 波特率分频高位(含预分频标志位)
0x0F2C 波特率分频低位
0x2518 LCR:0xC3 = 8 数据位 + 1 停止位 + 无校验 + bit7 允许写分频
0x0706 状态寄存器(初始化时读一下做同步)
0xFFFF / 0xFF9F(作为 0xA4 的 value) modem 控制字,见上

波特率算法

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 会失败——驱动里把这种失败翻译成了中文提示。