fxyc87 发表于 2020-11-13 08:43

【MM32 eMiniBoard测评】HID深入理解-2

<p>HID测试时遇到了一些问题,在官方找到了一篇非常好的文章,转发来,大家一起学习</p>

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

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

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

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

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

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

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

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

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

<p>&nbsp;</p>

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

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

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

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

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

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

<p>&nbsp;</p>

<p>三、HID 描述符</p>

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

<p>&nbsp;</p>

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

<p>struct _DEVICE_DEscriptOR_STRUCT</p>

<p>{</p>

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

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

<p>WORD &nbsp;bcdUSB; &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; //字符串描述符的字节数大小</p>

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

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

<p>}&nbsp;</p>

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

<p>struct _INTERFACE_DEscriptOR_STRUCT</p>

<p>{</p>

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

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

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

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

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

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

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

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

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

<p>}</p>

<p>&nbsp;</p>

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

<p>struct _ENDPOIN_DEscriptOR_STRUCT</p>

<p>{</p>

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

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

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

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

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

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

<p>}</p>

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

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

<p>&nbsp;</p>

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

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

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

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

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

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

<p>&nbsp;</p>

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

<p></p>

<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; // Wait for USB Device to configure</p>

<p>{</p>

<p>}</p>

<p>while (1)</p>

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

<p>}</p>

<p>}</p>

<p>&nbsp;</p>

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

<p>static volatile uint8_t &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 == 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;</p>

<p>*len = queue-&gt;resp_size;</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, reqbuf, len);</p>

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

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

<p>*retbuf = queue-&gt;USB_Request;</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>&nbsp;</p>

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

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

<p></p>

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

<p></p>

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

<p></p>

<p>以上就是MM32 MCU USB的HID功能</p>

w494143467 发表于 2020-11-13 10:42

<p>等后面我也测试一下HID,一直看到有人测评,自己还没试过。</p>

仙景 发表于 2020-11-13 11:45

<p>这个调试还挺方便的</p>

mig29 发表于 2020-11-22 15:11

<p>这个 HID 笔记还不错,先Mark一下</p>

zxopenljx 发表于 2021-5-21 10:11

<p>谢谢分享</p>
页: [1]
查看完整版本: 【MM32 eMiniBoard测评】HID深入理解-2