参考资料:

《圈圈教你玩USB》 《STM32 USB-FS-Device development kit》 沁雪微电子-USB开发视频教程 零声教育-Linux内核-USB系统架构 正点原子实验例程

源于我的有道云笔记,图片我懒得上传了:

文档:USB驱动开发.note
链接:http://note.youdao.com/noteshare?id=c0a1bdf8d93e876af261194547c92caf&sub=87C2944A1AFC402985AD9F7AD0F1E508

汇报思路:

Linux的驱动层层封装我们不能直观的理解USB的工作流程和重要的协议,设备驱动的代码虽然很简单,但是不了解USB的工作流程根本看不懂代码。所以打算:

  • 主机端:以STM官方给的USB驱动为模板理解USB设备驱动的一些重要的概念,再此基础上理解和写LinuxUSB设备的驱动。
  • 从机端:STM32对USB设备也进行了封装,为了深入理解USB设备端驱动,我找到了一个51单片机的USB例程(没有进行封装,纯净的固件程序),以此为模板理解USB设备端驱动的一些重要的概念,再此基础上理解和写USB设备端固件程序。

实验效果

  • stm32(从机端):模拟USB鼠标,在连接上主机后,隔一段时间上报信息(位移、点击、滑轮)
  • imx6ull(linux系统-主机端):在连接上鼠标后,能获取鼠标的信息(位移、点击、滑轮)

重点:

  • USB通信过程
  • Linux-USB驱动架构

一、USB简介

1、串行通信协议介绍

2、USB发展史

3、USB设备分类

二、USB的通信

1、Linux的USB主机-设备通信驱动框架

2、USB设备端的通信过程

3、USB设备的描述符

4、传输的构成:USB的传输协议(非重点)

三、USB通信分析(主从设备对比)

1、stm32的USB详细架构(主机)

2、LinuxUSB鼠标设备驱动

3、CH552设备端驱动

4、STM32设备端驱动

5、实验效果展示

附录:

一、USB通信抓包解析

二、INPUT设备驱动开发流程

一、USB简介

一个完整的硬件产品是由多种模块组合实现产品功能的,微控制器 MCU 充当大脑,外围的存储单元、显示单元、发声单元、传感器单元、运动单元结合起来,就离不开相互之间的数据通信,衍生出了繁多的协议,这些协议通常可以分为并行通信协议和串行通信协议。

  • 并行通信:在同一时刻发送多位数据(可以是多根线)。优点是发送速度快;缺点是传输距离短、资源占用多。
  • 串行通信:用一根线 在不同的时刻发送8位数据。优点是传输距离远 占用资源少;缺点是发送速度慢。

在同步通讯系统中,两个设备通讯则需要同步信号,同步信号分为时钟同步信号和自同步信号两种,时钟同步方式在通讯链路上具有时钟信号(IIC、SPI),自同步方式在通讯链路中没有同步信号(PCIE、USB),自同步方式常常适用于高速通讯系统中。USB使用NRZI编码的方式同步信号。

1、串行通信协议介绍

SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线。

I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

USB通用串行总线(英语:Universal Serial Bus,缩写:USB)是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,并扩展至摄影器材、数字电视(机顶盒)、游戏机等其它相关领域。

2、USB发展史

  • USB1.0-1.1
    • USB 1.0是在1996年出现的,速度只有1.5Mb/s,1998年升级为USB 1.1,其速度也提升至12Mb/s,目前我们在部分旧设备上还能看到这种标准的接口。USB1.1是较为普遍的USB规范,其高速方式的传输速率为12Mbps,低速方式的传输速率为1.5Mbps,b/s 一般表示位传输速度,bps 表示位传输速率,数值上相等。
  • USB2.0
    • USB2.0规范是由USB1.1规范演变而来的,传输速率达到了480Mbps(60MB/s),足以满足大多数外设的速率要求。USB 2.0中的“增强主机控制器接口”定义了一个与USB1.1相兼容的架构,可以用USB2.0的驱动程序驱动USB1.1设备。也就是说,所有支持USB1.1的设备都可以直接在USB2.0的接口上使用而不必担心兼容性问题,而且USB 线、插头等等附件可以直接使用。
  • USB3.0
    • 由Intel、微软、惠普、德州仪器、NEC、ST-NXP等业界巨头组成的USB3.0 Promoter Group,该组织制定的新一代USB 3.0的标准
    • USB3.0的理论速度为5.0Gb/s,其实只能达到理论值的5成,那也是接近于USB2.0的10倍了。USB3.0的物理层采用8b/10b编码方式,这样算下来的理论速度达到4Gb/s。USB3.0可广泛用于PC外围设备和消费电子产品
    • 除此之外USB3.0的规范也制定了之后被广泛使用的接口类型:Type-C。
  • USB3.1
  • USB3.2
  • USB4.0

3、USB设备分类

这里列举从USB设备接口描述符里面获得的部分分类

  • HID人机接口设备
    • 鼠标、键盘、游戏杆等
  • Mass Storage大存储设备
    • U盘,移动硬盘,SD等
  • audio
    • 音频设备
  • image
    • 扫描仪,摄像头等

二、USB的通信

1、Linux的USB主机-设备通信驱动框架

主机端(其他系统也是这种框架):

  • USB主机控制器:
    • 解析和维护urb(usb request block)
    • 不同USB传输类型的匹配和调度工作
    • USB数据的实际传输工作
  • USB核心层:
    • USB主控制器驱动提供封装,为USB设备驱动提供统一的接口
  • USB设备驱动:
    • 实现设备具体功能的USB驱动

主机端工作过程:

  • 设备连接(物理硬件)
  • USB主机控制器
    • 获取设备端的信息
    • 匹配USB设备驱动
  • USB设备驱动
    • 获得设备端的具体信息
    • 轮询/请求 设备端获取信息(注册urb,提交给USB主机控制器)

设备端的驱动一般都是写成固件的形式(使用中断的方式实现),在Linux封装下这一些操作已经“看不到了”,后面使用51的例子讲解工作流程。Linux有专用的USB设备端驱动框架,这里只做简述。

设备端:

  • USB设备控制器(UDC):
    • 负责将一个USB设备依附于一个USB主机控制器上
  • Gadget Function API:
    • 提供通用的API
  • Gadget Function驱动:
    • 设备的功能,比如作为U盘,需要文件存储的功能,则需要File Storage驱动,这个驱动也称为Function驱动。
    • Function驱动只是利用通用的API,并通过usb_request与底层UDC驱动交互。

2、USB设备端的通信过程

USB主机控制器获取信息的过程通常称为枚举,对应USB的主机端工作过程,在设备端工作过程为:

简单了解一下USB设备端-鼠标的工作流程,后面再详细讲:

在流程图中右边主要是枚举过程,左边分支主要是数据传输过程。

开发USB设备端驱动,我们只需要提供设备描述符与部分事件的响应即可。

3、USB设备的描述符

USB的设备描述符一般固化在固件中,USB的设备架构:

如果USB主机要和USB设备通信,光有设备地址是不够的,还需要一个端点地址。有了设备地址和端点地址,就能准确地对端点发送和读取数据了。而配置和接口,是为了更方便地管理端点而抽象出来的概念。一个设备可以有多个配置,但是同一时间只能有一个配置有效,每个配置下又可以有多个接口。当我们需要不同的功能时,只要选择不同的配置即可。同一个端点号不能出现在同一配置下的两个或多个不同的接口中。同一个端点号可用在不同的配置中。一个端点在主机端对应一个管道。

在Linux中,使用struct usb_driver结构体来描述一个USB(接口)驱动,通过usb_register在USB驱动中注册。

4、传输的构成:USB的传输协议(非重点)

USB的传输协议有很多,鼠标、键盘等设备只需要只用最基础的传输协议即可,但是在一些复杂设备上就会涉及其他的协议(比如在Mass Storage类就会涉及BulkOnly)如下图。

在下面简单讲讲基础的传输协议及其构成。

a、传输:

  • 控制传输:主机获取设备信息、状态,选择设备配置等的一系列命令式工作
  • 中断传输:收发数据量少、周期性传输
  • 批量传输:利用任何可获得的总线带宽进行数据传输
  • 等时传输:恒定速率、没有差错控制的传输

b、事务

  • Setup事务:主要向设备发送控制命令;
  • Data In事务:主要从设备读取数据;
  • Data OUT事务:主要向设备发送数据。

c、包:

根据不同类型的包,所包含的域是不一样的。但是不同的包有个共同的特点,就是都要以同步域开始,紧跟着一个包标识符PID(Packet Of Identifier),最终以包结束符EOP(End Of Packet)来结束这个包。

包标识符PID是用来标识一个包的类型的。USB协议规定了4类包,分别是:令牌包(toker packet,PID1~0为01),数据包(data packet,PID1~PID0为11),握手包(handshake packet,PID1~PID0位10)和特殊包(special packet,PID1~PID0为00)。

d、域:

USB是串行总线,所以数据是一位一位地在数据线上传递的。既然是一位一位地传递,就存在着一个数据位先后的问题。USB使用的是LSB在前的方式,即先出来的是最低位数据,接下来是次低位,最后是最高位(MSB)。一个包,又被分成了很多个域(field),而LSB、MSB就是以域为单位来划分的。包括同步域、地址域、端点域、帧号域、标识域、数据域、校验域。

三、USB通信分析(主从设备对比)

1、stm32的USB详细架构(主机)

  • USBDeviceDriver
    • usb_regs 模块实现了硬件抽象层,它提供了一组用于访问 USB-FS_Device 外设寄存器的基本功能。
    • usb_int模块处理正确的传输中断服务例程;它提供 USB 设备协议事件和库之间的链接。
    • usb_mem将缓冲区数据从用户内存区域复制到数据包内存区域 ,反之亦然。

代码分析一:STM32USB鼠标键盘(Host)实验(参考于正点原子的demo)

mian函数: 初始化USB主机,注册回调函数 USBH_Usr_cb_TypeDef USR_Callbacks = { USBH_USR_Init,//USB HOST 初始化 USBH_USR_DeInit,//重新初始化 USBH_USR_DeviceAttached,//检测到U盘插入 USBH_USR_ResetDevice,//复位从机 USBH_USR_DeviceDisconnected,//检测到U盘拔出 USBH_USR_OverCurrentDetected,//USB接口电流过载 USBH_USR_DeviceSpeedDetected,//检测到从机速度 USBH_USR_Device_DescAvailable,//检测到从机的设备描述符 USBH_USR_DeviceAddressAssigned,//从机地址分配成功 USBH_USR_Configuration_DescAvailable,//配置描述符获有效 USBH_USR_Manufacturer_String,//获取到设备Manufacturer String USBH_USR_Product_String,//获取到设备Product String USBH_USR_SerialNum_String,//获取到设备SerialNum String USBH_USR_EnumerationDone,//设备USB枚举完成 USBH_USR_UserInput,//等待用户输入按键,执行下一步操作 NULL, USBH_USR_DeviceNotSupported,//无法识别的USB设备 USBH_USR_UnrecoveredError//无法恢复的错误!! }; USBH_Process: 不断的检测USB的连接(会配置不同的状态字来处理不同的USB外设),监视USB的状态。并对注册的函数进行回调。 设备驱动程序: 当USBH识别设备为HID设备后,会调用USBH_HID_InterfaceInit识别设备的BOOT_CODE以此来判别鼠标 HID_cb_TypeDef HID_MOUSE_cb = { MOUSE_Init, MOUSE_Decode,//MOUSE_Decode(uint8_t *data) };

2、LinuxUSB鼠标设备驱动

我们在设备驱动里面只需要对urb进行处理

LinuxUSB-鼠标设备驱动:

代码分析二:LinuxUSB鼠标设备驱动

1、注册驱动模块 2、注册USB设备驱动:usb_register(&usb_mouse_driver) static struct usb_driver usb_mouse_driver = {     .name       = "hclusbmouse",     .probe      = usb_mouse_probe,     .disconnect = usb_mouse_disconnect,     .id_table   = usb_mouse_id_table, }; usb_mouse_probe 获取设备信息 完成urb的初始化 注册信息上报设备驱动(input驱动) 注册urb_completion回调函数

通过hexdump /dev/input/eventX查看上报的信息

3、CH552设备端驱动

CH552是一款支持USB的单片机。

代码分析三:CH552鼠标固件程序

USBDeviceInit(); //USB设备模式初始化 设置USB工作模式:设备模式 设置端点数据传输地址 USB设备寄存器初始化 对USB中断进行初始化 UEP1_T_LEN = 0; //预使用发送长度一定要清空 UEP2_T_LEN = 0; //清空端点2发送长度 FLAG = 0;//清空USB中断传输完成标志 Ready = 0;//清空USB枚举完成标志 然后轮询按键状态 DeviceInterrupt中断处理函数(一长串的switch-case语句) 对请求的处理 枚举:我们可以在HIDDescriptor.h看到描述符信息 HID类命令 端点数据上传 应答 错误处理

当定义一个设备为HID设备时,其设备描述符应为:

  • bDeviceClass=0;
  • bDeviceSubClass=0;
  • bDeviceProtocol=0;

接口描述符应该:bInterfaceClass=0x03

HID描述符用于识别HID设备中所包含的额外描述符,如下:

4、STM32设备端驱动

移植stm32官方提供的的USB库文件

重点关注:

  • usb_device.c:上报事件
  • usbd_desc.c:USB standard device descriptor(提供粗略信息:厂商、设备速率、设备类型)
  • usbd_hid.c:定义设备的描述符(配置、接口、端点等)
  • usbd_core.c:usbd核心API函数

对于繁多的描述符我选择用工具生成,然后进行改动。

如:

更改设备接口类型(usbd_hid.c改150行)

定义hid类型为鼠标(usbd_hid.c改152行)

然后通过USBD_HID_SendReport将数据提交到缓冲区,对中断的响应已经交给usb_core处理了.

5、实验效果展示

主机使用imx6ull(Linux操作系统)从机使用STM32F4(无操作系统)

当鼠标连接后每次鼠标上报数据后,我们在主机上都能看到上报的数据。

附录:

一、USB通信抓包解析

EOP:

  • 等效于SOF包,全速和高速是SOF包,低速用EOP代替.
  • 间隔1ms

setup:控制传输起始的令牌包

data:

  • 获得从机信息
  • EOP-IN-OUT-EOP

setup:设置从机地址到0B

。。。。。。

setup:获取设备描述符、配置描述符等(因为分层的思想可能会要很多遍)

OUT:设置初始状态

IN:定时取数据

二、INPUT设备驱动开发流程

1、简介

input 子系统就是管理输入的子系统,是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等

等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。

2、input设备驱动的使用

struct input_dev *inputdev; //1、申请 input_dev inputdev = input_allocate_device(); //2、设置产生事件的类型和事件值:点击、相对坐标等 //按键事件和相对位移事件 inputdev ->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); //左、右、中键、位移、侧边键、滑轮 inputdev ->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); inputdev ->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); inputdev ->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); inputdev ->relbit[0] |= BIT_MASK(REL_WHEEL); //3、注册 input_dev input_register_device(inputdev); //4、上报事件:事件以及值 input_report_key(input, BTN_LEFT, buf); input_report_key(input, BTN_RIGHT, buf); input_report_key(input, BTN_MIDDLE, buf); input_report_key(input, BTN_SIDE, buf); input_report_key(input, BTN_EXTRA, buf); input_report_rel(input, REL_X, buf); input_report_rel(input, REL_Y, buf); input_report_rel(input, REL_WHEEL, buf); 用户空间的/dev/input/eventX就是对应的input设备文件

三、USB接口驱动

OHCI、UHCI都是USB1.1的接口标准,而EHCI是对应USB2.0的接口标准,最新的xHCI是USB3.0的接口标准。

1. OHCI(Open Host Controller Interface)是支持USB1.1的标准,但它不仅仅是针对USB,还支持其他的一些接口,比如它还支持Apple的火线(Firewire,IEEE 1394)接口。与UHCI相比,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务,就相对较简单。主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。

2. UHCI(Universal Host Controller Interface),是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。UHCI的软件驱动的任务重,需要做得比较复杂,但可以使用较便宜、较简单的硬件的USB控制器。Intel和VIA使用UHCI,而其余的硬件提供商使用OHCI。

3. EHCI(Enhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。

4. xHCI(eXtensible Host Controller Interface),是最新最火的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI支持所有种类速度的USB设备(USB 3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面3中(UHCI/OHCI/EHCI)。

四、其他描述符

报表描述符由描述 HID 设备的数据 项目(Item ) 组成。

项目的 第一个字节 (项目前缀)由三部分构成:

  • 项目标志( item Tag ):说明项目的功能,
  • 项目类型( item Type ):说明项目的数据类型,
  • 项目长度( item Size ):说明项目的数据部分的长度。

五、USB枚举过程

  • USB主机检测到USB设备插入后,就会先对设备复位。USB设备在总线复位后其地址为0,这样主机就可以通过地址0和那些刚插入的设备通信。USB主机往地址为0的设备的端点0发送获取设备描述符的请求(控制传输的建立过程)。设备收到该请求后,会按照主机请求的参数,在数据过程将设备描述符返回给主机。主机在成功获取到一个数据包的设备描述符并确认没有错误后,就返回一个0长度的确认数据包(状态过程)给设备,从而进入到接下来的设置地址阶段。第一次主机只会读取一个数据包的设备描述符。标准的设备描述符有18字节,有些USB设备的端点0大小不足18字节(但至少具有8字节),在这种情况下,USB主机也是只发送一次数据输入请求,多余的数据将不会再次请求。因此当设备端点0大小不足18字节时,就需要注意到这个问题。也就是说在第一次获取设备描述符时,只需要返回一次数据即可,不要再等主机继续获取剩余数据(如果还有),因为主机不会这么做。当主机成功获取到设备描述符的前8字节之后(USB协议端点0最大包长至少8字节),它就知道端点0的最大包长度了,因为端点0最大包长度刚好再设备描述符的第八字节处。
  • 主机对设备又一次复位。这时就进入到了设置地址阶段。 USB主机往地址为0的设备端点0发送一个设置地址的请求(控制传输的建立阶段),新的设备地址包含在建立过程的数据包中。具体的地址由USB主机负责管理,主机会分配一个唯一的地址给刚接入的设备。USB设备在收到这个建立过程之后,就直接进入状态过程,因为这个控制传输没有数据过程。设备等待主机请求状态返回(一个输入令牌包),收到输入令牌包后,设备就返回0长度的状态数据包。如果主机确认该状态包已经正确收到,就会发送应答包ACK给设备,设备在收到这个ACK之后,就要启用新的设备地址了。这样设备就分配到了唯一的设备地址,以后主机就通过它来访问该设备。
  • 主机再次获取设备描述符。 这次能第一次有些不一样,首先是主机不再使用地址0来访问呢设备,而是使用新的设备地址;另外,这次需要获取全部的18字节的设备描述符。如果你的端点0最大包长小于18字节,那就会有多次请求数据输入(即发送多个IN令牌包)。
  • 主机获取配置描述符。 配置描述符共9字节。主机获取到配置描述符后,根据配置描述符中所描述的配置集合总长度,获取配置集合。获取配置描述符和获取配置描述符请求是差不多的,只是指定的长度不一样。有些主机干脆不单独获取配置描述符,而是直接使用最大长度来获取配置描述符集合,因为设备实际返回的数据可以少于指定的字节数。配置描述符集合包括配置描述符、接口描述符、类特殊描述符(如果有)、端点描述符等。接口描述符、类特殊描述符、端点描述符是不能单独获取的,必须跟随配置描述符以一个集合的方式一并返回。