USB 设备编程_2

USB 设备类型之 CDC (Communication Device Class)

CDC 概述

CDC(Communication Device Class,通信设备类)是 USB 设备类规范中专门用于 USB 通信设备的一类。其主要目的是为各种需要通信功能的设备(如调制解调器、网络适配器、虚拟串口设备等)提供一个统一、标准化的接口,使得主机能够识别并正确地与这些设备进行数据交换和控制。CDC 类中不仅定义了数据传输的基本机制,还包括一些特定的控制和管理命令,确保设备能按照既定的协议工作。

根据 CDC 类所针对通信设备的不同,CDC 类又被分成以下不同的模型:

  • USB 传统纯电话业务(POTS)模型,USB ISDN 模型和 USB 网络模型。
    其中,USB 传统纯电话业务模型,又可分为直接线控制模型(Direct Line Control Model)、抽象控制模型(Abstract Control Model)和 USB 电话模型(USB Telephone Model)

我们使用的 USB 转串口就是 USB CDC 设备。

依附在总线上的设备可以是完全定制的设备并根据需要提供特定的驱动程序,也可能属于某个设备类别。这些类别定义设备的行为和接口描述符,这样一个驱动程序可能用于所有此种类别的设备。一般操作系统都支持这些设备类别,为其提供通用驱动程序。

设备类型由 USB 工作组决定,并分配ID。

一些已经定义的类代码只允许在设备描述符中使用,其他类代码可以在设备和接口描述符中使用,而另外一些已经定义的类代码只能在接口描述符中使用。
详细参考 Defined Class Codes


在一个设备上有两个地方可以显示类代码信息。

一个位置在设备描述符中。另一个在接口描述符中。

USB 设备类型信息包由三个字节组成,分别是基类、子类和协议。

如果一个设备类型属于整个设备,该设备的描述符的 bDeviceClass 域保存类别 ID;如果它只是设备的一个接口,其 ID 保存在接口描述符的 bInterfaceClass 域。他们都占用一个字节,所以最多有 253 种设备类别(0x00和0xFF保留)。当 bDeviceClass 设为 0x00,操作系统会自动检查每个接口的 bInterfaceClass 以确定其接口的类别。

在设备描述符中使用 bDeviceClass,bDeviceSubClass,bDeviceProtocol 字段来标识。
在接口描述符中使用 bInterfaceClass, bInterfaceSubClass,bInterfaceProtocol字段来标识。

每种类别可选支持子类别(SubClass)和协议子定义(Protocol subdefinition)。这样可以用于主设备类型的不断修订。
使用这种 USB 定义类代码信息,可以识别设备的功能并基于该功能加载设备驱动程序。

如下链接中的表显示了当前已经定义的基类值集、泛型用法以及基类的使用位置(设备或接口描述符或两者)

Defined Class Codes

CDC 实现

USB CDC 设备类别编号为0x0A,且必须指定为接口设备类。

USB CDC 类设备是一个复合通用串行总线设备类。

USB CDC 用于类似于网卡的计算机网络设备,提供用于将以太网或 ATM 帧传输到某些物理介质的接口。它还用于调制解调器、ISDN、传真机和电话应用程序以执行常规语音呼叫。

此类设备也在嵌入式系统(例如移动电话)中实现,因此电话可以用作调制解调器、传真或网络端口。数据接口通常用于执行批量数据传输。


USB CDC 支持范围广泛,可以执行电信和网络功能的设备。通讯设备的例子有:

  • 使用 CDC 的 ACM(抽象控制模型)子类模拟虚拟 COM 端口。
  • 使用 CDC 的 ACM(抽象控制模型)子类使用 RDNIS 协议模拟网络连接。这支持 Windows 主机 PC 和嵌入式设备之间的 USB 网络连接,以及 USB 设备 RNDIS 到以太网桥应用程序。
  • 使用 CDC 的 NCM(网络控制模型)子类模拟以太网适配器(仅适用于USB 设备)。使用 CDC NCM 可以在基于 Linux 的主机系统上创建 Ethernet-over-USB(适用于 Linux 主机)应用程序。

通信设备具有三个基本任务:

  • 设备管理(控制配置特定设备并通知 USB 主机某些事件)
  • 呼叫管理(建立和终止电话呼叫或其他连接)
  • 数据传输(发送和接收应用数据)

USB 组件中的 CDC 实现具有:

  • 使用 CDC ACM(抽象控制模型)子类模拟虚拟 COM 端口(VCP)。
  • 使用 CDC ACM(抽象控制模型)子类使用 RDNIS 协议模拟网络连接。
  • 使用 CDC NCM(网络控制模型)子类模拟以太网适配器(仅适用于USB 设备)。

CDC 之 ACM (Abstract Control Model) 子类

ACM(Abstract Control Model)是 USB CDC 类中的一个子类,主要用于提供一种虚拟串口(VCP, Virtual COM Port)通信的接口。它的目的是让 USB 设备能够模拟传统串口设备(如 RS-232 / RS-485 串口通信设备),通过 USB 接口实现串行通信功能。ACM 子类在 CDC 类中扮演了非常重要的角色,尤其是在需要将设备与主机连接并通过串口协议交换数据的应用中。


ACM 子类用于模拟串口的控制模型,能够通过 USB 接口实现串口通信。其主要作用如下:

  • 虚拟串口通信:
    ACM 子类允许 USB 设备在主机系统上作为虚拟串口(COM 端口)出现,主机能够像访问真实的串口一样访问该设备。这对于那些需要串口接口的老旧应用程序或系统(例如一些调试工具、串口通信软件等)非常有用。

  • 控制命令支持:
    ACM 子类支持一些串口通信中的基本控制命令,例如设置串口的波特率、数据位、停止位等通信参数,以及控制串口的线路状态(例如 DTR 和 RTS)。

  • 无需物理串口硬件:
    通过 ACM,USB 设备可以模拟串口通信,主机系统通过 USB 连接就能与设备进行串口数据交换,而无需物理串口接口或额外的转换器。这大大简化了硬件设计和连接。


在不使用 USB 虚拟串口的情况下 嵌入式设备与 PC 主机进行串口通信的方案

  1. 直接使用物理串口通信,如果嵌入式设备和 PC 主机都具备传统的物理串口接口,可以直接利用这些接口进行串口通信。

    • 硬件连接:嵌入式设备的串口通过电平转换器芯片输出符合串口通信协议的电平信号(TTL),然后连接到 PC 上的串口接口。
    • 优点:通信延迟低,实现简单。
    • 缺点:现代 PC 都不再配置物理串口,需要额外的硬件支持。
  2. 使用 USB 转串口桥接芯片,将嵌入式设备的串口信号转换为 USB 信号,以 WCH CH340 为例。

    • WCH CH340 系列芯片是一种广泛应用的 USB 转串口桥接芯片,主要用于将嵌入式设备的 UART(通用异步收发传输器)数据转换为 USB 数据以便与 PC 进行通信。

      • 双接口设计:一端为 USB 接口,与 PC 连接;另一端为 UART 接口,与嵌入式设备相连。
      • 内置协议处理:内部集成了 USB 协议处理器和 UART 数据收发模块,可以实现数据的缓冲、打包和转换。
      • 驱动支持:PC 端通过专用驱动程序(或内置驱动)将 CH340 识别为虚拟串口设备,无需额外硬件接口。
    • 接口说明:

      • USB 端:接入 PC 侧的 USB 总线,采用 USB D+ 和 D- 差分信号传输。芯片在 USB 总线上以设备的形式出现,经过枚举后由 PC 驱动识别为虚拟串口。
      • UART 端:提供 TXD、RXD 等信号接口,与嵌入式设备的 UART 接口直接相连。通常工作在 TTL 电平下,嵌入式设备只需确保信号电平匹配即可。
    • 模拟传输过程:

      • 嵌入式设备向 PC 发送数据
        1. 数据生成与输出(嵌入式设备侧):
          • 嵌入式设备通过其内置 UART 模块准备要发送的数据。例如,发送字符 “A”(对应的 ASCII 值为 0x41)。
          • 数据在嵌入式设备的 UART TX 端输出,形成符合异步串口通信协议的信号格式:先是起始位(Start Bit),然后是数据位(如 8 位),可能还有校验位,最后是停止位(Stop Bit)。
        2. UART 信号接收(CH340 芯片接收侧):
          • CH340 的 UART 模块连接到嵌入式设备的 TX 引脚。芯片内部的 UART 接收器检测到起始位后,按照预先设定的波特率采样数据位,接收到完整的字节数据(0x41)。
          • 接收到的数据被存入内部缓冲区,等待后续处理。
        3. 数据打包与 USB 传输:
          • CH340 内部控制逻辑将缓冲区中的数据按照 USB Bulk 传输的要求打包成数据包。此时,芯片作为设备已经在 USB 总线上作为设备与 PC 建立了连接。
          • 数据包通过 USB 接口发送到 PC,传输过程中遵循 USB 协议,包括数据包的封装、CRC 校验等。
        4. PC 接收与驱动处理:
          • PC 端的 USB 主机控制器接收到来自 CH340 的 USB 数据包。
          • 安装好的 CH340 驱动程序将 USB 数据包解析,转换为虚拟串口数据,供应用程序读取。这样,PC 端的串口调试工具或应用程序就可以获取到“0x41”这一数据。
      • PC 向嵌入式设备发送数据
        1. 数据生成与 USB传输(PC 侧):
          • 在 PC 端,应用程序通过虚拟串口(如 COM3)发送数据。例如,发送字符 “B”(0x42)。
          • 驱动将数据打包成 USB 数据包,通过 USB 主机控制器发送给 CH340。
        2. USB 数据接收与解析(CH340 接收侧):
          • CH340 的 USB 模块接收到来自 PC 的数据包后,将数据从 USB 数据包中提取出来。
          • 数据被传递给内部 UART 发送模块,并存入发送缓冲区。
        3. UART 信号输出(CH340 发射侧):
          • 内部 UART 模块按照设定的波特率,将数据(0x42)从 CH340 的 TXD 端口输出,形成标准的串口信号格式。
          • 嵌入式设备的 UART 接收器接收这个信号,完成数据接收。
        4. 嵌入式设备处理:
          • 嵌入式设备解析并处理收到的数据,实现与 PC 的通信。
    • 与 USB CDC ACM 模拟虚拟串口的区别

      方面 USB CDC ACM WCH CH340
      实现位置 内部固件实现 USB 协议和 CDC 类,直接在 MCU 中完成 外部专用芯片实现转换,嵌入式设备仅需提供 UART 接口
      硬件要求 MCU 必须具备 USB 外设功能及足够的处理资源 MCU 无需 USB 控制器,仅需 UART,增加外部转换芯片
      灵活性 可在固件中定制扩展(例如自定义控制命令、调试功能) 功能固定,灵活性较低
      驱动支持 PC 端依赖标准 CDC 驱动,通常操作系统内置支持 PC 端需安装 CH340 驱动,但支持广泛,识别为虚拟串口
      成本与板载面积 芯片整合在 MCU 内,但可能需要更高性能 MCU 需要额外的转换芯片和外围电路,占用额外板载面积
  3. 使用无线串口通信模块


ux_device_cdc_acm.c 源码分析

USBD_CDC_ACM_Activate

在设备插入时被调用,用于激活 CDC ACM 类设备。

VOID USBD_CDC_ACM_Activate(VOID *cdc_acm_instance)

参数 cdc_acm_instance 指向 CDC ACM 类实例。

UX_SLAVE_CLASS_CDC_ACM_CALLBACK_PARAMETER parameter;

这里定义了一个 UX_SLAVE_CLASS_CDC_ACM_CALLBACK_PARAMETER 类型的变量 parameter。该变量将用来存储 CDC ACM 类设备相关的回调函数。

cdc_acm = (UX_SLAVE_CLASS_CDC_ACM*) cdc_acm_instance;

通过传入的 cdc_acm_instance 指针将其转换成 UX_SLAVE_CLASS_CDC_ACM 类型,并保存到全局变量 cdc_acm 中。这表示当前设备的 CDC ACM 类实例已经被成功保存,可以在后续的代码中进行操作。

parameter.ux_device_class_cdc_acm_parameter_write_callback = ux_device_class_cdc_acm_write_callback;
parameter.ux_device_class_cdc_acm_parameter_read_callback = ux_device_class_cdc_acm_read_callback;
  • parameter 结构体中设置了写和读的回调函数。具体来说:
    • ux_device_class_cdc_acm_write_callback:写操作的回调函数。
    • ux_device_class_cdc_acm_read_callback:读操作的回调函数。
ux_device_class_cdc_acm_ioctl(cdc_acm, UX_SLAVE_CLASS_CDC_ACM_IOCTL_TRANSMISSION_START, (VOID *)&parameter);

调用 ux_device_class_cdc_acm_ioctl 函数,传递 cdc_acm 类实例和一个命令 UX_SLAVE_CLASS_CDC_ACM_IOCTL_TRANSMISSION_START,并将 parameter 作为参数。这个函数的作用是启动传输,并传递相关的回调函数,用于后续的数据读写。

if (!g_xUSBUARTSend)
{
  g_xUSBUARTSend = xSemaphoreCreateBinary();
  g_xUSBUART_RX_Queue = xQueueCreate(200, 1);
}
  • 这里的代码检查 g_xUSBUARTSend 是否为空(表示尚未创建),如果为空,则创建一个二值信号量 g_xUSBUARTSend 和一个大小为 200 的接收队列 g_xUSBUART_RX_Queue
  • g_xUSBUARTSend 用于控制 USB 串口数据发送的同步操作,而 g_xUSBUART_RX_Queue 用于保存接收到的数据。队列的大小为 200,表示最多可以保存 200 个字节的数据。

  • USBD_CDC_ACM_Activate 函数在 CDC ACM 设备插入时调用,主要负责:
    1. 激活 CDC ACM 类设备。
    2. 保存设备实例。
    3. 配置写和读的回调函数。
    4. 启动数据传输。
    5. 创建 FreeRTOS 信号量和队列用于数据传输的同步与接收。

USBD_CDC_ACM_ParameterChange

这个函数用于管理 CDC ACM 类的请求。在 USB 设备中,CDC ACM(通信设备类抽象控制模型)用于模拟串口通信,这个函数主要用于响应和处理来自主机端的 CDC ACM 类请求。

VOID USBD_CDC_ACM_ParameterChange(VOID *cdc_acm_instance)
{
  /* USER CODE BEGIN USBD_CDC_ACM_ParameterChange */
  UX_PARAMETER_NOT_USED(cdc_acm_instance);

cdc_acm_instance指向 CDC ACM 实例的指针。

  ULONG request;
  UX_SLAVE_TRANSFER *transfer_request;
  UX_SLAVE_DEVICE *device;
  • request:用于存储当前请求的类型。
  • transfer_request:指向与控制端点(Control Endpoint)相关的传输请求。
  • device:指向当前 USB 从设备的信息。
  /* Get the pointer to the device.  */
  device = &_ux_system_slave -> ux_system_slave_device;

这里获取当前设备的指针。_ux_system_slave 是 USBX 系统中表示从设备的结构体,ux_system_slave_device 是其中表示设备信息的部分。通过该指针可以访问当前设备的相关信息。

  /* Get the pointer to the transfer request associated with the control endpoint. */
  transfer_request = &device -> ux_slave_device_control_endpoint.ux_slave_endpoint_transfer_request;
  • ux_slave_device_control_endpoint 是当前设备的控制端点(Control Endpoint)。控制端点用于处理与主机之间的控制传输请求。ux_slave_endpoint_transfer_request 是该端点的传输请求,包含了控制请求的具体信息。
  request = *(transfer_request -> ux_slave_transfer_request_setup + UX_SETUP_REQUEST);

从传输请求中提取请求类型(UX_SETUP_REQUEST 是控制传输包中的一个字段),并存储在 request 变量中。这个请求类型表示主机希望设备执行的操作。

  switch (request)
  {
    case UX_SLAVE_CLASS_CDC_ACM_SET_LINE_CODING :

使用 switch 语句检查请求类型,并根据请求类型来执行相应的操作。不同的请求会进入不同的 case 语句。

如果波特率小于最低值(MIN_BAUDRATE),则将波特率设置为最小值。接着,配置新的串口设置。这部分代码注释掉了,可能意味着串口配置会在其他地方完成。

    case UX_SLAVE_CLASS_CDC_ACM_GET_LINE_CODING :

      /* Set the Line Coding parameters */
      if (ux_device_class_cdc_acm_ioctl(cdc_acm, UX_SLAVE_CLASS_CDC_ACM_IOCTL_SET_LINE_CODING,
                                        &CDC_VCP_LineCoding) != UX_SUCCESS)
      {
        Error_Handler();
      }

      break;

当主机请求获取线路编码(GET_LINE_CODING)时,设备将当前的线路编码设置传送回主机。如果操作失败,调用 Error_Handler()

    case UX_SLAVE_CLASS_CDC_ACM_SET_CONTROL_LINE_STATE :
    default :
      break;
  }

USBD_CDC_ACM_ParameterChange 函数用于处理来自主机的 CDC ACM 类控制请求。主要操作包括:

  • 根据请求类型,处理设置和获取线路编码(SET_LINE_CODINGGET_LINE_CODING)。
  • 在设置线路编码时,检查波特率是否符合最低要求,并更新配置。
  • 当主机请求设置或获取线路编码时,通过 ux_device_class_cdc_acm_ioctl 函数读取或设置相应的参数。
  • 该函数为虚拟串口功能提供了对主机请求的响应机制,并实现了 CDC ACM 协议的一部分。