Nuoeriris · 2020年06月22日

MM32 USB功能学习笔记 —— USB复合设备

在上一节我们介绍了MM32 MCU的USB模拟U盘功能,通过四个章节把常用的USB设备功能编程一一做了介绍,通常来说,往往希望我们USB设备多个功能可以一起工作,因此我们本节我们讲解USB复合设备。

在USB Specification Revision 2.0定义USB复合装置(Composite Device)为具有多个独立控制接口(Interface)的装置。复合装置是一个具有多个功能和配置多个接口的组合设备,但是其只具有一个唯一的设备(Device)地址,透过相对应的USB设备驱动程式(Device Driver)来与主机通讯。如果复合装置中有单一功能对应多个接口的类别就必须额外使用接口关联描述元(Interface Association Descriptor, IAD),让设备中的不同介面与其相同功能建立关联。

接口(Interface)

复合装置只有一套PID/VID,透过不同的接口定义为不同的类别来实现多个功能。 USB设备必须额外使用接口关联描述元(IAD)来定义多重介面功能的复合装置。复合装置的接口描述元(Interface Descriptor)定义接口的每一个功能,USB主机透过此描述元来得知USB设备可用的功能。

端点(Endpoints)

复合装置的Endpoint分配,除了控制端点用于标准、特定类别和供应商特定的请求,其他Endpoint是给复合装置内的个别功能来使用,使用的传输方式与应用的需求有关。

描述元(Descriptor)

装置描述元(device descriptor)
组态描述元(configuration descriptor)
接口描述元(interface descriptor)
特定的类别描述元(class-specific descriptors) 
端点描述元(endpoint descriptor)
字串描述元(String descriptor)

一个USB装置的功能被使用之前,主机会先向USB装置取得组态资料,然后再由USB装置将这些描述元的资料当作组态资料一起传给主机,组态描述元除了本身组态描述元外,另外包含接口描述元、类别描述元和端点描述元,而HID类别则会有特定的类别描述元,此描述元会交代报告(report)描述元和实体(physical)描述元;

 本节我们来讲解如何在MM32 MCU实现USB复合设备功能,在前面我们介绍了MM32 实现HID、WINUSB、CDC和MSC功能,MM32系列MCU的USB功能有4个端点,所以我们可以自由组合上述的功能在一起,本节我们实现HID、WINUSB和CDC复合设备。

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的复合设备功能,我们已经封装好全部代码,用户不需要自己配置那些麻烦的描述符的关系等,参数已经函数处理好了,用户只需要配置只需要知道用之前的单一设备函数即可。

软件资源如下:

对于MM32 MCU的复合设备,我们可以配置一些参数来作为识别。

#define USBD_STRDESC_MAN    L"MM32" 
#define USBD_STRDESC_PROD   L"MM32 Composite"  //产品名称
#define USBD_HID_STRDESC     L"MM32 HID"
#define USBD_BULK_STRDESC   L"MM32 WINUSB"

参数设置如上可以看到电脑上显示的设备名称,如下:
1.png
图1 设备显示名称

根据这些参数,通过封装好的函数直接处理符合设备之间的关系,函数如下:

//MM32 USB复合设备类型初始化函数
void usbd_class_init(void)
{
U8  if_num = 0;
U16 desc_ptr = 0;
desc_ptr += start_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], if_num);

#if (USBD_ADC_ENABLE)
usbd_adc_init();
#endif
#if (USBD_MSC_ENABLE)
#if !(defined(MSC_BL)) &&  defined(DRAG_N_DROP_SUPPORT)
//change descriptors here
if (config_ram_get_disable_msd() == 1 || flash_algo_valid()==0 ){
usbd_if_num -= USBD_MSC_ENABLE;
USB_CONFIGURATION_DESCRIPTOR * usb_conf_desc = (USB_CONFIGURATION_DESCRIPTOR *)USBD_ConfigDescriptor;
usb_conf_desc->bNumInterfaces = usbd_if_num;
U16 usb_wtotal_len = USBD_WTOTALLENGTH_MAX - (USBD_MSC_DESC_LEN     * USBD_MSC_ENABLE);
usb_conf_desc->wTotalLength = usb_wtotal_len;
USBD_ConfigDescriptor[usb_wtotal_len] = 0;
USBD_HID_DescriptorOffset -= USBD_MSC_ENABLE * USBD_MSC_DESC_LEN;
#if (USBD_HS_ENABLE == 1)
usb_conf_desc = (USB_CONFIGURATION_DESCRIPTOR *)USBD_ConfigDescriptor_HS;
usb_conf_desc->bNumInterfaces = usbd_if_num;
usb_conf_desc->wTotalLength = usb_wtotal_len;
USBD_ConfigDescriptor_HS[usb_wtotal_len] = 0;
#endif
} else
#endif
{
usbd_msc_if_num = if_num++;
desc_ptr += msc_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_msc_if_num);
usbd_msc_init();
}
#endif //#if (USBD_MSC_ENABLE)
#if (USBD_CDC_ACM_ENABLE)
usbd_cdc_acm_cif_num = if_num++;
usbd_cdc_acm_dif_num = if_num++;
desc_ptr += acm_cdc_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_cdc_acm_cif_num);
USBD_CDC_ACM_Initialize();
#endif
#if (USBD_HID_ENABLE) 
usbd_hid_if_num = if_num++;
desc_ptr += hid_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_hid_if_num);
usbd_hid_init();
#endif
#if (USBD_WEBUSB_ENABLE)
usbd_webusb_if_num = if_num++;
desc_ptr += webusb_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_webusb_if_num);
#endif
#if (USBD_BULK_ENABLE)
usbd_bulk_if_num = if_num++;
desc_ptr += bulk_desc_fill(&USBD_ConfigDescriptor[desc_ptr], &USBD_ConfigDescriptor_HS[desc_ptr], usbd_bulk_if_num);
usbd_bulk_init();
#endif
#if (USBD_CLS_ENABLE)
usbd_cls_init();
#endif
}
//初始的描述配置填充
static U16 start_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 start_desc[] = { 
/* Configuration 1 */
USB_CONFIGUARTION_DESC_SIZE,          // bLength 
USB_CONFIGURATION_DESCRIPTOR_TYPE,  // bDescriptorType 
WBVAL(USBD_WTOTALLENGTH_MAX),       // wTotalLength 
USBD_IF_NUM_MAX,                        // bNumInterfaces 
0x01,                                       // bConfigurationValue: 0x01 is used to select this configuration 
0x00,                                       // iConfiguration: no string to describe this configuration 
USBD_CFGDESC_BMATTRIBUTES |          // bmAttributes 
(USBD_POWER << 6),
USBD_CFGDESC_BMAXPOWER   // bMaxPower, device power consumption 
};
pD = config_desc;
memcpy(pD, start_desc, sizeof(start_desc));
#if (USBD_HS_ENABLE == 1)
pD = config_desc_hs;
memcpy(pD, start_desc, sizeof(start_desc));
#endif
return sizeof(start_desc);
}
//HID的描述配置填充
static U16 hid_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 hid_desc[] = {
HID_DESC
#if ((USBD_HID_EP_INTOUT != 0) && (USBD_HID_EP_INTIN != 0))
HID_EP_INOUT
#elif (USBD_HID_EP_INTIN != 0)
HID_EP_IN
#elif (USBD_HID_EP_INTOUT != 0)
HID_EP_OUT
#endif
};
pD = config_desc;
memcpy(pD, hid_desc, sizeof(hid_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1) 
const U8 hid_desc_hs[] = {
HID_DESC
#if ((USBD_HID_EP_INTOUT != 0) && (USBD_HID_EP_INTIN != 0))
HID_EP_INOUT_HS
#elif (USBD_HID_EP_INTIN != 0) //#else
HID_EP_IN_HS
#elif (USBD_HID_EP_INTOUT != 0)
HID_EP_OUT_HS
#endif
};
pD = config_desc_hs;
memcpy(pD, hid_desc_hs, sizeof(hid_desc_hs));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
return sizeof(hid_desc);
}
//CDC的描述配置填充
static U16 acm_cdc_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 cdc_desc[] = {
#if (USBD_MULTI_IF)
CDC_ACM_DESC_IAD(0, 2)
#endif
CDC_ACM_DESC_IF0
CDC_ACM_EP_IF0
CDC_ACM_DESC_IF1
CDC_ACM_EP_IF1
};
pD = config_desc;
memcpy(pD, cdc_desc, sizeof(cdc_desc));
#if (USBD_MULTI_IF)
((USB_INTERFACE_ASSOCIATION_DESCRIPTOR *)pD)->bFirstInterface = if_num;
pD += USB_INTERFACE_ASSOC_DESC_SIZE;
#endif
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
pD += USB_INTERFACE_DESC_SIZE + CDC_HEADER_SIZE + CDC_CALL_MANAGEMENT_SIZE + CDC_ABSTRACT_CONTROL_MANAGEMENT_SIZE;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bMasterInterface = if_num;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bSlaveInterface0 = if_num + 1;pD += CDC_UNION_SIZE + USB_ENDPOINT_DESC_SIZE;
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num + 1;
 
#if (USBD_HS_ENABLE == 1)
const U8 cdc_desc_hs[] = {
#if (USBD_MULTI_IF)
CDC_ACM_DESC_IAD(0, 2)
#endif
CDC_ACM_DESC_IF0
CDC_ACM_EP_IF0_HS
CDC_ACM_DESC_IF1
CDC_ACM_EP_IF1_HS
};
pD = config_desc_hs;
memcpy(pD, cdc_desc_hs, sizeof(cdc_desc_hs));
#if (USBD_MULTI_IF)
((USB_INTERFACE_ASSOCIATION_DESCRIPTOR *)pD)->bFirstInterface = if_num;
pD += USB_INTERFACE_ASSOC_DESC_SIZE;
#endif
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
pD += USB_INTERFACE_DESC_SIZE + CDC_HEADER_SIZE + CDC_CALL_MANAGEMENT_SIZE + CDC_ABSTRACT_CONTROL_MANAGEMENT_SIZE;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bMasterInterface = if_num;
((UNION_FUNCTIONAL_DESCRIPTOR*)pD)->bSlaveInterface0 = if_num + 1;
pD += CDC_UNION_SIZE + USB_ENDPOINT_DESC_SIZE;
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num +1 ;
#endif  //(USBD_HS_ENABLE == 1)
return sizeof(cdc_desc);
}
//MSC的描述配置填充
static U16 msc_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 msc_desc[] = { 
MSC_DESC
MSC_EP
};
pD = config_desc;
memcpy(pD, msc_desc, sizeof(msc_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)
const U8 msc_desc_hs[] = { 
MSC_DESC
MSC_EP_HS
};
pD = config_desc_hs;
memcpy(pD, msc_desc_hs, sizeof(msc_desc_hs));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
return sizeof(msc_desc);
}
#if (USBD_WEBUSB_ENABLE)
//WEBUSB的描述配置填充
static U16 webusb_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 webusb_desc[] = {
WEBUSB_DESC
};
pD = config_desc;
memcpy(pD, webusb_desc, sizeof(webusb_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)
pD = config_desc_hs;
memcpy(pD, webusb_desc, sizeof(webusb_desc));
((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
#if (USBD_WINUSB_ENABLE)
pD = USBD_WinUSBDescriptorSetDescriptor + WINUSB_DESCRIPTOR_SET_HEADER_SIZE;
 ((WINUSB_FUNCTION_SUBSET_HEADER*)pD)->bFirstInterface = if_num;
#else
#error "WEBUSB requires WINUSB!"
#endif
return sizeof(webusb_desc); 
}
#endif
#if (USBD_BULK_ENABLE)
//BUCK USB的描述配置填充
static U16 bulk_desc_fill(U8 * config_desc, U8 * config_desc_hs, U8 if_num) {
U8 * pD = 0;
const U8 bulk_desc[] = { 
BULK_DESC
BULK_EP
};
pD = config_desc;
memcpy(pD, bulk_desc, sizeof(bulk_desc));
 ((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#if (USBD_HS_ENABLE == 1)
const U8 bulk_desc_hs[] = { 
BULK_DESC
BULK_EP_HS
};
pD = config_desc_hs;
memcpy(pD, bulk_desc_hs, sizeof(bulk_desc_hs));
 ((USB_INTERFACE_DESCRIPTOR *)pD)->bInterfaceNumber = if_num;
#endif
#if (USBD_WINUSB_ENABLE)
pD = USBD_WinUSBDescriptorSetDescriptor + WINUSB_DESCRIPTOR_SET_HEADER_SIZE;
#if (USBD_WEBUSB_ENABLE)
pD += WINUSB_FUNCTION_SUBSET_HEADER_SIZE + WINUSB_FEATURE_COMPATIBLE_ID_SIZE + DEVICE_INTERFACE_GUIDS_FEATURE_LEN;
#endif
 ((WINUSB_FUNCTION_SUBSET_HEADER*)pD)->bFirstInterface = if_num;
#else
#error "BULK interfaces requires WINUSB!"
#endif
return sizeof(bulk_desc);
}
#endif

在使用MM32复合设备功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)
{
// USB Device Initialization and connect
usbd_init();
usbd_connect(__TRUE);
while (!usbd_configured())   // Wait for USB Device to configure
{
}
while (1)
{
……
}
}

然后和之前一样实现HID、WINUSB、CDC的函数接口即可,这样我们就完成MM32 MCU的复合设备功能,将程序下载到板子中,USB插上电脑,电脑上会枚举出复合设备,如下显示:
2.png
图2 设备管理器复合设备枚举列表

从图上可以看到MM32实现的复合设备在电脑上同时显示出HID、WINUSB以及CDC。对于MM32的复合设备来说,和单一设备一样简单方便,使用时只需要加入各自功能的代码即可。

推荐阅读
关注数
6152
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息