USB 的 TMC 枚举——仪器行业不得不品的一坨

基本介绍

标准仪器设备接口是上位机和仪器通信使用的接口，如示波器、电源、信号发生器等。常用的标准仪器接口及对应的通信协议如下。

通信接口 | 通信协议
LAN | VXI-11
USB | USB-TMC
GPIB | IEEE488

LAN 的通信协议和 TCP/IP 的 TCP Client/Server 、RAW Socket 没什么本质区别，只要支持其中之一都可以正常识别。

GPIB 的通信协议比较复杂，而且物理层是并口传输，几乎只能使用专用的收发器芯片进行通信，专用收发器的控制方式类似 SRAM ，也并没有什么很难解决的问题。

USB 的通信方式就必须品鉴一下了。

而我司作为一个测试设备和仪器公司，居然在这方面没有任何技术积累，那就让我来细品一下什么是 USB-TMC 枚举吧。

整体思路

首先非常重要的是翻阅标准文档，有以下两份我自己机翻的双语对照版，需要双页视图对照查看。

USBTMC_1_00_EnCh.pdf | yono 的文件
USBTMC_usb488_subclass_1_00_EnCh.pdf | yono 的文件

其次非常不重要的是找一个友商的正规仪器抓数据包看看是啥情况。

ps：实际上我并没有看过第二份文档哪怕一眼，完全依靠抓数据和第一个文档互相映照就完成了驱动开发。

值得关注的是 USB-TMC 类枚举实际上只有 USB488 这一个子类，而且仍有许多的预留数据位置和通信过程，所以我认为 TMC 这个枚举仍然可以使用很多年。

枚举过程

众所周知，驱动程序在 USB 设备的枚举阶段需要询问描述符，这个描述符的编写是怎么枚举成 TMC 设备的关键。

经过抓包，一个示例的设备描述符就出来了，和他一模一样就好了，有如下。

Copy
UCHAR device_framework_full_speed_demo[] = {
/* Device descriptor 设备描述符 */
0x12, // len(static)
0x01, // device(static)
0x00, 0x02, // USB2.0(static)
0x00, // DeviceClass(static)
0x00, // DeviceSubClass(static)
0x00, // bDeviceProtocol(static)
0x40, // bMaxPacketSize0
0x69, 0x0A, // idVendor (VID): 0x0A69
0x8A, 0x08, // idProduct (PID): 0x088A
0x00, 0x01, // bcdDevice: Device release number 0x0100
0x01, // iManufacturer 厂商字符串描述符的索引(static)
0x02, // iProduct 产品字符串描述符的索引(static)
0x03, // iSerialNumber 序列号字符串描述符的索引(static)
0x01, // bNumConfigurations(static)

/* Device qualifier descriptor 设备限定符描述符 */
0x0a, // len(static)
0x06, // bDescriptorType: Device Qualifier Descriptor(static)
0x00, 0x02, // USB2.0(static)
0x00, // DeviceClass(static)
0x00, // DeviceSubClass(static)
0x00, // bDeviceProtocol(static)
0x40, // bMaxPacketSize0
0x01, // bNumConfigurations(static)
0x00, // reserve(static)

/* Configuration descriptor 配置描述符 */
0x09, // len(static)
0x02, // bDescriptorType: Configuration Descriptor(static)
0x20, 0x00, // wTotalLength: Total length of data for this configuration (32 bytes)
0x01, // bNumInterfaces(static)
0x01, // bConfigurationValue(static)
0x00, // iConfiguration: Index of configuration string descriptor
0x80, // bmAttributes: Self-powered 自供电
0x64, // bMaxPower: 200 mA (0x64 * 2 mA)

/* Interface descriptor 接口描述符 */
0x09, // len(static)
0x04, // bDescriptorType: Interface Descriptor(static)
0x00, // bInterfaceNumber: Interface 0
0x00, // bAlternateSetting
0x02, // bNumEndpoints: 2 endpoints(static)
0xFE, // bInterfaceClass: TMC(static)
0x03, // bInterfaceSubClass: USB488(static)
0x01, // bInterfaceProtocol(static)
0x00, // iInterface: Index of interface string descriptor(static)

/* Endpoint descriptor 端点描述符(Bulk Out) */
0x07, // len(static)
0x05, // bDescriptorType: Endpoint Descriptor(static)
0x81, // bEndpointAddress: 端点地址（IN 方向，端点号 1）
0x02, // bmAttributes: 传输类型为批量（Bulk）
0x40, 0x00, // wMaxPacketSize: 最大包大小 64 字节
0x00, // bInterval: 查询间隔（用于批量传输时为 0）

/* Endpoint descriptor 端点描述符(Bulk In) */
0x07, // len(static)
0x05, // bDescriptorType: Endpoint Descriptor(static)
0x01, // bEndpointAddress: 端点地址（OUT 方向，端点号 1）
0x02, // bmAttributes: 传输类型为批量（Bulk）
0x40, 0x00, // wMaxPacketSize: 最大包大小 64 字节
0x00, // bInterval: 查询间隔（用于批量传输时为 0）
};

其中最为关键的是 Interface descriptor 部分中的 bInterfaceClass 和 bInterfaceSubClass 这两个部分直接决定了我们的枚举类型。当然整个描述符中的其他标有 (static) 的部分都是必须与示例一模一样的，是标准的规定。有些是 TMC 标准，有些是 USB 标准，反正标有 (static) 的部分都一模一样就好了。

最为特殊的是 ，USB-TMC 的协议没有高速全速之分，无论工作在什么 USB 线路上，都需要设备限定符，这是和其他现代化的 USB 协议栈所不同的地方。

次为特殊的是，USB-TMC 设备不能使用组合枚举，会破坏 NI-VISA 对 USB-TMC 的识别和资源调用。例如我尝试组合枚举成 TMC + 串口 设备，设备管理器中都显示出来了 (枚举成功)，但是使用 VISA 调用时只能调用串口，TMC 设备不能调用了。

向仪器发送指令的过程

通信中，会通过此前描述符中的 Bulk Out 端点向仪器发送报文。所以设备应该反复开启这个 Bulk Out 端点以接收任何可能的报文。如果设备没有开启 Bulk Out 端点，将会使得设备产生大量的 NAK 包，触发向设备发送重启端点的 setup 指令。

例如一个经典的报文如下

0x01, 0x02, 0xFD, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2A, 0x49, 0x44, 0x4E, 0x3F, 0x0A, 0x00, 0x00

这个报文可以依据 USBTMC_1_00 标准文档中的描述进行解析，分为如下几个段。

报文段 | 含义
0x01 | 标识这个报文是 OUT 请求，也就是仪器需要解析这个报文的内容进行动作。
0x02, 0xFD | 报文 tag 及其反码，这个 tag 每个报文都会不同。
0x00 | 固定 0。
0x06, 0x00, 0x00, 0x00 | 指内容长度是 6 ，这个长度以小端 32 位传输。
0x01, 0x00, 0x00, 0x00 | 仅首字节的第一个 bit 有意义，是 1 表示内容语句已经完成传输，是 0 表示还有后续的内容报文。
0x2A, 0x49, 0x44, 0x4E, 0x3F, 0x0A | 6 个字节的正式内容，是 *IDN?\r ，\r 是回车的转义字符。
0x00, 0x00 | 保证整个报文长度是 4 字节对齐的补 0。

简单来说也就是解析 Bulk Out 端点接收到的报文数据的首个字节，如果是 0x01 那么进行后续解析，提取到 "*IDN?" 的正式 SCPI 指令并进行处理。

向仪器接收回复的过程

通信中，仪器并不能随意通过 USB 向上位机发送回复，这是和串口通信不同的。当上位机期望接收回复数据时，会在 Bulk Out 端点上发送一个报文，表示仪器现在可以发送回复了，一个经典开启回复的报文示例如下。

0x02, 0x03, 0xFC, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

这个报文可以依据 USBTMC_1_00 标准文档中的描述进行解析，分为如下几个段。

报文段 | 含义
0x02 | 标识这个报文是 IN 请求，也就是仪器应当发送任何可能的回复了。
0x03, 0xFC | 报文 tag 及其反码，这个 tag 每个报文都会不同。
0x00 | 固定 0。
0x00, 0x04, 0x00, 0x00 | 表示上位机开通了 0x0400 共 1024 个字节的接收 buffer，这个值完全不用理会，上位机资源那么丰富，如果通信失败让他自己开大点就好了。
0x00 | 其 D1 位也就是 &0x2 的那一位有意义，如果是 1 意味着下面一个字节是上位机支持且设备必须支持的终止符，如果是 0 则不用理会。其他位全是预留。
0x00 | 如果上一个字节的 D1 位是 1，那么这个字节表示支持的终止符，例如 \r。
0x00, 0x00 | 填充的保留字符，实际作用也是保证整个报文长度是 4 字节对齐的补 0。

简单来说也就是解析 Bulk Out 端点接收到的报文数据的首个字节，如果是 0x02 那么开启设备的发送通道，例如上位机此前传输了 "*IDN?" 的正式 SCPI 指令，那么设备此时需要传输原本产生的回复。

而所谓指定终止符功能，当然不会支持，在后面必须的 setup 包介绍中，会介绍如何让上位机知道我们不支持这个指定。

一个经典的回复传输报文如下。

0x02, 0x03, 0xFC, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2A, 0x49, 0x44, 0x4E, 0x3F, 0x0A, 0x00, 0x00,

报文段 | 含义
0x02 | 标识这个报文是 IN 请求的回复。
0x03, 0xFC | 报文 tag 及其反码，这个 tag 应该复制请求报文的。
0x00 | 固定 0。
0x06, 0x00, 0x00, 0x00 | 表示回复的有效数据共 6 个字节，这个长度以小端 32 位传输。
0x01, 0x00, 0x00, 0x00 | 仅首字节的第一个 bit 有意义，是 1 表示内容语句已经完成传输，是 0 表示还有后续的内容报文。
0x2A, 0x49, 0x44, 0x4E, 0x3F, 0x0A | *IDN?\r，这是在做回环测试，我的仪器会复制最后一条接收到的指令响应任何回复。
0x00, 0x00 | 保证整个报文长度是 4 字节对齐的补 0。

一个优秀的 4 字节对齐方法

使用位运算非常方便高效地进行 4 字节对齐

Copy
uint8_t *data; // 原有报文buffer
uint32_t len = OriginalLen; // 原有长度

for(uint32_t i = len; i < ((len + 0x3) & ~0x3U); i++)
{
data[i] = 0x00;
}

_write(data, ((len + 0x3) & ~0x3U));

setup 包的处理

0x80,0x06 开头的这些标准 setup 包，有如下类别，恕我也爱莫能助，我也是完全依赖 usbx 协议栈进行自动处理。

Copy
#define UX_GET_STATUS 0u
#define UX_CLEAR_FEATURE 1u
#define UX_SET_FEATURE 3u
#define UX_SET_ADDRESS 5u
#define UX_GET_DESCRIPTOR 6u
#define UX_SET_DESCRIPTOR 7u
#define UX_GET_CONFIGURATION 8u
#define UX_SET_CONFIGURATION 9u
#define UX_GET_INTERFACE 10u
#define UX_SET_INTERFACE 11u
#define UX_SYNCH_FRAME 12u

而 0xA1 or 0xA2 开头的 COMMAND_REQUEST 包，有如下内容是我们必须处理的。

以 0xA1,0x07 开头，表示在询问设备的特性，此时有一个示例的回复包如下。其中的内容比较多，可以自行翻阅文档，推荐是直接按这个回复就好了。此时会告诉上位机，我们不支持设定结尾符。

0x01, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

以 0xA2,0x01 开头，表示清除 bulk-out 传输，我们啥也不管，回复成功就好了，回复包如下。

0x01,tag (复制 setup 包以 0 计的第 2 个字节)

以 0xA2,0x02 开头，表示问询此前清除 bulk-out 传输的请求的状态，我们啥也不管，回复成功就好了，回复包如下。

0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00

至于其他的 COMMAND_REQUEST 包，不常用不用管。

[!NOTE]

首先你应该稍微熟悉 usbx 的实践，起码使用过其中已有的例子例如 CDC_ACM，否则以下内容毫无意义。以下均是在 usbx 这个 usb 协议栈框架下，如何将 TMC 枚举增加进去的示例。

枚举描述符在 usbx 中的实践

关注到 _ux_device_stack_initialize 函数，其中有这样的部分，也就是只会以 Interface descriptor 以及 Configuration descriptor 部分在描述符整体中的位置进行寄存。Configuration descriptor 部分之前的统统作为 == 设备描述符 ==， Interface descriptor 部分以后的作为 == 端点描述符 ==。

Copy
switch(descriptor_type)
{

case UX_INTERFACE_DESCRIPTOR_ITEM:
......
break;

case UX_CONFIGURATION_DESCRIPTOR_ITEM:
......
break;

default:
break;
}

那么需要稍微修改原本的 ux_device_descriptors.c 文件中 USBD_Device_Framework_Builder 函数的内容，原本函数中应该有类似如下的内容。其中指定只有高速 USB 才需要设备限定符，但是我们的 USB-TMC 现在通常都是工作在全速 USB 上的，又因为 USB-TMC 的协议没有高速全速之分，所以这里需要删掉这个 if 判断，无论高速还是全速，都把这个设备限定符编进描述符中。

Copy
/* Check if USBx is in high speed mode to add qualifier descriptor */
if(Speed == USBD_HIGH_SPEED)
{
pDevQualDesc = (USBD_DevQualiDescTypedef *)(pDevFrameWorkDesc + pdev->CurrDevDescSz);
pDevQualDesc->bLength = (uint8_t)sizeof(USBD_DevQualiDescTypedef);
pDevQualDesc->bDescriptorType = UX_DEVICE_QUALIFIER_DESCRIPTOR_ITEM;
pDevQualDesc->bcdDevice = 0x0200;
pDevQualDesc->Class = 0x00;
pDevQualDesc->SubClass = 0x00;
pDevQualDesc->Protocol = 0x00;
pDevQualDesc->bMaxPacketSize = 0x40;
pDevQualDesc->bNumConfigurations = 0x01;
pDevQualDesc->bReserved = 0x00;
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DevQualiDescTypedef);
}

其他部分不需要大的调整，只要按照示例描述符一段一段修改参数就可以了，在原文件基础上修改好的 ux_device_descriptors.c 和 ux_device_descriptors.h 文件如下。

Copy
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file ux_device_descriptors.c
* @author MCD Application Team
* @brief USBX Device descriptor header file
******************************************************************************
* @attention
*
* Copyright (c) 2020-2021 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "ux_device_descriptors.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
USBD_DevClassHandleTypeDef USBD_Device_FS, USBD_Device_HS;

uint8_t UserClassInstance[USBD_MAX_CLASS_INTERFACES] = {
CLASS_TYPE_TMC,
};

uint8_t UserTMCInterface[] = {
INTERFACE_TMC_USB488,
};

/* The generic device descriptor buffer that will be filled by builder
Size of the buffer is the maximum possible device FS descriptor size. */
#if defined(__ICCARM__) /* IAR Compiler */
#pragma data_alignment = 4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN static uint8_t DevFrameWorkDesc_FS[USBD_FRAMEWORK_MAX_DESC_SZ] __ALIGN_END = {0};

/* The generic device descriptor buffer that will be filled by builder
Size of the buffer is the maximum possible device HS descriptor size. */
#if defined(__ICCARM__) /* IAR Compiler */
#pragma data_alignment = 4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN static uint8_t DevFrameWorkDesc_HS[USBD_FRAMEWORK_MAX_DESC_SZ] __ALIGN_END = {0};

static uint8_t *pDevFrameWorkDesc_FS = DevFrameWorkDesc_FS;

static uint8_t *pDevFrameWorkDesc_HS = DevFrameWorkDesc_HS;
/* USER CODE BEGIN PV0 */

/* USER CODE END PV0 */

/* String Device Framework :
Byte 0 and 1 : Word containing the language ID : 0x0904 for US
Byte 2 : Byte containing the index of the descriptor
Byte 3 : Byte containing the length of the descriptor string
*/
#if defined(__ICCARM__) /* IAR Compiler */
#pragma data_alignment = 4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN UCHAR USBD_string_framework[USBD_STRING_FRAMEWORK_MAX_LENGTH] __ALIGN_END = {0};

/* Multiple languages are supported on the device, to add
a language besides English, the Unicode language code must
be appended to the language_id_framework array and the length
adjusted accordingly. */

#if defined(__ICCARM__) /* IAR Compiler */
#pragma data_alignment = 4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN UCHAR USBD_language_id_framework[LANGUAGE_ID_MAX_LENGTH] __ALIGN_END = {0};

/* USER CODE BEGIN PV1 */

/* USER CODE END PV1 */

/* Private function prototypes -----------------------------------------------*/
static void USBD_Desc_GetString(uint8_t *desc, uint8_t *Buffer, uint16_t *len);
static uint8_t USBD_Desc_GetLen(uint8_t *buf);

static uint8_t *USBD_Device_Framework_Builder(USBD_DevClassHandleTypeDef *pdev, uint8_t *pDevFrameWorkDesc, uint8_t *UserClassInstance, uint8_t Speed);

static uint8_t USBD_FrameWork_AddToConfDesc(USBD_DevClassHandleTypeDef *pdev, uint8_t Speed, uint8_t *pCmpstConfDesc);

static uint8_t USBD_FrameWork_AddClass(USBD_DevClassHandleTypeDef *pdev, USBD_CompositeClassTypeDef class, uint8_t cfgidx, uint8_t Speed, uint8_t *pCmpstConfDesc);

static uint8_t USBD_FrameWork_FindFreeIFNbr(USBD_DevClassHandleTypeDef *pdev);

static void USBD_FrameWork_AddConfDesc(uint32_t Conf, uint32_t *pSze);

static void USBD_FrameWork_AssignEp(USBD_DevClassHandleTypeDef *pdev, uint8_t Add, uint8_t Type, uint32_t Sze);

static void USBD_FrameWork_TMC_Desc(USBD_DevClassHandleTypeDef *pdev, uint32_t pConf, uint32_t *Sze);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
* @brief USBD_Get_Device_Framework_Speed
* Return the device speed descriptor
* @param Speed : HIGH or FULL SPEED flag
* @param length : length of HIGH or FULL SPEED array
* @retval Pointer to descriptor buffer
*/
uint8_t *USBD_Get_Device_Framework_Speed(uint8_t Speed, ULONG *Length)
{
uint8_t *pFrameWork = NULL;
/* USER CODE BEGIN Device_Framework0 */

/* USER CODE END Device_Framework0 */

if(USBD_FULL_SPEED == Speed)
{
USBD_Device_Framework_Builder(&USBD_Device_FS, pDevFrameWorkDesc_FS, UserClassInstance, Speed);

/* Get the length of USBD_device_framework_full_speed */
*Length = (ULONG)(USBD_Device_FS.CurrDevDescSz + USBD_Device_FS.CurrConfDescSz);

pFrameWork = pDevFrameWorkDesc_FS;
}
else
{
USBD_Device_Framework_Builder(&USBD_Device_HS, pDevFrameWorkDesc_HS, UserClassInstance, Speed);

/* Get the length of USBD_device_framework_high_speed */
*Length = (ULONG)(USBD_Device_HS.CurrDevDescSz + USBD_Device_HS.CurrConfDescSz);

pFrameWork = pDevFrameWorkDesc_HS;
}
/* USER CODE BEGIN Device_Framework1 */

/* USER CODE END Device_Framework1 */
return pFrameWork;
}

/**
* @brief USBD_Get_String_Framework
* Return the language_id_framework
* @param Length : Length of String_Framework
* @retval Pointer to language_id_framework buffer
*/
uint8_t *USBD_Get_String_Framework(ULONG *Length)
{
uint16_t len = 0U;
uint8_t count = 0U;

/* USER CODE BEGIN String_Framework0 */

/* USER CODE END String_Framework0 */

/* Set the Manufacturer language Id and index in USBD_string_framework */
USBD_string_framework[count++] = USBD_LANGID_STRING & 0xFF;
USBD_string_framework[count++] = USBD_LANGID_STRING >> 8;
USBD_string_framework[count++] = USBD_IDX_MFC_STR;

/* Set the Manufacturer string in string_framework */
USBD_Desc_GetString((uint8_t *)USBD_MANUFACTURER_STRING, USBD_string_framework + count, &len);

/* Set the Product language Id and index in USBD_string_framework */
count += len + 1;
USBD_string_framework[count++] = USBD_LANGID_STRING & 0xFF;
USBD_string_framework[count++] = USBD_LANGID_STRING >> 8;
USBD_string_framework[count++] = USBD_IDX_PRODUCT_STR;

/* Set the Product string in USBD_string_framework */
USBD_Desc_GetString((uint8_t *)USBD_PRODUCT_STRING, USBD_string_framework + count, &len);

/* Set Serial language Id and index in string_framework */
count += len + 1;
USBD_string_framework[count++] = USBD_LANGID_STRING & 0xFF;
USBD_string_framework[count++] = USBD_LANGID_STRING >> 8;
USBD_string_framework[count++] = USBD_IDX_SERIAL_STR;

/* Set the Serial number in USBD_string_framework */
USBD_Desc_GetString((uint8_t *)USBD_SERIAL_NUMBER, USBD_string_framework + count, &len);

/* USER CODE BEGIN String_Framework1 */

/* USER CODE END String_Framework1 */

/* Get the length of USBD_string_framework */
*Length = strlen((const char *)USBD_string_framework);

return USBD_string_framework;
}

/**
* @brief USBD_Get_Language_Id_Framework
* Return the language_id_framework
* @param Length : Length of Language_Id_Framework
* @retval Pointer to language_id_framework buffer
*/
uint8_t *USBD_Get_Language_Id_Framework(ULONG *Length)
{
uint8_t count = 0U;

/* Set the language Id in USBD_language_id_framework */
USBD_language_id_framework[count++] = USBD_LANGID_STRING & 0xFF;
USBD_language_id_framework[count++] = USBD_LANGID_STRING >> 8;

/* Get the length of USBD_language_id_framework */
*Length = strlen((const char *)USBD_language_id_framework);

return USBD_language_id_framework;
}

/**
* @brief USBD_Get_Interface_Number
* Return interface number
* @param class_type : Device class type
* @param interface_type : Device interface type
* @retval interface number
*/
uint16_t USBD_Get_Interface_Number(uint8_t class_type, uint8_t interface_type)
{
uint8_t itf_num = 0U;
uint8_t idx = 0U;

/* USER CODE BEGIN USBD_Get_Interface_Number0 */

/* USER CODE END USBD_Get_Interface_Number0 */

for(idx = 0; idx < USBD_MAX_SUPPORTED_CLASS; idx++)
{
if((USBD_Device_FS.tclasslist[idx].ClassType == class_type) && (USBD_Device_FS.tclasslist[idx].InterfaceType == interface_type))
{
itf_num = USBD_Device_FS.tclasslist[idx].Ifs[0];
}
}

/* USER CODE BEGIN USBD_Get_Interface_Number1 */

/* USER CODE END USBD_Get_Interface_Number1 */

return itf_num;
}

/**
* @brief USBD_Get_Configuration_Number
* Return configuration number
* @param class_type : Device class type
* @param interface_type : Device interface type
* @retval configuration number
*/
uint16_t USBD_Get_Configuration_Number(uint8_t class_type, uint8_t interface_type)
{
uint8_t cfg_num = 1U;

/* USER CODE BEGIN USBD_Get_CONFIGURATION_Number0 */

/* USER CODE END USBD_Get_CONFIGURATION_Number0 */

/* USER CODE BEGIN USBD_Get_CONFIGURATION_Number1 */

/* USER CODE END USBD_Get_CONFIGURATION_Number1 */

return cfg_num;
}

/**
* @brief USBD_Desc_GetString
* Convert ASCII string into Unicode one
* @param desc : descriptor buffer
* @param Unicode : Formatted string buffer (Unicode)
* @param len : descriptor length
* @retval None
*/
static void USBD_Desc_GetString(uint8_t *desc, uint8_t *unicode, uint16_t *len)
{
uint8_t idx = 0U;
uint8_t *pdesc;

if(desc == NULL)
{
return;
}

pdesc = desc;
*len = (uint16_t)USBD_Desc_GetLen(pdesc);

unicode[idx++] = *(uint8_t *)len;

while(*pdesc != (uint8_t)'\0')
{
unicode[idx++] = *pdesc;
pdesc++;
}
}

/**
* @brief USBD_Desc_GetLen
* return the string length
* @param buf : pointer to the ASCII string buffer
* @retval string length
*/
static uint8_t USBD_Desc_GetLen(uint8_t *buf)
{
uint8_t len = 0U;
uint8_t *pbuff = buf;

while(*pbuff != (uint8_t)'\0')
{
len++;
pbuff++;
}

return len;
}

/**
* @brief USBD_Device_Framework_Builder
* Device Framework builder
* @param pdev: device instance
* @param pDevFrameWorkDesc: Pointer to the device framework descriptor
* @param UserClassInstance: type of the class to be added
* @param Speed: Speed parameter HS or FS
* @retval status
*/
static uint8_t *USBD_Device_Framework_Builder(USBD_DevClassHandleTypeDef *pdev, uint8_t *pDevFrameWorkDesc, uint8_t *UserClassInstance, uint8_t Speed)
{
static USBD_DeviceDescTypedef *pDevDesc;
static USBD_DevQualiDescTypedef *pDevQualDesc;
uint8_t Idx_Instance = 0U;

/* Set Dev and conf descriptors size to 0 */
pdev->CurrConfDescSz = 0U;
pdev->CurrDevDescSz = 0U;

/* Set the pointer to the device descriptor area*/
pDevDesc = (USBD_DeviceDescTypedef *)pDevFrameWorkDesc;

/* Start building the generic device descriptor common part */
pDevDesc->bLength = (uint8_t)sizeof(USBD_DeviceDescTypedef);
pDevDesc->bDescriptorType = UX_DEVICE_DESCRIPTOR_ITEM;
pDevDesc->bcdUSB = USB_BCDUSB;
pDevDesc->bDeviceClass = 0x00;
pDevDesc->bDeviceSubClass = 0x00;
pDevDesc->bDeviceProtocol = 0x00;
pDevDesc->bMaxPacketSize = USBD_MAX_EP0_SIZE;
pDevDesc->idVendor = USBD_VID;
pDevDesc->idProduct = USBD_PID;
pDevDesc->bcdDevice = 0x0100;
pDevDesc->iManufacturer = USBD_IDX_MFC_STR;
pDevDesc->iProduct = USBD_IDX_PRODUCT_STR;
pDevDesc->iSerialNumber = USBD_IDX_SERIAL_STR;
pDevDesc->bNumConfigurations = USBD_MAX_NUM_CONFIGURATION;
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DeviceDescTypedef);

/* Check if USBx is in high speed mode to add qualifier descriptor */
pDevQualDesc = (USBD_DevQualiDescTypedef *)(pDevFrameWorkDesc + pdev->CurrDevDescSz);
pDevQualDesc->bLength = (uint8_t)sizeof(USBD_DevQualiDescTypedef);
pDevQualDesc->bDescriptorType = UX_DEVICE_QUALIFIER_DESCRIPTOR_ITEM;
pDevQualDesc->bcdDevice = 0x0200;
pDevQualDesc->Class = 0x00;
pDevQualDesc->SubClass = 0x00;
pDevQualDesc->Protocol = 0x00;
pDevQualDesc->bMaxPacketSize = 0x40;
pDevQualDesc->bNumConfigurations = 0x01;
pDevQualDesc->bReserved = 0x00;
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DevQualiDescTypedef);

/* Build the device framework */
while(Idx_Instance < USBD_MAX_SUPPORTED_CLASS)
{
if((pdev->classId < USBD_MAX_SUPPORTED_CLASS) && (pdev->NumClasses < USBD_MAX_SUPPORTED_CLASS) && (UserClassInstance[Idx_Instance] != CLASS_TYPE_NONE))
{
/* Call the composite class builder */
(void)USBD_FrameWork_AddClass(pdev, (USBD_CompositeClassTypeDef)UserClassInstance[Idx_Instance], 0, Speed, (pDevFrameWorkDesc + pdev->CurrDevDescSz));

/* Increment the ClassId for the next occurrence */
pdev->classId++;
pdev->NumClasses++;
}

Idx_Instance++;
}

/* Check if there is a composite class and update device class */
if(pdev->NumClasses > 1)
{
pDevDesc->bDeviceClass = 0xEF;
pDevDesc->bDeviceSubClass = 0x02;
pDevDesc->bDeviceProtocol = 0x01;
}
else
{
/* Check if the CDC ACM class is set and update device class */
if(UserClassInstance[0] == CLASS_TYPE_CDC_ACM)
{
pDevDesc->bDeviceClass = 0x02;
pDevDesc->bDeviceSubClass = 0x02;
pDevDesc->bDeviceProtocol = 0x00;
}
}

return pDevFrameWorkDesc;
}

/**
* @brief USBD_FrameWork_AddToConfDesc
* Add a new class to the configuration descriptor
* @param pdev: device instance
* @param Speed: device speed
* @param pCmpstConfDesc: to composite device configuration descriptor
* @retval status
*/
uint8_t USBD_FrameWork_AddToConfDesc(USBD_DevClassHandleTypeDef *pdev, uint8_t Speed, uint8_t *pCmpstConfDesc)
{
uint8_t interface = 0U;

pdev->Speed = Speed;

if(pdev->classId == 0U)
{
/* Add configuration and IAD descriptors */
USBD_FrameWork_AddConfDesc((uint32_t)pCmpstConfDesc, &pdev->CurrConfDescSz);
}

switch(pdev->tclasslist[pdev->classId].ClassType)
{
case CLASS_TYPE_TMC:
switch(pdev->tclasslist[pdev->classId].InterfaceType)
{
case INTERFACE_TMC_USB488:

/* Find the first available interface slot and Assign number of interfaces */
interface = USBD_FrameWork_FindFreeIFNbr(pdev);
pdev->tclasslist[pdev->classId].NumIf = 1U;
pdev->tclasslist[pdev->classId].Ifs[0] = interface;

/* Assign endpoint numbers */
pdev->tclasslist[pdev->classId].NumEps = 2U; /* EP_IN, EP_OUT */

/* Check the current speed to assign endpoints */
if(pdev->Speed == USBD_HIGH_SPEED)
{
/* Assign IN Endpoint */
USBD_FrameWork_AssignEp(pdev, USBD_TMC_NONE_EPIN_ADDR, USBD_EP_TYPE_BULK, USBD_TMC_NONE_EPIN_HS_MPS);

/* Assign OUT Endpoint */
USBD_FrameWork_AssignEp(pdev, USBD_TMC_NONE_EPOUT_ADDR, USBD_EP_TYPE_BULK, USBD_TMC_NONE_EPOUT_HS_MPS);
}
else
{
USBD_FrameWork_AssignEp(pdev, USBD_TMC_NONE_EPIN_ADDR, USBD_EP_TYPE_BULK, USBD_TMC_NONE_EPIN_FS_MPS);

USBD_FrameWork_AssignEp(pdev, USBD_TMC_NONE_EPOUT_ADDR, USBD_EP_TYPE_BULK, USBD_TMC_NONE_EPOUT_FS_MPS);
}

USBD_FrameWork_TMC_Desc(pdev, (uint32_t)pCmpstConfDesc, &pdev->CurrConfDescSz);

break;

default:
break;
}
break;

default:
break;
}

return UX_SUCCESS;
}

/**
* @brief USBD_FrameWork_AddClass
* Register a class in the class builder
* @param pdev: device instance
* @param class: type of the class to be added (from USBD_CompositeClassTypeDef)
* @param cfgidx: configuration index
* @param speed: device speed
* @param pCmpstConfDesc: to composite device configuration descriptor
* @retval status
*/
uint8_t USBD_FrameWork_AddClass(USBD_DevClassHandleTypeDef *pdev, USBD_CompositeClassTypeDef class, uint8_t cfgidx, uint8_t Speed, uint8_t *pCmpstConfDesc)
{
static uint8_t interface_idx = 0U;

if((pdev->classId < USBD_MAX_SUPPORTED_CLASS) && (pdev->tclasslist[pdev->classId].Active == 0U))
{
/* Store the class parameters in the global tab */
pdev->tclasslist[pdev->classId].ClassId = pdev->classId;
pdev->tclasslist[pdev->classId].Active = 1U;
pdev->tclasslist[pdev->classId].ClassType = class;

if(class == CLASS_TYPE_TMC)
{
pdev->tclasslist[pdev->classId].InterfaceType = UserTMCInterface[interface_idx];

interface_idx++;

if(interface_idx == sizeof(UserTMCInterface))
{
interface_idx = 0U;
}
}

/* Call configuration descriptor builder and endpoint configuration builder */
if(USBD_FrameWork_AddToConfDesc(pdev, Speed, pCmpstConfDesc) != UX_SUCCESS)
{
return UX_ERROR;
}
}

UNUSED(cfgidx);

return UX_SUCCESS;
}

/**
* @brief USBD_FrameWork_FindFreeIFNbr
* Find the first interface available slot
* @param pdev: device instance
* @retval The interface number to be used
*/
static uint8_t USBD_FrameWork_FindFreeIFNbr(USBD_DevClassHandleTypeDef *pdev)
{
uint32_t idx = 0U;

/* Unroll all already activated classes */
for(uint32_t i = 0U; i < pdev->NumClasses; i++)
{
/* Unroll each class interfaces */
for(uint32_t j = 0U; j < pdev->tclasslist[i].NumIf; j++)
{
/* Increment the interface counter index */
idx++;
}
}

/* Return the first available interface slot */
return (uint8_t)idx;
}

/**
* @brief USBD_FrameWork_AddConfDesc
* Add a new class to the configuration descriptor
* @param Conf: configuration descriptor
* @param pSze: pointer to the configuration descriptor size
* @retval none
*/
static void USBD_FrameWork_AddConfDesc(uint32_t Conf, uint32_t *pSze)
{
/* Intermediate variable to comply with MISRA-C Rule 11.3 */
USBD_ConfigDescTypedef *ptr = (USBD_ConfigDescTypedef *)Conf;

ptr->bLength = (uint8_t)sizeof(USBD_ConfigDescTypedef);
ptr->bDescriptorType = UX_CONFIGURATION_DESCRIPTOR_ITEM;
ptr->wDescriptorLength = 0x20;
ptr->bNumInterfaces = 1U;
ptr->bConfigurationValue = 1U;
ptr->iConfiguration = USBD_CONFIG_STR_DESC_IDX;
ptr->bmAttributes = USBD_CONFIG_BMATTRIBUTES;
ptr->bMaxPower = USBD_CONFIG_MAXPOWER;
*pSze += sizeof(USBD_ConfigDescTypedef);
}

/**
* @brief USBD_FrameWork_AssignEp
* Assign and endpoint
* @param pdev: device instance
* @param Add: Endpoint address
* @param Type: Endpoint type
* @param Sze: Endpoint max packet size
* @retval none
*/
static void USBD_FrameWork_AssignEp(USBD_DevClassHandleTypeDef *pdev, uint8_t Add, uint8_t Type, uint32_t Sze)
{
uint32_t idx = 0U;

/* Find the first available endpoint slot */
while(((idx < (pdev->tclasslist[pdev->classId]).NumEps) && ((pdev->tclasslist[pdev->classId].Eps[idx].is_used) != 0U)))
{
/* Increment the index */
idx++;
}

/* Configure the endpoint */
pdev->tclasslist[pdev->classId].Eps[idx].add = Add;
pdev->tclasslist[pdev->classId].Eps[idx].type = Type;
pdev->tclasslist[pdev->classId].Eps[idx].size = (uint16_t)Sze;
pdev->tclasslist[pdev->classId].Eps[idx].is_used = 1U;
}

/**
* @brief USBD_FrameWork_TMC_Desc
* Configure and Append the TMC Descriptor
* @param pdev: device instance
* @param pConf: Configuration descriptor pointer
* @param Sze: pointer to the current configuration descriptor size
* @retval None
*/
static void USBD_FrameWork_TMC_Desc(USBD_DevClassHandleTypeDef *pdev, uint32_t pConf, uint32_t *Sze)
{
static USBD_IfDescTypedef *pIfDesc;
static USBD_EpDescTypedef *pEpDesc;

switch(pdev->tclasslist[pdev->classId].InterfaceType)
{
case INTERFACE_TMC_USB488:

/* Append Interface descriptor to Configuration descriptor */
__USBD_FRAMEWORK_SET_IF(pdev->tclasslist[pdev->classId].Ifs[0], 0U, (uint8_t)(pdev->tclasslist[pdev->classId].NumEps), UX_DEVICE_CLASS_TMC_CLASS, UX_DEVICE_SUB_CLASS_USB488_CLASS, INTERFACE_TMC_USB488, 0U);

if(pdev->Speed == USBD_HIGH_SPEED)
{
/* Append Endpoint descriptor to Configuration descriptor */
__USBD_FRAMEWORK_SET_EP(pdev->tclasslist[pdev->classId].Eps[0].add, USBD_EP_TYPE_BULK, (uint16_t)pdev->tclasslist[pdev->classId].Eps[0].size, USBD_TMC_NONE_EPIN_FS_BINTERVAL, USBD_TMC_NONE_EPIN_HS_BINTERVAL);

__USBD_FRAMEWORK_SET_EP(pdev->tclasslist[pdev->classId].Eps[1].add, USBD_EP_TYPE_BULK, (uint16_t)pdev->tclasslist[pdev->classId].Eps[1].size, USBD_TMC_NONE_EPOUT_HS_BINTERVAL, USBD_TMC_NONE_EPOUT_FS_BINTERVAL);
}
else
{
/* Append Endpoint descriptor to Configuration descriptor */
__USBD_FRAMEWORK_SET_EP(pdev->tclasslist[pdev->classId].Eps[0].add, USBD_EP_TYPE_BULK, (uint16_t)pdev->tclasslist[pdev->classId].Eps[0].size, USBD_TMC_NONE_EPIN_FS_BINTERVAL, USBD_TMC_NONE_EPIN_HS_BINTERVAL);

__USBD_FRAMEWORK_SET_EP(pdev->tclasslist[pdev->classId].Eps[1].add, USBD_EP_TYPE_BULK, (uint16_t)pdev->tclasslist[pdev->classId].Eps[1].size, USBD_TMC_NONE_EPOUT_HS_BINTERVAL, USBD_TMC_NONE_EPOUT_FS_BINTERVAL);
}

break;

default:
break;
}
}

Copy
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file ux_device_descriptors.h
* @author MCD Application Team
* @brief USBX Device descriptor header file
******************************************************************************
* @attention
*
* Copyright (c) 2020-2021 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __UX_DEVICE_DESCRIPTORS_H__
#define __UX_DEVICE_DESCRIPTORS_H__

#ifdef __cplusplus
extern "C"
{
#endif

/* Includes ------------------------------------------------------------------*/
#include "ux_api.h"
#include "ux_stm32_config.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
/* USER CODE END Includes */

/* Private defines -----------------------------------------------------------*/
#define USBD_MAX_NUM_CONFIGURATION 1U
#define USBD_MAX_SUPPORTED_CLASS 3U
#define USBD_MAX_CLASS_ENDPOINTS 9U
#define USBD_MAX_CLASS_INTERFACES 12U

#define USBD_HID_CUSTOM_ACTIVATED 1U

#define USBD_CONFIG_MAXPOWER 0x64 // bMaxPower: 200 mA (0x64 * 2 mA)
#define USBD_COMPOSITE_USE_IAD 1U
#define USBD_DEVICE_FRAMEWORK_BUILDER_ENABLED 1U

#define USBD_FRAMEWORK_MAX_DESC_SZ 200U
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Enum Class Type */
typedef enum
{
CLASS_TYPE_NONE = 0,
CLASS_TYPE_HID = 1,
CLASS_TYPE_CDC_ACM = 2,
CLASS_TYPE_MSC = 3,
CLASS_TYPE_CDC_ECM = 4,
CLASS_TYPE_DFU = 5,
CLASS_TYPE_VIDEO = 6,
CLASS_TYPE_PIMA_MTP = 7,
CLASS_TYPE_CCID = 8,
CLASS_TYPE_PRINTER = 9,
CLASS_TYPE_RNDIS = 10,
CLASS_TYPE_AUDIO_10 = 11,
CLASS_TYPE_AUDIO_20 = 12,
CLASS_TYPE_TMC = 40,
} USBD_CompositeClassTypeDef;

/* Enum NONE Interface Type */
typedef enum
{
INTERFACE_NONE_DEFIN = 0
} USBD_NONEInterfaceTypeDef;

/* Enum HID Interface Type */
typedef enum
{
INTERFACE_HID_CUSTOM = 0,
INTERFACE_HID_KEYBOARD = 1,
INTERFACE_HID_MOUSE = 2,
} USBD_HIDInterfaceTypeDef;

typedef enum
{
INTERFACE_TMC_USB488 = 1,
} USBD_TMCInterfaceTypeDef;

/* USB Endpoint handle structure */
typedef struct
{
uint32_t status;
uint32_t total_length;
uint32_t rem_length;
uint32_t maxpacket;
uint16_t is_used;
uint16_t bInterval;
} USBD_EndpointTypeDef;

/* USB endpoint handle structure */
typedef struct
{
uint8_t add;
uint8_t type;
uint16_t size;
uint8_t is_used;
} USBD_EPTypeDef;

/* USB Composite handle structure */
typedef struct
{
USBD_CompositeClassTypeDef ClassType;
uint32_t ClassId;
uint8_t InterfaceType;
uint32_t Active;
uint32_t NumEps;
uint32_t NumIf;
USBD_EPTypeDef Eps[USBD_MAX_CLASS_ENDPOINTS];
uint8_t Ifs[USBD_MAX_CLASS_INTERFACES];
} USBD_CompositeElementTypeDef;

/* USB Device handle structure */
typedef struct _USBD_DevClassHandleTypeDef
{
uint8_t Speed;
uint32_t classId;
uint32_t NumClasses;
USBD_CompositeElementTypeDef tclasslist[USBD_MAX_SUPPORTED_CLASS];
uint32_t CurrDevDescSz;
uint32_t CurrConfDescSz;
} USBD_DevClassHandleTypeDef;

/* USB Device endpoint direction */
typedef enum
{
OUT = 0x00,
IN = 0x80,
} USBD_EPDirectionTypeDef;

/* USB Device descriptors structure */
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} __PACKED USBD_DeviceDescTypedef;

/* USB Iad descriptors structure */
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bFirstInterface;
uint8_t bInterfaceCount;
uint8_t bFunctionClass;
uint8_t bFunctionSubClass;
uint8_t bFunctionProtocol;
uint8_t iFunction;
} __PACKED USBD_IadDescTypedef;

/* USB interface descriptors structure */
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} __PACKED USBD_IfDescTypedef;

/* USB endpoint descriptors structure */
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} __PACKED USBD_EpDescTypedef;

/* USB Config descriptors structure */
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wDescriptorLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
} __PACKED USBD_ConfigDescTypedef;

/* USB Qualifier descriptors structure */
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdDevice;
uint8_t Class;
uint8_t SubClass;
uint8_t Protocol;
uint8_t bMaxPacketSize;
uint8_t bNumConfigurations;
uint8_t bReserved;
} __PACKED USBD_DevQualiDescTypedef;

typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdTMC;
uint16_t bCountryCode;
uint8_t bNumDescriptors;
uint8_t bTMCDescriptorType;
uint16_t wDescriptor;
} __PACKED USBD_TMCDescTypedef;
/* Exported functions prototypes ---------------------------------------------*/
/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

uint8_t *USBD_Get_Device_Framework_Speed(uint8_t Speed, ULONG *Length);
uint8_t *USBD_Get_String_Framework(ULONG *Length);
uint8_t *USBD_Get_Language_Id_Framework(ULONG *Length);
uint16_t USBD_Get_Interface_Number(uint8_t class_type, uint8_t interface_type);
uint16_t USBD_Get_Configuration_Number(uint8_t class_type, uint8_t interface_type);

/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN Private_defines */

/* USER CODE END Private_defines */

#define USBD_VID 0x0A69
#define USBD_PID 0x088A
#define USBD_LANGID_STRING 1033 /* English (United States) */
#define USBD_MANUFACTURER_STRING "YONO"
#define USBD_PRODUCT_STRING "yonoDevice"
#define USBD_SERIAL_NUMBER "123456789"

#define USB_DESC_TYPE_INTERFACE 0x04U
#define USB_DESC_TYPE_ENDPOINT 0x05U

#define USBD_EP_TYPE_CTRL 0x00U
#define USBD_EP_TYPE_ISOC 0x01U
#define USBD_EP_TYPE_BULK 0x02U
#define USBD_EP_TYPE_INTR 0x03U

#define USBD_FULL_SPEED 0x00U
#define USBD_HIGH_SPEED 0x01U

#define USB_BCDUSB 0x0200U
#define LANGUAGE_ID_MAX_LENGTH 2U

#define USBD_IDX_MFC_STR 0x01U
#define USBD_IDX_PRODUCT_STR 0x02U
#define USBD_IDX_SERIAL_STR 0x03U

#define USBD_MAX_EP0_SIZE 64U
#define USBD_DEVICE_QUALIFIER_DESC_SIZE 0x0AU

#define USBD_STRING_FRAMEWORK_MAX_LENGTH 256U

/* */
#define UX_DEVICE_CLASS_TMC_CLASS 0xFEU
#define UX_DEVICE_SUB_CLASS_USB488_CLASS 0x03U

/* Device TMC none */
#define USBD_TMC_NONE_EPIN_ADDR 0x81U
#define USBD_TMC_NONE_EPOUT_ADDR 0x01U
#define USBD_TMC_NONE_EPIN_FS_MPS 0x40U
#define USBD_TMC_NONE_EPIN_HS_MPS 0x40U
#define USBD_TMC_NONE_EPIN_FS_BINTERVAL 5U
#define USBD_TMC_NONE_EPIN_HS_BINTERVAL 0U
#define USBD_TMC_NONE_EPOUT_FS_MPS 0x40U
#define USBD_TMC_NONE_EPOUT_HS_MPS 0x40U
#define USBD_TMC_NONE_EPOUT_FS_BINTERVAL 0U
#define USBD_TMC_NONE_EPOUT_HS_BINTERVAL 5U

#ifndef USBD_CONFIG_STR_DESC_IDX
#define USBD_CONFIG_STR_DESC_IDX 0x00
#endif /* USBD_CONFIG_STR_DESC_IDX */

#ifndef USBD_CONFIG_BMATTRIBUTES
#define USBD_CONFIG_BMATTRIBUTES 0x80
#endif /* USBD_CONFIG_BMATTRIBUTES */

/* Private macro -----------------------------------------------------------*/
/* USER CODE BEGIN Private_macro */

/* USER CODE END Private_macro */
#define __USBD_FRAMEWORK_SET_EP(epadd, eptype, epsize, HSinterval, FSinterval) \
do \
{ \
/* Append Endpoint descriptor to Configuration descriptor */ \
pEpDesc = ((USBD_EpDescTypedef *)((uint32_t)pConf + *Sze)); \
pEpDesc->bLength = (uint8_t)sizeof(USBD_EpDescTypedef); \
pEpDesc->bDescriptorType = USB_DESC_TYPE_ENDPOINT; \
pEpDesc->bEndpointAddress = (epadd); \
pEpDesc->bmAttributes = (eptype); \
pEpDesc->wMaxPacketSize = (epsize); \
if(pdev->Speed == USBD_HIGH_SPEED) \
{ \
pEpDesc->bInterval = (HSinterval); \
} \
else \
{ \
pEpDesc->bInterval = (FSinterval); \
} \
*Sze += (uint32_t)sizeof(USBD_EpDescTypedef); \
} \
while(0)

#define __USBD_FRAMEWORK_SET_IF(ifnum, alt, eps, class, subclass, protocol, istring) \
do \
{ \
/* Interface Descriptor */ \
pIfDesc = ((USBD_IfDescTypedef *)((uint32_t)pConf + *Sze)); \
pIfDesc->bLength = (uint8_t)sizeof(USBD_IfDescTypedef); \
pIfDesc->bDescriptorType = USB_DESC_TYPE_INTERFACE; \
pIfDesc->bInterfaceNumber = (ifnum); \
pIfDesc->bAlternateSetting = (alt); \
pIfDesc->bNumEndpoints = (eps); \
pIfDesc->bInterfaceClass = (class); \
pIfDesc->bInterfaceSubClass = (subclass); \
pIfDesc->bInterfaceProtocol = (protocol); \
pIfDesc->iInterface = (istring); \
*Sze += (uint32_t)sizeof(USBD_IfDescTypedef); \
} \
while(0)
#ifdef __cplusplus
}
#endif
#endif /* __UX_DEVICE_DESCRIPTORS_H__ */

发送和接收在 usbx 中的实践

管你这那的，看代码。开了一共 3 个 buffer，这是必须的，因为 USB 传输线路会占用一个 buffer，接收到的完整语句的寄存需要一个，发送寄存需要一个。值得注意的是，_ux_device_class_dpump_tmc_read 函数哪怕经过我的改造，也依然是一个阻塞的函数，所以 TMC_Engine () 的 TMC 收发支持需要独立占用一个线程。

TMC_Dpump 指针将会在 TMC_Device 的 activate 和 deactivate 中进行绑定和解绑。

Copy
/********************************************************************************

**** Copyright (C), 2024, Yuanlong Xu <Yono233@outlook.com> ****
**** All rights reserved ****

********************************************************************************
* File Name : ux_device_tmc.c
* Author : yono
* Date : 2025-01-07
* Version : 1.0
********************************************************************************/
/**************************************************************************/
/*
tmc用户解析
*/

/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "ux_api.h"
#include "ux_device_class_dpump.h"
/* Private types -------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
#define UX_HOST_CLASS_DPUMP_PACKET_SIZE 255
extern UX_SLAVE_CLASS_DPUMP *TMC_Dpump;

/* 信道buffer */
uint8_t device_buffer[UX_HOST_CLASS_DPUMP_PACKET_SIZE];
/* 帧信息 */
uint8_t TransferEMO = 0; // 传输结束标志 0x00继续传输 0x01传输结束
uint8_t TransferTag = 0; // 传输令牌，回复时复制
/* 信息buffer */
uint8_t SCPI_Read[255];
uint32_t SCPI_Read_Length = 0;
uint8_t SCPI_Write[255];
uint32_t SCPI_Write_Length = 0;

/* Private Constants ---------------------------------------------------------*/
/* Private macros ------------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
uint32_t TMC_Write(uint8_t *data, uint32_t len);
extern UINT _ux_device_class_dpump_tmc_read(UX_SLAVE_CLASS_DPUMP *dpump, UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
/* Private functions ---------------------------------------------------------*/

void TMC_Engine(void)
{
UINT status;
ULONG actual_length;

if(TMC_Dpump != UX_NULL)
{
/* 收取部分 */
status = _ux_device_class_dpump_tmc_read(TMC_Dpump, device_buffer, UX_HOST_CLASS_DPUMP_PACKET_SIZE, &actual_length);
if(status == UX_SUCCESS)
{
if((0xff ^ device_buffer[1] ^ device_buffer[2]) || device_buffer[3] != 0)
return; // 不良报文

switch(device_buffer[0]) // 命令
{
case 0x01:
// 接收分支
ux_utility_memory_copy(&SCPI_Read[0 + SCPI_Read_Length], // 兼容分包传输，以前一次传输的长度为起始
&device_buffer[12], // 真实携带数据的起始位置
*(uint32_t *)(&device_buffer[4])); // 4~7字节以小端组成数据长度
SCPI_Read_Length += *(uint32_t *)(&device_buffer[4]);

TransferEMO = device_buffer[8] & 0x01;

if(TransferEMO)
{
/* 回环测试 */
TMC_Write(SCPI_Read, SCPI_Read_Length);
/* 结尾兼容 */
// 传输结束 触发解析
SCPI_Read_Length = 0;
}
else
{
// 传输未结束 不处理
}
break;

case 0x02:
TransferTag = device_buffer[1]; // 寄存令牌

/* 0~3 是请求及令牌，无需更改*/
*(uint32_t *)(&device_buffer[4]) = SCPI_Write_Length; // 4~7字节以小端组成数据长度

/* 成功固定数据 */
device_buffer[8] = 0x1; // 成功回复
device_buffer[9] = 0x00;
device_buffer[10] = 0x00;
device_buffer[11] = 0x00;

/* 填充发送数据 */
ux_utility_memory_copy(&device_buffer[12], SCPI_Write, SCPI_Write_Length);

/* 填充0x00 此处如果原本就是4字节对齐，将多补4个0 */
for(uint32_t i = 12 + SCPI_Write_Length; i < ((12 + SCPI_Write_Length + 0x3) & ~0x3U); i++)
{
device_buffer[i] = 0x00;
}

/* 请求发送 */
status = _ux_device_class_dpump_write(TMC_Dpump, device_buffer, ((12 + SCPI_Write_Length + 0x3) & ~0x3U), &actual_length);

/* 清理发送请求 */
SCPI_Write_Length = 0;
break;
default:
break;
}
}
}
}

uint32_t TMC_Write(uint8_t *data, uint32_t len)
{
if(sizeof(SCPI_Write) < (SCPI_Write_Length + len))
return 1;

ux_utility_memory_copy(&SCPI_Write[0 + SCPI_Write_Length], // 兼容分包填充
data,
len);
SCPI_Write_Length += len;

return 0;
}

Copy
/**************************************************************************/
/* */
/* Copyright (c) Microsoft Corporation. All rights reserved. */
/* */
/* This software is licensed under the Microsoft Software License */
/* Terms for Microsoft Azure RTOS. Full text of the license can be */
/* found in the LICENSE file at https://aka.ms/AzureRTOS_EULA */
/* and in the root directory of this software. */
/* */
/**************************************************************************/

/**************************************************************************/
/**************************************************************************/
/** */
/** USBX Component */
/** */
/** Device DPUMP Class */
/** */
/**************************************************************************/
/**************************************************************************/

#define UX_SOURCE_CODE

/* Include necessary system files. */

#include "ux_api.h"
#include "ux_device_class_dpump.h"
#include "ux_device_stack.h"

/**
* @brief 基于ux_device_class_dpump_read 的非完全阻塞版，适合TMC设备的接收
*/

/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _ux_device_class_dpump_tmc_read PORTABLE C */
/* 6.1 */
/* AUTHOR */
/* */
/* yono, yono233.cn */
/* */
/* DESCRIPTION */
/* */
/* This function reads from the DPUMP class of TMC, */
/* non-blocking of function _ux_device_class_dpump_write. */
/* */
/* INPUT */
/* */
/* dpump Address of dpump class */
/* instance */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* _ux_device_stack_transfer_request Request transfer */
/* _ux_utility_memory_copy Copy memory */
/* */
/* CALLED BY */
/* */
/* ThreadX */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 05-19-2020 Chaoqiong Xiao Initial Version 6.0 */
/* 09-30-2020 Chaoqiong Xiao Modified comment(s), */
/* verified memset and memcpy */
/* cases, */
/* resulting in version 6.1 */
/* 01-07-2025 yono Copy and modify to */
/* non-blocking */
/* */
/**************************************************************************/
UINT _ux_device_class_dpump_tmc_read(UX_SLAVE_CLASS_DPUMP *dpump, UCHAR *buffer, ULONG requested_length, ULONG *actual_length)
{
UX_SLAVE_ENDPOINT *endpoint;
UX_SLAVE_DEVICE *device;
UX_SLAVE_TRANSFER *transfer_request;
UINT status;
ULONG local_requested_length;

/* If trace is enabled, insert this event into the trace buffer. */
UX_TRACE_IN_LINE_INSERT(UX_TRACE_DEVICE_CLASS_DPUMP_READ, dpump, buffer, requested_length, 0, UX_TRACE_DEVICE_CLASS_EVENTS, 0, 0)

/* Get the pointer to the device. */
device = &_ux_system_slave->ux_system_slave_device;

/* As long as the device is in the CONFIGURED state. */
if(device->ux_slave_device_state != UX_DEVICE_CONFIGURED)
{
/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_CLASS, UX_CONFIGURATION_HANDLE_UNKNOWN);

/* If trace is enabled, insert this event into the trace buffer. */
UX_TRACE_IN_LINE_INSERT(UX_TRACE_ERROR, UX_CONFIGURATION_HANDLE_UNKNOWN, device, 0, 0, UX_TRACE_ERRORS, 0, 0)

/* Cannot proceed with command, the interface is down. */
return (UX_CONFIGURATION_HANDLE_UNKNOWN);
}

/* Locate the OUT endpoint. */
endpoint = dpump->ux_slave_class_dpump_bulkout_endpoint;

/* Check endpoint. If NULL, we have not yet received the proper SET_INTERFACE command. */
if(endpoint == UX_NULL)
{
/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_CLASS, UX_ENDPOINT_HANDLE_UNKNOWN);

return (UX_ENDPOINT_HANDLE_UNKNOWN);
}

/* All DPUMP reading are on the endpoint OUT, from the host. */
transfer_request = &endpoint->ux_slave_endpoint_transfer_request;

/* Reset the actual length. */
*actual_length = 0;

/* Set return status to SUCCESS to make certain compilers happy. */
status = UX_SUCCESS;

/* Check if we need more transactions. */
while(device->ux_slave_device_state == UX_DEVICE_CONFIGURED && requested_length != 0)
{
/* Check if we have enough in the local buffer. */
if(requested_length > UX_SLAVE_REQUEST_DATA_MAX_LENGTH)

/* We have too much to transfer. */
local_requested_length = UX_SLAVE_REQUEST_DATA_MAX_LENGTH;

else

/* We can proceed with the demanded length. */
local_requested_length = requested_length;

/* Send the request to the device controller. */
status = _ux_device_stack_transfer_request(transfer_request, local_requested_length, local_requested_length);

/* Check the status */
if(status == UX_SUCCESS)
{
/* We need to copy the buffer locally. */
_ux_utility_memory_copy(buffer, transfer_request->ux_slave_transfer_request_data_pointer, local_requested_length); /* Use case of memcpy is verified. */

/* Next buffer address. */
buffer += transfer_request->ux_slave_transfer_request_actual_length;

/* Set the length actually received. */
*actual_length += transfer_request->ux_slave_transfer_request_actual_length;

/* Decrement what left has to be done. */
requested_length -= transfer_request->ux_slave_transfer_request_actual_length;

/* actual_length > 0 */
if(*actual_length > 0)
{
return UX_SUCCESS;
}
}
else

/* We got an error. */
return (status);
}

/* Check why we got here, either completion or device was extracted. */
if(device->ux_slave_device_state != UX_DEVICE_CONFIGURED)
{
/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_CLASS, UX_TRANSFER_NO_ANSWER);

/* If trace is enabled, insert this event into the trace buffer. */
UX_TRACE_IN_LINE_INSERT(UX_TRACE_ERROR, UX_TRANSFER_NO_ANSWER, transfer_request, 0, 0, UX_TRACE_ERRORS, 0, 0)

/* Device must have been extracted. */
return (UX_TRANSFER_NO_ANSWER);
}
else

/* Simply return the last transaction result. */
return (status);
}

setup 特殊支持在 usbx 中的实践

我们知道，usbx 中想要开启设备类，必须调用 ux_device_stack_class_register() API，这个适合需要传入一个 _entry 函数作为类的驱动，依据 usbx 的官方示例和文档，自定义的类枚举最好以 dpump 类为基础，所以这里以 _ux_device_class_dpump_entry 为基础改造一个 TMC 类的驱动。

其中主要需要改写的是 UX_SLAVE_CLASS_COMMAND_QUERY 和 UX_SLAVE_CLASS_COMMAND_REQUEST 分支，UX_SLAVE_CLASS_COMMAND_QUERY 分支需要改写成对于 0xFE InterfaceClass: TMC 的支持，而 UX_SLAVE_CLASS_COMMAND_REQUEST 分支需要增加此前提到的 0xA1 0xA2 TMC 特殊命令的支持。

Copy
/**************************************************************************/
/* */
/* Copyright (c) Microsoft Corporation. All rights reserved. */
/* */
/* This software is licensed under the Microsoft Software License */
/* Terms for Microsoft Azure RTOS. Full text of the license can be */
/* found in the LICENSE file at https://aka.ms/AzureRTOS_EULA */
/* and in the root directory of this software. */
/* */
/**************************************************************************/

/**************************************************************************/
/**************************************************************************/
/** */
/** USBX Component */
/** */
/** Device Data Pump Class */
/** */
/**************************************************************************/
/**************************************************************************/

#define UX_SOURCE_CODE

/* Include necessary system files. */

#include "ux_api.h"
#include "ux_device_class_dpump.h"

#define UX_SLAVE_CLASS_DPUMP_TMC_CLASS 0xFE

/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _ux_device_class_dpump_tmc_entry PORTABLE C */
/* 6.1 */
/* AUTHOR */
/* */
/* yono, yono233.cn */
/* */
/* DESCRIPTION */
/* */
/* This function is the entry point of the device dpump class. It */
/* will be called by the device stack enumeration module when the */
/* host has sent a SET_CONFIGURATION command and the dpump interface */
/* needs to be mounted. */
/* */
/* INPUT */
/* */
/* command Pointer to class command */
/* */
/* OUTPUT */
/* */
/* Completion Status */
/* */
/* CALLS */
/* */
/* _ux_device_class_dpump_initialize Initialize dpump class */
/* _ux_device_class_dpump_activate Activate dpump class */
/* _ux_device_class_dpump_deactivate Deactivate dpump class */
/* _ux_device_class_dpump_change Alternate setting change */
/* */
/* CALLED BY */
/* */
/* Device Data Pump Class */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 05-19-2020 Chaoqiong Xiao Initial Version 6.0 */
/* 09-30-2020 Chaoqiong Xiao Modified comment(s), */
/* resulting in version 6.1 */
/* 01-07-2025 yono Initial */
/* */
/**************************************************************************/
UINT _ux_device_class_dpump_tmc_entry(UX_SLAVE_CLASS_COMMAND *command)
{
UINT status;

/* The command request will tell us we need to do here, either a enumeration
query, an activation or a deactivation. */
switch(command->ux_slave_class_command_request)
{
case UX_SLAVE_CLASS_COMMAND_INITIALIZE:

/* Call the init function of the DPUMP class. */
status = _ux_device_class_dpump_initialize(command);

/* Return the completion status. */
return (status);

case UX_SLAVE_CLASS_COMMAND_QUERY:

/* Check the CLASS definition in the interface descriptor. */
if(command->ux_slave_class_command_class == UX_SLAVE_CLASS_DPUMP_TMC_CLASS)
return (UX_SUCCESS);
else
return (UX_NO_CLASS_MATCH);

case UX_SLAVE_CLASS_COMMAND_ACTIVATE:

/* The activate command is used when the host has sent a SET_CONFIGURATION command
and this interface has to be mounted. Both Bulk endpoints have to be mounted
and the dpump thread needs to be activated. */
status = _ux_device_class_dpump_activate(command);

/* Return the completion status. */
return (status);

case UX_SLAVE_CLASS_COMMAND_CHANGE:

/* The change command is used when the host has sent a SET_INTERFACE command
to go from Alternate Setting 0 to 1 or revert to the default mode. */
status = _ux_device_class_dpump_change(command);

/* Return the completion status. */
return (status);

case UX_SLAVE_CLASS_COMMAND_DEACTIVATE:

/* The deactivate command is used when the device has been extracted.
The device endpoints have to be dismounted and the dpump thread canceled. */
status = _ux_device_class_dpump_deactivate(command);

/* Return the completion status. */
return (status);

case UX_SLAVE_CLASS_COMMAND_REQUEST:

{ // my request begin
status = UX_SUCCESS;

UX_SLAVE_DEVICE *device;
UX_SLAVE_TRANSFER *transfer_request;
UX_SLAVE_ENDPOINT *endpoint;
ULONG length;

/* Get the pointer to the device. */
device = &_ux_system_slave->ux_system_slave_device;

/* Get the control endpoint associated with the device. */
endpoint = &device->ux_slave_device_control_endpoint;

/* Get the pointer to the transfer request associated with the endpoint. */
transfer_request = &endpoint->ux_slave_endpoint_transfer_request;

switch(transfer_request->ux_slave_transfer_request_setup[0])
{
case 0xA1: /* Use case of memcpy is verified. */
length = transfer_request->ux_slave_transfer_request_setup[6] + (transfer_request->ux_slave_transfer_request_setup[7] << 8);

/* Set the direction to OUT. */
transfer_request->ux_slave_transfer_request_phase = UX_TRANSFER_PHASE_DATA_OUT;

switch(transfer_request->ux_slave_transfer_request_setup[1])
{
case 0x07:
/* Copy the device descriptor into the transfer request memory. */
_ux_utility_memory_copy(transfer_request->ux_slave_transfer_request_data_pointer, //
"\x01\x00\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", //
0x18);
break;

default:
break;
}

_ux_device_stack_transfer_request(transfer_request, length, length);
break;
case 0xA2:
length = transfer_request->ux_slave_transfer_request_setup[6] + (transfer_request->ux_slave_transfer_request_setup[7] << 8);

/* Set the direction to IN. */
transfer_request->ux_slave_transfer_request_phase = UX_TRANSFER_PHASE_DATA_OUT;

switch(transfer_request->ux_slave_transfer_request_setup[1])
{
case 0x01:
transfer_request->ux_slave_transfer_request_data_pointer[0] = 0x01; // USBTMC_status values
transfer_request->ux_slave_transfer_request_data_pointer[1] = transfer_request->ux_slave_transfer_request_setup[2]; // 复制令牌
length = 2;
break;

case 0x02:
transfer_request->ux_slave_transfer_request_data_pointer[0] = 0x01; // USBTMC_status values

transfer_request->ux_slave_transfer_request_data_pointer[1] = 0x00; // fixed segment
transfer_request->ux_slave_transfer_request_data_pointer[2] = 0x00;
transfer_request->ux_slave_transfer_request_data_pointer[3] = 0x00;

transfer_request->ux_slave_transfer_request_data_pointer[4] = 0x00; // 剩余未处理的字节数
transfer_request->ux_slave_transfer_request_data_pointer[5] = 0x00;
transfer_request->ux_slave_transfer_request_data_pointer[6] = 0x00;
transfer_request->ux_slave_transfer_request_data_pointer[7] = 0x00;

break;

default:
break;
}

/* Receive the data from the host. */
_ux_device_stack_transfer_request(transfer_request, length, length);
break;
default:
break;
}

} // my request end

return (UX_SUCCESS);

default:

/* Error trap. */
_ux_system_error_handler(UX_SYSTEM_LEVEL_THREAD, UX_SYSTEM_CONTEXT_CLASS, UX_FUNCTION_NOT_SUPPORTED);

/* If trace is enabled, insert this event into the trace buffer. */
UX_TRACE_IN_LINE_INSERT(UX_TRACE_ERROR, UX_FUNCTION_NOT_SUPPORTED, 0, 0, 0, UX_TRACE_ERRORS, 0, 0)

/* Return an error. */
return (UX_FUNCTION_NOT_SUPPORTED);
}
}

此文由 Mix Space 同步更新至 xLog
原始链接为 https://www.yono233.cn/posts/novel/25_1_8_usb_tmc