在上一节我们介绍了 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也不用编写。
下面将介绍如何在设备中增加对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驱动程序,然后显示如下所示:
本次我们采用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功能,使用如下测试工具测试通信情况。
在VID栏填入我们MM32 MCU WinUSB的VID号0x2F81,点击Refresh刷新即可识别,点击OK就可以打开WinUSB。在图上可以看到有输入输出端点,我们MM32 MCU WinUSB采用的的端点EP3来收发数据,填入数据收发如下:
发送端填入01 02 03 04 05 06 07 08 09,点击Send发送,接收端收到01 ff数据,测试结果说明我们的MM32 MCU的WinUSB数据通信正常。
以上就是MM32 MCU USB的WinUSB功能,下一节我们继续介绍MM32 MCU USB的虚拟串口CDC功能。
来源:灵动MM32MCU