完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 MMCU5721167 于 2020-1-15 11:57 编辑 转眼间来到了2020年,新年伊始,小编将和大家一起学习使用MM32 MCU的USB功能。对于USB来说,主要应用是HID、CDC、MSC以及WINUSB等功能,此讲先介绍如何使用MM32 MCU的HID功能。 对于USB设备来说,其中有一大类就是HID设备,即Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,其主要用于人与计算机进行交互。它是USB协议最早支持的一种设备类。HID设备可以作为低速、全速、高速设备用。由于HID设备要求用户输入能得到及时响应,所以其传输方式通常采用中断方式,而且无需安装驱动就能进行交互,简单方便。 在USB通信协议中,HID设备的定义放置在接口描述符中,USB的设备描述符和配置描述符中不包含HID设备的信息。所以对于某些特定的HID设备,我们可以定义多个接口,只要其中一个接口为HID设备类即可,在学习HID之前,先来复习一下USB协议的相关内容。 一、USB设备描述符-概述 当插入USB设备后,主机需要发送比较短的请求来确认设备的身份、类型、速度等信息,这个过程称之为枚举。 那什么是设备描述符呢?Descriptor即描述符,是一个完整的数据结构,可以通过C语言等编程实现,并存储在USB设备中,用于描述一个USB设备的所有属性,USB主机是通过一系列命令来要求设备发送这些信息的。 描述符的作用就是通过命令操作作来给主机传递信息,从而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作。 USB有那些标准描述符呢?对于 USB来说有5种标准描述符:设备描述符、配置描述符、字符描述符、接口描述符、端点描述符 。 描述符之间有一定的关系,一个设备只有一个设备描述符,而一个设备描述符可以包含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。由此我们可以看出来,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置描述符,再下面是接口描述符,然后是端点描述符。在获取描述符时,先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号,厂商字符串,产品字符串等。 枚举的过程: 1、等待稳定:主机通过电平差检测到设备,等待100ms让设备电平趋于稳定; 2、首次复位:HUB发起复位,让设备进入初始的地址0模式; 3、首次查询设备描述符:GET_DESCRIPTOR 主机查询设备描述符,只要前8字节 ==> 80 06 01 00 00 00 12 00 ; 4、二次复位:在接收到设备描述符前8个字节后,再次重启设备; 5、设置地址:SET_ADDRESS 主机下发设置地址命令,设备获取新地址 ==> 00 05 01 00 00 00 00 00 ; 6、二次查询设备描述符:GET_DEVICE_DESCRPTOR获取整个18字节的设备描述符 ==> 80 06 01 00 00 00 12 00 ; 7、获取配置描述符:GET_CONFIGURAtiON 获取9字节配置描述符 ==> 80 06 02 00 00 00 09 00 ; 8、完成配置:SET_CONFIGURATION; 二、HID设备简述 2.1 HID设备的特点 交换的数据储存在称为报表(Report)的结构内,设备的固件必须支持HlD报表的格式。主机通过控制和中断传输中的传送和请求报表来传送和接收数据。报表的格式非常灵活。 每一笔事务可以携带小量或中量的数据。低速设备每一笔事务最大是8B ,全速设备每一笔事务最大是64B,高速设备每一笔事务最大是1024B,一个报表可以使用多笔事务。 设备可以在未预期的时间传送信息给主机,例如键盘的按键或是鼠标的移动。所以主机会定时轮询设备,以取得最新的数据。 HID 设备的最大传输速度有限制。主机可以保证低速的中断端点每10ms 内最多 1笔事务,每一秒最多是 800B,保证全速端点每1ms 一笔事务,每一秒最多是64000B,保证高速端点每125 us 三笔事务,每一秒最多是 24.576MB。 HID 设备没有保证的传输速率。如果设备是设置在 10ms 的时距,事务之间的时间可能等于或小于10ms。除非设备是设置在全速时在每个帧传输数据,或是在高速时在每个微帧传输数据。这个是最快的轮询速率,所以端点可以保证有正确的带宽可供使用。 HID 设备除了传送数据给主机外,它也会从主机接收数据。只要能够符合HlD 类别规范的设备都可以是HID 设备。设备除了HlD 接口之外,它可能同时还包含有其他的USB 接口。 2.2 HID设备的硬件要求 HID 接口必须要符合 Device Class Definition for Human interface Devices 规范内所定义的 HID 类别的需求。在此文件内描述了所需的描述符、传输的频率以及传输的类型等。为了符合规范,HID 接口的端点与描述符都必须符合数个要求。所有的 HID 传输都是使用默认控制管道或是一个中断管道,HID设备必须有一个中断输入端点来传送数据到主机,中断输出端点则不是必需的。Control管道用于接收和响应USB控制和类数据的请求,在由HID类驱动程序轮询时传输数据(使用Get_Reportrequest),从主机接收数据。 对于主机与设备之间所交换的数据,可以分成两种类型:低延迟的数据,必须尽快地到达目的;配置或其他的数据,没有严格时间限制的需求。中断管道是控制管道之外的另一种数据交换的方式,特别适合使用在接收端需要定时或是尽可能及时收到数据的时候。中断输入管道携带数据到主机,中断输出管道则是携带数据到设备。在总线忙的时候,控制管道可能会被延迟,而中断管道保证会有可得到的带宽。HID不需要一定有中断输出管道。如果没有中断输出管道,主机会在控制管道上使用HID 设备特有的 Set_Report 请求来传送所有的报表。 2.3 HID的程序要求 主机的驱动程序要与 HID 设备通信,其设备的固件必须符合如下几个需求,设备的描述符必须识别该设备包含有 HID 接口(描述符)。除了默认控制管道外,固件必须另外支持一个中断输入管道。固件必须包含一个报表描述符来定义要传送与接收的设备数据。如果要传送数据,固件必须支持 Get_Report 控制传输与中断输入传输。如果要接收数据,固件必须支持 Set_Report 控制传输与选择性的中断输出传输。所有的 HID 数据都必须使用定义过的报表格式来定义报表中数据的大小与内容。设备可以支持一个或多个报表。在固件中的一个报表描述符用来描述此报表,以及如何使用报表数据的信息。在每一个报表中的一个数值,定义此报表是一个输入(Input )、输出(Output )或是特征(Feature )报表。主机在输入报表中接收数据,在输出报表中传送数据,特征报表可以在任何方向传递。 三、HID 描述符 HID 设备除了支持 USB 设备的 5 种标准描述符之外,还支持 HID 设备特有的 3 种描述符。这些描述符是:1、USB 标准描述符:设备、配置、接口、端点和字符串描述符;2、HID 特有的描述符: HID 、报表(Report )和实体(Physical )描述符。从描述符的关联关系看, HID 描述符是关联于接口。所以如果一个 HID 设备有 2 个端点,设备不需要每个端点有一个 HID 描述符,具体参考如下代码: 设备描述符 struct _DEVICE_DEscriptOR_STRUCT { BYTE bLength; //设备描述符的字节数大小 BYTE bDescriptorType; //描述符类型编号,为0x01 WORD bcdUSB; //USB版本号 BYTE bDeviceClass; //USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型,0x00不是在设备描述符中定义的,如HID BYTE bDeviceSubClass; //USB分配的子类代码,同上,值由USB规定和分配的,HID设备此值为0 BYTE bDeviceProtocl; //USB分配的设备协议代码,同上HID设备此值为0 BYTE bMaxPacketSize0; //端点0的最大包的大小 WORD idVendor; //厂商编号 WORD idProduct; //产品编号 WORD bcdDevice; //设备出厂编号 BYTE iManufacturer; //描述厂商字符串的索引 BYTE iProduct; //描述产品字符串的索引 BYTE iSerialNumber; //描述设备序列号字符串的索引 BYTE bNumConfiguration; //可能的配置数量 } 配置描述符 struct _CONFIGURATION_DEscriptOR_STRUCT { BYTE bLength; //配置描述符的字节数大小 BYTE bDescriptorType; //描述符类型编号,为0x02 WORD wTotalLength; //配置所返回的所有数量的大小 BYTE bNumInterface; //此配置所支持的接口数量 BYTE bConfigurationVale; //Set_Configuration命令需要的参数值 BYTE iConfiguration; //描述该配置的字符串的索引值 BYTE bmAttribute; //供电模式的选择 BYTE MaxPower; //设备从总线提取的最大电流 } 字符描述符 struct _STRING_DEscriptOR_STRUCT { BYTE bLength; //字符串描述符的字节数大小 BYTE bDescriptorType; //描述符类型编号,为0x03 BYTE SomeDescriptor[36]; //UNICODE编码的字符串 } 接口描述符 struct _INTERFACE_DEscriptOR_STRUCT { BYTE bLength; //接口描述符的字节数大小 BYTE bDescriptorType; //描述符类型编号,为0x04 BYTE bInterfaceNunber; //接口的编号 BYTE bAlternateSetting; //备用的接口描述符编号 BYTE bNumEndpoints; //该接口使用端点数,不包括端点0 BYTE bInterfaceClass; //接口类型 HID设备此值为0x03 BYTE bInterfaceSubClass; //接口子类型 HID设备此值为0或者1 BYTE bInterfaceProtocol; //接口所遵循的协议 BYTE iInterface; //描述该接口的字符串索引值 } 端点描述符 struct _ENDPOIN_DEscriptOR_STRUCT { BYTE bLength; //端点描述符的字节数大小 BYTE bDescriptorType; //描述符类型编号,为0x05 BYTE bEndpointAddress; //端点地址及输入输出属性 BYTE bmAttribute; //端点的传输类型属性 WORD wMaxPacketSize; //端点收、发的最大包的大小 BYTE bInterval; //主机查询端点的时间间隔 } 四、MM32 MCU HID代码实现 本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的HID功能,我们已经封装好全部代码,用户不需要自己配置以上的那些描述符等参数,只需要了解MM32 MCU HID的VID和PID以及如何处理HID的数据接收和发送即可。 软件资源如下: 以下为函数初始化配置及相关全局变量定义内容,代码如下: #define USBD_POWER 0 #define USBD_MAX_PACKET0 64 #define USBD_DEVDESC_IDVENDOR 0x2F81 #define USBD_DEVDESC_IDPRODUCT 0x0001 以上是定义的MM32 MCU HID设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的HID设备,如图1所示: 图1 PC设备管理器列表 对于MM32 MCU的HID功能来说,在使用HID功能之前先调用USB初始化函数来初始化USB协议栈。 int main(void) { // USB Device Initialization and connect u***d_init(); u***d_connect(__TRUE); while (!u***d_configured()) // Wait for USB Device to configure { } while (1) { } } 然后就是HID数据收发处理函数,USB数据处理函数如下: static volatile uint8_t USB_ResponseIdle; static HID_queue HID_Cmd_queue; void hid_send_packet() { uint8_t ****uf; int slen; if (HID_queue_get_send_buf(&HID_Cmd_queue, &***uf, &slen)) { if (slen > USBD_HID_OUTREPORT_MAX_SZ) { util_assert(0); } else { u***d_hid_get_report_trigger(0, ***uf, USBD_HID_OUTREPORT_MAX_SZ); } } } // USB HID Callback: when system initializes void u***d_hid_init(void) { USB_ResponseIdle = 1; HID_queue_init(&HID_Cmd_queue); } // USB HID Callback: when data needs to be prepared for the host int u***d_hid_get_report(U8 rtype, U8 rid, U8 *buf, U8 req) { uint8_t ****uf; int slen; switch (rtype) { case HID_REPORT_INPUT: switch (req) { case USBD_HID_REQ_PERIOD_UPDATE: break; case USBD_HID_REQ_EP_CTRL: case USBD_HID_REQ_EP_INT: if (HID_queue_get_send_buf(&HID_Cmd_queue, &***uf, &slen)) { if (slen > USBD_HID_OUTREPORT_MAX_SZ) { util_assert(0); } else { memcpy(buf, ***uf, slen); return (USBD_HID_OUTREPORT_MAX_SZ); } } else if (req == USBD_HID_REQ_EP_INT) { USB_ResponseIdle = 1; } break; } break; case HID_REPORT_FEATURE: break; } return (0); } // USB HID override function return 1 if the activity is trivial or response is null __attribute__((weak)) uint8_t u***d_hid_no_activity(U8 *buf) { return 0; } // USB HID Callback: when data is received from the host void u***d_hid_set_report(U8 rtype, U8 rid, U8 *buf, int len, U8 req) { uint8_t *rbuf; main_led_state_t led_next_state = MAIN_LED_FLASH; switch (rtype) { case HID_REPORT_OUTPUT: if (len == 0) { break; } if (buf[0] == ID_HID_TransferAbort) { HID_TransferAbort = 1; break; } // execute and store to HID_queue if (HID_queue_execute_buf(&HID_Cmd_queue, buf, len, &rbuf)) { if (u***d_hid_no_activity(rbuf) == 1) { //revert HID LED to default if the response is null led_next_state = MAIN_LED_DEF; } if (USB_ResponseIdle) { hid_send_packet(); USB_ResponseIdle = 0; } } else { util_assert(0); } break; case HID_REPORT_FEATURE: break; } } void HID_queue_init(HID_queue *queue) { queue->recv_idx = 0; queue->send_idx = 0; queue->free_count = FREE_COUNT_INIT; queue->send_count = SEND_COUNT_INIT; } BOOL HID_queue_get_send_buf(HID_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) % HID_PACKET_COUNT; queue->free_count++; return (__TRUE); } return (__FALSE); } BOOL HID_queue_execute_buf(HID_queue *queue, const uint8_t *reqbuf, int len, uint8_t **retbuf) { uint32_t rsize; if (queue->free_count > 0) { if (len > HID_PACKET_SIZE) { len = HID_PACKET_SIZE; } queue->free_count--; memcpy(queue->USB_Request[queue->recv_idx], reqbuf, len); rsize = HID_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) % HID_PACKET_COUNT; queue->send_count++; return (__TRUE); } return (__FALSE); } 如上,我们只需要实现修改如下HID_ExecuteCommand可处理我们收到的收据,并且填入我们发送数据出去队列即可发送出去。 本次我们使用HID工具V1.3.3测试我们的HID功能,打开软件如图2所示: |
|
相关推荐
1 条评论
|
|
只有小组成员才能发言,加入小组>>
2249个成员聚集在这个小组
加入小组灵动微电子MM32全系列MCU产品应用手册,库函数和例程和选型表
11703 浏览 3 评论
【MM32 eMiniBoard试用连载】+基于OLED12864的GUI---U8G2
5930 浏览 1 评论
【MM32 eMiniBoard试用连载】移植RT-Thread至MM32L373PS
10965 浏览 0 评论
【MM32 eMiniBoard测评报告】+ 开箱 + 初探
4577 浏览 1 评论
灵动微课堂(第106讲) | MM32 USB功能学习笔记 —— WinUSB设备
4302 浏览 1 评论
[MM32软件] MM32F002使用内部flash存储数据怎么操作?
981浏览 1评论
806浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-28 00:02 , Processed in 0.658467 second(s), Total 65, Slave 49 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号