- 新增"先例与定位":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 补"与其它实现的关系"
12 KiB
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"时安装,桌面端不会被改变行为。
平台动向(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。
安卓注意事项
- 必须 HTTPS(
localhost除外),且要用用户手势(点击)触发连接。 - 只有 Chromium 系浏览器可以:Chrome / Edge / Brave / Kiwi。Firefox 安卓没有 WebUSB,iOS 全线没有。 注意别和"安卓已经有 Web Serial 了"混淆:138+ 的安卓 Web Serial 只覆盖蓝牙串口,USB 串口要等 Android Serial API(2026 Q2 起、部分机型)。
- 需要 OTG:手机要支持 USB Host;部分机型 OTG 与充电互斥、供电不足会表现为反复掉线。
- CH340 是 vendor-specific 类(0xFF),安卓内核不会去绑定它,所以能被浏览器抢占;
CDC-ACM 类设备在个别 ROM 上会被内核
cdc_acm先占,claimInterface会失败(报错已给出提示)。 - 连接后页面切后台 / 锁屏会被节流断流,长任务建议申请 Wake Lock:
await navigator.wakeLock.request('screen')。 - 拔插后
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。
先例与定位(我们不是第一个)
CH340 用 WebUSB 直连这件事早有先例,本项目的价值在于"把它做成能直接用的库",而不是发明了新方法:
| 先例 | 做了什么 | 与本库的关系 |
|---|---|---|
| Linux 内核 drivers/usb/serial/ch341.c | CH340/CH341 寄存器与初始化序列的原始出处 | 本库的控制传输序列就是对齐它 |
| selevo/WebUsbSerialTerminal 15星 | WebUSB 串口终端,README 明写 work on android browser,serial.js 注释写明"把 Linux ch341.c 改写成 WebUSB" | 核心手法相同;形态是终端页面,不是可复用库 |
| 3fffff/webusb-ch341 6星 | WebUSB CH341:UART / I2C / SPI / GPIO | 单芯片、偏工具 |
| calcite/WebUSB-CP2102 | WebUSB CP2102 通信库 | 同类思路,另一种芯片 |
| web-serial-polyfill(Chrome 团队原型) | 用 WebUSB 实现 Serial API,官方说明"对安卓这类有 WebUSB 无 Web Serial 的平台仍然有用" | 垫片思路的先例;但它依赖设备未被系统驱动占用,不做 CH340 的厂商寄存器初始化 |
| tools.bbamorachi.us(生产站点) | 五芯片 WebUSB 驱动 + 按 UA 分流,安卓可用 | 本库的参考实现之一 |
| SchoepsLabs/webserial-android(Kotlin/APK) | 套壳浏览器,替网页桥接 USB 串口 | 说明平台缺口真实存在,而本库是免安装的网页侧解法 |
| therealmacoy360/aries-vigilant-gui 等 | "桌面 Web Serial + 安卓 WebUSB" 双路模式 | 与本库同一套取舍 |
本库补上的空白(截至发版时未找到对应开源实现):
- npm 上没有 CH340/CH341 的浏览器端驱动包 —— 检索 ch340 / ch341 / webusb ch340 driver 只得到 Node 侧或无关包;
- CH340 版的 navigator.serial 垫片:让未修改的 Web Serial 站点在安卓上跑起来(web-serial-polyfill 不做厂商初始化,CH340 用不了);
- 五芯片 + 全部支持 DTR/RTS + 拔插事件 + 诊断 + 单测 + TS 类型 + CI 的打包形态。
换句话说:不是开创先河,是把已验证的路子工程化。想真正"开创",最实在的两条路是把 CH340 支持提给上游(如 web-serial-polyfill)或发布到 npm 让别人直接用。
许可
MIT