阅读源码时,经常碰到如下术语:
USB 本身是一个很庞大、复杂的体系, 本课程的重点在于工业互联, USB 是其中的一个 小小知识点。本章课程的目的在于:能理解 USB 的一些概念,能使用 USB 传输数据。 4.24.5 节, 介绍 USB 概念;4.64.7 节,移植 USBX 实现 USB 串口功能。
参考资料:
https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX过 USB 设备驱动,直接通过 USB 控制器驱动访问 USB 设备。
现象: 把 USB 设备比如 Android 手机接到 PC
在 USB 系统中, 有 2 个硬件概念:
APP 可以通过 USB 设备驱动程序访问 USB 设备,也可以绕过 USB 设备驱动,直接通过 USB 控制器驱动访问 USB 设备。
参考资料:
USB 2.0 协议支持 3 种速率: 低速(Low Speed,1.5Mbps)、全速(Full Speed, 12Mbps)、 高速(High Speed, 480Mbps)。
USB Hub、USB 设备, 也分为低速、全速、高速三种类型。 一个 USB 设备, 可能兼容低 速、全速, 可能兼容全速、高速, 但是不会同时兼容低速、高速。
下图是兼容高速模式的 USB 收发器电路图:
USB 连接涉及 Hub Port 和 USB 设备,硬件连接如下:
USB 连接线有 4 条: 5V、D+、D-、GND。数据线 D+、D-,只能表示 4 种状态。 USB 协议 中,很巧妙地使用这两条线路实现了空闲(Idle)、开始(SOP)、传输数据(Data)、结束(EOP) 等功能。
Hub 端口的 D+、D-都有 15K 的下拉电阻,平时为低电平。全速设备内部的 D+有 1.5K 的 上拉电阻, 低速设备内部的 D-有 1.5K 的上拉电阻,连接到 Hub 后会导致 Hub 的 D+或 D-电 平变化,Hub 根据变化的引脚分辨接进来的是全速设备还是低速设备。
高速设备一开始也是作为全速设备被识别的。
全速设备、高速设备连接时, D+引脚的电平由低变高:
低速设备连接时,D-引脚的电平由低变高:
对于低速、全速设备,接到 Hub 时导致 D-或 D+引脚变为高电平, 断开设备后, D-或 D+ 引脚变为低电平:
对于高速设备,它先作为全速设备被识别出来,然后再被识别为高速设备。工作于高 速模式时, D+的上拉电阻是断开的,所以对于工作于高速模式的 USB 设备, 无法通过 D+的 引脚电平变化监测到它已经断开。
工作于高速模式的设备, D+、D-两边有 45 欧姆的下拉电阻,用来消除反射信号:
当断开高速设备后, Hub 发出信号,得到的反射信号无法衰减, Hub 监测到这些信号后 就知道高速设备已经断开,内部电路图如下:
从状态切换图上看,一个 USB 设备连接后,它将会被供电, 然后被复位。当软件出错 时,我们也可以发出复位信号重新驱动设备。
那么, USB Hub 端口或 USB 控制器端口如何发出复位信号? 发出 SE0 信号,并维持至少 10ms。
USB 设备看到 Reset 信号后,需要准备接收"SetAddress()"请求; 如果它不能回应这个 请求, 就是"不能识别的设备"。
Hub 端口的 D+、D-都有 15K 的下拉电阻,平时为低电平。全速设备内部的 D+有 1.5K 的 上拉电阻, 低速设备内部的 D-有 1.5K 的上拉电阻,连接到 Hub 后会导致 Hub 的 D+或 D-电
平变化,Hub 根据变化的引脚分辨接进来的是全速设备还是低速设备。
高速设备必定兼容全速模式, 所以高速设备内部 D+也有 1.5K 的上拉电阻, 只不过这个 电阻是可以断开的: 工作于高速模式时要断开它。
高速设备首先作为全速设备被识别出来,然后 Hub 如何确定它是否支持高速模式? Hub 端口如何监测一个新插入的 USB 设备能否工作于高速模式? 流程如下:
"a high-speed detection handshake"信号,就是"高速设备监测握手信号",既然是 握手信号, 自然是有来有回:
SOP:Start Of Packet,Hub 驱动 D+、D-这两条线路从 Idle 状态变为 K 状态。 SOP 中 的 K 状态就是 SYNC 信号的第 1 位数据, SYNC 格式为 3 对 KJ 外加 2 个 K。
EOP:End Of Packet,由数据的发送方发出EOP,数据发送方驱动D+、D-这两条线路, 先设为 SE0 状态并维持 2 位时间, 再设置为 J 状态并维持 1 位时间, 最后 D+、D-变为高阻 状态, 这时由线路的上下拉电阻使得总线进入 Idle 状态。
高速的 EOP 比较复杂,作为软件开发人员无需掌握。
高速模式中,Ide 状态为:D+、D-接地。SOP 格式为: 从 Idle 状态切换为 K 状态。 SOP 中的 K 状态就是 SYNC 信号的第 1 位数据。
高速模式中的 SYNC 格式为:KJKJKJKJ KJKJKJKJ KJKJKJKJ KJKJKJKK,即 15 对 KJ,外 加 2 个 K。
参考文章:USB 的 NRZI 信号格式, https://zhuanlan.zhihu.com/p/460018993
NRZI:Non Return Zero Inverted Code,反向不归零编码。 NRZI 的编码方位为:对于 数据 0,波形翻转;对于数据 1,波形不变。
使用 NRZI,发送端可以很巧妙地把"时钟频率"告诉接收端: 只要传输连续的数据 0 即 可。在下图中, 低速/全速协议中"Sync Pattern"的原始数据是"00000001",接收端从前面 的 7 个 0 波形就可以算出"时钟频率"。
使用 NRZI 时, 如果传输的数据总是"1",会导致波形维持不变。如果电平长时间维持 不变, 比如传输 100 位 1 时, 如果接收方稍有偏差,就可能认为接收到了 99 位 1、101 位 1。而 USB 中采用了 Bit-Stuffing 位填充处理,即在连续发送 6 个 1 后面会插入 1 个 0,强 制翻转发送信号,从而让接收方调整频率,同步接收。而接收方在接收时只要接收到连续 的 6 个 1 后,直接将后面的 0 删除即可恢复数据的原貌。
NRZI 数据格式如上图所示。
sidebar_position: 5
参考资料:
compound device :多个设备组合起来,通过 HUB 跟 Host 相连
composite device :一个物理设备有多个逻辑设备(multiple interfaces)
在软件开发过程中, 我们可以忽略 Hub 的存在,硬件拓扑图简化如下:
一个物理设备里面可能有多个逻辑设备, Hos 可以外接多个逻辑设备, 硬件拓扑图如 下:
要理解协议层、理解数据如何传输,带着这几个问题去看文档、看视频:
提前罗列出答案:
先传输最低位(LSB)。在后续文档中,描述数据时按照传输顺序从左到右列出来
Host 发出 SOP 信号后, 就会发出 SYNC 信号:它是一系列的、最大传输频率的脉冲,接 收方使用它来同步数据。对于低速/全速设备, SYNC信号是8位数据(从做到右是00000001); 对于高速设备, SYNC信号是32位数据(从左到右是00000000000000000000000000000001)。 使用 NRZI 编码时,前面每个"0"都对应一个跳变。
在很多文档里, 把 SOP 和 SYNC 统一称为"SYNC",它的意思是"SYNC"中含有"SOP"。
USB 总线上传输的数据以包为单位。 USB 包里含有哪些内容("域")?
发起一次完整的传输, 可能涉及多个包。那么,第 1 个包里含有设备地址、端点号, 后续的包就没必要包含设备地址、端点号。
注意: 所有的 USB 文档提到的"输入"、"输出",都是基于 Host 的角度, "输出"表示从 Host 输出到设备,"输入"表示 Host 从设备得到数据。
有哪些 USB 包? 根据包数据里的 PID 的 bit1, bit0 可以分为 4 类:
PID 有 4 位,使用 bit1,bit0 确定分类, 使用 bit3,bit2 进一步细分。如下表(来自 《圈圈教你玩 USB》)所示:
在 USB 包中,PID 域使用 8 位来表示,格式如下:
前 4 位表示 PID,后 4 位是对应位的取反。接收方发现后 4 位不是前 4 位的取反的话, 就认为发生了错误。
令牌类 的 PID ,起 "通知作用 " ,通知谁 ?SOF 令牌包被用来通 知所有设 备, OUT/IN/SETUP 令牌包被用来通知某个设备。
对于 OUT、IN、SETUP 令牌包, 它们都是要通知到具体的设备, 格式如下:
USB 设备的地址有 7 位,格式如下:
USB 设备的端点号有 4 位, 格式如下:
对于 SOF 包,英文名为"Start-of-Frame marker and frame number"。对于 USB 全速 设备, Host 每 1ms 产生一个帧; 对于高速设备, 每 125us 产生一个微帧, 1 帧里有 8 个微 帧。 Host 会对当前帧号进行累加计数, 在每帧或每微帧开始时, 通过 SOF 令牌包发送帧号。 对于高速设备, 每 1 毫秒里有 8 个微帧,这 8 个微帧的帧号是一样的, 每 125us 发送一个 SOF 令牌包。
SOF 令牌包格式如下:
Host 使用 OUT、IN、SETUP 来通知设备:我要传输数据了。数据通过"数据包"进行传 输。
数据包也有 4 种类型:DATA0、DATA1、DATA2、MDATA。其中 DATA2、MDATA 在高速设备 中使用。对软件开发人员来说,我们暂时仅需了解 DATA0、DATA1。
为什么要引入 DATA0、DATA1 这些不同类型的数据包? 为了纠错。
Host 和设备都会维护自己的数据包切换机制,当数据包成功发送或者接收时,数据包 类型切换。当检测到对方使用的数据包类型不对时,USB 系统认为发生了错误。
比如:
数据包格式如下:
对于全速设备, 数据包中的数据做大是 1023 字节;对于全速设备, 数据包中的数据做 大是 1024 字节。
握手包有 4 类: ACK、NAK、STALL、NYET
USB 传输的基本单位是包(Packet),包的类型由PID 表示。 一个单纯的包,是无法传输 完整的数据。
为什么?比如想输出数据,可以发出 OUT 令牌包, OUT 令牌包可以指定目的地。但是数 据如何传输呢? 还需要发出 DATA0 或 DATA1 数据包。设备收到数据后, 还要回复一个 ACK 握手包。
所以,完整的数据传输, 需要涉及多个包:令牌包、数据包、握手包。这个完整的数 据传输过程,被称为事务(Transaction)。
有些事务需要握手包,有些事务不需要握手包,有些事务可以传输很大的数据,有些 事务只能传输小量数据。
事务由多个包组成, 比如 Host 要发送数据给设备,这就会涉及很多个包:
这个完整的事务涉及 3 个包(Packet),分为 3 个阶段(Phase):
事务由包组成, 这些包分别处于 3 个阶段(phase):令牌阶段,数据阶段, 握手阶段。
对于批量传输、中断传输、实时传输,它们分别由一个事务组成,不再细分为若干个 过程。
但是控制传输由多个事务组成,这些事务分别处于 3 个过程: 建立过程(stage)、数据 过程(stage)、状态过程(stage)。
总结起来就是:
批量传输用批量事务来实现,用于传输大量的数据, 数据的正确性有保证, 时效没有 保证。
批量事务由 3 个阶段(phase)组成: 令牌阶段、数据阶段、握手阶段。每个阶段都是一 个完整的包,含有 SOP、SYNC、PID、EOP。
下图中各个矩形框就对应一个完整的包。
《圈圈教你玩 USB》中有详细的示例:
中断传输用中断事务来实现,用于传输小量的、周期性的数据,数据的正确性和时效 都有保证。
中断事务由 3 个阶段(phase)组成: 令牌阶段、数据阶段、握手阶段。每个阶段都是一 个完整的包,含有 SOP、SYNC、PID、EOP。
下图中各个矩形框就对应一个完整的包。
中断事务跟批量事务非常类似,Host 使用它来周期性地读数据、写数据。
以鼠标为例,我们需要及时获得鼠标的数据, 不及时的话你会感觉鼠标很迟钝。但是 USB 协议中并没有中断功能,它使用"周期性的读、写"来实现及时性。具体过程如下:
中断事务的优先级比批量事务更高,它要求实时性,而批量事务不要求实时性。
实时传输用实时事务来实现, 用于传输实时数据, 对数据的正确性没有要求。
实时事务由 2 个阶段(phase)组成: 令牌阶段、数据阶段。每个阶段都是一个完整的包, 含有 SOP、SYNC、PID、EOP。
实时事务不需要握手阶段,一个示例的场景是:为了传输摄像头的实时数据,偶尔的 数据错误是可以忍受的,大不了出现短暂的花屏。如果为了解决花屏而重传数据, 那就会
导致后续画面被推迟,实时性无法得到保证。
下图中各个矩形框就对应一个完整的包。
实时事务跟中断事务非常类似,Host 也会周期性的发起实时事务,主要区别在于:
在使用批量传输时, 使用 IN 令牌包或 OUT 令牌包表示数据传输方向。
控制传输的令牌包永远是 SETUP,怎么分辨是读数据, 还是写数据? 发出 SETUP 令牌包 后,还要发出 DATA0 数据包,根据数据的内容来确定后续是读数据,还是写数据。这个过 程称为"建立事务"(SETUP Transaction)
但是控制传输由多个事务组成,这些事务分别处于 3 个过程: 建立过程(stage)、数据 过程(stage)、状态过程(stage)。
上图中的每一个方框,都是一个完整的事务, 含有: Token Packet、Data Packet、 Handshake Packet。
LeCroy(力科)成立于 1964 年, 是一家专业生产示波器厂家。旗下生产有数字示波器、 SDA 系列数字示波器、混合信号示波器、模块化仪器、任意波形发生器。
官网是:https://teledynelecroy.com/,似乎无法注册新用户,无法下载软件。 可以在搜索引擎里搜"usbprotocolsuite"。
安装"usbprotocolsuite"后, 可以在文档目录里找打很多示程序(后缀名为 usb):
使用"usbprotocolsuite"打开这些文件,即可体验 USB 数据传输:
Host 使用控制传输来识别设备、设置设备地址、启动设备的某些特性, 对于控制传输, 它首先发出"setup 事务",如下:
在"setup 事务"中,
Host 通过 DATA0 数据包发送 8 字节数据给设备,它的格式如下图所示:
控制传输的建立事务中, 可以使用下列格式的数据:
上表中各个"宏"取值如下:
在 SETUP 事务的数据里, 表示了要访问的是什么: Device?Interface?Endpoint?
对于一个USB 设备, 它可以多种配置(Configuration)。比如4G 上网卡就有 2 种配置: U 盘、上网卡。第 1 次把 4G 上网卡插入电脑时,它是一个 U 盘,可以按照里面的程序。装 好程序后, 把它再次插入电脑,它就是一个上网卡。驱动程序可以选择让它工作于哪种配 置,同一时间只能有一种配置。大多数的 USB 设备只有一种配置。
一个配置下,可以有多个接口(Interface),接口等同于功能(Function)。比如 USB 耳 机有两个接口(功能):声音收发、按键控制。
一个接口, 可能有多个设置(Setting),比如默认设置下它使用较低的带宽, 可以选择 其他设置以使用更高带宽。
一个接口, 由一个或多个端点(Endpoint)组成。端点 0 属于整个设备的, 端点 0 是双 向的。接口还可以有其他端点, 这些端点是单向的, 要么是批量(Bulk)端点、要么是中断 (Interrupt)端点、要么是同步(Isochronous)端点。
怎么描述设备、配置、接口、端点?使用描述符(Descriptors),有设备描述符、配置 描述符、接口描述符、端点描述符。所谓描述符,就是一些格式化的数据, 用来描述信息。
一个 USB 设备:
还有一些字符串描述符(String descriptors),它用可读的文字来描述设备,是可选 的。
在 Ubuntu 中可以执行 lsusb -v查看 USB 设备的描述符信息:
book@100ask:~$ sudo lsusb -v
[sudo] password for book:
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 9 Hub
bDeviceSubClass 0 Unused
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 64
idVendor 0x1d6b Linux Foundation
idProduct 0x0002 2.0 root hub
bcdDevice 5.04
iManufacturer 3 Linux 5.4.0-124-generic ehci_hcd
iProduct 2 EHCI Host Controller
iSerial 1 0000:02:03.0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 25
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 12
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 6
wHubCharacteristic 0x000a
No power switching (usb 1.0)
Per-port overcurrent protection
bPwrOn2PwrGood
bHubContrCurrent DeviceRemovable PortPwrCtrlMask
Hub Port Status:
Port 1: 0000.0100 Port 2: 0000.0100 Port 3: 0000.0100 Port 4: 0000.0100 Port 5: 0000.0100
Port 6: 0000.0100
10 * 2 milli seconds
0 milli Ampere
0x00
0xff
power
power
power
power
power
power
Device Status: 0x0001
Self Powered
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 9 Hub
bDeviceSubClass 0 Unused
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 8
idVendor 0x0e0f VMware, Inc.
idProduct 0x0002 Virtual USB Hub
bcdDevice 1.00
iManufacturer 1 VMware, Inc.
iProduct 2 VMware Virtual USB Hub
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 25
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 1 VMware, Inc.
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 1 VMware, Inc.
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0001 1x 1 bytes
bInterval 255
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 7
wHubCharacteristic 0x0009
Per-port power switching
Per-port overcurrent protection
bPwrOn2PwrGood
bHubContrCurrent DeviceRemovable PortPwrCtrlMask
Hub Port Status:
Port 1: 0000.0100 Port 2: 0000.0100 Port 3: 0000.0100 Port 4: 0000.0100 Port 5: 0000.0100 Port 6: 0000.0100
Port 7: 0000.0100
50 * 2 milli seconds
100 milli Ampere
0x00
0xfe
power
power
power
power
power
power
power
Device Status: 0x2909
Self Powered
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0e0f VMware, Inc.
idProduct 0x0003 Virtual Mouse
bcdDevice 1.03
iManufacturer 1 VMware
iProduct 2 VMware Virtual USB Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 1 VMware
bmAttributes 0xc0
Self Powered
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 2 Mouse
iInterface 1 VMware
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 46
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 1
Device Status: 0x0001
Self Powered
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 9 Hub
bDeviceSubClass 0 Unused
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 64
idVendor 0x1d6b Linux Foundation
idProduct 0x0001 1.1 root hub
bcdDevice 5.04
iManufacturer 3 Linux 5.4.0-124-generic uhci_hcd
iProduct 2 UHCI Host Controller
iSerial 1 0000:02:00.0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 25
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0002 1x 2 bytes
bInterval 255
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 2
wHubCharacteristic 0x000a
No power switching (usb 1.0)
Per-port overcurrent protection
bPwrOn2PwrGood 1 * 2 milli seconds
bHubContrCurrent 0 milli Ampere
DeviceRemovable 0x00
PortPwrCtrlMask 0xff
Hub Port Status:
Port 1: 0000.0103 power enable connect
Port 2: 0000.0107 power suspend enable connect
Device Status: 0x0001
Self Powered
使用"usbprotocolsuite"打开,可以看到设备的枚举过程:
Azure RTOS 平台是运行时解决方案的集合,包括 Azure RTOS ThreadX、Azure RTOS NetX 和 NetX Duo、Azure RTOS FileX、Azure RTOS GUIX 和 Azure RTOS USBX。
Azure RTOS ThreadX 是专用于深度嵌入式应用程序的高级实时操作系统 (RTOS)。 Azure RTOS ThreadX 具有多种优势,其中包括高级调度设施、消息传递、中断管理和消息 服务。 Azure RTOS ThreadX 具有许多高级功能, 其中包括 picokernel 体系结构、抢占 式阈值调度、事件链和一系列丰富的系统服务。
USBX 是 Azure®RTOS USB 主机和 USB 设备嵌入式堆栈。它与 ThreadX 紧密耦合。在某些 类中, 它需要 FileX 和 NetX Duo 堆栈。它允许使用具有多种配置的 USB 设备、复合设备和 USB OTG 进行操作。它支持 USB 电源管理。
USBX 为 USB 主机和 USB 设备堆栈提供了大量的 USB 类。 一旦低级驱动程序能够响应 USBX 请求, 模块化架构就可以更容易地移植到不同的 USB 硬件 IP 上。
所有 STM32 USB IP(主机、设备、 OTG、高速和全速) 均由 USBX 通过通用 STM32 HAL 驱动程序 API 透明支持。
参考资料:
https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX
USBX 分为三层, 如下图所示:
在 STM32 的固件中, 可以看到 USBX 目录,比如:
移植 Controller layer、stack layer、Class layer 并不复杂, 重点在于 2 点:
USBX 依赖于 Azure®RTOS ThreadX,但是也可以单独使用 USBX,这需要配置。通常在 “ux_user.h”里进行配置,配置项如下:
/* Defined, this macro will enable the standalone mode of usbx. */
#define UX_STANDALONE
当没有定义“UX_STANDALONE”时就是使用 RTOS 模式, 可以使用 ThreadX 提供的互斥 量函数实现阻塞式读写(“blocking”), 比如对于 USB 虚拟串口, 可以使用如下函数:
UINT _ux_device_class_cdc_acm_read(UX_SLAVE_CLASS_CDC_ACM *cdc_acm, UCHAR *buffer,
ULONG requested_length, ULONG *actual_length);
UINT _ux_device_class_cdc_acm_write(UX_SLAVE_CLASS_CDC_ACM *cdc_acm, UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
这 2 个函数发起数据传输,在传输过程中线程阻塞,传输完成后线程被唤醒。
当定义“UX_STANDALONE”时就是使用单独模式, 不能再使用上面的阻塞函数,而要使 用非阻塞的函数(non-blocke):
UINT _ux_device_class_cdc_acm_read_run(UX_SLAVE_CLASS_CDC_ACM *cdc_acm,
UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
UINT _ux_device_class_cdc_acm_write_run(UX_SLAVE_CLASS_CDC_ACM *cdc_acm,
UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
它们只是发起传输,然后就即刻返回。需要提供回调函数,在回调函数里分辨数据是 否传输完成。
/* Defined, this macro disables CDC ACM non-blocking transmission support. */ //#define UX_DEVICE_CLASS_CDC_ACM_TRANSMISSION_DISABLE
定义 UX_DEVICE_CLASS_CDC_ACM_TRANSMISSION_DISABLE 是,就禁止了“非阻塞模式”, 这时只能使用基于 RTOS 的阻塞函数。
换句话说, 要使用单独模式的非阻塞函数, 就不能定义这个配置项。
/* Defined, this value will only enable the host side of usbx. */
/* #define UX_HOST_SIDE_ONLY */
/* Defined, this value will only enable the device side of usbx. */
#define UX_DEVICE_SIDE_ONLY
本课程定义“UX_DEVICE_SIDE_ONLY”, 仅作为 USB Device。
本节程序源码为“3_程序源码01_视频配套的源码 4-7_移植 USBX 实现虚拟串口 uart_usb.7z”,在上一节代码 uart_rtos.7z 的基础上修改得来。
移植 Controller layer、stack layer、Class layer 并不复杂, 重点在于 2 点:
找到固件库,如下:
把 usbx 整个目录复制到工程“MiddlewaresThird_Party”目录下, 如下:
需要添加 USBX 的 3 层源码。
先仿照下图添加“Class layer”源码,添加含有“ux_device_class_cdc_acm ”前缀 的 C 文件:
再仿照下图添加“stack layer”源码,可以从文件名的前面看出它们的作用, 比如 “ ux_device_stack ”表示这是 stack 源码,“ ux_utility ”表示这 是 辅助 函数 , “ux_system”表示是这是系统函数:
最后仿照下图添加“Controller layer”, 添加“ux_dcd_stm32”前缀的 C 文件:
参考工程:
STM32CubeH5ProjectsNUCLEO-H563ZIApplicationsUSBXUx_Device_HID_CDC_ACM
在网盘资料中, 找到如下目录:
把 app 文件夹复制到工程的“MiddlewaresThird_Partyusbx”目录下, 如下图所示:
各个文件的作用为:
在工程里添加上述文件, 如下图所示:
使用 STM32CubeMX 配置 usb 后生成的 usb.c 里,只是初始化了 USB 控制器,并未启动 它,也没有跟 USBX 建立联系, 需要修改代码。
代码如下:
23 /* USER CODE BEGIN 0 */
24 #include "ux_port.h"
25 #include "ux_device_descriptors.h"
26 #include "ux_dcd_stm32.h"
27 /* USER CODE END 0 */
/* 省略 */
33 void MX_USB_PCD_Init(void)
34 {
35
36 /* USER CODE BEGIN USB_Init 0 */
37 UINT MX_USBX_Device_Init(void);
38 MX_USBX_Device_Init();
39
40 /* USER CODE END USB_Init 0 */
41
42 /* USER CODE BEGIN USB_Init 1 */
43
44 /* USER CODE END USB_Init 1 */
45 hpcd_USB_DRD_FS.Instance = USB_DRD_FS;
46 hpcd_USB_DRD_FS.Init.dev_endpoints = 8;
47 hpcd_USB_DRD_FS.Init.speed = USBD_FS_SPEED;
48 hpcd_USB_DRD_FS.Init.phy_itface = PCD_PHY_EMBEDDED;
49 hpcd_USB_DRD_FS.Init.Sof_enable = DISABLE;
50 hpcd_USB_DRD_FS.Init.low_power_enable = DISABLE;
51 hpcd_USB_DRD_FS.Init.lpm_enable = DISABLE;
52 hpcd_USB_DRD_FS.Init.battery_charging_enable = DISABLE;
53 hpcd_USB_DRD_FS.Init.vbus_sensing_enable = DISABLE;
54 hpcd_USB_DRD_FS.Init.bulk_doublebuffer_enable = DISABLE;
55 hpcd_USB_DRD_FS.Init.iso_singlebuffer_enable = DISABLE;
56 if (HAL_PCD_Init(&hpcd_USB_DRD_FS) != HAL_OK)
57 {
58 Error_Handler();
59 }
60 /* USER CODE BEGIN USB_Init 2 */
61
62 HAL_PWREx_EnableVddUSB();
63 HAL_PWREx_EnableUSBVoltageDetector();
64
65 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x00, PCD_SNG_BUF, 0x14);
66 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x80, PCD_SNG_BUF, 0x54);
67 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPINCMD_ADDR, PCD_SNG_BUF, 0x94); 68 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPOUT_ADDR, PCD_SNG_BUF, 0xD4);
69 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPIN_ADDR, PCD_SNG_BUF, 0x114); 70 ux_dcd_stm32_initialize((ULONG)USB_DRD_FS, (ULONG)&hpcd_USB_DRD_FS);
71
72 HAL_PCD_Start(&hpcd_USB_DRD_FS);
73
74 /* USER CODE END USB_Init 2 */
75 }
第 38 行:调用 USBX 的函数, 添加 USB 串口的支持。
第 62~63 行:使能 USB 控制器的电源。
第 65 69 行:设置 endpoint 的“Packet Buffer Memory”,这个概念可以参考:
http://www.51hei.com/bbs/dpj-40953-1.html。
第 70 行 : 把 STM32 USB 控 制 器 的 句 柄 , 传 给 USBX 系 统 ,
“usbx_stm32_device_controllers”的代码会使用这个句柄来操作硬件。 第 72 行:启动 USB 控制器。
使 用 单 独 模 式 (STANDALONE ) 时 , 需 要 创 建 一 个 任 务 , 不 断 运 行 “_ux_system_tasks_run ”函数。以下代码是在 FreeRTOS 的默认任务里运行和这个函数:
26 /* USER CODE BEGIN Includes */
27 #include "stdio.h"
28 #include "draw.h"
29 #include "ux_api.h"
30 /* USER CODE END Includes */
/* 省略 */
195 /* USER CODE END Header_StartDefaultTask */
196 void StartDefaultTask(void *argument)
197 {
198 /* USER CODE BEGIN defaultTask */
199 /* Infinite loop */
200 for(;;)
201 {
202 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET);
203 vTaskDelay(500);
204
205 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);
206 vTaskDelay(500);
207 ux_system_tasks_run();
208 }
209 /* USER CODE END defaultTask */
210 }
第 29 行,包含 USBX 的头文件。
第 207 行, 调用 USBX 的系统函数。
如下图配置:
在“CoreSrcapp_freertos.c”里添加 USB 串口的发送测试代码:
26 /* USER CODE BEGIN Includes */
27 #include "stdio.h"
28 #include "draw.h"
29 #include "ux_api.h"
30 /* USER CODE END Includes */
/* 省略 */
69 static void SPILCDTaskFunction( void *pvParameters )
70 {
71 char buf[100];
72 int cnt = 0;
73
74 while (1)
75 {
76 sprintf(buf, "USB Serial Send Test : %drn", cnt++);
77 //Draw_String(0, 0, buf, 0x0000ff00, 0);
78
79 int ux_device_cdc_acm_send(uint8_t *datas, uint32_t len, uint32_t timeout);
80 ux_device_cdc_acm_send((uint8_t *)buf, strlen(buf), 1000);
81 vTaskDelay(1000);
82 }
83 }
第 29 行:包含头文件。
第 79~80 行:使用 USB 串口发送数据。
在“MiddlewaresThird_Partyusbxappux_device_cdc_acm.c”中,有如下代码:
111 static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length)
112 {
113 int Draw_String(uint32_t x, uint32_t y, char *str, uint32_t front_color, uint32_t
back_color);
114 if (status == UX_SUCCESS)
115 {
116 data_pointer[length] = '�';
117 Draw_String(0, 0, (char *)data_pointer, 0x0000ff00, 0);
118 }
119 return 0;
120 }
当 USB 串口收到数据后, ux_device_class_cdc_acm_read_callback 函数被调用。 第 117 行把接收到的数据在 LCD 上显示处来。
烧写运行程序后,接上 USB 线,在电脑上可以识别出 USB 串口,查看设备管理器,可 以看到如下设备:
使用串口工具打开这个串口, 可以连续不断接收到数据,如下所示:
在串口工具上发送数据时,在板子的 LCD 上会有显示。
本节程序源码为“3_程序源码�1_视频配套的源码 4-8_虚拟串口源码分析与改造 uart_usb_freertos.7z”,在上一节代码 uart_usb.7z 的基础上修改得来。
在“MiddlewaresThird_Partyusbxappux_device_descriptors.c”有设备描述符、 配置描述符、接口描述符、端点描述符的定义。
比如, 设备描述符在如下代码中设置:
配置描述符在如下代码中设置:
涉及文件为:demoMiddlewaresThird_Partyusbxappux_device_cdc_acm.c。 开发板通过 USB 串口发出数据时, 使用以下函数:
/* 启动发送 */
UINT ux_device_class_cdc_acm_write_with_callback(UX_SLAVE_CLASS_CDC_ACM *cdc_acm, UCHAR *buffer, ULONG requested_length);
/* 发送完毕的回调函数 */
static UINT ux_device_class_cdc_acm_write_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, ULONG length);
我们将会实现如下函数,它使用“ux_device_class_cdc_acm_write_with_callback ” 来启动发送,然后等待“ux_device_class_cdc_acm_write_callback”唤醒:
int ux_device_cdc_acm_send(uint8_t *datas, uint32_t len, uint32_t timeout);
开发板接收到 USB 串口数据时,以下回调函数被调用:
static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length);
我们可以改造这个函数, 把接收到的数据写入队列。
对于发送, 实现以下函数:启动发送之后阻塞,等待回调函数唤醒或超时。
static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length);
对于接收, 实现以下函数:把接收到的数据写入队列。
static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length);
然后提供这个函数:
int ux_device_cdc_acm_getchar(uint8_t *pData, uint32_t timeout);
全部0条评论
快来发表一下你的评论吧 !