Files
ch340-webusb/README.md
T
jiangchen 6c5e647c8a
test / test (push) Canceled after 0s
feat: v1.0.0 收尾 — 五芯片 DTR/RTS、类型声明、示例、CI
- CP210x / FTDI / PL2303 补齐 setSignals()(编码与各自初始化序列一致)
- 新增 index.d.ts(TypeScript 类型声明)、examples/minimal.html(最小接入示例)
- 新增 .gitea/workflows/ci.yml(push 时跑离线单测)、.gitattributes(强制 LF)、CHANGELOG.md
- package.json 补 repository / homepage / bugs / types / engines
- 单测 16 项全部通过:node test/driver.test.mjs
2026-09-23 00:41:29 +08:00

8.4 KiB
Raw Permalink Blame History

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    │  (无需驱动)   │
└──────────────┘                            └──────────────┘

快速开始

<script src="/ch340-webusb.js?v=1"></script>
<script src="/ch340-serial-shim.js?v=1"></script>  <!-- 需要免改业务代码时加载 -->

方式一:直接用(WebUSB,安卓 + 桌面通用)

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 直接就能用:

const port = await navigator.serial.requestPort();  // 安卓上返回的是 WebUSB 的 CH340
await port.open({ baudRate: 460800 });

桌面浏览器本来就有真 Web Serial,垫片不会安装,避免改变原有行为。

方式三:测试台

打开 ch340-test.html(需要 HTTPS 或 localhost):连接、收发、HEX、DTR/RTS、回环自测、诊断计数一屏搞定。

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"时安装,桌面端不会被改变行为。

安卓注意事项

  1. 必须 HTTPS(localhost 除外),且要用用户手势(点击)触发连接。
  2. 只有 Chromium 系浏览器可以:Chrome / Edge / Brave / Kiwi。Firefox 安卓没有 WebUSB,iOS 全线没有。
  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。

测试

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。

许可

MIT