跳转到主要内容

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

<p>转眼间来到了2020年,新年伊始,小编将和大家一起学习使用MM32 MCU的USB功能。对于USB来说,主要应用是HID、CDC、MSC以及WINUSB等功能,此讲先介绍如何使用MM32 MCU的HID功能。</p>

<p>对于USB设备来说,其中有一大类就是HID设备,即Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,其主要用于人与计算机进行交互。它是USB协议最早支持的一种设备类。HID设备可以作为低速、全速、高速设备用。由于HID设备要求用户输入能得到及时响应,所以其传输方式通常采用中断方式,而且无需安装驱动就能进行交互,简单方便。</p>

<p>在USB通信协议中,HID设备的定义放置在接口描述符中,USB的设备描述符和配置描述符中不包含HID设备的信息。所以对于某些特定的HID设备,我们可以定义多个接口,只要其中一个接口为HID设备类即可,在学习HID之前,先来复习一下USB协议的相关内容。</p>

<p><strong>一、</strong><strong>USB</strong><strong>设备描述符-概述</strong></p>

<p>当插入USB设备后,主机需要发送比较短的请求来确认设备的身份、类型、速度等信息,这个过程称之为枚举。</p>

<p>那什么是设备描述符呢?Descriptor即描述符,是一个完整的数据结构,可以通过C语言等编程实现,并存储在USB设备中,用于描述一个USB设备的所有属性,USB主机是通过一系列命令来要求设备发送这些信息的。</p>

<p>描述符的作用就是通过命令操作作来给主机传递信息,从而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作。</p>

<p>USB有那些标准描述符呢?对于&nbsp;USB来说有5种标准描述符:设备描述符、配置描述符、字符描述符、接口描述符、端点描述符&nbsp;。</p>

<p>描述符之间有一定的关系,一个设备只有一个设备描述符,而一个设备描述符可以包含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。由此我们可以看出来,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置描述符,再下面是接口描述符,然后是端点描述符。在获取描述符时,先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号,厂商字符串,产品字符串等。</p>

<p>枚举的过程:</p>

<p>1、等待稳定:主机通过电平差检测到设备,等待100ms让设备电平趋于稳定;</p>

<p>2、首次复位:HUB发起复位,让设备进入初始的地址0模式;</p>

<p>3、首次查询设备描述符:GET_DESCRIPTOR&nbsp;主机查询设备描述符,只要前8字节&nbsp;==&gt; 80 06 01 00 00 00 12 00&nbsp;;</p>

<p>4、二次复位:在接收到设备描述符前8个字节后,再次重启设备;</p>

<p>5、设置地址:SET_ADDRESS&nbsp;主机下发设置地址命令,设备获取新地址&nbsp;==&gt; 00 05 01 00 00 00 00 00&nbsp;;</p>

<p>6、二次查询设备描述符:GET_DEVICE_DESCRPTOR获取整个18字节的设备描述符&nbsp;==&gt; 80 06 01 00 00 00 12 00&nbsp;;</p>

<p>7、获取配置描述符:GET_CONFIGURATION&nbsp;获取9字节配置描述符&nbsp;==&gt; 80 06 02 00 00 00 09 00&nbsp;;</p>

<p>8、完成配置:SET_CONFIGURATION;</p>

<p><strong>二、</strong><strong>HID</strong><strong>设备简述</strong></p>

<p><strong>2.1 HID</strong><strong>设备的特点</strong></p>

<p>交换的数据储存在称为报表(Report)的结构内,设备的固件必须支持HlD报表的格式。主机通过控制和中断传输中的传送和请求报表来传送和接收数据。报表的格式非常灵活。</p>

<p>每一笔事务可以携带小量或中量的数据。低速设备每一笔事务最大是8B&nbsp;,全速设备每一笔事务最大是64B,高速设备每一笔事务最大是1024B,一个报表可以使用多笔事务。</p>

<p>设备可以在未预期的时间传送信息给主机,例如键盘的按键或是鼠标的移动。所以主机会定时轮询设备,以取得最新的数据。</p>

<p>HID&nbsp;设备的最大传输速度有限制。主机可以保证低速的中断端点每10ms&nbsp;内最多&nbsp;1笔事务,每一秒最多是&nbsp;800B,保证全速端点每1ms&nbsp;一笔事务,每一秒最多是64000B,保证高速端点每125 us&nbsp;三笔事务,每一秒最多是&nbsp;24.576MB。</p>

<p>HID&nbsp;设备没有保证的传输速率。如果设备是设置在&nbsp;10ms&nbsp;的时距,事务之间的时间可能等于或小于10ms。除非设备是设置在全速时在每个帧传输数据,或是在高速时在每个微帧传输数据。这个是最快的轮询速率,所以端点可以保证有正确的带宽可供使用。</p>

<p>HID&nbsp;设备除了传送数据给主机外,它也会从主机接收数据。只要能够符合HlD&nbsp;类别规范的设备都可以是HID&nbsp;设备。设备除了HlD&nbsp;接口之外,它可能同时还包含有其他的USB&nbsp;接口。</p>

<p><strong>2.2 HID</strong><strong>设备的硬件要求</strong></p>

<p>HID&nbsp;接口必须要符合&nbsp;Device Class Definition for Human interface Devices&nbsp;规范内所定义的&nbsp;HID&nbsp;类别的需求。在此文件内描述了所需的描述符、传输的频率以及传输的类型等。为了符合规范,HID&nbsp;接口的端点与描述符都必须符合数个要求。所有的&nbsp;HID&nbsp;传输都是使用默认控制管道或是一个中断管道,HID设备必须有一个中断输入端点来传送数据到主机,中断输出端点则不是必需的。Control管道用于接收和响应USB控制和类数据的请求,在由HID类驱动程序轮询时传输数据(使用Get_Reportrequest),从主机接收数据。</p>

<p>对于主机与设备之间所交换的数据,可以分成两种类型:低延迟的数据,必须尽快地到达目的;配置或其他的数据,没有严格时间限制的需求。中断管道是控制管道之外的另一种数据交换的方式,特别适合使用在接收端需要定时或是尽可能及时收到数据的时候。中断输入管道携带数据到主机,中断输出管道则是携带数据到设备。在总线忙的时候,控制管道可能会被延迟,而中断管道保证会有可得到的带宽。HID不需要一定有中断输出管道。如果没有中断输出管道,主机会在控制管道上使用HID&nbsp;设备特有的&nbsp;Set_Report&nbsp;请求来传送所有的报表。</p>

<p><strong>2.3 HID</strong><strong>的程序要求</strong></p>

<p>主机的驱动程序要与&nbsp;HID&nbsp;设备通信,其设备的固件必须符合如下几个需求,设备的描述符必须识别该设备包含有&nbsp;HID&nbsp;接口(描述符)。除了默认控制管道外,固件必须另外支持一个中断输入管道。固件必须包含一个报表描述符来定义要传送与接收的设备数据。如果要传送数据,固件必须支持&nbsp;Get_Report&nbsp;控制传输与中断输入传输。如果要接收数据,固件必须支持&nbsp;Set_Report&nbsp;控制传输与选择性的中断输出传输。所有的&nbsp;HID&nbsp;数据都必须使用定义过的报表格式来定义报表中数据的大小与内容。设备可以支持一个或多个报表。在固件中的一个报表描述符用来描述此报表,以及如何使用报表数据的信息。在每一个报表中的一个数值,定义此报表是一个输入(Input&nbsp;)、输出(Output&nbsp;)或是特征(Feature&nbsp;)报表。主机在输入报表中接收数据,在输出报表中传送数据,特征报表可以在任何方向传递。</p>

<p><strong>三、</strong><strong>HID&nbsp;</strong><strong>描述符</strong></p>

<p>HID&nbsp;设备除了支持&nbsp;USB&nbsp;设备的&nbsp;5&nbsp;种标准描述符之外,还支持&nbsp;HID&nbsp;设备特有的&nbsp;3&nbsp;种描述符。这些描述符是:1、USB&nbsp;标准描述符:设备、配置、接口、端点和字符串描述符;2、HID&nbsp;特有的描述符:&nbsp;HID&nbsp;、报表(Report&nbsp;)和实体(Physical&nbsp;)描述符。从描述符的关联关系看,&nbsp;HID&nbsp;描述符是关联于接口。所以如果一个&nbsp;HID&nbsp;设备有&nbsp;2&nbsp;个端点,设备不需要每个端点有一个&nbsp;HID&nbsp;描述符,具体参考如下代码:</p>

<p>设备描述符</p>

<p>struct _DEVICE_DEscriptOR_STRUCT</p>

<p>{</p>

<p>BYTE &nbsp; bLength;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;//设备描述符的字节数大小</p>

<p>BYTE &nbsp; bDescriptorType; &nbsp; //描述符类型编号,为0x01</p>

<p>WORD&nbsp; bcdUSB; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//USB版本号</p>

<p>BYTE&nbsp; bDeviceClass; &nbsp; //USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型,0x00不是在设备描述符中定义的,如HID</p>

<p>BYTE &nbsp; bDeviceSubClass;&nbsp; //USB分配的子类代码,同上,值由USB规定和分配的,HID设备此值为0</p>

<p>BYTE&nbsp; bDeviceProtocl;&nbsp; //USB分配的设备协议代码,同上HID设备此值为0</p>

<p>BYTE &nbsp; bMaxPacketSize0;&nbsp; //端点0的最大包的大小</p>

<p>WORD &nbsp; idVendor;&nbsp; //厂商编号</p>

<p>WORD &nbsp; idProduct;&nbsp; //产品编号</p>

<p>WORD&nbsp; bcdDevice;&nbsp; //设备出厂编号</p>

<p>BYTE &nbsp; iManufacturer; &nbsp; //描述厂商字符串的索引</p>

<p>BYTE &nbsp; iProduct; &nbsp; //描述产品字符串的索引</p>

<p>BYTE &nbsp; iSerialNumber;&nbsp; //描述设备序列号字符串的索引</p>

<p>BYTE &nbsp; bNumConfiguration; &nbsp; //可能的配置数量</p>

<p>}</p>

<p>配置描述符&nbsp;</p>

<p>struct _CONFIGURATION_DEscriptOR_STRUCT</p>

<p>{</p>

<p>BYTE&nbsp; bLength; &nbsp; //配置描述符的字节数大小</p>

<p>BYTE &nbsp; bDescriptorType; &nbsp; //描述符类型编号,为0x02</p>

<p>WORD &nbsp; wTotalLength; &nbsp; //配置所返回的所有数量的大小</p>

<p>BYTE &nbsp; bNumInterface;&nbsp; //此配置所支持的接口数量</p>

<p>BYTE &nbsp; bConfigurationVale; &nbsp; //Set_Configuration命令需要的参数值</p>

<p>BYTE &nbsp; iConfiguration;&nbsp; //描述该配置的字符串的索引值</p>

<p>BYTE&nbsp; bmAttribute;&nbsp; //供电模式的选择</p>

<p>BYTE &nbsp; MaxPower; &nbsp; //设备从总线提取的最大电流</p>

<p>}</p>

<p>字符描述符&nbsp;</p>

<p>struct _STRING_DEscriptOR_STRUCT</p>

<p>{</p>

<p>BYTE bLength;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//字符串描述符的字节数大小</p>

<p>BYTE bDescriptorType;&nbsp;&nbsp;&nbsp;&nbsp;//描述符类型编号,为0x03</p>

<p>BYTE SomeDescriptor[36];&nbsp;&nbsp;&nbsp;//UNICODE编码的字符串</p>

<p>}&nbsp;</p>

<p>接口描述符</p>

<p>struct _INTERFACE_DEscriptOR_STRUCT</p>

<p>{</p>

<p>BYTE bLength;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//接口描述符的字节数大小</p>

<p>BYTE bDescriptorType;&nbsp;&nbsp;&nbsp;&nbsp;//描述符类型编号,为0x04</p>

<p>BYTE bInterfaceNunber;&nbsp;&nbsp;&nbsp;&nbsp;//接口的编号</p>

<p>BYTE bAlternateSetting;&nbsp;&nbsp;&nbsp;//备用的接口描述符编号</p>

<p>BYTE bNumEndpoints;&nbsp;&nbsp;&nbsp;&nbsp;//该接口使用端点数,不包括端点0</p>

<p>BYTE bInterfaceClass;&nbsp;&nbsp;&nbsp;&nbsp;//接口类型&nbsp;HID设备此值为0x03</p>

<p>BYTE bInterfaceSubClass;&nbsp;&nbsp;&nbsp;//接口子类型&nbsp;HID设备此值为0或者1</p>

<p>BYTE bInterfaceProtocol;&nbsp;&nbsp;&nbsp;//接口所遵循的协议</p>

<p>BYTE iInterface;&nbsp;&nbsp;&nbsp;//描述该接口的字符串索引值</p>

<p>}</p>

<p>端点描述符</p>

<p>struct _ENDPOIN_DEscriptOR_STRUCT</p>

<p>{</p>

<p>BYTE bLength;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//端点描述符的字节数大小</p>

<p>BYTE bDescriptorType;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//描述符类型编号,为0x05</p>

<p>BYTE bEndpointAddress;&nbsp;&nbsp;&nbsp;&nbsp;//端点地址及输入输出属性</p>

<p>BYTE bmAttribute;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//端点的传输类型属性</p>

<p>WORD wMaxPacketSize;&nbsp;&nbsp;&nbsp;&nbsp;//端点收、发的最大包的大小</p>

<p>BYTE bInterval;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//主机查询端点的时间间隔</p>

<p>}</p>

<p><strong>四、</strong><strong>MM32 MCU HID</strong><strong>代码实现</strong></p>

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

<p><strong>软件资源如下:</strong></p>

<p>以下为函数初始化配置及相关全局变量定义内容,代码如下:</p>

<p>#define USBD_POWER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0</p>

<p>#define USBD_MAX_PACKET0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;64</p>

<p>#define USBD_DEVDESC_IDVENDOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0x2F81</p>

<p>#define USBD_DEVDESC_IDPRODUCT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0x0001</p>

<p>以上是定义的MM32 MCU HID设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的HID设备,如图1所示:</p>

<center><img src="http://mcu.eetrend.com/files/2020-01/wen_zhang_/100046872-87981-weixint…;
<center><i>图1 PC设备管理器列表</i></center>

<p>对于MM32 MCU的HID功能来说,在使用HID功能之前先调用USB初始化函数来初始化USB协议栈。</p>

<p>int main(void)</p>

<p>{</p>

<p>// USB Device Initialization and connect</p>

<p>usbd_init();</p>

<p>usbd_connect(__TRUE);</p>

<p>while (!usbd_configured())&nbsp;&nbsp;&nbsp;// Wait for USB Device to configure</p>

<p>{</p>

<p>}</p>

<p>while (1)</p>

<p>{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>

<p>}</p>

<p>}</p>

<p>&nbsp;</p>

<p>然后就是HID数据收发处理函数,USB数据处理函数如下:</p>

<p>static volatile uint8_t&nbsp;&nbsp;USB_ResponseIdle;</p>

<p>static HID_queue HID_Cmd_queue;</p>

<p>&nbsp;</p>

<p>void hid_send_packet()</p>

<p>{</p>

<p>uint8_t *sbuf;</p>

<p>int slen;</p>

<p>if (HID_queue_get_send_buf(&amp;HID_Cmd_queue, &amp;sbuf, &amp;slen))</p>

<p>{</p>

<p>if (slen &gt; USBD_HID_OUTREPORT_MAX_SZ)</p>

<p>{</p>

<p>util_assert(0);</p>

<p>}</p>

<p>else</p>

<p>{</p>

<p>usbd_hid_get_report_trigger(0, sbuf, USBD_HID_OUTREPORT_MAX_SZ);</p>

<p>}</p>

<p>}</p>

<p>}</p>

<p>&nbsp;</p>

<p>// USB HID Callback: when system initializes</p>

<p>void usbd_hid_init(void)</p>

<p>{</p>

<p>USB_ResponseIdle = 1;</p>

<p>HID_queue_init(&amp;HID_Cmd_queue);</p>

<p>}</p>

<p>&nbsp;</p>

<p>// USB HID Callback: when data needs to be prepared for the host</p>

<p>int usbd_hid_get_report(U8 rtype, U8 rid, U8 *buf, U8 req)</p>

<p>{</p>

<p>uint8_t *sbuf;</p>

<p>int slen;</p>

<p>switch (rtype)</p>

<p>{</p>

<p>case HID_REPORT_INPUT:</p>

<p>switch (req)</p>

<p>{</p>

<p>case USBD_HID_REQ_PERIOD_UPDATE:</p>

<p>break;</p>

<p>&nbsp;</p>

<p>case USBD_HID_REQ_EP_CTRL:</p>

<p>case USBD_HID_REQ_EP_INT:</p>

<p>if (HID_queue_get_send_buf(&amp;HID_Cmd_queue, &amp;sbuf, &amp;slen))</p>

<p>{</p>

<p>if (slen &gt; USBD_HID_OUTREPORT_MAX_SZ)</p>

<p>{</p>

<p>util_assert(0);</p>

<p>}</p>

<p>else</p>

<p>{</p>

<p>memcpy(buf, sbuf, slen);</p>

<p>return (USBD_HID_OUTREPORT_MAX_SZ);</p>

<p>}</p>

<p>}</p>

<p>else if (req == USBD_HID_REQ_EP_INT)</p>

<p>{</p>

<p>USB_ResponseIdle = 1;</p>

<p>}</p>

<p>break;</p>

<p>}</p>

<p>&nbsp;</p>

<p>break;</p>

<p>&nbsp;</p>

<p>case HID_REPORT_FEATURE:</p>

<p>break;</p>

<p>}</p>

<p>&nbsp;</p>

<p>return (0);</p>

<p>}</p>

<p>&nbsp;</p>

<p>// USB HID override function return 1 if the activity is trivial or response is null</p>

<p>__attribute__((weak))</p>

<p>uint8_t usbd_hid_no_activity(U8 *buf)</p>

<p>{</p>

<p>return 0;</p>

<p>}</p>

<p>&nbsp;</p>

<p>// USB HID Callback: when data is received from the host</p>

<p>void usbd_hid_set_report(U8 rtype, U8 rid, U8 *buf, int len, U8 req)</p>

<p>{</p>

<p>uint8_t *rbuf;</p>

<p>main_led_state_t led_next_state = MAIN_LED_FLASH;</p>

<p>switch (rtype)</p>

<p>{</p>

<p>case HID_REPORT_OUTPUT:</p>

<p>if (len == 0)</p>

<p>{</p>

<p>break;</p>

<p>}</p>

<p>if (buf[0] == ID_HID_TransferAbort)</p>

<p>{</p>

<p>HID_TransferAbort = 1;</p>

<p>break;</p>

<p>}</p>

<p>&nbsp;</p>

<p>// execute and store to HID_queue</p>

<p>if (HID_queue_execute_buf(&amp;HID_Cmd_queue, buf, len, &amp;rbuf))</p>

<p>{</p>

<p>if (usbd_hid_no_activity(rbuf) == 1)</p>

<p>{</p>

<p>//revert HID LED to default if the response is null</p>

<p>led_next_state = MAIN_LED_DEF;</p>

<p>}</p>

<p>if (USB_ResponseIdle)</p>

<p>{</p>

<p>hid_send_packet();</p>

<p>USB_ResponseIdle = 0;</p>

<p>}</p>

<p>}</p>

<p>else</p>

<p>{</p>

<p>util_assert(0);</p>

<p>}</p>

<p>&nbsp;</p>

<p>break;</p>

<p>&nbsp;</p>

<p>case HID_REPORT_FEATURE:</p>

<p>break;</p>

<p>}</p>

<p>}</p>

<p>&nbsp;</p>

<p>void HID_queue_init(HID_queue *queue)</p>

<p>{</p>

<p>queue-&gt;recv_idx = 0;</p>

<p>queue-&gt;send_idx = 0;</p>

<p>queue-&gt;free_count = FREE_COUNT_INIT;</p>

<p>queue-&gt;send_count = SEND_COUNT_INIT;</p>

<p>}</p>

<p>&nbsp;</p>

<p>BOOL HID_queue_get_send_buf(HID_queue *queue, uint8_t **buf, int *len)</p>

<p>{</p>

<p>if (queue-&gt;send_count)</p>

<p>{</p>

<p>queue-&gt;send_count--;</p>

<p>*buf = queue-&gt;USB_Request[queue-&gt;send_idx];</p>

<p>*len = queue-&gt;resp_size[queue-&gt;send_idx];</p>

<p>queue-&gt;send_idx = (queue-&gt;send_idx + 1) % HID_PACKET_COUNT;</p>

<p>queue-&gt;free_count++;</p>

<p>return (__TRUE);</p>

<p>}</p>

<p>return (__FALSE);</p>

<p>}</p>

<p>&nbsp;</p>

<p>BOOL HID_queue_execute_buf(HID_queue *queue, const uint8_t *reqbuf, int len, uint8_t **retbuf)</p>

<p>{</p>

<p>uint32_t rsize;</p>

<p>if (queue-&gt;free_count &gt; 0)</p>

<p>{</p>

<p>if (len &gt; HID_PACKET_SIZE)</p>

<p>{</p>

<p>len = HID_PACKET_SIZE;</p>

<p>}</p>

<p>queue-&gt;free_count--;</p>

<p>memcpy(queue-&gt;USB_Request[queue-&gt;recv_idx], reqbuf, len);</p>

<p>rsize = HID_ExecuteCommand(reqbuf, queue-&gt;USB_Request[queue-&gt;recv_idx]);</p>

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

<p>*retbuf = queue-&gt;USB_Request[queue-&gt;recv_idx];</p>

<p>queue-&gt;recv_idx = (queue-&gt;recv_idx + 1) % HID_PACKET_COUNT;</p>

<p>queue-&gt;send_count++;</p>

<p>return (__TRUE);</p>

<p>}</p>

<p>return (__FALSE);</p>

<p>}</p>

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

<p>本次我们使用HID工具V1.3.3测试我们的HID功能,打开软件如图2所示:</p>

<center><img src="http://mcu.eetrend.com/files/2020-01/wen_zhang_/100046872-87982-weixint…;
<center><i>图2 HID工具连接</i></center>

点击选择HID设备,选择我们MM32 MCU的HID设备,如图3:

<center><img src="http://mcu.eetrend.com/files/2020-01/wen_zhang_/100046872-87983-weixint…;
<center><i>图3 HID工具设备选择</i></center>

点击连接,发送数据,可以看到图4结果,发送两次共128字节,收到两次数据,共128字节。

<center><img src="http://mcu.eetrend.com/files/2020-01/wen_zhang_/100046872-87984-weixint…;
<center><i>图4 HID工具数据收发</i></center>

以上就是MM32 MCU USB的HID功能,下一节我们继续介绍MM32 MCU USB的WINUSB功能。

来源:<a href="https://mp.weixin.qq.com/s/kpoUpCjQfCOnkoQsQvnmMw"&gt; 灵动MM32MCU</a>