前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >第4章_USB 设备编程

第4章_USB 设备编程

作者头像
韦东山
发布于 2024-06-29 00:23:04
发布于 2024-06-29 00:23:04
39600
代码可运行
举报
文章被收录于专栏:韦东山嵌入式韦东山嵌入式
运行总次数:0
代码可运行

4.1 USB 学习指南

阅读源码时,经常碰到如下术语:

  • HCD(Host Controller Driver)
  • DCD(Device Controller Driver)
  • PCD(Low layer USB Peripheral Control Driver)
  • CDC(Communication Device Class)

4.1 USB 学习指南

USB 本身是一个很庞大、复杂的体系, 本课程的重点在于工业互联, USB 是其中的一个 小小知识点。本章课程的目的在于:能理解 USB 的一些概念,能使用 USB 传输数据。 4.2~4.5 节, 介绍 USB 概念;4.6~4.7 节,移植 USBX 实现 USB 串口功能。

参考资料:

  • 《圈圈教你玩 USB》
  • 官网:https://www.usb.org/documents
  • 我们从官网下载后放在网盘如下目录:
  • ST 官方资料:

https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX过 USB 设备驱动,直接通过 USB 控制器驱动访问 USB 设备。

4.2 USB 系统硬件框架和软件框架

4.2.1 实验现象

现象: 把 USB 设备比如 Android 手机接到 PC

  • 右下角弹出"发现 android phone"
  • 跳出一个对话框, 提示你安装驱动程序
    • 问 1:USB 设备插到电脑上去, 接触到的对方设备是什么? 答 1:是 USB 控制器,是 USB 控制器内嵌的 root hub
    • 问 2. 既然还没有"驱动程序",为何能知道是"android phone"? 答 2. windows 里已经有了 USB 的总线驱动程序, 接入 USB 设备后, 是"总线驱动程序" 知道你是"android phone"、提示你安装的是"设备驱动程序"。USB 总线驱动程序负责:识 别 USB 设备, 给 USB 设备找到对应的驱动程序。
    • 问 3. 为什么一接入 USB 设备, PC 机就能发现它? 答 3. PC 的 USB 口内部,D-和 D+接有 15K 的下拉电阻, 未接 USB 设备时为低电平。USB 设备的 USB 口内部, D-或 D+接有 1.5K 的上拉电阻;它一接入 PC,就会把 PC USB 口的 D-或 D+拉高,从硬件的角度通知 PC 有新设备接入。
    • 问 4. USB 设备种类非常多,为什么一接入电脑, 就能识别出来它的种类? 答 4. PC 和 USB 设备都得遵守一些规范。比如: USB 设备接入电脑后, PC 机会发出"你 是什么"?USB 设备就必须回答"我是 xxx", 并且回答的格式是固定的。USB 总线驱动程序会 发出某些命令想获取设备信息(描述符),USB 设备必须返回"描述符"给 PC。
    • 问 5. PC 机上接有非常多的 USB 设备, 怎么分辨它们? 答 5. 每一个 USB 设备接入 PC 时, USB 总线驱动程序都会给它分配一个编号。 PC 机想 访问某个 USB 设备时,发出的命令都含有对应的编号(地址)。
    • 问 6. USB 设备刚接入 PC 时, 还没有编号; 那么 PC 怎么把"分配的编号"告诉它? 答 6. 新接入的 USB 设备的默认编号是 0,在未分配新编号前, PC 使用 0 编号和它通 信。
4.2.2 硬件框架

在 USB 系统中, 有 2 个硬件概念:

  • USB Host:它跟处理器相连,处理器通过 USB Host 跟各类 USB 设备通信。 USB Host 中 集成有一个 root hub
  • USB Device:这分为两类设备
    • Hub:用来扩展 USB 接口
    • Function:就是普通的 USB 设备,比如 U 盘、声卡等
4.2.3 软件框架

APP 可以通过 USB 设备驱动程序访问 USB 设备,也可以绕过 USB 设备驱动,直接通过 USB 控制器驱动访问 USB 设备。

4.3 软件工程师眼里的 USB 电气信号

参考资料:

  • 《圈圈教你玩 USB》
  • 简书 jianshu_kevin@126.com 的文章
    • https://www.jianshu.com/p/3afc1eb5bd32
    • https://www.jianshu.com/p/cf8e7df5ff09
    • USB 协议(三):https://www.jianshu.com/p/2a6e22194cd3
  • 官网:https://www.usb.org/documents
  • 《usb_20.pdf》的《chapter5 7 Electrical》
  • USB 的 NRZI 信号格式: https://zhuanlan.zhihu.com/p/460018993
  • USB2.0 包 Packet 的组成: https://www.usbzh.com/article/detail-459.html
4.3.1 USB 设备状态切换图

USB 2.0 协议支持 3 种速率: 低速(Low Speed,1.5Mbps)、全速(Full Speed, 12Mbps)、 高速(High Speed, 480Mbps)。

USB Hub、USB 设备, 也分为低速、全速、高速三种类型。 一个 USB 设备, 可能兼容低 速、全速, 可能兼容全速、高速, 但是不会同时兼容低速、高速。

4.3.2 硬件线路

下图是兼容高速模式的 USB 收发器电路图:

USB 连接涉及 Hub Port 和 USB 设备,硬件连接如下:

4.3.3 电子信号

USB 连接线有 4 条: 5V、D+、D-、GND。数据线 D+、D-,只能表示 4 种状态。 USB 协议 中,很巧妙地使用这两条线路实现了空闲(Idle)、开始(SOP)、传输数据(Data)、结束(EOP) 等功能。

4.3.4 低速/全速信号电平
4.3.5 高速信号电平
4.3.6 设备连接与断开
1. 连接

Hub 端口的 D+、D-都有 15K 的下拉电阻,平时为低电平。全速设备内部的 D+有 1.5K 的 上拉电阻, 低速设备内部的 D-有 1.5K 的上拉电阻,连接到 Hub 后会导致 Hub 的 D+或 D-电 平变化,Hub 根据变化的引脚分辨接进来的是全速设备还是低速设备。

高速设备一开始也是作为全速设备被识别的。

全速设备、高速设备连接时, D+引脚的电平由低变高:

低速设备连接时,D-引脚的电平由低变高:

img
img
2. 断开

对于低速、全速设备,接到 Hub 时导致 D-或 D+引脚变为高电平, 断开设备后, D-或 D+ 引脚变为低电平:

img
img

对于高速设备,它先作为全速设备被识别出来,然后再被识别为高速设备。工作于高 速模式时, D+的上拉电阻是断开的,所以对于工作于高速模式的 USB 设备, 无法通过 D+的 引脚电平变化监测到它已经断开。

工作于高速模式的设备, D+、D-两边有 45 欧姆的下拉电阻,用来消除反射信号:

img
img

当断开高速设备后, Hub 发出信号,得到的反射信号无法衰减, Hub 监测到这些信号后 就知道高速设备已经断开,内部电路图如下:

img
img
4.3.7 复位

从状态切换图上看,一个 USB 设备连接后,它将会被供电, 然后被复位。当软件出错 时,我们也可以发出复位信号重新驱动设备。

那么, USB Hub 端口或 USB 控制器端口如何发出复位信号? 发出 SE0 信号,并维持至少 10ms。

USB 设备看到 Reset 信号后,需要准备接收"SetAddress()“请求; 如果它不能回应这个 请求, 就是"不能识别的设备”。

4.3.8 设备速率识别
1. 低速/全速

Hub 端口的 D+、D-都有 15K 的下拉电阻,平时为低电平。全速设备内部的 D+有 1.5K 的 上拉电阻, 低速设备内部的 D-有 1.5K 的上拉电阻,连接到 Hub 后会导致 Hub 的 D+或 D-电

平变化,Hub 根据变化的引脚分辨接进来的是全速设备还是低速设备。

img
img
2. 高速

高速设备必定兼容全速模式, 所以高速设备内部 D+也有 1.5K 的上拉电阻, 只不过这个 电阻是可以断开的: 工作于高速模式时要断开它。

高速设备首先作为全速设备被识别出来,然后 Hub 如何确定它是否支持高速模式? Hub 端口如何监测一个新插入的 USB 设备能否工作于高速模式? 流程如下:

  • 对于低速设备,Hub 端口不会监测它能否工作于高速模式。低速设备不能兼容高速模式。
  • Hub 端口发出 SE0 信号,这就是复位信号
  • USB 设备监测到 SE0 信号后,会发出"a high-speed detection handshake"信号表示自 己能支持高速模式, 这可以细分为一下 3 种情景:
    • 如果 USB 设备原来处于"suspend"状态,它检测到 SE0 信号后, 就发出"a high- speed detection handshake"信号。
    • 如果 USB 设备原来处于"non-suspend"状态,并且处于全速模式, 它检测到 SE0 信 号后, 就发出"a high-speed detection handshake"信号。这个情景,就是一个设备刚插 到 Hub 端口时的情况,它一开始工作于全速模式。
    • 如果 USB 设备原来处于"non-suspend"状态, 并且处于高速模式,它会切换回到全 速模式(重新连接 D+的上拉电阻),然后发出"a high-speed detection handshake"信号。

“a high-speed detection handshake"信号,就是"高速设备监测握手信号”,既然是 握手信号, 自然是有来有回:

  • USB 设备维持 D+的上拉电阻,发出"Chirp K "信号, 表示自己能支持高速模式
  • 如果 Hub 没监测到"Chirp K "信号, 它就知道这个设备不支持高速模式
  • 如果 Hub 监测到"Chirp K “信号后, 如果 Hub 能支持高速模式, 就发出一系列的"Chirp K”、"Chirp J"信号,这是用来通知 USB 设备: Hub 也能支持高速模式。发出一系列的 “Chirp K”、"Chirp J"信号后,Hub 继续维持 SE0 信号直到 10ms。
  • USB 设备发出"Chirp K “信号后,就等待 Hub 回应一系列的"Chirp K”、"Chirp J"信号
    • 收到一系列的"Chirp K"、"Chirp J"信号: USB 设备端口 D+的上拉电阻,使能高速模式
    • 没有收到一系列的"Chirp K"、"Chirp J"信号: USB 设备转入全速模式
img
img
4.3.9 数据信号
img
img
1. 低速/全速的 SOP 和 EOP

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 状态。

img
img
2. 高速的 SOP

高速的 EOP 比较复杂,作为软件开发人员无需掌握。

高速模式中,Ide 状态为:D+、D-接地。SOP 格式为: 从 Idle 状态切换为 K 状态。 SOP 中的 K 状态就是 SYNC 信号的第 1 位数据。

高速模式中的 SYNC 格式为:KJKJKJKJ KJKJKJKJ KJKJKJKJ KJKJKJKK,即 15 对 KJ,外 加 2 个 K。

3. NRZI 与位填充

参考文章:USB 的 NRZI 信号格式, https://zhuanlan.zhihu.com/p/460018993

NRZI:Non Return Zero Inverted Code,反向不归零编码。 NRZI 的编码方位为:对于 数据 0,波形翻转;对于数据 1,波形不变。

img
img

使用 NRZI,发送端可以很巧妙地把"时钟频率"告诉接收端: 只要传输连续的数据 0 即 可。在下图中, 低速/全速协议中"Sync Pattern"的原始数据是"00000001",接收端从前面 的 7 个 0 波形就可以算出"时钟频率"。

img
img

使用 NRZI 时, 如果传输的数据总是"1",会导致波形维持不变。如果电平长时间维持 不变, 比如传输 100 位 1 时, 如果接收方稍有偏差,就可能认为接收到了 99 位 1、101 位 1。而 USB 中采用了 Bit-Stuffing 位填充处理,即在连续发送 6 个 1 后面会插入 1 个 0,强 制翻转发送信号,从而让接收方调整频率,同步接收。而接收方在接收时只要接收到连续 的 6 个 1 后,直接将后面的 0 删除即可恢复数据的原貌。 NRZI 数据格式如上图所示。


sidebar_position: 5


4.4 USB 协议层数据格式

参考资料:

  • 《圈圈教你玩 USB》
  • 简书 jianshu_kevin@126.com 的文章
    • https://www.jianshu.com/p/3afc1eb5bd32
    • https://www.jianshu.com/p/cf8e7df5ff09
    • USB 协议(三):https://www.jianshu.com/p/2a6e22194cd3
  • 官网:https://www.usb.org/documents
img
img
  • 《usb_20.pdf》的《chapter5 8 Protocol Layer》
  • USB 的 NRZI 信号格式: https://zhuanlan.zhihu.com/p/460018993
  • USB2.0 包 Packet 的组成: https://www.usbzh.com/article/detail-459.html
4.4.1 硬件拓扑结构
img
img

compound device :多个设备组合起来,通过 HUB 跟 Host 相连

composite device :一个物理设备有多个逻辑设备(multiple interfaces)

在软件开发过程中, 我们可以忽略 Hub 的存在,硬件拓扑图简化如下:

img
img

一个物理设备里面可能有多个逻辑设备, Hos 可以外接多个逻辑设备, 硬件拓扑图如 下:

4.4.2 协议层

要理解协议层、理解数据如何传输,带着这几个问题去看文档、看视频:

  • 如何寻址设备?
  • 如何表示数据方向(读、还是写)
  • 如何确认结果?

提前罗列出答案:

  • USB 系统是一个 Host 对应多个设备, 要传输数据首先要通知设备:
    • 发出 IN 令牌包: 表示想读数据,里面含有设备地址
    • 发出 OUT 令牌包:表示想写数据, 里面含有设备地址
  • 数据阶段:
    • Host 想读数据: 前面发出 IN 令牌包后, 现在读取数据包
    • Host 想发出数据:前面发出 OUT 令牌包后, 现在发出数据包
  • 结果如何?有握手包
    • Host 想读数据, 设备可能未就绪, 就会回应 NAK 包
    • Host 想写数据, 它发出数据后,设备正确接收了, 就回复 ACK 包
4.4.3 字节/位传输顺序

先传输最低位(LSB)。在后续文档中,描述数据时按照传输顺序从左到右列出来

4.4.4 SYNC 域

Host 发出 SOP 信号后, 就会发出 SYNC 信号:它是一系列的、最大传输频率的脉冲,接 收方使用它来同步数据。对于低速/全速设备, SYNC信号是8位数据(从做到右是00000001); 对于高速设备, SYNC信号是32位数据(从左到右是00000000000000000000000000000001)。 使用 NRZI 编码时,前面每个"0"都对应一个跳变。

在很多文档里, 把 SOP 和 SYNC 统一称为"SYNC",它的意思是"SYNC"中含有"SOP"。

4.4.5包格式
img
img

USB 总线上传输的数据以包为单位。 USB 包里含有哪些内容(“域”)?

  • SOP:用来表示包的起始
  • SYNC:用来同步时钟
  • PID:表示包的类型
  • 地址:在 USB 硬件体系中, 一个 Host 对应多个 Logical Device,那么 Host 发出的包, 如何确定发给谁?
    • 发给所有设备:包里不含有设备地址
    • 发给某个设备:包里含有设备地址、端点号
  • 帧号、数据等跟 PID 相关的内容
  • CRC 校验码

发起一次完整的传输, 可能涉及多个包。那么,第 1 个包里含有设备地址、端点号, 后续的包就没必要包含设备地址、端点号。

1. PID 域

注意: 所有的 USB 文档提到的"输入"、“输出”,都是基于 Host 的角度, "输出"表示从 Host 输出到设备,"输入"表示 Host 从设备得到数据。

有哪些 USB 包? 根据包数据里的 PID 的 bit1, bit0 可以分为 4 类:

  • 令牌包(Token):01B
  • 数据包(Data):11B
  • 握手包(Handshake):10B
  • 特殊包(Special):00B

PID 有 4 位,使用 bit1,bit0 确定分类, 使用 bit3,bit2 进一步细分。如下表(来自 《圈圈教你玩 USB》)所示:

img
img

在 USB 包中,PID 域使用 8 位来表示,格式如下:

img
img

前 4 位表示 PID,后 4 位是对应位的取反。接收方发现后 4 位不是前 4 位的取反的话, 就认为发生了错误。

2. 令牌包(Token)

令牌类 的 PID ,起 "通知作用 " ,通知谁 ?SOF 令牌包被用来通 知所有设 备, OUT/IN/SETUP 令牌包被用来通知某个设备。

对于 OUT、IN、SETUP 令牌包, 它们都是要通知到具体的设备, 格式如下:

img
img

USB 设备的地址有 7 位,格式如下:

img
img

USB 设备的端点号有 4 位, 格式如下:

img
img

对于 SOF 包,英文名为"Start-of-Frame marker and frame number"。对于 USB 全速 设备, Host 每 1ms 产生一个帧; 对于高速设备, 每 125us 产生一个微帧, 1 帧里有 8 个微 帧。 Host 会对当前帧号进行累加计数, 在每帧或每微帧开始时, 通过 SOF 令牌包发送帧号。 对于高速设备, 每 1 毫秒里有 8 个微帧,这 8 个微帧的帧号是一样的, 每 125us 发送一个 SOF 令牌包。

SOF 令牌包格式如下:

img
img
3. 数据包

Host 使用 OUT、IN、SETUP 来通知设备:我要传输数据了。数据通过"数据包"进行传 输。

数据包也有 4 种类型:DATA0、DATA1、DATA2、MDATA。其中 DATA2、MDATA 在高速设备 中使用。对软件开发人员来说,我们暂时仅需了解 DATA0、DATA1。

为什么要引入 DATA0、DATA1 这些不同类型的数据包? 为了纠错。

Host 和设备都会维护自己的数据包切换机制,当数据包成功发送或者接收时,数据包 类型切换。当检测到对方使用的数据包类型不对时,USB 系统认为发生了错误。

比如:

  • Host 发送 DATA0 给设备,设备返回 ACK 表示成功接收, 设备期待下一个数据是 DATA1
  • 但是 Host 没有接收到 ACK,Host 认为数据没有发送成功,Host 继续使用 DATA0 发送上 一次的数据
  • 设备再次接收到 DATA0 数据包, 它就知道:哦,这是重传的数据包

数据包格式如下:

img
img

对于全速设备, 数据包中的数据做大是 1023 字节;对于全速设备, 数据包中的数据做 大是 1024 字节。

4. 握手包

握手包有 4 类: ACK、NAK、STALL、NYET

  • ACK:数据接收方用来回复发送方,表示正确接收到了数据并且有足够的空间保存数据。
  • NAK:Host 发送数据给设备时, 设备可以回应 NAK 表示"我还没准备好,没办法接收数据"; Host 想读取设备的数据时, 设备可以回复 NAK 表示"我没有数据给你"。
  • STALL:表示发生了错误,比如设备无法执行这个请求(不支持该断点等待)、断点已经挂起。设备返回 STALL 后,需要主机进行干预才能接触 STALL 状态。
  • NYET:仅适用于高速设备。 Host 可以发出 PING 包用来确认设备有数据,设备可以回应 NYET 表示"还没呢"。Hub 也可以回应 NYET 表示低速/全速传输还没完结。
4.4.6 传输细节
1. 传输(Transfer)和事务(Transaction)

USB 传输的基本单位是包(Packet),包的类型由PID 表示。 一个单纯的包,是无法传输 完整的数据。

为什么?比如想输出数据,可以发出 OUT 令牌包, OUT 令牌包可以指定目的地。但是数 据如何传输呢? 还需要发出 DATA0 或 DATA1 数据包。设备收到数据后, 还要回复一个 ACK 握手包。

所以,完整的数据传输, 需要涉及多个包:令牌包、数据包、握手包。这个完整的数 据传输过程,被称为事务(Transaction)。

有些事务需要握手包,有些事务不需要握手包,有些事务可以传输很大的数据,有些 事务只能传输小量数据。

  • 有四类事务:
    • 批量事务:用来传输大量的数据,数据的正确性有保证,时效没有保证。
    • 中断事务:用来传输周期性的、小量的数据, 数据的正确性和时效都有保证。 ③ 实时事务:用来传输实时数据, 数据的正确性没有保证,时效有保证。
    • 建立事务:跟批量事务类似,只不过令牌包是 SETUP 令牌包。
  • 有四类传输(Transfer):
    • 批量传输:就是使用批量事务实现数据传输, 比如 U 盘。
    • 中断传输:就是使用中断事务实现数据传输, 比如鼠标。
    • 实时传输:就是使用实时事务实现数据传输, 比如摄像头。
    • 控制传输:由建立事务、批量事务组成,所有的 USB 设备都必须支持控制传输, 用于" 识别/枚举"
  • 暂时记住这个关系:
    • BIT 组成域(Field)
    • 域组成包(Packet)
    • 包组成事务(Transaction)
    • 事务组成传输(Transfer)
2. 过程(stage)和阶段(phase)

事务由多个包组成, 比如 Host 要发送数据给设备,这就会涉及很多个包:

  • Host 发出 OUT 令牌包, 表示要发数据给哪个设备
  • Host 发出 DATA0 数据包
  • 设备收到数据后, 回应 ACK 包

这个完整的事务涉及 3 个包(Packet),分为 3 个阶段(Phase):

  • 令牌阶段(Token phase):由令牌包实现
  • 数据阶段(Data phase):由数据包实现
  • 握手阶段(Handshake phase):由握手包实现

事务由包组成, 这些包分别处于 3 个阶段(phase):令牌阶段,数据阶段, 握手阶段。

对于批量传输、中断传输、实时传输,它们分别由一个事务组成,不再细分为若干个 过程。

但是控制传输由多个事务组成,这些事务分别处于 3 个过程: 建立过程(stage)、数据 过程(stage)、状态过程(stage)。

总结起来就是:

  • 控制传输由多个过程(stage)组成, 每个过程由一个事务来实现
  • 每个事务由多个阶段(phase)组成, 每个阶段有一个包来实现
3. 批量传输

批量传输用批量事务来实现,用于传输大量的数据, 数据的正确性有保证, 时效没有 保证。

批量事务由 3 个阶段(phase)组成: 令牌阶段、数据阶段、握手阶段。每个阶段都是一 个完整的包,含有 SOP、SYNC、PID、EOP。

下图中各个矩形框就对应一个完整的包。

img
img

《圈圈教你玩 USB》中有详细的示例:

img
img
4.中断传输

中断传输用中断事务来实现,用于传输小量的、周期性的数据,数据的正确性和时效 都有保证。

中断事务由 3 个阶段(phase)组成: 令牌阶段、数据阶段、握手阶段。每个阶段都是一 个完整的包,含有 SOP、SYNC、PID、EOP。

下图中各个矩形框就对应一个完整的包。

img
img

中断事务跟批量事务非常类似,Host 使用它来周期性地读数据、写数据。

以鼠标为例,我们需要及时获得鼠标的数据, 不及时的话你会感觉鼠标很迟钝。但是 USB 协议中并没有中断功能,它使用"周期性的读、写"来实现及时性。具体过程如下:

  • Host 每隔 n 毫秒发出一个 IN 令牌包
  • 鼠标有数据的话,发出 DATA0 或 DATA1 数据包给 Host;鼠标没有数据的话,发出 NAK 给 Host。

中断事务的优先级比批量事务更高,它要求实时性,而批量事务不要求实时性。

5.实时传输

实时传输用实时事务来实现, 用于传输实时数据, 对数据的正确性没有要求。

实时事务由 2 个阶段(phase)组成: 令牌阶段、数据阶段。每个阶段都是一个完整的包, 含有 SOP、SYNC、PID、EOP。

实时事务不需要握手阶段,一个示例的场景是:为了传输摄像头的实时数据,偶尔的 数据错误是可以忍受的,大不了出现短暂的花屏。如果为了解决花屏而重传数据, 那就会

导致后续画面被推迟,实时性无法得到保证。

下图中各个矩形框就对应一个完整的包。

img
img

实时事务跟中断事务非常类似,Host 也会周期性的发起实时事务,主要区别在于:

  • 实时事务不要求准确性,没有握手阶段
  • 实时事务传输的数据量比较大, 中断事务传输的数据量比较小
6. 控制传输

在使用批量传输时, 使用 IN 令牌包或 OUT 令牌包表示数据传输方向。

控制传输的令牌包永远是 SETUP,怎么分辨是读数据, 还是写数据? 发出 SETUP 令牌包 后,还要发出 DATA0 数据包,根据数据的内容来确定后续是读数据,还是写数据。这个过 程称为"建立事务"(SETUP Transaction)

但是控制传输由多个事务组成,这些事务分别处于 3 个过程: 建立过程(stage)、数据 过程(stage)、状态过程(stage)。

  • 建立过程(stage),使用 SETUP 事务:Host 发出 SETUP 令牌包、DATA0 数据包、得到 ACK 握手包
  • 数据过程(stage),使用批量事务:
    • 对于输出:Host 发出 OUT 令牌包,发出 DATA0、DATA1 数据包、得到 ACK 握手包
    • 对于输入:Host 发出 IN 令牌包,读到 DATA0、DATA1 数据包、发出 ACK 握手包 ③ 状态过程(stage),使用批量事务:
    • 对于输出:Host 发出 IN 令牌包,读到 DATA1 数据包,发出 ACK 握手包 b. 对于输入:Host 发出 OUT 令牌包,发出 DATA1 数据包,等待 ACK 握手包
img
img

上图中的每一个方框,都是一个完整的事务, 含有: Token Packet、Data Packet、 Handshake Packet。

4.4.7 使用工具体验数据格式

LeCroy(力科)成立于 1964 年, 是一家专业生产示波器厂家。旗下生产有数字示波器、 SDA 系列数字示波器、混合信号示波器、模块化仪器、任意波形发生器。

官网是:https://teledynelecroy.com/,似乎无法注册新用户,无法下载软件。 可以在搜索引擎里搜"usbprotocolsuite"。

安装"usbprotocolsuite"后, 可以在文档目录里找打很多示程序(后缀名为 usb):

img
img

使用"usbprotocolsuite"打开这些文件,即可体验 USB 数据传输:

img
img

4.5 USB 描述符

4.5.1
1. USB 设备状态切换图
img
img
img
img
4.5.2 标准设备请求
1.SETUP事务的数据格式

Host 使用控制传输来识别设备、设置设备地址、启动设备的某些特性, 对于控制传输, 它首先发出"setup 事务",如下:

img
img

在"setup 事务"中,

  • SETUP 令牌包:用来通知设备, “要开始传输了”
  • DATA0 数据包:它含有固定的格式, 用来告诉设备"是读还是写"、“读什么”、“写什么”

Host 通过 DATA0 数据包发送 8 字节数据给设备,它的格式如下图所示:

img
img
2. 标准设备请求

控制传输的建立事务中, 可以使用下列格式的数据:

img
img

上表中各个"宏"取值如下:

img
img
3. 设备/配置/接口/端点

在 SETUP 事务的数据里, 表示了要访问的是什么: Device?Interface?Endpoint?

对于一个USB 设备, 它可以多种配置(Configuration)。比如4G 上网卡就有 2 种配置: U 盘、上网卡。第 1 次把 4G 上网卡插入电脑时,它是一个 U 盘,可以按照里面的程序。装 好程序后, 把它再次插入电脑,它就是一个上网卡。驱动程序可以选择让它工作于哪种配 置,同一时间只能有一种配置。大多数的 USB 设备只有一种配置。

一个配置下,可以有多个接口(Interface),接口等同于功能(Function)。比如 USB 耳 机有两个接口(功能):声音收发、按键控制。

一个接口, 可能有多个设置(Setting),比如默认设置下它使用较低的带宽, 可以选择 其他设置以使用更高带宽。

一个接口, 由一个或多个端点(Endpoint)组成。端点 0 属于整个设备的, 端点 0 是双 向的。接口还可以有其他端点, 这些端点是单向的, 要么是批量(Bulk)端点、要么是中断 (Interrupt)端点、要么是同步(Isochronous)端点。

4.5.3 描述符

怎么描述设备、配置、接口、端点?使用描述符(Descriptors),有设备描述符、配置 描述符、接口描述符、端点描述符。所谓描述符,就是一些格式化的数据, 用来描述信息。

一个 USB 设备:

  • 只有一个设备描述符:用来表示设备的 ID、它有多少个配置、它的端点 0一次最大能传 输多少字节数据
  • 可能有多个配置描述符:用来表示它有多少个接口、供电方式、最大电流
  • 一个配置描述符下面,可能有多个接口描述符:用来表示它是哪类接口、有几个设置 (Setting)、有几个端点
  • 一个接口描述符符下面,可能有多个端点描述符: 用来表示端点号、方向(IN/OUT)、类 型(批量/中断/同步)

还有一些字符串描述符(String descriptors),它用可读的文字来描述设备,是可选 的。

img
img
1. 设备描述符
img
img
2. 配置描述符
img
img
3. 接口描述符
img
img
4. 端点描述符
img
img
5.示例

Ubuntu 中可以执行 lsusb -v查看 USB 设备的描述符信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
4.5.4 设备枚举过程示例

使用"usbprotocolsuite"打开,可以看到设备的枚举过程:

  • 使用控制传输,读取设备信息(设备描述符):第一次读取时, 它只需要得到 8 字节数据, 因为第 8 个数据表示端点 0 能传输的最大数据长度。
img
img
  • Host 分配地址给设备, 然后把新地址发给设备:
img
img
  • 使用新地址, 重新读取设备描述符, 设备描述符长度是 18:
img
img
  • 读取配置描述符: 它传入的长度是 255,想一次性把当前配置描述符、它下面的接口描 述符、端点描述符全部读出来
img
img
  • 读取字符描述符
img
img

4.6 USBX 组件

4.6.1 Azure RTOS 介绍

Azure RTOS 平台是运行时解决方案的集合,包括 Azure RTOS ThreadX、Azure RTOS NetX 和 NetX Duo、Azure RTOS FileX、Azure RTOS GUIX 和 Azure RTOS USBX。

img
img

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 透明支持。

4.6.2 USBX 层次

参考资料:

https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX

USBX 分为三层, 如下图所示:

  • 控制器层:最底层,USB 设备控制器的驱动程序,通常是 HAL 库
  • stack layer:实现 USB 设备的基本操作,比如描述符的操作、使用 endpoint 进行数据 传输
  • Class layer:实现各类 USB 设备的操作,比如 HID 设备、音频设备、虚拟串口,给 APP 提供接口
img
img

在 STM32 的固件中, 可以看到 USBX 目录,比如:

img
img

移植 Controller layer、stack layer、Class layer 并不复杂, 重点在于 2 点:

  • 怎么初始化硬件以确保 Controller layer 可以正常运行
  • 怎么编写 APP:提供设备信息、传输数据
4.6.3 USBX 的基本配置

USBX 依赖于 Azure®RTOS ThreadX,但是也可以单独使用 USBX,这需要配置。通常在 “ux_user.h”里进行配置,配置项如下:

  • 使用单独模式或 RTOS 模式:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* Defined, this macro will enable the standalone mode of usbx.  */
#define UX_STANDALONE

当没有定义“UX_STANDALONE”时就是使用 RTOS 模式, 可以使用 ThreadX 提供的互斥 量函数实现阻塞式读写(“blocking”), 比如对于 USB 虚拟串口, 可以使用如下函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

它们只是发起传输,然后就即刻返回。需要提供回调函数,在回调函数里分辨数据是 否传输完成。

  • 非阻塞模式:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* 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 的阻塞函数。

换句话说, 要使用单独模式的非阻塞函数, 就不能定义这个配置项。

  • USB HOST/Device 模式
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* 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。

4.7 移植 USBX 实现虚拟串口

本节程序源码为“3_程序源码01_视频配套的源码 4-7_移植 USBX 实现虚拟串口 uart_usb.7z”,在上一节代码 uart_rtos.7z 的基础上修改得来。

移植 Controller layer、stack layer、Class layer 并不复杂, 重点在于 2 点:

  • 怎么初始化硬件以确保 Controller layer 可以正常运行
  • 怎么编写 APP:提供设备信息、传输数据
4.7.1 配置 USB
img
img
4.7.2 添加 USBX 代码
1. 复制代码

找到固件库,如下:

img
img

把 usbx 整个目录复制到工程“MiddlewaresThird_Party”目录下, 如下:

img
img
2. 添加进工程

需要添加 USBX 的 3 层源码。

先仿照下图添加“Class layer”源码,添加含有“ux_device_class_cdc_acm ”前缀 的 C 文件:

img
img

再仿照下图添加“stack layer”源码,可以从文件名的前面看出它们的作用, 比如 “ ux_device_stack ”表示这是 stack 源码,“ ux_utility ”表示这 是 辅助 函数 , “ux_system”表示是这是系统函数:

img
img

最后仿照下图添加“Controller layer”, 添加“ux_dcd_stm32”前缀的 C 文件:

img
img
4.7.3 添加 USBX APP 代码

参考工程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STM32CubeH5\Projects\NUCLEO-H563ZI\Applications\USBX\Ux_Device_HID_CDC_ACM

在网盘资料中, 找到如下目录:

img
img

把 app 文件夹复制到工程的“MiddlewaresThird_Partyusbx”目录下, 如下图所示:

img
img

各个文件的作用为:

  • ux_user.h:配置 USBX
  • ux_stm32_config.h:里面含有配置项, 表示 STM32 支持多少个 endpoint
  • ux_device_descriptors.c/h:USB 虚拟串口的描述符信息
  • ux_device_cdc_acm.c:USB 串口的 Activate/DeActivate 函数
  • app_usbx_device.c:调用 stack layer 函数, 模拟 USB 串口

在工程里添加上述文件, 如下图所示:

img
img
4.7.4 修改 usb.c

使用 STM32CubeMX 配置 usb 后生成的 usb.c 里,只是初始化了 USB 控制器,并未启动 它,也没有跟 USBX 建立联系, 需要修改代码。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 控制器。

4.7.5 创建 USBX 任务

使 用 单 独 模 式 (STANDALONE ) 时 , 需 要 创 建 一 个 任 务 , 不 断 运 行 “_ux_system_tasks_run ”函数。以下代码是在 FreeRTOS 的默认任务里运行和这个函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的系统函数。

4.7.6 设置 MDK-ARM 工程

如下图配置:

  • 添加宏开关: UX_INCLUDE_USER_DEFINE_FILE(图中标号 2)
  • 添加头文件目录(图中标号 5)
img
img
4.7.7 添加使用串口的代码

在“CoreSrcapp_freertos.c”里添加 USB 串口的发送测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 : %d\r\n", 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”中,有如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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] = '\0';
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 上显示处来。

4.7.8 上机实验

烧写运行程序后,接上 USB 线,在电脑上可以识别出 USB 串口,查看设备管理器,可 以看到如下设备:

img
img

使用串口工具打开这个串口, 可以连续不断接收到数据,如下所示:

img
img

在串口工具上发送数据时,在板子的 LCD 上会有显示。

4.8 虚拟串口源码分析与改造

本节程序源码为“3_程序源码\01_视频配套的源码\ 4-8_虚拟串口源码分析与改造 \uart_usb_freertos.7z”,在上一节代码 uart_usb.7z 的基础上修改得来。

4.8.1 描述符的设置

在“Middlewares\Third_Party\usbx\app\ux_device_descriptors.c”有设备描述符、 配置描述符、接口描述符、端点描述符的定义。

比如, 设备描述符在如下代码中设置:

配置描述符在如下代码中设置:

4.8.2 数据收发函数

涉及文件为:demo\Middlewares\Third_Party\usbx\app\ux_device_cdc_acm.c。 开发板通过 USB 串口发出数据时, 使用以下函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* 启动发送 */
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”唤醒:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int ux_device_cdc_acm_send(uint8_t *datas, uint32_t len, uint32_t timeout);

开发板接收到 USB 串口数据时,以下回调函数被调用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

我们可以改造这个函数, 把接收到的数据写入队列。

4.8.3 使用 FreeRTOS 改造代码

对于发送, 实现以下函数:启动发送之后阻塞,等待回调函数唤醒或超时。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

对于接收, 实现以下函数:把接收到的数据写入队列。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

然后提供这个函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int ux_device_cdc_acm_getchar(uint8_t *pData, uint32_t timeout);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
USB协议简介「建议收藏」
https://blog.csdn.net/songze_lee/article/details/77658094
全栈程序员站长
2022/09/13
2.7K0
USB协议基本知识[通俗易懂]
USB 基本知识 USB的重要关键概念: 1、 端点:位于USB设备或主机上的一个数据缓冲区,用来存放和发送USB的各种数据,每一个端点都有惟一的确定地址,有不同的传输特性(如输入端点、输出端点、配置端点、批量传输端点) 2、 帧:时间概念,在USB中,一帧就是1MS,它是一个独立的单元,包含了一系列总线动作,USB将1帧分为好几份,每一份中是一个USB的传输动作。 3、upstream、downstream(上行、下行):设备到主机为上行,主机到设备为下行
全栈程序员站长
2022/07/02
3.6K0
USB协议基本知识[通俗易懂]
全速USB和高速USB的识别过程分析
我们知道USB2.0向下兼容USB1.x,即高速2.0的hub能支持所有的速度类型的设备,而USB1.x的hub不能支持高速设备(High Speed Device)。因此,如果高速设备挂到USB1.x的hub上,那该设备只能工作在全速模式下。不管是hub还是设备(device),对于速度的区分是非常重要的,否则,后续的通信根本无法进行。
233333
2021/01/20
4.9K0
全速USB和高速USB的识别过程分析
USB 描述符和 USB 枚举
Host 使用控制传输来识别设备、设置设备地址、启动设备的某些特性,对于控制传输,它首先发出"setup 事务",如下:
Jasonangel
2023/08/22
8110
USB 描述符和 USB 枚举
Linux驱动开发: USB驱动开发
USB是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,USB就是简写,中文叫通用串行总线。最早出现在1995年,伴随着奔腾机发展而来。自微软在Windows 98中加入对USB接口的支持后,USB接口才推广开来,USB设备也日渐增多,如数码相机、摄像头、扫描仪、游戏杆、打印机、键盘、鼠标等等,其中应用最广的就是摄像头和U盘了。
DS小龙哥
2022/01/12
72.2K0
Linux驱动开发: USB驱动开发
USB通信基础知识
主机:提供USB接口和接口管理功能的硬件、软件、固件的复合体。PC机或OTG设备,一个USB系统只能有一个主机
心跳包
2021/09/26
2.3K0
USB通信基础知识
单片机外围模块漫谈之四,USB总线基本概念。
USB设备现在是用的非常普遍的一种接口了,它即插即用的特性给人们带来了很大的方便。在嵌入式的应用中, USB经常被用来作为与上位机通信的接口,还用来通过U盘存储数据等。USB按通讯速度可分为低速,全速和高速设备。在我们的应用中,低速和全速是最为普遍的,在此我们对USB从物理层到协议层做一个简要的介绍。高速USB的原理是一样的,在理解了低速和全速设备的工作原理后再去理解高速设备就比较简单了,在此我们暂不讨论。
用户2366192
2021/05/31
8680
13.USB驱动
问1. 既然还没有"驱动程序",为何能知道是"android phone" 答1. windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone" 提示你安装的是"设备驱动程序" USB总线驱动程序负责:识别USB设备, 给USB设备找到对应的驱动程序
嵌入式与Linux那些事
2021/05/20
2.2K0
19.Linux-USB总线驱动分析
本文总结了USB总线驱动程序的实现原理和流程。首先介绍了USB总线驱动程序的基本概念和作用,然后详细阐述了USB总线驱动程序的实现流程,包括设备加载、设备初始化、设备配置、设备接口、端点、读写请求、中断和轮询机制。最后对USB总线驱动程序中涉及到的几个重要概念进行了详细说明。通过本文的总结,可以更好地理解USB总线驱动程序的实现原理和流程,为后续的USB驱动开发打下坚实的基础。
诺谦
2018/01/03
9.3K0
19.Linux-USB总线驱动分析
【STM32H7】第2章 ThreadX USBX协议栈介绍
教程更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=108546 第2章 ThreadX USBX协议栈介绍 本章节介绍 ThreadX
Simon223
2021/10/20
7060
USB总线-USB协议简介(一)
USB是通用串行总线(Universal Serial Bus)的缩写。在USB总线出现之前,计算机与键盘、鼠标、扫描仪、打印机都使用专用的接口连接,不同设备的接口不能互用,扩展性很差,每次插拔设备都要关闭计算机,不支持热插拔,且通信速率很低。为了解决上述问题,USB总线诞生了。USB总线就好像一条管道,管道里流过的东西只要符合USB协议,至于具体流的是什么东西,USB总线并不关心,可以是自来水,可以是污水,可以是天然气,也可以是石油。对应具体的设备上,只要是支持USB协议的设备,都可以连接计算机,如USB键盘、USB鼠标、USB摄像头、USB音箱等。USB的出现简化了计算机与外围设备的连接,增强了扩展性,支持热插拔,且通信速度很快。
233333
2022/12/05
6.7K0
USB总线-USB协议简介(一)
USB通信协议深入理解
https://blog.csdn.net/g200407331/article/details/51682597/
233333
2022/05/10
1.1K0
带你遨游USB世界
USB的全称是Universal Serial Bus,通用串行总线。它的出现主要是为了简化个人计算机与外围设备的连接,增加易用性。USB支持热插拔,并且是即插即用的,另外,它还具有很强的可扩展性,传输速度也很快,这些特性使支持USB接口的电子设备更易用、更大众化。
233333
2020/07/21
2.8K0
带你遨游USB世界
USB 电气信号
USB Hub、USB 设备,也分为低速、全速、高速三种类型。一个 USB 设备,可能兼容【低速、全速】,可能兼容【全速、高速】,但是不会同时兼容低速、高速。
Jasonangel
2023/08/22
4780
USB 电气信号
硬件笔记(8)----USB学习笔记5
如前面所述,当某个设备被连接到 USB 主机上,该设备会向主机提供其功能和电源要求。通常,设备会通过一个描述符表格(其固件的一部分)来提供这些信息。描述符表格是数据的结构化序列,描述了设备信息;这些值由开发人员定义。所有描述符表格都具有一个标准信息,用于介绍设备属性和电源要求。如果某个设计满足指定 USB 设备类别的要求,则该 USB 设备必须具备的其他描述符信息都将包含在设备描述符结构中。附录 A 包含一个 PSoC USB 的全功能设备描述符的示例。如果您正在阅读或创建您自己的描述符,那么请注意,传输数据字段时,优先传输最低有效位。许多参数的长度均为 2个字节。请确保先发送低字节,然后再发送高字节。
小火柴棒
2019/07/02
1.8K0
手把手教你制作DAPLink
这篇文章主要描述利用RT-THREAD+CherryUSB制作DapLink调试器(R_DapLink)全流程。这里先感谢网友:sakumisu提供cherryUSB协议栈的技术支持。
Rice加饭
2023/12/26
2.2K0
手把手教你制作DAPLink
USB总线-Linux内核USB3.0设备控制器之UDC驱动分析(六)
UDC驱动的接口都定义在drivers/usb/gadget/udc/core.c文件中。USB Function驱动通过调用这些接口匹配及访问USB设备控制器,而底层USB控制器驱动要实现这些接口定义的功能。下面分析一下主要的UDC驱动接口调用流程。
233333
2022/12/31
10.3K0
USB总线-Linux内核USB3.0设备控制器之UDC驱动分析(六)
【专业技术】USB体系结构
近来基于MSC类协议做了一个模拟U盘实现USB读写功能的项目,看到一个对USB框架讲得不错的文章,这里转载过来,方便需要的人看看。当然USB协议是一个很庞大的工程,这篇只是一个提纲契领的作用,如要深入研究还是需要认真学习相关协议。 USB总线接口层:物理连接、电气信号环境、信息包传输机制;主机一方由USB主控制器和根集线器组成,而USB方则由设备中的USB接口组成。 USB设备层:由主机方的USB系统软件和设备方的USB设备逻辑视图组成。 USB功能层:代表客户软件和一个给定的设备功能接口之间的关系。 US
程序员互动联盟
2018/03/14
1.3K0
usb协议开发_基于事件驱动的架构
大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说usb协议开发_基于事件驱动的架构,希望能够帮助大家进步!!!
Java架构师必看
2022/06/15
2.1K0
usb协议开发_基于事件驱动的架构
USB 协议层数据格式
一个物理设备里面可能有多个逻辑设备,Host 可以外接多个逻辑设备,硬件拓扑图如下:
Jasonangel
2023/08/22
5570
USB 协议层数据格式
相关推荐
USB协议简介「建议收藏」
更多 >
LV.1
深圳百问网科技有限公司技术总监
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验