WebUSB API 让浏览器拥有直接访问 USB 设备的能力,这使得网页应用可以绕过安装驱动和本地代理程序,直接与 Arduino 等微控制器交换数据。传统方案通常依赖串口转 USB 驱动,并由桌面软件读取 COM 端口;WebUSB 则改变了这个思路:设备在固件里声明自己支持 WebUSB,浏览器通过标准 USB 协议枚举设备并读写端点。需要注意的是,普通的 Arduino Uno 使用 ATmega16U2 作为串口转换芯片,默认固件只暴露 CDC 串口类,无法直接作为 WebUSB 设备使用;而 Arduino Leonardo、Micro 以及基于 ATmega32U4 或 SAMD21 的开发板,因为原生 USB 控制器可由用户程序配置,更适合构建 WebUSB 实验。

WebUSB API 的工作机制与浏览器支持
USB 通信本质上是主机与设备之间的分层协议。设备可以包含多个配置,每个配置下包含多个接口,每个接口又由若干端点组成。端点分为控制端点、批量端点、中断端点和同步端点。WebUSB 并没有重新发明这一套协议,而是把 navigator.usb 对象暴露给网页,让 JavaScript 可以执行请求设备、打开设备、选择配置、声明接口以及读写端点等操作。
在 WebUSB 中,控制传输通常用于获取设备信息、设置设备状态或发送厂商自定义命令。批量传输则用于持续数据流,例如回传传感器数据或接收网页下发的控制指令。Arduino 端的 WebUSB 实现通常会把一个或多个批量端点映射到类似串口的读写接口,让开发者可以用熟知的 read 和 write 逻辑操作,但底层发送的是原始 USB 数据包,不再经过操作系统串口驱动。
浏览器支持是必须考虑的现实条件。Chromium 内核浏览器对 WebUSB 支持最完整,包括 Chrome 和 Edge;Firefox 与 Safari 目前仍没有默认开启该能力。因此如果应用面向普通用户,需要提供降级方案,例如提示用户安装桌面桥接程序。此外,WebUSB 要求页面运行在安全上下文,也就是 HTTPS 或 localhost 环境。localhost 开发调试时可以直接使用,但部署到局域网或公网必须配置有效证书。
在Arduino端实现WebUSB设备
并不是所有 Arduino 都能直接改造成 WebUSB 设备。Arduino Uno 的 USB 口由一个独立芯片负责,用户程序无法修改该芯片的 USB 描述符。Arduino Leonardo、Micro、Pro Micro 等使用 ATmega32U4 芯片的开发板,USB 外设直接由主控管理,因此可以通过库修改 USB 接口定义。基于 SAMD21 的 MKR 系列或 Zero 板也支持类似改造。
实现 WebUSB 的关键是在 USB 描述符中加入 WebUSB Platform Capability 和一个额外的 vendor-specific 接口。Arduino 的 WebUSB 库封装了这个过程,开发者只需声明设备来源和数据处理逻辑。下面的示例会初始化一个 WebUSB 串口实例,并在 loop 中把收到的字节原样回传:
#include <WebUSB.h>
WebUSB WebUSBSerial(1, "https://ipipp.com");
void setup() {
Serial.begin(9600);
WebUSBSerial.begin(9600);
while (!WebUSBSerial) {
delay(10);
}
}
void loop() {
if (WebUSBSerial.available()) {
String input = WebUSBSerial.readString();
WebUSBSerial.print("Arduino received: ");
WebUSBSerial.println(input);
}
}
这里的构造参数中,数字 1 表示 https 协议标志,后面跟着的来源 URL 必须与浏览器页面来源一致。固件里声明的来源会参与浏览器的安全校验,如果网页部署在 https://ipipp.com,而固件里写的是另一个域名,连接会被拒绝。编译上传时,还需要注意开发板的 USB PID 和 VID。某些库会要求修改 boards.txt 以启用 WebUSB 描述符,具体步骤应参考所用库的文档。
使用 WebUSB 后,Arduino 原生串口仍然可以保留用于日志输出。示例中同时初始化了 Serial 和 WebUSBSerial,两者互不干扰。不过要注意,WebUSB 设备的接口号与端点号需要和浏览器端代码保持一致。Arduino WebUSB 库通常使用接口 2 和端点 3、4,但不同版本或不同开发板可能不同,连接失败时先核对端点号。
浏览器端连接与数据传输
网页端的首要步骤是请求用户选择设备。navigator.usb.requestDevice 会弹出浏览器设备选择器,用户可以从中选择已经插入的 Arduino。filters 字段可以按厂商 ID 过滤设备,例如 Arduino 的 vendorId 通常是 0x2341。如果不设置过滤器,列表可能会很长,但用户仍然可以选择目标设备。
const device = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x2341 }]
});
await device.open();
await device.selectConfiguration(1);
await device.claimInterface(2);
const endpointIn = 3;
const endpointOut = 4;
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
await device.transferOut(endpointOut, textEncoder.encode("Hello from browser"));
const result = await device.transferIn(endpointIn, 64);
console.log(textDecoder.decode(result.data));
代码中的 transferOut 用于发送数据,transferIn 用于接收数据。第二个参数是期望读取的最大字节数,实际返回长度以 result.data 的 byteLength 为准。与串口不同,WebUSB 不会自动按行切分数据,如果 Arduino 端一次发送多个字符串,浏览器端可能需要自行设计帧头、长度字段或换行符来拆包。示例使用 TextDecoder 直接解码,适合文本协议;如果传输二进制数据,则建议通过 DataView 或 Uint8Array 处理。
在真实应用中,数据监听通常需要循环调用 transferIn。由于每一次 transferIn 都是异步操作,可以把它封装成一个读取循环。浏览器页面关闭或设备拔出时,会触发 device 对象的断开事件,开发者应监听该事件并清理界面状态,避免重复连接。若 Arduino 端没有数据可读,transferIn 会等待直到超时,因此也可以用它实现长连接下的低延迟读取。
调试常见错误与安全限制
WebUSB 开发中最容易遇到的错误是 NotFoundError,它在用户取消设备选择器时出现。这个错误不等于设备不存在,而是用户没有授权。另一个常见问题是设备列表里看不到 Arduino,可能原因是固件没有正确声明 WebUSB 描述符,或者操作系统没有加载设备。Windows 上有时需要为开发板安装通用 USB 驱动,Linux 则可能需要添加 udev 规则,允许普通用户访问该 USB 设备。
安全问题贯穿 WebUSB 的整个生命周期。页面必须运行在 HTTPS 或 localhost 下,并且 iframe 中必须显式声明 allow="usb" 权限策略。固件中的来源白名单和网页来源会被严格比对,这意味着开发环境与生产环境域名不同时,需要重新烧录固件或准备不同版本。浏览器还会限制某些设备类,例如键盘、鼠标、U 盘等受保护设备不能通过 WebUSB 访问,以避免网页静默读取敏感设备。
调试时可以打开 Chrome 的 chrome://device-log 页面查看设备枚举日志,或使用 chrome://usb-internals 检查设备和接口信息。如果 transferIn 一直挂起、transferOut 返回错误码,可以先用 USB 抓包工具确认端点号是否正确。端点是 WebUSB 通信中最容易出错的参数,固件里声明的是哪一个编号,浏览器端就必须使用哪一个编号。把端点号、接口号、来源域名和权限状态逐项核对,大多数连接失败都能快速定位。
WebUSB APIArduino通信微控制器修改时间:2026-08-21 01:45:52