当前位置: 首页 > news >正文

USB枚举介绍 以及linux USBFFS应用demo

USB 枚举(Enumeration) 是 USB 设备连接到主机(PC、嵌入式系统等)后,主机识别并初始化该设备的一系列过程。枚举过程发生在设备插入后,主机与设备通过 USB 总线通信,完成以下任务:


🧩 一、USB 枚举的作用

USB 枚举的主要作用是:

  • 获取设备的基本信息(Vendor ID、Product ID 等)
  • 识别设备类型(HID、Mass Storage、Video、Vendor 等)
  • 为设备分配唯一地址(Device Address)
  • 选择配置(Configuration)
  • 加载合适的驱动(在主机侧)

🛠️ 二、枚举过程详细步骤(以主机为主导)

下图是 USB 枚举过程的简要流程:

主机检测到插入设备↓
复位(Reset)+ 供电(Vbus)↓
设备使用默认地址 0 响应↓
主机请求设备描述符(Device Descriptor)↓
主机分配新的地址(Set Address)↓
主机重新请求描述符(包括配置、接口、端点等)↓
主机选择配置(Set Configuration)↓
枚举完成,设备准备好使用

🧾 三、各阶段细节说明

阶段主机操作设备响应描述
1. 插入检测5V 上电 + D+/D- 电平变化上拉电阻指示速度(FS/HS)主机检测设备连接
2. 总线复位拉低 D+/D- 一段时间复位内部状态进入默认地址 0
3. 请求设备描述符GET_DESCRIPTOR(Device)返回 18 字节基本信息包含 VID/PID、版本等
4. 设置地址SET_ADDRESS(N)改变地址为 N后续通信使用该地址
5. 请求其他描述符GET_DESCRIPTOR(Configuration/Interface/Endpoint/String)返回各类描述符完整描述设备能力
6. 设置配置SET_CONFIGURATION(N)进入工作状态激活对应接口和端点
✅ 7. 枚举完成驱动加载,数据传输开始-主机可以正常使用设备

📦 四、USB 枚举中涉及的描述符

  1. Device Descriptor

    • USB 版本、厂商 ID、产品 ID、设备类等
  2. Configuration Descriptor

    • 配置数量、电源需求、接口数量等
  3. Interface Descriptor

    • 每个功能的接口定义,如摄像头、音频、HID
  4. Endpoint Descriptor

    • 每个接口上的 IN/OUT 端点定义,如 Bulk/Interrupt/Isochronous
  5. String Descriptor(可选)

    • 语言 ID、厂商名、产品名、序列号等字符串

🔌 五、主机与设备的通信方式(控制传输)

  • 全部通过 Endpoint 0(控制端点)

  • 使用 USB 标准请求,如:

    • GET_DESCRIPTOR
    • SET_ADDRESS
    • SET_CONFIGURATION

🧪 六、如何调试枚举过程?

在 Linux 上你可以使用以下工具:

  • dmesg 查看插入设备日志
  • lsusb -v 查看详细描述符信息
  • usbmon + Wireshark 捕获枚举过程的 USB 报文
  • usb-devices 查看系统识别信息

🧠 七、枚举失败的常见原因

错误表现可能原因
设备不识别描述符错误 / 未回应 / 电源不足
报错代码 -71、-32控制传输异常
未分配驱动VID/PID 未匹配已知驱动
Set Address 失败固件中未正确处理请求

📘 八、实际开发中 USB 设备需要实现什么?

在设备固件或 FunctionFS 驱动中要:

  • 正确响应标准请求(setup stage)
  • 提供合法的描述符
  • 正确处理 SET_ADDRESS、SET_CONFIGURATION
  • 准备好工作状态的端点

如果你做的是嵌入式 USB 设备(比如 RK3588 上实现 FunctionFS),你就需要自己实现整个设备端的控制请求处理逻辑、描述符发送、状态机响应等。需要我详细讲讲这一部分吗?(比如 FunctionFS 枚举流程)




太好了,那我来系统地为你讲解 FunctionFS(Function Filesystem)在 USB 设备端(如 RK3588S)中如何参与 USB 枚举过程,这对你正在做的深度相机 + DTOF 雷达 USB 传输系统非常关键。


🧩 FunctionFS 下的 USB 枚举机制概览

FunctionFS 是 Linux 提供的一个 USB Function(设备端)框架,通过将 USB Function 逻辑抽象成文件系统接口,让用户态程序可以参与设备端数据收发。

📌 它主要包括两部分:

  1. 内核空间:FunctionFS 驱动

    • 负责注册为 UDC(USB Device Controller)设备
    • 处理主机发来的控制请求(setup 包)
    • 读取用户提供的描述符,并向主机回应
  2. 用户空间:使用 ep0 和 data 端点的用户态程序

    • 向 ep0 响应控制请求
    • 通过 /dev/ 下面的 endpoint 文件与主机通信

🧱 枚举流程中的 FunctionFS 工作细节

假设你通过 gadget configfs 创建了一个 FunctionFS function,比如:

mkdir /sys/kernel/config/usb_gadget/mygadget/functions/ffs.myfunc

用户空间的程序通常以如下顺序工作:


1️⃣ 准备工作(挂载 FunctionFS)

mount -t functionfs myfunc /dev/ffs-myfunc

此时 /dev/ffs-myfunc 下会出现:

  • ep0(控制端点)
  • ep1, ep2, …(数据传输端点,取决于你定义的端点数量)

2️⃣ 提供描述符(必须在 UDC enable 前完成)

用户态程序需先打开 ep0,然后将以下信息写入:

✅ 第一阶段:向 ep0 写入 descriptor blob,包括:
  • Device Descriptor
  • Configuration Descriptor
  • Interface 和 Endpoint Descriptors
  • String Descriptors(按语言编号组织)

👉 这些描述符定义了设备的能力,主机会在枚举阶段通过 GET_DESCRIPTOR 获取。

你写入的这些内容会被内核 FunctionFS 驱动缓存,当主机通过控制传输请求时,由内核返回。


3️⃣ 枚举阶段开始(由主机发起)

主机插入 USB 设备后,进行如下操作:

Host 请求FunctionFS 响应
GET_DESCRIPTOR (Device)内核根据你提供的 device desc 返回
SET_ADDRESS内核自动处理
GET_DESCRIPTOR (Configuration)返回 config + interface + endpoint
SET_CONFIGURATION内核记录配置,创建端点文件
其他标准请求内核处理或传给用户态程序处理

4️⃣ 用户态程序等待控制事件(ep0)

打开 ep0 后,你会收到各种 control event,如:

  • FUNCTIONFS_BIND
  • FUNCTIONFS_ENABLE
  • FUNCTIONFS_SETUP(标准或自定义控制请求)
  • FUNCTIONFS_DISABLE

你需要处理这些事件,并根据需要回应控制传输。


📦 示例:用户态 FunctionFS 初始化流程(伪代码)

int ep0_fd = open("/dev/ffs-myfunc/ep0", O_RDWR);// 第一步:写入 descriptors
write(ep0_fd, descriptors_blob, sizeof(descriptors_blob));// 第二步:写入 string descriptors
write(ep0_fd, strings_blob, sizeof(strings_blob));// 第三步:监听 ep0 上的事件
while (1) {struct usb_functionfs_event evt;read(ep0_fd, &evt, sizeof(evt));switch (evt.type) {case FUNCTIONFS_BIND:// 初始化 OKbreak;case FUNCTIONFS_ENABLE:// 主机配置完成,设备 readybreak;case FUNCTIONFS_SETUP:// 控制请求,如 GET_STATUS, 自定义请求等handle_control_request(evt.u.setup);break;}
}

🧪 如何验证 FunctionFS 枚举过程是否成功?

  1. 主机端是否能 lsusb 看到设备
  2. dmesg 是否有 FunctionFS 驱动被加载的日志
  3. 是否能进入 FUNCTIONFS_ENABLE 状态
  4. Wireshark 抓包看控制传输是否完整(SET_ADDRESS, GET_DESCRIPTOR, SET_CONFIGURATION)
  5. 是否能访问 bulk/intr/iso 数据端点

🧠 实际项目中的建议(结合你的 RK3588S 方案)

项目目标枚举阶段工作建议
深度图、点云等大数据传输使用 Bulk 端点,在 descriptors 中描述
多种数据类型(IR图、DTOF、IMU)使用 多个接口 + 多个端点 实现复合设备
上位机自定义协议可通过 FunctionFS 处理 自定义控制请求(如 bRequest=0x40
高速 USB 传输descriptor 中声明 USB 3.0 (bcdUSB=0x0300),并匹配支持 HS/SS 的 UDC

如果你已经有了 FunctionFS 工程框架,我可以帮你检查描述符设计是否规范,或给出具体描述符结构模板。是否需要我提供 FunctionFS 完整的 descriptor 定义和 ep0 控制事件处理 demo?




好的,下面我为你提供一个完整的 FunctionFS 用户空间工程框架(C/C++ 版),用于 RK3588S 设备端通过 USB 向 PC 提供一个自定义接口(可以扩展为图像、雷达、点云等高速传输通道)。这个框架重点包括:

  • FunctionFS 描述符定义(Device、Config、Interface、Endpoint、String)
  • ep0 控制端点事件处理
  • 多端点打开与传输框架
  • 初始化流程规范(兼容 USB 枚举)
  • 可直接用于深度相机 + DTOF USB 项目

✅ 1. 描述符定义(以 bulk-in/out 接口为例)

// descriptors.c#include <linux/usb/functionfs.h>
#include <stdint.h>const struct {struct usb_functionfs_descs_head_v2 header;__le32 fs_count;struct {struct usb_interface_descriptor intf;struct usb_endpoint_descriptor_no_audio ep_in;struct usb_endpoint_descriptor_no_audio ep_out;} __attribute__((packed)) fs_descs;
} __attribute__((packed)) descriptors = {.header = {.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),.length = htole32(sizeof(descriptors)),.flags = FUNCTIONFS_HAS_FS_DESC,},.fs_count = htole32(3),.fs_descs = {.intf = {.bLength = sizeof(struct usb_interface_descriptor),.bDescriptorType = USB_DT_INTERFACE,.bInterfaceNumber = 0,.bAlternateSetting = 0,.bNumEndpoints = 2,.bInterfaceClass = 0xff,  // Vendor.bInterfaceSubClass = 0,.bInterfaceProtocol = 0,.iInterface = 1,},.ep_in = {.bLength = sizeof(struct usb_endpoint_descriptor),.bDescriptorType = USB_DT_ENDPOINT,.bEndpointAddress = 0x81, // EP1 IN.bmAttributes = USB_ENDPOINT_XFER_BULK,.wMaxPacketSize = htole16(512),.bInterval = 0,},.ep_out = {.bLength = sizeof(struct usb_endpoint_descriptor),.bDescriptorType = USB_DT_ENDPOINT,.bEndpointAddress = 0x01, // EP1 OUT.bmAttributes = USB_ENDPOINT_XFER_BULK,.wMaxPacketSize = htole16(512),.bInterval = 0,}}
};

📚 2. String 描述符定义(英文)

// strings.c#include <linux/usb/functionfs.h>const struct {struct usb_functionfs_strings_head header;struct {__le16 code;const char str1[16];} __attribute__((packed)) lang0;
} __attribute__((packed)) strings = {.header = {.magic = htole32(FUNCTIONFS_STRINGS_MAGIC),.length = htole32(sizeof(strings)),.str_count = htole32(1),.lang_count = htole32(1),},.lang0 = {.code = htole16(0x0409), // English (US).str1 = "My USB Device",}
};

⚙️ 3. 主程序框架:处理 ep0 + 打开端点

// main.cpp#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <linux/usb/functionfs.h>extern const struct descriptors descriptors;
extern const struct strings strings;int ep0_fd = -1;
int ep_in_fd = -1;
int ep_out_fd = -1;void handle_control_event(struct usb_functionfs_event *evt) {switch (evt->type) {case FUNCTIONFS_BIND:printf("F: BIND received\n");break;case FUNCTIONFS_ENABLE:printf("F: ENABLE received\n");break;case FUNCTIONFS_DISABLE:printf("F: DISABLE received\n");break;case FUNCTIONFS_SETUP:printf("F: SETUP received. bRequest=0x%x\n", evt->u.setup.bRequest);// 可在这里处理自定义控制请求,如获取固件版本break;default:printf("F: Unknown control event: %d\n", evt->type);}
}int main() {// Step 1: 打开 ep0 并写入 descriptors 和 stringsep0_fd = open("/dev/ffs-myfunc/ep0", O_RDWR);if (ep0_fd < 0) {perror("ep0 open");return 1;}if (write(ep0_fd, &descriptors, sizeof(descriptors)) < 0) {perror("write descriptors");return 1;}if (write(ep0_fd, &strings, sizeof(strings)) < 0) {perror("write strings");return 1;}// Step 2: 等待 ep0 事件struct usb_functionfs_event evt;while (1) {ssize_t r = read(ep0_fd, &evt, sizeof(evt));if (r == sizeof(evt)) {handle_control_event(&evt);}}return 0;
}

📦 4. 运行环境准备(简化版)

modprobe libcompositemkdir /sys/kernel/config/usb_gadget/mygadget
cd /sys/kernel/config/usb_gadget/mygadgetecho 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSBmkdir strings/0x409
echo "0123456789" > strings/0x409/serialnumber
echo "Vic Inc" > strings/0x409/manufacturer
echo "My FFS Device" > strings/0x409/productmkdir configs/c.1
mkdir functions/ffs.myfuncmount -t functionfs myfunc /dev/ffs-myfuncln -s functions/ffs.myfunc configs/c.1/echo <你的 UDC 名> > UDC# 然后运行上面 main 程序(必须在 mount 后、UDC enable 前写入 descriptors)

🧠 后续扩展建议(针对你的项目)

目标建议
多种数据类型通道添加多个 Interface,每个 Interface 一组端点
高带宽图像流使用 USB 3.0 + 更大 maxpacket + 多线程收发
支持自定义控制命令FUNCTIONFS_SETUP 中处理 bRequest=0x400xC0
性能优化使用 AIO + memory map 或多线程 ep read/write

📁 你需要的源码工程(目录结构建议)

my_functionfs_project/
├── descriptors.c
├── strings.c
├── main.cpp
├── Makefile

是否需要我直接为你打包一个完整的 CMake 工程?或者给你生成 .tar.gz.zip 工程包?你可以直接在 RK3588 上编译运行。

http://www.lryc.cn/news/612810.html

相关文章:

  • 抖音、快手、视频号等多平台视频解析下载 + 磁力嗅探下载、视频加工(提取音频 / 压缩等)
  • Go语言Ebiten坦克大战
  • JVM类加载
  • Redis中间件(三):Redis存储原理与数据模型
  • Spring MVC拦截器与过滤器的区别详解
  • Ubuntu24.04的“errors from xkbcomp are not fatal to the X server”终极修复方案
  • Ethereum:如何优雅部署 NPM 包中的第三方智能合约?
  • SpringBoot学习日记 Day5:解锁企业级开发核心技能
  • 90-基于Flask的中国博物馆数据可视化分析系统
  • 8- 知识图谱 — 应用案例怎么 “落地” 才有效?构建流程与行业实践全解析
  • LoRaWAN的网络拓扑
  • Kong vs. NGINX:从反向代理到云原生网关的全景对比
  • PCL提取平面上的圆形凸台特征
  • 阿里系bx_et加密分析
  • 构造函数:C++对象初始化的核心机制
  • 天猫商品评论API技术指南
  • uni-app X能成为下一个Flutter吗?
  • Flutter报错...Unsupported class file major version 65
  • C# 异步编程(async_await特性的结构)
  • PyTorch 核心三件套:Tensor、Module、Autograd
  • `/dev/vdb` 是一个新挂载的 4TB 硬盘,但目前尚未对其进行分区和格式化。
  • vscode 打开设置
  • Flutter 三棵树
  • 【物联网】基于树莓派的物联网开发【25】——树莓派安装Grafana与Influxdb无缝集成
  • CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南
  • 人工智能的20大应用
  • 从Centos 9 Stream 版本切换到 Rocky Linux 9
  • 360纳米AI、实在Agent、CrewAI与AutoGen……浅析多智能体协作系统
  • 构建在 OpenTelemetry eBPF 基础之上:详解 Grafana Beyla 2.5 新特性
  • 【0基础3ds Max】菜单栏介绍