MM32 USB功能学习笔记 —— WinUSB设备

demi的头像
demi 发布于:周一, 01/13/2020 - 09:16 ,关键词:

在上一节我们介绍了 MM32 MCU 的 HID 功能,对于HID 来说,虽然免驱,但是速度相对慢,有没有更好的选择呢?当然有,那就是WinUSB ,速度更快,对于现在的 PC 来说,基本上都是 Win10 ,WinUSB 在 Win10 免驱,此节我们就介绍如何使用 MM32 MCU的 WinUSB 功能。

WinUSB 设备是一种通用串行总线 (USB) 设备,其固件定义了某些 Microsoft 操作系统 (OS) 特征描述符,这些描述符将兼容 ID 报告为 "WINUSB"。

WinUSB 设备的用途是让 Windows 将 Winusb.sys 作为设备的功能驱动程序载入,而无需自定义 INF 文件。对于 WinUSB 设备,你无须为设备分发 INF 文件,对最终用户而言,这大大简化了驱动程序安装过程。相反,如果你需要提供自定义 INF,则不应将设备定义为 WinUSB 设备和在 INF 中指定设备的硬件 ID。

通常USB自定义设备都需要用户自己开发驱动,为了免去USB驱动开发,减少用户开发时间,微软花了不少心思,在Win8 或更高版本的Windows 系统中,集成了WinUSB 的WCID设备,从而WinUSB 作为微软提供的一个USB设备的通用驱动程序提供给用户,通过使用这个驱动,用户不再需要编写内核层的驱动程序就能够访问到USB设备。WCID是USB驱动一种新的匹配机制,在2012年左右引入的,通常USB设备都是通过VID和PID来进行匹配的,然而使用了WCID之后,USB设备不再通过VID和PID来匹配驱动,而是通过一个叫做兼容ID(Windows Compatible ID)来匹配,这样用户就不用为每一个VID和PID不同的设备编写INF文件了。当然我们要注意到,这里所说的免驱动包含两层含义:一层是用户不需要编写驱动程序,系统自带了驱动程序,只需写一个INF文件,比如USB串口;另一层则是不需要编写INF文件,系统会根据设备类型来安装驱动,这需要操作系统的支持。对于WinUSB设备来说,在Win8之前不用编写驱动程序,但是需要编写INF文件,匹配设备。在Win8之后,如果设备支持WCID,连INF也不用编写。


图1 WinUSB配置

下面将介绍如何在设备中增加对WCID的支持,让在能在Win8之后的系统上实在真正免驱即插即用。

首先我们得要有一个能够使用起来的自定义设备,在这个设备的设备描述符中,USB版本号设置为2.00,在这个设备的基础之上进行如下的修改:

修改一:响应ID为0xEE的字符描述符请求,字符描述的内容为:

{

0x12,                                       /* bLength */

USB_STRING_DESCRIPTOR_TYPE,          /* bDescriptorType */

'M', 0x00,                                    /* wcChar0 */

'S', 0x00,                                    /* wcChar1 */

'F', 0x00,                                    /* wcChar2 */

'T', 0x00,                                    /* wcChar3 */

'1', 0x00,                                    /* wcChar4 */

'0', 0x00,                                    /* wcChar5 */

'0', 0x00,                                    /* wcChar6 */

0x17,                                       /* bVendorCode */

0x00,                                       /* bReserved */

}

修改一:是为了让我们的自定设备被识别为WCID设备。

修改二:响应请求号为0x17并且index为4的厂商自定义请求,返回内容为:

{

0x28, 0x00, 0x00, 0x00,                       /* dwLength */

0x00, 0x01,                                  /* bcdVersion */

0x04, 0x00,                                  /* wIndex */

0x01,                                        /* bCount */

0,0,0,0,0,0,0,                                 /* Reserved */

/* WCID Function  */

0x00,                                        /* bFirstInterfaceNumber */

0x01,                                        /* bReserved */

/* CID */

'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, 

/* sub CID */

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 

0,0,0,0,0,0,                                  /* Reserved */

}

修改二是为了向Windows系统上报我们USB设备的WCID值,因为我们需要的是WinUSB的驱动程序,所以我们上报的内容信息就是WinUSB驱动的WCID:“WINUSB”。在修改上述内容完成后,将MM32 MCU的USB插入电脑,会发现设备能够自动安装上WinUSB驱动程序,然后显示如下所示:


图2 WinUSB驱动程序

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的WinUSB功能,我们已经封装好全部代码,用户不需要自己配置以上的那些描述符等参数,只需要了解MM32 MCU WinUSB的VID和PID以及如何处理WinUSB的数据接收和发送即可。

对于MM32 MCU的WinUSB功能来说,在使用WinUSB功能之前先调用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)

{      

}

}

然后就是WinUSB数据收发处理函数,USB数据处理函数如下:

static U8 *ptrDataIn;

static U16 DataInReceLen;

static Bulk_queue Bulk_Cmd_queue;

 

static volatile uint8_t  USB_ResponseIdle;

 

void usbd_bulk_init(void)

{

ptrDataIn = USBD_Bulk_BulkOutBuf;

DataInReceLen = 0;

Bulk_queue_init(&Bulk_Cmd_queue);

USB_ResponseIdle = 1;

}

 

/*

 *  USB Device Bulk In Endpoint Event Callback

 *    Parameters:      event: not used (just for compatibility)

 *    Return Value:    None

 */

void USBD_BULK_EP_BULKIN_Event(U32 event)

{

uint8_t * sbuf = 0;

int slen;

if(Bulk_queue_get_send_buf(&Bulk_Cmd_queue, &sbuf, &slen)){

USBD_WriteEP(usbd_bulk_ep_bulkin | 0x80, sbuf, slen);

} else {

USB_ResponseIdle = 1;

}

}

 

/*

 *  USB Device Bulk Out Endpoint Event Callback

 *    Parameters:      event: not used (just for compatibility)

 *    Return Value:    None

*/

void USBD_BULK_EP_BULKOUT_Event(U32 event)

{    

U16 bytes_rece;

uint8_t * rbuf;

 

bytes_rec = USBD_ReadEP(usbd_bulk_ep_bulkout, ptrDataIn, USBD_Bulk_BulkBufSize - DataInReceLen);

ptrDataIn += bytes_rece;

DataInReceLen  += bytes_rece;

if ((DataInReceLen >= USBD_Bulk_BulkBufSize) ||

(bytes_rece <  usbd_bulk_maxpacketsize[USBD_HighSpeed])) {

if(Bulk_queue_execute_buf(&Bulk_Cmd_queue,USBD_Bulk_BulkOutBuf, DataInReceLen, &rbuf)) {

//Trigger the BULKIn for the reply

if (USB_ResponseIdle) {

USBD_BULK_EP_BULKIN_Event(0);

USB_ResponseIdle = 0;

}

}

//revert the input pointers

DataInReceLen = 0;

ptrDataIn = USBD_Bulk_BulkOutBuf;

}

}

 

/*

 *  USB Device Bulk In/Out Endpoint Event Callback

 *    Parameters:      event: USB Device Event

 *                       USBD_EVT_OUT: Output Event

 *                       USBD_EVT_IN:  Input Event

 *    Return Value:    None

*/

void USBD_BULK_EP_BULK_Event(U32 event)

{

if (event & USBD_EVT_OUT) {

USBD_BULK_EP_BULKOUT_Event(0);

}

 

if (event & USBD_EVT_IN) {

USBD_BULK_EP_BULKIN_Event(0);

}

}

 

void Bulk_queue_init(Bulk_queue *queue)

{

queue->recv_idx = 0;

queue->send_idx = 0;

queue->free_count = FREE_COUNT_INIT;

queue->send_count = SEND_COUNT_INIT;

}

BOOL Bulk_queue_get_send_buf(Bulk_queue *queue, uint8_t **buf, int *len)

{

if (queue->send_count)

{

queue->send_count--;

*buf = queue->USB_Request[queue->send_idx];

*len = queue->resp_size[queue->send_idx];

queue->send_idx = (queue->send_idx + 1) % Bulk_PACKET_COUNT;

queue->free_count++;

return (__TRUE);

}

return (__FALSE);

}

 

BOOL Bulk_queue_execute_buf(Bulk_queue *queue, const uint8_t *reqbuf, int len, uint8_t **retbuf)

{

uint32_t rsize;

if (queue->free_count > 0)

{

if (len > Bulk_PACKET_SIZE)

{

len = Bulk_PACKET_SIZE;

}

queue->free_count--;

memcpy(queue->USB_Request[queue->recv_idx], reqbuf, len);

rsize = Bulk_ExecuteCommand(reqbuf, queue->USB_Request[queue->recv_idx]);

queue->resp_size[queue->recv_idx] = rsize & 0xFFFF; //get the response size

*retbuf = queue->USB_Request[queue->recv_idx];

queue->recv_idx = (queue->recv_idx + 1) % Bulk_PACKET_COUNT;

queue->send_count++;

return (__TRUE);

}

return (__FALSE);

}

如上,我们只需要实现修改如下Bulk_ExecuteCommand即可处理我们收到的收据,并且填入我们发送数据出去队列即可发送出去。

为了验证MM32 MCU WinUSB功能,使用如下测试工具测试通信情况。


图3 USB调试工具

在VID栏填入我们MM32 MCU WinUSB的VID号0x2F81,点击Refresh刷新即可识别,点击OK就可以打开WinUSB。在图上可以看到有输入输出端点,我们MM32 MCU WinUSB采用的的端点EP3来收发数据,填入数据收发如下:


图4 USB数据通信

发送端填入01 02 03 04 05 06 07 08 09,点击Send发送,接收端收到01 ff数据,测试结果说明我们的MM32 MCU的WinUSB数据通信正常。

以上就是MM32 MCU USB的WinUSB功能,下一节我们继续介绍MM32 MCU USB的虚拟串口CDC功能。

来源:灵动MM32MCU

围观 903