在上一节我们介绍了 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功能。