Linux下的USB通信

Linux下的USB通信


什么是USB

Usb全称为通用串行总线,即通用串行总线。Usb是一个标准,用于在计算机和外部设备之间传输数据。

什么是USB设备

USB设备是指通过USB接口连接到计算机的外部设备,如键盘、鼠标、打印机、扫描仪、摄像头、U盘、移动硬盘等。

什么是USB驱动

USB驱动是操作系统中用于管理USB设备的软件,它负责识别、配置和控制USB设备。

USB的标准和速率

1996 年,USB1.0 标准发布。理论速度 1.5Mbit/s。(当时一个 3.5 寸的软盘容量在 140M 左右)
1998 年,USB1.1 标准发布。理论速度 12Mbit/s。
2000年,USB2.0标准发布。理论速度达480Mbit/s。
2008年,USB3.0标准,5Gbit/s
2013 年,USB3.1 标准,10Gbit/s,随后又发布了USB3.2标准,20Gbit/s
2019年,USB4.0 标准,40Gbit/s

USB的HOST和SLAVE

USB 设备分为 HOST(主设备)和 SLAVE(从设备),只有当一台 HOST 与一台 SLAVE 连接时才能实现数据的传输。
USBHOST(USB 主设备):
USB HOST是指具有 USB 主机功能的设备。USB 主设备是控制和管理 USB 总线的设备,USB主设备通常是计算机或其他主机设备,如平板电脑、笔记本台式机等。
USB Slave(USB 从设备):
USB Slave 是指具有 USB 从设备功能的设备。USB 从设备是受 USB 主机控制的设备,USB 从设备依赖于 USB 主机设备以进行数据传输和通信。USB 从设备可以是各种外围设备,如键盘、鼠标、U盘等
USB OTG(USB On-The-Go)(2014年开始普及):
USB OTG是指支持 USB OTG 功能的设备(即插即用),USB OTG 允许设备在主设备和从设备之间进行切换,从而能够直接与其他 USB 设备进行通信,无需传统的 USB 主机设备。如通过 OTG 可以实现将摄像机和打印机直接连接。

USB的OTG模式

USB的OTG结构图

1735897846764

USB的OTG模式

USB3_OTGO_VBUSDET 是 OTG 和 Device(SLAVE) 模式检测脚,高有效。
alt text
OTG 模式可以设置以下三种模式:
OTG 模式:根据 ID 脚状态自动切换是 device 模式或 HOST 模式, ID 高为 device, ID拉低为 HOST,处在 device 模式时,还会判断 VBUSDET 脚是否为高,如果为高,才会拉高 DP,开始枚举
Device 模式:
设置为这个模式时,无需ID 脚,只需判断 VBUSDET 脚是否为高,如果为高,才会拉高 DP,开始枚举
HOST 模式:
设置为这个模式时, ID 和 VBUSDET 状态都无需要关心。 (如果产品只需要 HOST 模式,但是由于仅 USB3 OTGO DP/USB3 OTGO DM是系统固件烧写口, 在调试与生产过程都需要用这个口,烧写和adb 调试时,需要设置成device 模式,因此USB3 OTGO VBUSDET 信号也必须接)。在 uboot,默认为 device 模式,进 uboot,后,可根据实际需求配置这三种模式。

USB的拓扑结构

USB扩展器

1735984152868

但是所有从机都必须经过集线器(hub)才能与主机连接。也就是设备不能直接和主机想连接。
根集线器 : 与主机直接连接的集线器叫做根集线器,用户外接的叫做普通集线器
集线的作用: 集线器是为了扩展更多的接口。但是并不是可以无休止的可以扩展。USB2.0协议中最多扩展七层。每一层所有设备相加不能超过127个(包括集线器)

1735984429351

USB设备如何被识别

在 USB2.0 协议中定义了设备的6中状态,分别是上电状态(Powered)默认状态(Default)地址状态(Address),配置状态(Configured)连接状态(Attached)挂起状态(Suspend))。👇

1735984562643

USB2.0下如何判断设备已经插入

在 hub 一侧,数据线 D+和 D-都有一个阻值在 15k 的下拉电阻 Rpd(Pull-down Resistors)
而在设备端,D+(全速,高速)和 D-(低速)上有一个 1.5k 的上拉电阻 Rpu(Pull-up Resistor )
当设备插入时,设备会向 D+或 D-上拉电阻 Rpu(Pull-up Resistors)供电,当设备上电后,D+或 D-上拉电阻 Rpu(Pull-up Resistors)会向 hub 的下拉电阻 Rpd(Pull-down Resistors)供电,hub 会检测到这个变化,从而判断设备已经插入。

1736125224690

不同低速和全速设备,USB高速设备先被识别为全速设备,然后通过 HOST 去检测 DEVICE是不是高速设备,并且和 HOSTDEVICE 两者之间要互相确认,再切换到高速模式的。在高速模式下是电流传输模式,要把 D+上的上拉电阳断开

USB描述符

USB 描述符主要有设备描述符(Device Descriptor)配置描述符(Configuration Descriptor)接口描述符(lnterface Descriptor)端点描述符(Endpoint Descriptor)

1736126375572

这些描述符共同构建了 USB 设备的特征和功能,通过读取这些描述符,系统可以配置驱动和参数,使得操作系统可以正确识别设备和通信。
设备描述符:包含设备的基本信息,比如设备的厂商ID,产品ID等。设备描述符是设备连接到主机时第一个被请求和返回的信息。他提供了设备的基本特征。
配置描述符:描述设备支持的不同配置。
接口描述符:描述了配置中的一个接口。
端点描述符:描述接口中的一个端点。
端点是数据在设备和主机之间传输的终点。一个具体的端点只能属于四种传输模式中的一种。
注意:
一个USB 设备有1个设备描述符;
一个 USB 设备有1个或多个配置描述符;
一个 USB 配置有1个或多个接口描述符;
一个 USB接口有0个或多个端点描述符(不包括端点 0)

USB的传输模式

USB 的四种传输模式有控制模式,实时模式,中断模式,批量式。

  1. 批量传输:一般用到数据量比较大,对实时性要求不高的场景,比如U盘。
  2. 中断传输:和硬件中断不太一样,这里的中断指的是主机在一定的时间间隔内进行一次传
    输,主要用在少量的,不可以预测的数据,比如鼠标。
  3. 实时传输:一般用到数据量大,对实时性要求高,但是可以允许有少量数据错误的场景,
    比如音视频传输。
  4. 控制传输:主要进行查询,配置和给 USB 设备发送通用的命令,控制传输是双向传输,数
    据量通常较小。

USB数据格式

USB 通信数据格式由域,包,事务,传输组成。多个域组成包,多个包组成事务,多个事务组成传输。所以域是 USB 数据最小的单位
域: 域可分为七个类型,分别是同步域,地址域,端点域,号域,标识域,数据域,校验域。这七种不同类型的域由1个或者多个可以组成包。
包: 包分为令牌包(Token)、数据包(Data)、握手包(Handshake)和特殊包(Special),不同的包的域结构不同。几种包在一起组合又组合成了事务。
事务: 分由令牌包、数据包、握手包组成。其中数据包和握手包是可选的。事务是最基本的单位,所以对于我们开发人员来说关注事务和传输就可以了。

1736131920244

USB枚举

  1. 主机发送 获取设备描述符的请求,设备返回设备描述符。(主机使用默认的地址0和端点0)
  2. 主机给设备分配一个唯一的地址
  3. 主机以新的地址再次获取设备描述符,再以新的地址获取配置描述符
  4. 根据获得的描述符信息,主机给设备分配配置,并使设备进入配置状态

USB的应用开发

USB的libusb库

libusb,是一个使用C语言编写的开源库,通过这个库可以方便的编写应用程序操作 usb,设备无需了解底层的 USB 协议。
特点: 可移植性好:跨平台 APl,在 Linux、macOs、Windows 等均可使用。
简单易用: libusb 提供了一套简单的 AP1,支持所有版本的 USB 协议,从 USB1.0 到 USB3.1,并且 API接口保持不变。方便开发。

交叉编译移植

  1. 下载 libusb 源码
wget https://github.com/libusb/libusb/releases/download/v1.0.27/libusb-1.0.27.tar.bz2
tar -vxf libusb-1.0.27.tar.bz2
  1. 交叉编译
 ./configure --host=arm-rockchip830-linux-uclibcgnueabihf --prefix=$(pwd)/output CC=arm-rockchip830-linux-uclibcgnueabihf-gcc CXX=arm-rockchip830-linux-uclibcgnueabihf-g++ --disable-udev

 make 

 make install

libUSB的应用

libusb 实现 lsusb 命令

列出当前系统上所有 usb 设备的信息。

#include <stdio.h>
#include "libusb.h"

int main(int argc, char *argv[]){
   

    int ret = 0 ;
    ret = libusb_init(NULL); // Initialize the libusb library
    if(ret < 0){
   
        fprintf(stderr, "Failed to initialize libusb\n");
        return ret;
    }
    
    libusb_device **devs = NULL;
    ssize_t cnt = libusb_get_device_list(NULL, &devs); // Get the list of devices , return the number of devices
    if(cnt < 0){
   
        fprintf(stderr, "Failed to get device list\n");
        libusb_exit(NULL);
        return (int)cnt;
    }

    printf("Found %zd devices\n", cnt);

    for(int i = 0; i < cnt; i++){
   
        libusb_device *dev = devs[i];
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc); // Get the device descriptor for the every dev
        if(r < 0){
   
            fprintf(stderr, "Failed to get device descriptor\n");
            return r;
        }
        printf("Device %d: Vendor ID: %04x, Product ID: %04x\n", i, desc.idVendor, desc.idProduct);
    }

    libusb_free_device_list(devs, 1); // Free the device list
    libusb_exit(NULL); // Exit the libusb library
    return 0;
}

libusb 实现键盘接收的应用程序

同步模式

一个USB设备有一个设备描述符,一个或多个配置描述符,每个配置描述符包含一个或多个接口描述符,每个接口描述符包含一个或多个端点描述符。

1736149838440

#include <stdio.h>
#include "libusb.h"

int main(int argc, char *argv[]){
   

    int ret = 0 ;
    int exit_flag = 0;
    int keyboard_endpoint = 0;
    unsigned char buffer[8];
    int actual_length = 0;
    libusb_device *dev = NULL;
    const struct libusb_interface_descriptor *interface_desc = NULL; // 接口描述符
    const struct libusb_endpoint_descriptor *endpoint_desc = NULL; // 端点描述符
    struct libusb_device_handle *dev_handle = NULL; // 设备句柄
    ret = libusb_init(NULL); // Initialize the libusb library
    if(ret < 0){
   
        fprintf(stderr, "Failed to initialize libusb\n");
        return ret;
    }
    
    libusb_device **devs = NULL;
    ssize_t cnt = libusb_get_device_list(NULL, &devs); // Get the list of devices , return the number of devices
    if(cnt < 0){
   
        fprintf(stderr, "Failed to get device list\n");
        libusb_exit(NULL);
        return (int)cnt;
    }

    printf("Found %zd devices\n", cnt);
    struct libusb_config_descriptor *config_desc = NULL;
    for(int i = 0; i < cnt; i++){
   
        dev = devs[i];
        // usb device 的配置描述符有多个,0 是第一个配置描述符 , 键盘设备比较单一,只有一个配置描述符
        ret = libusb_get_config_descriptor(devs[i], 0, &config_desc);// 0 is the config index ,config_desc is the config descriptor
        if(ret < 0){
    // Get the config descriptor for the every dev
            fprintf(stderr, "Failed to get config descriptor\n");
            return ret;
        }
        // 配置描述符中包含多个接口描述符,遍历配置描述符中的接口描述符
        for(int k = 0; k < config_desc->bNumInterfaces; k++){
   
            interface_desc = &config_desc->interface[k].altsetting[0];
            printf("Interface %d: Class: %02x, Subclass: %02x, Protocol: %02x\n", k, interface_desc->bInterfaceClass, interface_desc->bInterfaceSubClass, interface_desc->bInterfaceProtocol);
            // 判断设备是否是人机交互设备 , 键盘设备是 HID 设备 ,bInterfaceProtocol 来判断是键盘设备(1),还是鼠标设备(2)
            if(interface_desc->bInterfaceClass == LIBUSB_CLASS_HID || interface_desc->bInterfaceProtocol == 1){
   
                for(int j = 0; j < interface_desc->bNumEndpoints; j++){
   
                    endpoint_desc = &interface_desc->endpoint[j];
                    printf("Endpoint %d: Address: %02x, Type: %02x, Transfer Type: %02x, Max Packet Size: %02x\n", j, endpoint_desc->bEndpointAddress, endpoint_desc->bmAttributes, endpoint_desc->bDescriptorType, endpoint_desc->wMaxPacketSize);
                    // 判断端点是否是中断传输,或是是输入端点
                    if((interface_desc->endpoint[j].bmAttributes & 3 ) == LIBUSB_TRANSFER_TYPE_INTERRUPT || (interface_desc->endpoint[j].bmAttributes & 0x80 ) == LIBUSB_ENDPOINT_IN){
   
                        //找到键盘设备的端点描述符后将其保存,用于后续传输
                        keyboard_endpoint = interface_desc->endpoint[j].bEndpointAddress;
                        exit_flag = 1;
                        break;
                    }
                }
            }
            if(exit_flag == 1){
   
                break;
            }
        }
        if(exit_flag == 1){
   
            break;
        }
    }
    printf("keyboard_endpoint: %02x\n", keyboard_endpoint);
    // 打开设备
    ret = libusb_open(dev, &dev_handle);
    if(ret < 0){
   
        fprintf(stderr, "Failed to open device\n");
        return ret;
    }
    // 设置自动分离内核驱动,剥离内核驱动对改设备的控制,否则在传输数据时会报错
    ret = libusb_set_auto_detach_kernel_driver(dev_handle, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想和我重名?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

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

抵扣说明:

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

余额充值