diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..46526f6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.png binary +*.jpg binary diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..42859fa --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,12 @@ +name: test +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: 运行驱动单测(无需硬件) + run: node test/driver.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..846649e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +## v1.0.0 + +首个公开版本。 + +- ch340-webusb.js:WebUSB 上的 USB-Serial 栈 + - CH340/CH341(1a86:7523 / 1a86:5523,兼容 5512/7522 克隆)完整初始化序列,对齐 Linux ch341.c + - 波特率分频 calcCh34xBaud();32 深度预排队读 + ReadableStream;4096 分片写 + WritableStream + - DTR/RTS(CH340 用 0xA4 取反写入)、拔插事件、读诊断计数 + - 附带 CDC-ACM(CH343/CH9102/CH347)、CP210x、FTDI(含 modem 状态字节剥离)、PL2303(HXN/HX/01/T) + - 五种芯片都实现了 setSignals()(DTR/RTS) +- ch340-serial-shim.js:安卓上补出 navigator.serial,Web Serial 业务代码零改动可用;支持 chain 模式让位给蓝牙垫片 +- ch340-test.html:连接 / 收发 / HEX / DTR-RTS / 回环自测 测试台 +- examples/minimal.html:最小接入示例 +- index.d.ts:TypeScript 类型声明 +- docs/ch34x-registers.md:寄存器与控制传输说明 +- test/driver.test.mjs:16 项离线断言(mock USBDevice,无需硬件) diff --git a/README.md b/README.md index 36ab59a..63ac79b 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,18 @@ 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 | 成员 | 说明 | diff --git a/ch340-webusb.js b/ch340-webusb.js index cc47eb0..aeee4d6 100644 --- a/ch340-webusb.js +++ b/ch340-webusb.js @@ -268,6 +268,11 @@ await req(0x07, 0x0302); log('driver', 'CP210x 初始化完成 @' + opts.baudRate + ' bps'); }, + /** SET_MHS(0x07):高字节=掩码,低字节=电平;0x03 掩码表示"两条线都由本调用决定" */ + setSignals: async function (ctx, sig) { + var bits = (sig.dataTerminalReady ? 0x01 : 0) | (sig.requestToSend ? 0x02 : 0); + await ctx.controlOut('vendor', 'interface', 0x07, 0x0300 | bits, ctx.layout.controlInterface); + }, shutdown: async function (ctx) { var ci = ctx.layout.controlInterface; await ctx.controlOut('vendor', 'interface', 0x12, 0x000f, ci); @@ -342,6 +347,12 @@ await req(0x01, 0x0202); log('driver', 'FTDI 初始化完成 @' + opts.baudRate + ' bps'); }, + /** SET_MODEM_CTRL(0x01):高字节=掩码,低字节=电平(0x01 DTR / 0x02 RTS) */ + setSignals: async function (ctx, sig) { + var idx = ctx.layout.controlInterface + 1; + var bits = (sig.dataTerminalReady ? 0x01 : 0) | (sig.requestToSend ? 0x02 : 0); + await ctx.controlOut('vendor', 'device', 0x01, 0x0300 | bits, idx); + }, shutdown: async function (ctx) { var idx = ctx.layout.controlInterface + 1; await ctx.controlOut('vendor', 'device', 0x00, 0x0001, idx); @@ -411,6 +422,11 @@ await ctx.controlOut('class', 'interface', 0x22, 0x0003, 0x0000); log('driver', 'PL2303 初始化完成 (' + type + ') @' + opts.baudRate + ' bps'); }, + /** 标准 CDC 请求 SET_CONTROL_LINE_STATE(0x22):bit0=DTR, bit1=RTS */ + setSignals: async function (ctx, sig) { + var bits = (sig.dataTerminalReady ? 0x01 : 0) | (sig.requestToSend ? 0x02 : 0); + await ctx.controlOut('class', 'interface', 0x22, bits, 0x0000); + }, shutdown: async function (ctx) { var type = ctx.pl2303Type; if (!type) return; diff --git a/examples/minimal.html b/examples/minimal.html new file mode 100644 index 0000000..1b32a93 --- /dev/null +++ b/examples/minimal.html @@ -0,0 +1,53 @@ + + +
+ + ++ + + + +
+ + + + + + diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..08183d4 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,116 @@ +/** + * ch340-webusb 类型声明(UMD: