从0到1掌握WebUSB:TinyUSB实现浏览器与嵌入式设备通信

从0到1掌握WebUSB:TinyUSB实现浏览器与嵌入式设备通信

【免费下载链接】tinyusb An open source cross-platform USB stack for embedded system 【免费下载链接】tinyusb 项目地址: https://gitcode.com/gh_mirrors/ti/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的工作流程可以概括为以下几个步骤:

mermaid

TinyUSB库简介

TinyUSB概述

TinyUSB是一个开源的跨平台USB协议栈,支持USB设备和主机功能。它设计小巧、高效,适用于资源受限的嵌入式系统。TinyUSB支持多种USB类,包括CDC(虚拟串口)、HID(人机接口设备)、MSC(大容量存储)等,并且提供了对WebUSB的原生支持。

TinyUSB核心架构

TinyUSB的架构采用分层设计,主要包含以下几个部分:

mermaid

TinyUSB对WebUSB的支持

TinyUSB通过以下组件支持WebUSB功能:

  1. WebUSB描述符:提供设备信息和URL描述符
  2. 自定义Vendor类实现:处理WebUSB特定的控制请求
  3. 数据传输API:简化与浏览器之间的数据交换
  4. 跨平台兼容性处理:解决不同操作系统和浏览器的差异

开发环境搭建

硬件准备

本教程使用以下硬件:

  • 支持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
  1. 安装 Chocolatey
  2. 打开管理员命令提示符:
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 = '';
    }
  });
  
  // 其他事件监听器...
});

测试与调试

设备枚举测试

  1. 将开发板连接到电脑
  2. 编译并烧录固件
  3. 打开Chrome浏览器,访问chrome://device-log/查看设备枚举情况
  4. 如果设备枚举成功,应该会看到类似以下的日志:
[设备] USB设备已连接: TinyUSB WebUSB Serial (VID: 0xcafe, PID: 0x4000)
[WebUSB] 设备支持WebUSB,URL: example.tinyusb.org/webusb-serial/index.html

数据通信测试

  1. 打开示例网页(可通过访问website/index.html或设备提供的URL)
  2. 点击"Connect"按钮,选择TinyUSB设备
  3. 在命令输入框中输入文本,按Enter发送
  4. 确认设备返回相同的文本(回显功能)

跨平台兼容性测试

在不同操作系统和浏览器上测试:

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

常见问题解决

设备无法被浏览器识别
  1. 检查USB描述符是否正确配置
  2. 确认设备固件已正确烧录
  3. 尝试更换USB线缆或端口
  4. 检查操作系统是否需要特定驱动
数据传输不稳定
  1. 增加数据传输缓冲区大小
  2. 实现数据校验机制
  3. 优化USB中断处理
  4. 减少不必要的数据传输
浏览器兼容性问题
  1. 使用特性检测:
if ('usb' in navigator) {
  // WebUSB可用
} else {
  alert('您的浏览器不支持WebUSB,请使用Chrome或Edge');
}
  1. 提供降级方案(如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设备可以使用多个端点实现不同功能:

mermaid

低功耗优化

  1. 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;
}
  1. 数据传输优化:
    • 使用适当的数据包大小
    • 实现流控制机制
    • 避免频繁的小数据传输

安全性考虑

  1. 设备认证:
async function authenticateDevice(device) {
  // 请求设备证明其身份
  const challenge = crypto.getRandomValues(new Uint8Array(16));
  const response = await device.transferOut(1, challenge);
  
  // 验证响应...
  return isValid;
}
  1. 数据加密:
    • 使用AES加密敏感数据
    • 实现简单的校验和或CRC
    • 考虑使用Web Cryptography API

实际应用案例

远程设备监控系统

WebUSB可用于构建简单的远程设备监控系统:

mermaid

关键实现步骤:

  1. 传感器数据采集
  2. 通过WebUSB传输到本地浏览器
  3. 浏览器通过WebSocket转发到云服务器
  4. 远程用户通过Web界面访问数据

工业控制界面

WebUSB可用于构建基于浏览器的工业控制界面:

  1. 实时数据可视化
  2. 设备状态监控
  3. 远程控制指令
  4. 历史数据记录与分析

教育与演示工具

WebUSB非常适合教育场景:

  • 无需安装专用软件
  • 直观的网页界面
  • 跨平台兼容性
  • 易于分享和演示

总结与展望

主要知识点回顾

本文介绍了使用TinyUSB实现WebUSB设备的完整流程,包括:

  • WebUSB技术原理及优势
  • TinyUSB库的使用方法
  • 嵌入式设备固件开发
  • Web应用界面设计
  • 数据通信实现
  • 测试与调试技巧
  • 高级应用与优化

WebUSB技术前景

WebUSB技术正在快速发展,未来可能的发展方向包括:

  • 更广泛的浏览器支持
  • 标准化的设备类定义
  • 更好的低功耗支持
  • 与WebHID、WebSerial等API的集成
  • 安全机制增强

后续学习建议

  1. 深入学习USB协议规范
  2. 探索TinyUSB的其他功能(如USB主机模式)
  3. 研究WebHID、WebSerial等相关Web API
  4. 开发更复杂的WebUSB应用
  5. 参与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_VIDUSB_PID宏定义。

参考资料

  1. WebUSB规范
  2. TinyUSB官方文档
  3. WebUSB API MDN文档
  4. USB设备类规范

相关资源

【免费下载链接】tinyusb An open source cross-platform USB stack for embedded system 【免费下载链接】tinyusb 项目地址: https://gitcode.com/gh_mirrors/ti/tinyusb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值