从0到1掌握WebUSB:TinyUSB实现浏览器与嵌入式设备通信
引言:WebUSB如何解决传统嵌入式开发的三大痛点
你是否还在为嵌入式设备调试时频繁插拔USB线而烦恼?是否因安装不同厂商的驱动程序而头疼?是否想过通过浏览器直接与你的开发板通信?TinyUSB的WebUSB功能正是为解决这些问题而生。
本文将带你深入了解WebUSB技术,并通过TinyUSB开源库实现浏览器与嵌入式设备的无缝通信。通过本教程,你将获得:
- WebUSB技术原理及与传统USB通信的对比分析
- 使用TinyUSB实现WebUSB设备的完整步骤
- 跨平台兼容性处理及常见问题解决方案
- 基于WebUSB的交互式应用开发实例
- 性能优化与高级功能实现指南
WebUSB技术概述
WebUSB简介
WebUSB是一项允许网页应用程序直接与USB设备通信的API,它打破了传统USB设备需要安装特定驱动程序的限制,使网页能够直接访问USB设备。这为嵌入式开发带来了革命性的变化,特别是在设备调试、数据可视化和远程控制方面。
WebUSB与传统USB通信方式对比
| 特性 | WebUSB | 传统USB |
|---|---|---|
| 驱动需求 | 无需特定驱动 | 需要安装厂商驱动 |
| 跨平台支持 | 浏览器一致支持 | 需针对不同OS开发驱动 |
| 访问方式 | 网页直接访问 | 通过专用应用程序 |
| 安全性 | 基于用户授权 | 依赖设备驱动安全性 |
| 开发复杂度 | 低(JavaScript API) | 高(需硬件相关知识) |
| 远程访问 | 原生支持 | 需要额外网络服务 |
WebUSB工作原理
WebUSB的工作流程可以概括为以下几个步骤:
TinyUSB库简介
TinyUSB概述
TinyUSB是一个开源的跨平台USB协议栈,支持USB设备和主机功能。它设计小巧、高效,适用于资源受限的嵌入式系统。TinyUSB支持多种USB类,包括CDC(虚拟串口)、HID(人机接口设备)、MSC(大容量存储)等,并且提供了对WebUSB的原生支持。
TinyUSB核心架构
TinyUSB的架构采用分层设计,主要包含以下几个部分:
TinyUSB对WebUSB的支持
TinyUSB通过以下组件支持WebUSB功能:
- WebUSB描述符:提供设备信息和URL描述符
- 自定义Vendor类实现:处理WebUSB特定的控制请求
- 数据传输API:简化与浏览器之间的数据交换
- 跨平台兼容性处理:解决不同操作系统和浏览器的差异
开发环境搭建
硬件准备
本教程使用以下硬件:
- 支持USB的嵌入式开发板(如ESP32-S3、RP2040等)
- USB数据线
- 电脑(Windows、macOS或Linux)
软件环境
需要安装的软件:
- 代码编辑器(VS Code推荐)
- 嵌入式开发工具链(如ARM GCC、ESP-IDF等)
- CMake(构建系统)
- Git(版本控制)
- Chrome或Edge浏览器(支持WebUSB)
获取TinyUSB源代码
git clone https://gitcode.com/gh_mirrors/ti/tinyusb.git
cd tinyusb
安装依赖
根据你的操作系统,安装相应的依赖:
Ubuntu/Debian
sudo apt-get install build-essential cmake git gcc-arm-none-eabi libnewlib-arm-none-eabi
macOS
brew install cmake arm-none-eabi-gcc
Windows
- 安装 Chocolatey
- 打开管理员命令提示符:
choco install cmake git gcc-arm-none-eabi
WebUSB设备实现
项目结构
我们将使用TinyUSB提供的WebUSB示例作为基础。该示例位于examples/device/webusb_serial/目录下,结构如下:
webusb_serial/
├── CMakeLists.txt
├── CMakePresets.json
├── Makefile
├── src/
│ ├── main.c
│ └── usb_descriptors.c
└── website/
├── application.js
├── divider.js
├── index.html
├── serial.js
└── style.css
核心代码分析
USB描述符配置
WebUSB设备需要特定的USB描述符,包括自定义的URL描述符和微软兼容描述符。以下是关键代码片段:
// usb_descriptors.c
const tusb_desc_webusb_url_t desc_url = {
.bLength = 3 + sizeof(URL) - 1,
.bDescriptorType = 3, // WEBUSB URL类型
.bScheme = 1, // 0: http, 1: https
.url = URL
};
// 微软OS 2.0描述符,用于Windows自动驱动安装
uint8_t const desc_ms_os_20[] = {
// 微软OS 2.0描述符头部
0x0A, 0x00, // 长度
0x00, 0x00, // 类型
0x00, 0x00, 0x03, 0x06, // Windows版本
0x00, 0x00, // 总长度
// 微软扩展配置描述符
0x08, 0x00, // 长度
0x01, 0x00, // 类型
0x04, 0x00, // 配置类型
0x01, // 配置数量
0x00, // 保留
// 微软扩展功能描述符
0x1C, 0x00, // 长度
0x02, 0x00, // 类型
0x02, 0x00, // 功能类型
0x00, 0x00, 0x00, 0x00, // 兼容ID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 子兼容ID
0x00, 0x00, // 保留
0x00, 0x00, // 设备状态
0x00, 0x00 // 保留
};
设备初始化
设备初始化代码负责配置USB控制器并启动TinyUSB栈:
// main.c
int main(void) {
board_init();
// 初始化设备栈
tusb_rhport_init_t dev_init = {
.role = TUSB_ROLE_DEVICE,
.speed = TUSB_SPEED_AUTO
};
tusb_init(BOARD_TUD_RHPORT, &dev_init);
if (board_init_after_tusb) {
board_init_after_tusb();
}
while (1) {
tud_task(); // 运行tinyusb设备任务
cdc_task(); // 处理CDC数据
led_blinking_task(); // 状态LED控制
}
}
WebUSB控制请求处理
处理来自浏览器的控制请求,包括WebUSB特定的请求:
// main.c
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) {
// 只处理SETUP阶段
if (stage != CONTROL_STAGE_SETUP) return true;
switch (request->bmRequestType_bit.type) {
case TUSB_REQ_TYPE_VENDOR:
switch (request->bRequest) {
case VENDOR_REQUEST_WEBUSB:
// 返回WebUSB URL描述符
return tud_control_xfer(rhport, request, (void*)(uintptr_t)&desc_url, desc_url.bLength);
case VENDOR_REQUEST_MICROSOFT:
if (request->wIndex == 7) {
// 返回微软OS 2.0兼容描述符
uint16_t total_len;
memcpy(&total_len, desc_ms_os_20 + 8, 2);
return tud_control_xfer(rhport, request, (void*)(uintptr_t)desc_ms_os_20, total_len);
} else {
return false;
}
default: break;
}
break;
case TUSB_REQ_TYPE_CLASS:
if (request->bRequest == 0x22) {
// WebUSB连接状态控制
web_serial_connected = (request->wValue != 0);
// 连接时点亮LED
if (web_serial_connected) {
board_led_write(true);
blink_interval_ms = BLINK_ALWAYS_ON;
tud_vendor_write_str("\r\nWebUSB接口已连接\r\n");
tud_vendor_write_flush();
} else {
blink_interval_ms = BLINK_MOUNTED;
}
// 返回状态OK
return tud_control_status(rhport, request);
}
break;
default: break;
}
// 不支持的请求,返回stall
return false;
}
数据接收与发送
处理从WebUSB接口接收的数据并回传:
// main.c
void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize) {
(void) itf;
// 将接收到的数据回传
echo_all(buffer, bufsize);
// 如果启用了RX缓冲区,需要刷新以腾出空间
#if CFG_TUD_VENDOR_RX_BUFSIZE > 0
tud_vendor_read_flush();
#endif
}
// 发送字符到WebUSB和CDC接口
void echo_all(const uint8_t buf[], uint32_t count) {
// 发送到WebUSB
if (web_serial_connected) {
tud_vendor_write(buf, count);
tud_vendor_write_flush();
}
// 发送到CDC
if (tud_cdc_connected()) {
for (uint32_t i = 0; i < count; i++) {
tud_cdc_write_char(buf[i]);
if (buf[i] == '\r') {
tud_cdc_write_char('\n');
}
}
tud_cdc_write_flush();
}
}
构建与烧录
以ESP32-S3开发板为例:
cd examples/device/webusb_serial
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DBOARD=esp32s3_devkitm_1 ..
make -j4
make flash
对于不同的开发板,只需更改BOARD参数,例如:
- Raspberry Pi Pico:
-DBOARD=rpi_pico - STM32F4 Discovery:
-DBOARD=stm32f4disco - Nordic nRF52840 DK:
-DBOARD=nrf52840dk
Web应用开发
WebUSB API简介
WebUSB API提供了与USB设备通信的JavaScript接口。核心API包括:
navigator.usb.requestDevice(): 请求用户选择USB设备usbDevice.open(): 打开设备连接usbDevice.claimInterface(): 声明要使用的接口usbDevice.transferIn()/usbDevice.transferOut(): 数据传输- 事件监听: 处理设备连接、断开和数据接收
网页界面设计
示例网页界面包含以下主要部分:
- 连接控制区:连接/断开按钮和状态显示
- 命令输入区:用于发送指令到设备
- 数据接收区:显示设备返回的数据
- 历史记录区:保存命令和响应历史
JavaScript核心实现
设备连接
// serial.js
async function connectWebUSBDevice() {
try {
const device = await navigator.usb.requestDevice({
filters: [
{ vendorId: 0xcafe }, // TinyUSB的默认厂商ID
{ classCode: 0xff } // 自定义设备类
]
});
await openDevice(device);
return device;
} catch (error) {
console.error('连接设备失败:', error);
setStatus(`连接失败: ${error.message}`, 'error');
return null;
}
}
async function openDevice(device) {
try {
await device.open();
if (device.configuration === null) {
await device.selectConfiguration(1);
}
await device.claimInterface(0);
// 发送CDC_SET_CONTROL_LINE_STATE命令,通知设备已连接
await device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x00
});
setStatus(`已连接到 ${device.manufacturerName} ${device.productName}`, 'success');
startListeningForData(device);
return device;
} catch (error) {
console.error('打开设备失败:', error);
throw error;
}
}
数据传输
// serial.js
async function startListeningForData(device) {
try {
while (device.opened) {
const result = await device.transferIn(5, 64); // 端点5,最大64字节
const decoder = new TextDecoder();
const data = decoder.decode(result.data);
if (data.length > 0) {
receiveData(data);
}
}
} catch (error) {
if (device.opened) {
console.error('数据接收错误:', error);
setStatus(`通信错误: ${error.message}`, 'error');
disconnectDevice(device);
}
}
}
async function sendData(device, data) {
if (!device || !device.opened) {
setStatus('未连接设备', 'error');
return false;
}
try {
const encoder = new TextEncoder();
const encodedData = encoder.encode(data);
await device.transferOut(4, encodedData); // 端点4发送数据
return true;
} catch (error) {
console.error('发送数据失败:', error);
setStatus(`发送失败: ${error.message}`, 'error');
return false;
}
}
用户交互处理
// application.js
document.addEventListener('DOMContentLoaded', () => {
// 连接按钮
document.getElementById('connect_webusb_serial_btn').addEventListener('click', async () => {
if (currentDevice) {
setStatus('已连接', 'success');
return;
}
currentDevice = await connectWebUSBDevice();
if (currentDevice) {
updateUIState(true);
}
});
// 断开按钮
document.getElementById('disconnect_btn').addEventListener('click', () => {
if (currentDevice) {
disconnectDevice(currentDevice);
currentDevice = null;
updateUIState(false);
}
});
// 发送命令
document.getElementById('command_line_input').addEventListener('keydown', async (e) => {
if (e.key === 'Enter' && currentDevice) {
const command = e.target.value;
addToCommandHistory(command);
await sendData(currentDevice, command + getNewlineCharacter());
e.target.value = '';
}
});
// 其他事件监听器...
});
测试与调试
设备枚举测试
- 将开发板连接到电脑
- 编译并烧录固件
- 打开Chrome浏览器,访问
chrome://device-log/查看设备枚举情况 - 如果设备枚举成功,应该会看到类似以下的日志:
[设备] USB设备已连接: TinyUSB WebUSB Serial (VID: 0xcafe, PID: 0x4000)
[WebUSB] 设备支持WebUSB,URL: example.tinyusb.org/webusb-serial/index.html
数据通信测试
- 打开示例网页(可通过访问
website/index.html或设备提供的URL) - 点击"Connect"按钮,选择TinyUSB设备
- 在命令输入框中输入文本,按Enter发送
- 确认设备返回相同的文本(回显功能)
跨平台兼容性测试
在不同操作系统和浏览器上测试:
Windows
- 需要Zadig工具安装WinUSB驱动(仅Windows 7及更早版本)
- Windows 8及以上会自动识别
macOS
- 无需额外驱动
- Safari不支持WebUSB,需使用Chrome或Edge
Linux
- 需要设置udev规则:
sudo cp ../../99-tinyusb.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
常见问题解决
设备无法被浏览器识别
- 检查USB描述符是否正确配置
- 确认设备固件已正确烧录
- 尝试更换USB线缆或端口
- 检查操作系统是否需要特定驱动
数据传输不稳定
- 增加数据传输缓冲区大小
- 实现数据校验机制
- 优化USB中断处理
- 减少不必要的数据传输
浏览器兼容性问题
- 使用特性检测:
if ('usb' in navigator) {
// WebUSB可用
} else {
alert('您的浏览器不支持WebUSB,请使用Chrome或Edge');
}
- 提供降级方案(如CDC串口)
高级应用与优化
批量数据传输
对于需要传输大量数据的应用,可以使用批量传输端点:
// 批量数据发送示例
bool send_bulk_data(uint8_t const* data, uint32_t len) {
uint32_t sent = 0;
while (sent < len) {
uint32_t remaining = len - sent;
uint32_t to_send = remaining > BULK_EP_MAX_SIZE ? BULK_EP_MAX_SIZE : remaining;
// 等待端点准备就绪
while (!tud_vendor_n_available(0)) {
tud_task();
}
// 发送数据块
tud_vendor_n_write(0, data + sent, to_send);
sent += to_send;
}
tud_vendor_n_write_flush(0);
return true;
}
多端点应用
WebUSB设备可以使用多个端点实现不同功能:
低功耗优化
- USB挂起处理:
void tud_suspend_cb(bool remote_wakeup_en) {
(void)remote_wakeup_en;
// 进入低功耗模式
board_enter_low_power();
blink_interval_ms = BLINK_SUSPENDED;
}
void tud_resume_cb(void) {
// 退出低功耗模式
board_exit_low_power();
blink_interval_ms = tud_mounted() ? BLINK_MOUNTED : BLINK_NOT_MOUNTED;
}
- 数据传输优化:
- 使用适当的数据包大小
- 实现流控制机制
- 避免频繁的小数据传输
安全性考虑
- 设备认证:
async function authenticateDevice(device) {
// 请求设备证明其身份
const challenge = crypto.getRandomValues(new Uint8Array(16));
const response = await device.transferOut(1, challenge);
// 验证响应...
return isValid;
}
- 数据加密:
- 使用AES加密敏感数据
- 实现简单的校验和或CRC
- 考虑使用Web Cryptography API
实际应用案例
远程设备监控系统
WebUSB可用于构建简单的远程设备监控系统:
关键实现步骤:
- 传感器数据采集
- 通过WebUSB传输到本地浏览器
- 浏览器通过WebSocket转发到云服务器
- 远程用户通过Web界面访问数据
工业控制界面
WebUSB可用于构建基于浏览器的工业控制界面:
- 实时数据可视化
- 设备状态监控
- 远程控制指令
- 历史数据记录与分析
教育与演示工具
WebUSB非常适合教育场景:
- 无需安装专用软件
- 直观的网页界面
- 跨平台兼容性
- 易于分享和演示
总结与展望
主要知识点回顾
本文介绍了使用TinyUSB实现WebUSB设备的完整流程,包括:
- WebUSB技术原理及优势
- TinyUSB库的使用方法
- 嵌入式设备固件开发
- Web应用界面设计
- 数据通信实现
- 测试与调试技巧
- 高级应用与优化
WebUSB技术前景
WebUSB技术正在快速发展,未来可能的发展方向包括:
- 更广泛的浏览器支持
- 标准化的设备类定义
- 更好的低功耗支持
- 与WebHID、WebSerial等API的集成
- 安全机制增强
后续学习建议
- 深入学习USB协议规范
- 探索TinyUSB的其他功能(如USB主机模式)
- 研究WebHID、WebSerial等相关Web API
- 开发更复杂的WebUSB应用
- 参与TinyUSB开源社区贡献
附录
常见问题解答
Q: WebUSB在哪些浏览器中可用?
A: 目前主要支持Chrome、Edge和Opera浏览器。Firefox尚未实现WebUSB支持。
Q: Windows系统需要特殊驱动吗?
A: Windows 8及以上系统通常不需要额外驱动,但Windows 7需要使用Zadig工具安装WinUSB驱动。
Q: 如何修改设备的VID和PID?
A: 可以在usb_descriptors.c文件中修改USB_VID和USB_PID宏定义。
参考资料
相关资源
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



