
腊八节快乐
转眼间来到了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
usbd_init();
usbd_connect(__TRUE);
while (!usbd_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 *sbuf;
int slen;
if (HID_queue_get_send_buf(&HID_Cmd_queue, &sbuf, &slen))
{
if (slen > USBD_HID_OUTREPORT_MAX_SZ)
{
util_assert(0);
}
else
{
usbd_hid_get_report_trigger(0, sbuf, USBD_HID_OUTREPORT_MAX_SZ);
}
}
}
// USB HID Callback: when system initializes
void usbd_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 usbd_hid_get_report(U8 rtype, U8 rid, U8 *buf, U8 req)
{
uint8_t *sbuf;
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, &sbuf, &slen))
{
if (slen > USBD_HID_OUTREPORT_MAX_SZ)
{
util_assert(0);
}
else
{
memcpy(buf, sbuf, 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 usbd_hid_no_activity(U8 *buf)
{
return 0;
}
// USB HID Callback: when data is received from the host
void usbd_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 (usbd_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所示:
图2 HID工具连接
点击选择 HID 设备,选择我们 MM32 MCU 的 HID 设备,如图 3 :图3 HID工具设备选择
点击连接,发送数据,可以看到图4结果,发送两次共128字节,收到两次数据,共128字节。
图4 HID工具数据收发
以上就是MM32 MCU USB的HID功能,下一节我们继续介绍MM32 MCU USB的WINUSB功能。
往期精彩:
MM32-LINK使用教程 —— 使用小技巧
MM32-LINK使用教程 —— 远程授权编程
MM32-LINK使用教程 —— 编程规则设置
MM32-LINK使用教程——读写保护操作
MM32-LINK使用教程——编程计数功能
MM32-LINK使用教程 —— 串口功能及硬件连接方式介绍
基于MM32 MCU的shell调试教程(一)
基于MM32 MCU的shell调试教程(二)
基于MM32 MCU的shell调试教程(三)
MM32W无线MCU系列产品应用笔记 —— 数据采集仪器
MM32W无线MCU系列产品应用笔记 —— 数据透传
MM32W无线MCU系列产品应用笔记 —— 蓝牙智能锁方案
MM32W无线MCU系列产品应用笔记 —— 温湿度监测仪方案
MM32W无线MCU系列产品应用笔记 —— 蓝牙自拍杆方案
MM32W无线MCU系列产品应用笔记 —— 智能炫彩遥控灯方案
MM32W无线MCU系列产品应用笔记 —— 自定义AT指令
MM32W无线MCU系列产品应用笔记 —— 自定义服务和特征值
MM32W无线MCU系列产品应用笔记 —— 接口函数调用
MM32W无线MCU系列产品应用笔记 —— 低功耗BLE蓝牙应用
MM32W无线MCU系列产品应用笔记 —— 阻塞式例程
MM32W无线MCU系列产品应用笔记——中断式例程
MM32W无线MCU系列产品应用笔记——蓝牙开发套件介绍
MM32W无线MCU系列产品应用手册——模组与AT指令
MM32SPIN2x 电机专用MCU功能特色——CRC计算单元
MM32SPIN2x 电机专用MCU功能特色——ADC DMA模块配置
MM32SPIN2x 电机专用MCU功能特色 —— I2C功能
MM32SPIN2x 电机专用MCU功能特色 —— SPI功能
MM32SPIN2x 电机专用MCU功能特色 —— 独立看门狗低功耗唤醒
MM32SPIN2x 电机专用MCU功能特色 —— 高集成度产品的电源管理
基于MM32SPIN电机/电源专用芯片的无感方波驱动应用方案——中小功率水泵
基于MM32SPIN电机/电源专用芯片的无传感弦波驱动应用方案——手持式吸尘器
基于MM32SPIN电机/电源专用芯片的有刷电机驱动应用方案——机器人舵机
