Files
ch340-webusb/README.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

210 lines
12 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.
# ch340-webusb
浏览器直连 **CH340 / CH341**(以及 CDC-ACM、CP210x、FTDI、PL2303)USB 转串口芯片的纯前端驱动。
- **不用装 App、不用 root、不用装驱动**:安卓 Chrome/Edge 走 **WebUSB**,桌面 Chrome/Edge 走 **Web Serial**。
- **安卓浏览器终于能连 CH340 了**:安卓版 Chrome 有 WebUSB,但至今没有 Web Serial;本项目在 JS 里补上了 Web Serial 缺失的"芯片驱动层"(波特率寄存器、流控、LCR、DTR/RTS),并提供一层 `navigator.serial` 垫片,**让已有的串口业务代码零改动跑在安卓浏览器里**。
```
┌──────────────┐ navigator.usb (WebUSB) ┌──────────────┐
│ 你的网页 │ ─────────────────────────▶ │ CH340 芯片 │──── UART ────▶ 设备
│ ch340-webusb │ vendor 控制传输 + bulk │ (无需驱动) │
└──────────────┘ └──────────────┘
```
## 快速开始
```html
<script src="/ch340-webusb.js?v=1"></script>
<script src="/ch340-serial-shim.js?v=1"></script> <!-- 需要免改业务代码时加载 -->
```
### 方式一:直接用(WebUSB,安卓 + 桌面通用)
```js
const port = await TamaCh340.requestPort({ baudRate: 460800 }); // 弹系统选设备框
await port.open({ baudRate: 460800 }); // 内部完成 CH340 初始化
const reader = port.readable.getReader();
const writer = port.writable.getWriter();
await writer.write(new TextEncoder().encode('hello\r\n'));
const { value } = await reader.read(); // Uint8Array
await port.close();
```
### 方式二:当它是 Web Serial(零改动接入老站)
垫片会在**安卓**上(有 WebUSB、没有 Web Serial 时)自动补出 `navigator.serial`,
之后原来写给 Web Serial 的代码/打包好的 SPA 直接就能用:
```js
const port = await navigator.serial.requestPort(); // 安卓上返回的是 WebUSB 的 CH340
await port.open({ baudRate: 460800 });
```
> 桌面浏览器本来就有真 Web Serial,垫片**不会**安装,避免改变原有行为。
### 方式三:测试台
打开 `ch340-test.html`(需要 HTTPS 或 localhost):连接、收发、HEX、DTR/RTS、回环自测、诊断计数一屏搞定。
```bash
npx serve . # 或 python -m http.server 8080
# 手机访问 https 地址;安卓上建议用 cloudflared/ngrok 暴露本地端口
```
## 仓库结构
| 文件 | 说明 |
|---|---|
| ch340-webusb.js | 核心驱动(UMD:script 标签直接引,也可 require / import) |
| ch340-serial-shim.js | navigator.serial 垫片(安卓零改动接入) |
| ch340-test.html | 测试台(连接 / 收发 / HEX / DTR-RTS / 回环自测) |
| examples/minimal.html | 最小接入示例 |
| index.d.ts | TypeScript 类型声明 |
| docs/ch34x-registers.md | 寄存器与控制传输说明 |
| test/driver.test.mjs | 离线单测(mock USBDevice,无需硬件) |
## API
| 成员 | 说明 |
|---|---|
| `TamaCh340.requestPort(opts)` | 弹设备选择框,返回**未打开**的端口对象 |
| `TamaCh340.connect(opts)` | 选择 + 打开一步到位 |
| `TamaCh340.fromDevice(device, opts)` | 用已授权的 `USBDevice` 建端口 |
| `TamaCh340.getAuthorizedDevices()` | 之前授权过的设备(拔插后可免弹窗重连) |
| `TamaCh340.chipName(vid, pid)` | `'ch340' / 'cp210x' / 'ftdi' / 'pl2303' / ...` |
| `TamaCh340.calcCh34xBaud(baud)` | `{factor, divisor}`,与 Linux `ch341.c` 一致 |
| `TamaCh340.onLog = fn` / `debug = true` | 日志钩子 / 控制台输出 |
端口对象(与 Web Serial 的 `SerialPort` 对齐):
`open({baudRate,dataBits,stopBits,parity,flowControl,readTransferSize,readQueueDepth})` ·
`close()` · `getInfo()` · `readable` · `writable` · `read(ms)` · `write(bytes)` ·
`setSignals({dataTerminalReady,requestToSend})` · `forget()` · `addEventListener('disconnect', fn)` ·
`getDiagnostics()` · `device`(原始 `USBDevice`)· `chip`
## 支持的芯片
| 驱动 | VID:PID | 说明 |
|---|---|---|
| **CH340/CH341** | `1a86:7523`, `1a86:5523`(+ `5512/7522` 克隆) | 主目标;原厂拓麻歌子 USB 线就是 CH340C |
| CDC-ACM | `1a86:55d2…55e7` 等 | CH343 / CH9102 / CH347 的 CDC 模式 |
| CP210x | `10c4:ea60/ea70/ea71` | Silicon Labs |
| FTDI | `0403:6001/6010/6011/6014/6015` | 含 modem 状态字节剥离 |
| PL2303 | `067b:2303…23f3` | 含 HXN/HX/01/T 变种识别 |
## 各平台要不要装驱动?
| 平台 | 浏览器通道 | 需要额外驱动吗 |
|---|---|---|
| **安卓**(Chrome/Edge/Brave/Kiwi) | WebUSB | **完全不用**。CH340 是 vendor-specific 类,内核不绑定,浏览器直接抢占接口 |
| **Linux** | Web Serial(桌面首选) | 不用,内核自带 `ch341` |
| **macOS** | Web Serial(桌面首选) | 不用,系统自带 Apple USB 串口驱动 |
| **Windows** | Web Serial(桌面首选) | 装一次 WCH 官方 CH341SER 即可;**WebUSB 反而需要把设备换成 WinUSB 驱动(Zadig)才能用**——所以桌面端本库优先走 Web Serial |
也就是说:**安卓端零驱动**(这正是本项目的重点),桌面端用系统/官方驱动 + Web Serial 即可。
垫片只在"有 WebUSB 且没有 Web Serial"时安装,桌面端不会被改变行为。
> **平台动向(2026-09 现状,做选型时要看)**
> Chrome for Android **138+ 起已经有 Web Serial**,但覆盖的是**蓝牙 RFCOMM** 串口;
> **USB 串口**要等新的 Android Serial API——按 blink-dev 的 PSA,
> 它从 **2026 Q2** 起、且**仅在部分机型**上可用(预计里程碑 M149)。
> 也就是说:对绝大多数安卓机来说,USB 串口今天仍然只能靠 WebUSB 自己带驱动,
> 这正是本项目存在的理由;等 Android Serial API 普及后,本库依然适用于
> 未覆盖机型、WebView 套壳应用与旧版浏览器。
> 参考:chromestatus feature 6043992171085824、blink-dev PSA "Web Serial API on Android"(2026-02-10)、caniuse issue #7413。
## 安卓注意事项
1. **必须 HTTPS**(`localhost` 除外),且要用**用户手势**(点击)触发连接。
2. 只有 **Chromium 系**浏览器可以:Chrome / Edge / Brave / Kiwi。Firefox 安卓没有 WebUSB,iOS 全线没有。
注意别和"安卓已经有 Web Serial 了"混淆:138+ 的安卓 Web Serial 只覆盖**蓝牙**串口,**USB** 串口要等 Android Serial API(2026 Q2 起、部分机型)。
3. 需要 **OTG**:手机要支持 USB Host;部分机型 OTG 与充电互斥、供电不足会表现为反复掉线。
4. **CH340 是 vendor-specific 类(0xFF)**,安卓内核不会去绑定它,所以能被浏览器抢占;
CDC-ACM 类设备在个别 ROM 上会被内核 `cdc_acm` 先占,`claimInterface` 会失败(报错已给出提示)。
5. 连接后页面切后台 / 锁屏会被节流断流,长任务建议申请 Wake Lock:
`await navigator.wakeLock.request('screen')`。
6. 拔插后 `getDevices()` 能拿回已授权设备,可直接重连,不必再弹窗。
## 实现要点(为什么必须自己写驱动)
浏览器的 Web Serial 在桌面端内置了芯片驱动,而 WebUSB 只给"裸 USB"。
所以本驱动用控制传输复刻了内核里的那套寄存器操作,CH340 的初始化序列:
```
0x5F 读版本自检
0xA1 0000 0000 清 modem 控制
0x9A 1312 <factor> 波特率(先按 9600 配置)
0x9A 0F2C <divisor>
0x95 2518 (读 LCR) 0x9A 2518 00C3 → 8 数据位 / 1 停止位 / 无校验
0x95 0706 (读)
0xA1 501F D90A 关硬件流控
0x9A 1312/0F2C <目标波特率>
0x9A 2518 00C3
0xA4 FFFF DTR/RTS 复位
0xA4 FF9F DTR+RTS 有效(位取反写入)
```
波特率分频:`factor = floor(1532620800 / baud)`,>65520 则右移 3 位并叠加预分频,
再取 `0x10000 - factor`,高位写 `0x1312`、低位写 `0x0F2C`。
详见 `docs/ch34x-registers.md`。
## 测试
```bash
node test/driver.test.mjs
```
14 项断言全部离线跑(mock `USBDevice`):波特率算法、完整控制传输序列、8N1 校验、
4096 分片写、ReadableStream 读、DTR/RTS、关闭清理、CDC 布局与 SET_LINE_CODING、
FTDI modem 字节剥离、垫片自动安装。
## 验证状态
```
$ node test/driver.test.mjs
结果: 15 通过, 0 失败
```
| 项目 | 状态 |
|---|---|
| 波特率算法(9600/115200/460800)与 Linux `ch341.c` 逐值一致 | ✅ 单测断言 |
| CH340 完整 16 步控制传输序列 | ✅ 单测逐条比对 |
| 4096 分片写 / ReadableStream 读 / DTR-RTS / 关闭清理 | ✅ mock USBDevice |
| CDC 布局 + SET_LINE_CODING + FTDI modem 字节剥离 | ✅ 单测 |
| 垫片自动安装 / chain 让位蓝牙 | ✅ 单测 |
| 真实 CH340 硬件(`1a86:7523` rev 0264,Windows) | ✅ 115200 / 460800 打开、DTR/RTS、写入均正常(走系统 CH341SER 驱动) |
| 安卓真机 WebUSB 全链路 | ⏳ 待测:手机 OTG 插 CH340 → 打开 `ch340-test.html` → 连接设备 |
> Windows 桌面注意:CH340 一旦被 WCH 的 `CH341S64.SYS` 驱动接管,WebUSB 就**看不到**这个设备
> (WebUSB 只认 WinUSB/libusb 绑定的设备),所以桌面端请用 Web Serial(本库的垫片在桌面不会安装,
> 测试页会自动走原生 Web Serial)。安卓没有这个问题——内核根本不绑定 CH340。
## 先例与定位(我们不是第一个)
CH340 用 WebUSB 直连这件事**早有先例**,本项目的价值在于"把它做成能直接用的库",而不是发明了新方法:
| 先例 | 做了什么 | 与本库的关系 |
|---|---|---|
| Linux 内核 drivers/usb/serial/ch341.c | CH340/CH341 寄存器与初始化序列的原始出处 | 本库的控制传输序列就是对齐它 |
| [selevo/WebUsbSerialTerminal](https://github.com/selevo/WebUsbSerialTerminal) 15星 | WebUSB 串口终端,README 明写 work on android browser,serial.js 注释写明"把 Linux ch341.c 改写成 WebUSB" | 核心手法相同;形态是终端页面,不是可复用库 |
| [3fffff/webusb-ch341](https://github.com/3fffff/webusb-ch341) 6星 | WebUSB CH341:UART / I2C / SPI / GPIO | 单芯片、偏工具 |
| [calcite/WebUSB-CP2102](https://github.com/calcite/WebUSB-CP2102) | WebUSB CP2102 通信库 | 同类思路,另一种芯片 |
| [web-serial-polyfill](https://www.npmjs.com/package/web-serial-polyfill)(Chrome 团队原型) | 用 WebUSB 实现 Serial API,官方说明"对安卓这类有 WebUSB 无 Web Serial 的平台仍然有用" | 垫片思路的先例;但它依赖设备未被系统驱动占用,不做 CH340 的厂商寄存器初始化 |
| tools.bbamorachi.us(生产站点) | 五芯片 WebUSB 驱动 + 按 UA 分流,安卓可用 | 本库的参考实现之一 |
| [SchoepsLabs/webserial-android](https://github.com/SchoepsLabs/webserial-android)(Kotlin/APK) | 套壳浏览器,替网页桥接 USB 串口 | 说明平台缺口真实存在,而本库是免安装的网页侧解法 |
| [therealmacoy360/aries-vigilant-gui](https://github.com/therealmacoy360/aries-vigilant-gui) 等 | "桌面 Web Serial + 安卓 WebUSB" 双路模式 | 与本库同一套取舍 |
本库补上的空白(截至发版时未找到对应开源实现):
1. **npm 上没有 CH340/CH341 的浏览器端驱动包** —— 检索 ch340 / ch341 / webusb ch340 driver 只得到 Node 侧或无关包;
2. **CH340 版的 navigator.serial 垫片**:让未修改的 Web Serial 站点在安卓上跑起来(web-serial-polyfill 不做厂商初始化,CH340 用不了);
3. **五芯片 + 全部支持 DTR/RTS + 拔插事件 + 诊断 + 单测 + TS 类型 + CI** 的打包形态。
换句话说:**不是开创先河,是把已验证的路子工程化**。想真正"开创",最实在的两条路是把
CH340 支持提给上游(如 web-serial-polyfill)或发布到 npm 让别人直接用。
## 许可
MIT