一、USB 描述符
1.1 USB 设备状态切换图
Attached为USB设备插上电脑,Powered是Host为其供电,接着Host需要去识别该设备为全速、高速还是低速设备,于是会发起一个复位(Reset),在复位过程中会识别设备速率,设备会进入默认状态(Default),一个处于默认状态的设备,其USB地址为0,以后主机会使用地址0与其进行通信,会访问地址为0的设备,访问端点0,将分配的新地址发给设备(Address Assigned),让设备工作与新地址模式下,此时Address会等于某一个新的值,以后Host会使用这个新地址与该设备进行通信,该设备可能会有多种配置,即使只有一种配置我们也需要对其进行配置(Device Configured),工作于某一种配置下,设就是设备的枚举过程。
1.2 标准设备请求
1.2.1 SETUP事务的数据格式
Host 使用控制传输来识别设备、设置设备地址、启动设备的某些特性, 对于控制传输, 它首先发出"setup 事务",如下:
在"setup 事务"中,
-
SETUP 令牌包:用来通知设备, “要开始传输了”
-
DATA0 数据包:它含有固定的格式, 用来告诉设备"是读还是写"、“读什么”、“写什么”
Host 通过 DATA0 数据包发送 8 字节数据给设备,它的格式如下图所示:
在bRequest可以列出自己的请求,是想去读描述符还是设置描述符,想去设置地址等等,比如去设置地址,地址的位置应该是wValue,wValue的数依赖于bRequest,windex也是依赖于bReaquest,wLength为数据长度,上面图片中set up事务的数据格式,其中Data Stage的长度(wLength)为多少,由建立事务中最后两个字节决定,在建立事务中,总共传输了8字节的数据,其中最后2字节决定了在数据阶段要传输的数据长度。(第一个bmRequestType表示请求类型)
1.2.2 标准设备请求
控制传输的建立事务中, 可以使用下列格式的数据:
上表中各个"宏"取值如下:
1.2.3 设备/配置/接口/端点
在 SETUP 事务的数据里, 表示了要访问的是什么: Device?Interface?Endpoint?
对于一个USB 设备, 它可以多种配置(Configuration)。比如4G 上网卡就有 2 种配置: U 盘、上网卡。第 1 次把 4G 上网卡插入电脑时,它是一个 U 盘,可以按照里面的程序。装好程序后, 把它再次插入电脑,它就是一个上网卡。驱动程序可以选择让它工作于哪种配 置,同一时间只能有一种配置。大多数的 USB 设备只有一种配置。
一个配置下,可以有多个接口(Interface),接口等同于功能(Function)。比如 USB 耳机有两个接口(功能):声音收发、按键控制。
一个接口, 可能有多个设置(Setting),比如默认设置下它使用较低的带宽, 可以选择其他设置以使用更高带宽。
一个接口, 由一个或多个端点(Endpoint)组成。端点 0 属于整个设备的, 端点 0 是双向的。接口还可以有其他端点, 这些端点是单向的, 要么是批量(Bulk)端点、要么是中断 (Interrupt)端点、要么是同步(Isochronous)端点。
1.3 描述符
怎么描述设备、配置、接口、端点?使用描述符(Descriptors),有设备描述符、配置 描述符、接口描述符、端点描述符。所谓描述符,就是一些格式化的数据, 用来描述信息。
一个 USB 设备:
-
只有一个设备描述符:用来表示设备的 ID、它有多少个配置、它的端点 0一次最大能传输多少字节数据(为什么把端点0放在设备描述符这:因为一个设备刚插进电脑时,电脑以及Host软件都是与端点0进行传输的)
-
可能有多个配置描述符:用来表示它有多少个接口、供电方式、最大电流
-
一个配置描述符下面,可能有多个接口描述符:用来表示它是哪类接口、有几个设置 (Setting)、有几个端点
-
一个接口描述符符下面,可能有多个端点描述符: 用来表示端点号、方向(IN/OUT)、类型(批量/中断/同步)
还有一些字符串描述符(String descriptors),它用可读的文字来描述设备,是可选的。
1.3.1 设备描述符
1.3.2 配置描述符
1.3.3 接口描述符
1.3.4 端点描述符
1.3.5 示例
在 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
1.4 设备枚举过程示例
使用"usbprotocolsuite"打开,可以看到设备的枚举过程:
- 使用控制传输,读取设备信息(设备描述符):第一次读取时, 它只需要得到 8 字节数据, 因为第 8 个数据表示端点 0 能传输的最大数据长度。
①、设置令牌包表明要跟0号设备的0号端点发起数据传输
②、Host发出一个Data0数据包 0x80表示bit7为1 1为Device到Host 表明想读取Device中的数据 第二个06表示相关操作 在9-3图表中可知为获得描述符 接下来的00 01为描述符的类型 在9-5图表中查询可知该描述符为设备描述符 最后的40 00表示数据的长度 十六进制就是64 表示读取64分字节的长度 第一部分就是Host想从Device中读取64字节的设备描述符
③、Device接收到请求后 需要将设备描述符发送给Host 此时Host发送一个读数据包(IN)的操作 首先发送一个IN令牌包 表明要读取数据 但Device正忙 回复了一个NAK握手包
④、Host过后再次发送一个读取数据的令牌包 表明要读取数据 Device不忙于是将数据返回给Host 返回的是18字节的数据(看到9-8图表可知设备描述符的长度为18字节)
⑤、最后就是状态阶段 Host向Device发送一个OUT令牌包 同时发送0字节数据 等到Device向Host发送一个ACK握手包 说明Host读取数据完毕
- Host 分配地址给设备, 然后把新地址发给设备:
- 使用新地址, 重新读取设备描述符, 设备描述符长度是 18:
- 读取配置描述符: 它传入的长度是 255,想一次性把当前配置描述符、它下面的接口描 述符、端点描述符全部读出来
- 读取字符描述符
二、USBX 组件
2.1 Azure RTOS 介绍
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 透明支持。
2.2 USBX 层次
参考资料:
https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX
USBX 分为三层, 如下图所示:
- 控制器层:最底层,USB 设备控制器的驱动程序,通常是 HAL 库
- stack layer:实现 USB 设备的基本操作,比如描述符的操作、使用 endpoint 进行数据传输
- Class layer:实现各类 USB 设备的操作,比如 HID 设备、音频设备、虚拟串口,给 APP 提供接口
在 STM32 的固件中, 可以看到 USBX 目录,比如:
移植 Controller layer、stack layer、Class layer 并不复杂, 重点在于 2 点:
- 怎么初始化硬件以确保 Controller layer 可以正常运行
- 怎么编写 APP:提供设备信息、传输数据
2.3 USBX 的基本配置
USBX 依赖于 Azure®RTOS ThreadX,但是也可以单独使用 USBX,这需要配置。通常在 “ux_user.h”里进行配置,配置项如下:
- 使用单独模式或 RTOS 模式:
/* 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 的阻塞函数。
换句话说, 要使用单独模式的非阻塞函数, 就不能定义这个配置项。
- USB HOST/Device 模式
/* 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。
三、移植 USBX 实现虚拟串口
移植 Controller layer、stack layer、Class layer 并不复杂, 重点在于 2 点:
- 怎么初始化硬件以确保 Controller layer 可以正常运行
- 怎么编写 APP:提供设备信息、传输数据
3.1 配置 USB
3.2 添加 USBX 代码
3.2.1 复制代码
找到固件库,如下:
把 usbx 整个目录复制到工程“MiddlewaresThird_Party”目录下, 如下:
3.2.2 添加进工程
需要添加 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 文件:
3.3 添加 USBX APP 代码
参考工程:
STM32CubeH5\Projects\NUCLEO-H563ZI\Applications\USBX\Ux_Device_HID_CDC_ACM
在网盘资料中, 找到如下目录:
把 app 文件夹复制到工程的“MiddlewaresThird_Partyusbx”目录下, 如下图所示:
各个文件的作用为:
-
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 串口
在工程里添加上述文件, 如下图所示:
3.4 修改 usb.c
使用 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 }
第24~26:包含相关头文件。
第 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 控制器。
3.5 创建 USBX 任务
使 用 单 独 模 式 (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 的系统函数。
3.6 设置 MDK-ARM 工程
如下图配置:
- 添加宏开关: UX_INCLUDE_USER_DEFINE_FILE(图中标号 2)
- 添加头文件目录(图中标号 5)
3.7 添加使用串口的代码
在“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 : %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”中,有如下代码:
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, 0x0000ffff, 0);
118 }
119 return 0;
120 }
当 USB 串口收到数据后, ux_device_class_cdc_acm_read_callback 函数被调用。 第 117 行把接收到的数据在 LCD 上显示处来。
3.8 上机实验
烧写运行程序后,接上 USB 线,在电脑上可以识别出 USB 串口,查看设备管理器,可 以看到如下设备:
在这里插入图片描述
使用串口工具打开这个串口, 可以连续不断接收到数据,如下所示:
在串口工具上发送数据时,在板子的 LCD 上会有显示。
四、虚拟串口源码分析与改造
本节程序源码为“3_程序源码\01_视频配套的源码\ 4-8_虚拟串口源码分析与改造 \uart_usb_freertos.7z”,在上一节代码 uart_usb.7z 的基础上修改得来。
4.1 描述符的设置
在“Middlewares\Third_Party\usbx\app\ux_device_descriptors.c”有设备描述符、 配置描述符、接口描述符、端点描述符的定义。
比如, 设备描述符在如下代码中设置:
成功对应上产家ID和产品ID。
配置描述符在如下代码中设置:
4.2 数据收发函数
涉及文件为:demo\Middlewares\Third_Party\usbx\app\ux_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);
我们可以改造这个函数, 把接收到的数据写入队列。
4.3 使用 FreeRTOS 改造代码
对于发送, 实现以下函数:启动发送之后阻塞,等待回调函数唤醒或超时。
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);
顺序:ux_device_cdc_acm_send 启动传输 -> ux_device_class_cdc_acm_write_with_callback 调用此回调函数 -> ux_device_class_cdc_acm_write_callback 发送完成回调函数会被调用并释放信号量 -> pdTRUE == xSemaphoreTake(g_xUSBUARTSend, timeout) 得到信号量被唤醒 -> ux_device_class_cdc_acm_read_callback 此回调函数会被调用 当读取到数据时将数据写入队列中 -> ux_device_cdc_acm_getchar 此函数获取队列中的内容于是主函数调用并打印。