一、标准鼠标分析与演示
1.1简介
1.2页面显示
其中页面显示的“×”不用管它,因为鼠标作为物理抓包,里面有时候会抓到一些错误,不一定是真正的通讯错误,很可能是本身线路接触质量不好等原因才打印出来的“×”。
1.3按下鼠标左键
(按键测试时尽量保证不移动鼠标)
1.4按下鼠标右键
1.5按下鼠标中键
1.6鼠标横向向左移动
数据的第二字节发生改变,变成 FF 、FE。
1.7鼠标横向向右移动
数据的第二字节发生改变,变成 01、02。
1.8鼠标纵向上下移动
1.9鼠标滚轮上下滚动
1.10双击SETUP
颜色还是没有变化,黄色是SETUP的起始。
该逻辑分析仪(软件)具有一个功能:双击SETUP起始包,软件会显示自带的标准命令分析,一方面会将主机发出命令的具体含义以及它属于的字段标注出来,另一方面(这条命令是获取设备描述符的)将IN上来的设备描述符按照USB协议本身属于的字段以及对应的含义也都标注出来。
上面的通讯过程就是:
1、获取设备描述符,设备反馈 3 包数据
2、为设备设置地址为 36H
3、再次获取设备描述符,设备反馈 3 包数据
二、过程简要分析
中间省略很多类似的获取各种描述符的过程,直到:
2.1 SET CONFIGURE 设置配置
到此处设备的功能开始被启用
2.2 SET IDLE 设置 HID 设备上传速率
下图表示:遇到设备参数改变的情况下才上传,平时不用上传,所以后续不动鼠标的时候会看到很多 NAK 的回应。
2.3获取鼠标的报表描述符
是鼠标告诉计算机:鼠标是如何表达自身状态改变的,如何表示按键按下、光标移动等 。
后续有详细讲解。
2.4鼠标报表定义鼠标动作
2.4.1端点号00→01
从这里开始,虽然地址没有变化(仍然是 36H),但端点已经变了(从 00 → 01)。
上节有讲过,黄色的都是控制传输,控制传输一定是以端点 0 进行通讯的,到数据传输的时候会有具体的端点号,现在这个鼠标就是端点 1 上传数据。
解释:
-
USB规定:所有USB设备一开始只能用端点0来通信,因为电脑连你是谁都不知道,只能用一个统一的方式找你(端点0就是USB的"公共入口")。
-
之后,当设备告诉电脑“我是一个鼠标”“我有一个用来上传数据的端点,叫端点1”,那真正上传鼠标移动、按键动作这些数据的时候,才用端点1。
简单理解:
-
设备自我介绍、基本设置 → 端点0(固定的)
-
正式工作、传输鼠标动作 → 端点1(鼠标自己定义的)
2.4.2演示
这后面的演示就是第一章的所有页面(多往上翻翻哟~)。
在第一章演示的所有页面中,并不是所有的鼠标都像演示的那样,而是取决于鼠标的报表,这款鼠标的报表描述如下:
1、第一个字节:指示鼠标本身按键的动作,一个位对应一个按键。鼠标的左、右、中三个按键分别对应第一个字节的 bit 0 (01)、bit 1(02) 、bit 2(04) 三个位,剩下的 bit 3~bit 7 是该鼠标的保留位。
解释:
-
按下左键 → 第一个字节是
0000 0001(二进制)
→01h(十六进制) -
按下右键 → 第一个字节是
0000 0010
(二进制)
→02h(十六进制) -
按下中键 → 第一个字节是
0000 0100
(二进制)
→04h(十六进制) -
这就是用"哪一位是1"来表达“哪个键被按了”。
-
推测:同时按左键和右键 →
0000 0011(二进制)
→03h(十六进制) -
1代表按下,0代表抬起。
2、第二个字节:鼠标的左右移动(向左移动FF、FE;向右移动01、02)。
解释:
-
第二个字节的意思是:鼠标在X轴上移动了多少,并以向右为X轴正方向。
-
USB鼠标一般用有符号的8位整数(叫
int8_t
)来表达:-
01h
(1)→ 向右移动1个单位 -
FFh
(-1)→ 向左移动1个单位 -
FEh
(-2)→ 向左移动2个单位
-
-
因为是有符号数,所以
FFh
并不是255,它代表-1。具体换算的过程如下:
①FFh的二进制是什么?FFh = 1111 1111(二进制)。
②判断是不是负数?8位二进制,最高位(最左边那一位)是1,就代表这是负数!
③负值的计算:取反加一。取反 → 0000 0000,加1 → 0000 0001。
3、第三个字节:鼠标的上下移动同鼠标的左右移动,以鼠标向上移动为Y轴的正方向。
-
01h
→ 向下移动1单位 -
FFh
→ 向上移动1单位
4、第四个字节:滚轮的滚动,同上,以滚轮向上移动为Y轴的正方向。
-
01h
→ 滚轮往上滚(通常是页面向上) -
FFh
→ 滚轮往下滚(通常是页面向下)
2.5抓包结果总结
(1)相对鼠标与绝对鼠标
相对鼠标:传输数据时相对坐标(就是相对于上一个点,这一次移动了多少,这也解释了在上面的演示中移动的值都是01,-1这样很小的值)
绝对鼠标:传输数据时绝对坐标( 将屏幕划分成了多少格,告知主机每一次具体位置的坐标)
(2)注意:在键盘的讲解中还有一条 HID 类命令请求如下:
21 09 00 02 00 00 01 00 SET_REPORT(点灯)
对于鼠标来讲是没有的,在实际的应用中也可以知道鼠标没有什么需要主机下传来操作鼠标的步骤。
(3)更正
如果在上一讲中有将获取报表描述符归为HID类命令请求,那么注意了获取报表描述符属于标准请求。
区分类请求和标准请求的方式:
第一个字节中的 bit6 和 bit5 如果这两个位为 00 表示标准请求;如果为 01 的话表示类请求。
之所以会习惯将获取报表描述符归为HID类命令请求的原因是:
获取报表描述符只有像鼠标、键盘这种 HID 才会有,一般其他的设别不会有,所以可以认为这个报表是 HID 比较特有的,可以将这条命令认为是 HID 特有的获取命令,但是它并不属于 HID 类请求。简写:
-
获取的内容是HID特有的(内容上看像类请求)
-
使用的是标准请求的流程(协议上看属于标准请求)
三、软件模拟鼠标
3.1简介
(1)硬件平台:CH549 最小系统板
(2)软件设计思路:
(3)软件框架
(4)需要实现的功能
- 电脑识别到模拟的相对鼠标
- 画图板,相对鼠标绘制图案
- 电脑识别到模拟的绝对鼠标
- 画图板,绝对鼠标绘制图案
3.2软件代码讲解
写在前面:本讲的软件代码讲解不再像上一讲键盘中的那么详细了,更多的是讲解在键盘代码中做了哪些改动和少量鼠标自带的代码部分。
相应的描述符信息和键盘的差异不是很大:
3.2.1设备描述符
没有改动,因为设备描述符没有像主机上报任何关于当前设备类型的信息。
3.2.2接口描述符
该鼠标属于接口 HID 类,在对应的接口描述符处需要改成 02 ,对应的是鼠标协议。(之前是 01 ,代表的是键盘协议。)
3.2.3HID 类描述符
描述鼠标报表的总长度和键盘报表的总长度不一样,所以下面红框处发生的改变。
3.2.4端点描述符
在上将中描述的是一个低速的键盘设备,低速设备限定了端点的长度必须是 8 个字节长度,但是本讲中,描述绝对鼠标的时候上传的数据长度会比较大,所以为了方便描述全速鼠标将端点描述符改大了,这样可以将一个报表完整的以一包的形式上传。
3.2.5相对鼠标功能报表描述符
/* 相对鼠标功能报表描述符 */
UINT8C MouseRepDesc_Relt[] =
{0x05, 0x01, // Usage Page (Generic Desktop) —— 通用桌面控制用途页0x09, 0x02, // Usage (Mouse) —— 用途是鼠标0xA1, 0x01, // Collection (Application) —— 应用集合(开始一段鼠标描述)0x09, 0x01, // Usage (Pointer) —— 指针设备(鼠标光标)0xA1, 0x00, // Collection (Physical) —— 物理集合(表示实际物理输入)// 按键(Button)部分0x05, 0x09, // Usage Page (Button) —— 用途页切换到按钮(鼠标按键)0x19, 0x01, // Usage Minimum (Button 1) —— 最小用途是按钮1(左键)0x29, 0x03, // Usage Maximum (Button 3) —— 最大用途是按钮3(右键、中键)0x15, 0x00, // Logical Minimum (0) —— 逻辑最小值为0(未按下)0x25, 0x01, // Logical Maximum (1) —— 逻辑最大值为1(按下)0x75, 0x01, // Report Size (1) —— 每个按钮占用1位0x95, 0x03, // Report Count (3) —— 有3个按钮0x81, 0x02, // Input (Data, Variable, Absolute) —— 输入数据,变量,绝对值(每个按键独立)// 补齐剩下的5位0x75, 0x05, // Report Size (5) —— 补齐5位(使一个字节对齐)0x95, 0x01, // Report Count (1) —— 只补一组0x81, 0x01, // Input (Constant) —— 固定数据(不用管,填充位)// X、Y移动和滚轮(Wheel)部分0x05, 0x01, // Usage Page (Generic Desktop Controls) —— 用途页切换回通用桌面0x09, 0x30, // Usage (X) —— X方向位移0x09, 0x31, // Usage (Y) —— Y方向位移0x09, 0x38, // Usage (Wheel) —— 滚轮(一般是鼠标中键滚动)0x15, 0x81, // Logical Minimum (-127) —— 最小值-127(有方向)0x25, 0x7F, // Logical Maximum (127) —— 最大值1270x75, 0x08, // Report Size (8) —— 每个数值占8位(1字节)0x95, 0x03, // Report Count (3) —— 有3个值(X、Y、Wheel)0x81, 0x06, // Input (Data, Variable, Relative) —— 输入数据,变量,相对变化(不是绝对坐标,是相对移动)0xC0, // End Collection —— 结束物理集合0xC0 // End Collection —— 结束应用集合
};
3.2.6绝对鼠标功能报表描述符
/* 绝对鼠标方向功能报表描述符 */
UINT8C MouseRepDesc_Abs[] =
{0x05, 0x01, // Usage Page (Generic Desktop) —— 用途页:通用桌面控制0x09, 0x02, // Usage (Mouse) —— 用途:鼠标0xA1, 0x01, // Collection (Application) —— 应用集合(开始一段鼠标描述)0x85, 0x01, // Report ID (1) —— 报告ID设为1// 按钮(Button)部分0x05, 0x09, // Usage Page (Button) —— 用途页切换到按钮0x19, 0x01, // Usage Minimum (Button 1) —— 最小用途是按钮1(左键)0x29, 0x03, // Usage Maximum (Button 3) —— 最大用途是按钮3(右键、中键)0x15, 0x00, // Logical Minimum (0) —— 按钮逻辑最小值0(未按下)0x25, 0x01, // Logical Maximum (1) —— 按钮逻辑最大值1(按下)0x75, 0x01, // Report Size (1) —— 每个按钮占1位0x95, 0x03, // Report Count (3) —— 有3个按钮0x81, 0x02, // Input (Data, Variable, Absolute) —— 输入数据,变量,绝对值(每个按钮单独描述)// 补齐剩下的5位0x75, 0x05, // Report Size (5) —— 补齐5位0x95, 0x01, // Report Count (1) —— 补1组0x81, 0x03, // Input (Constant) —— 固定数据(用来填充,保持字节对齐)// X轴绝对坐标0x05, 0x01, // Usage Page (Generic Desktop Controls) —— 切换回桌面控制0x09, 0x01, // Usage (Pointer) —— 用途:指针(鼠标指针)0xA1, 0x00, // Collection (Physical) —— 物理集合(真实物理输入)0x09, 0x30, // Usage (X) —— 用途:X轴0x15, 0x00, // Logical Minimum (0) —— X最小值00x26, 0x0F, 0x3F, // Logical Maximum (0x3F0F) —— X最大值(0x3F0F=16143,分辨率较高)0x75, 0x0F, // Report Size (15) —— 15位描述X轴(高分辨率)0x95, 0x01, // Report Count (1) —— 只发一组0x81, 0x02, // Input (Data, Variable, Absolute) —— 输入数据,变量,绝对值// 补齐X轴后剩下的一位0x75, 0x01, // Report Size (1) —— 补齐1位0x95, 0x01, // Report Count (1) —— 补齐一组0x81, 0x03, // Input (Constant) —— 固定数据(填充)// Y轴绝对坐标0x09, 0x31, // Usage (Y) —— 用途:Y轴0x15, 0x00, // Logical Minimum (0) —— Y最小值00x26, 0x0F, 0x3F, // Logical Maximum (0x3F0F) —— Y最大值(0x3F0F=16143)0x75, 0x0F, // Report Size (15) —— 15位描述Y轴0x95, 0x01, // Report Count (1) —— 只发一组0x81, 0x02, // Input (Data, Variable, Absolute) —— 输入数据,变量,绝对值// 补齐Y轴后剩下的一位0x75, 0x01, // Report Size (1) —— 补齐1位0x95, 0x01, // Report Count (1) —— 补齐一组0x81, 0x03, // Input (Constant) —— 固定数据(填充)0xC0, // End Collection —— 结束物理集合// 滚轮Wheel部分0x09, 0x38, // Usage (Wheel) —— 用途:滚轮0x15, 0x81, // Logical Minimum (-127) —— 滚轮最小值-1270x25, 0x7F, // Logical Maximum (127) —— 滚轮最大值1270x75, 0x08, // Report Size (8) —— 8位表示滚轮滚动量0x95, 0x01, // Report Count (1) —— 只发一组0x81, 0x06, // Input (Data, Array, Relative) —— 输入数据,数组,相对值(滚轮是相对滚动)0xC0 // End Collection —— 结束应用集合
};
总体小总结:
-
这段描述符定义了一个绝对定位鼠标:
-
3个按钮(左中右键)。
-
X轴、Y轴是15位高分辨率绝对坐标(大概可以表示0~16143之间的位置,非常精细)。
-
滚轮是相对值(上下滚动量-127~127)。
-
-
报表结构大概是:
-
1字节:按钮状态(含补齐)
-
2字节:X坐标(15位有效位+1位补齐)
-
2字节:Y坐标(15位有效位+1位补齐)
-
1字节:滚轮滚动量
-
因为上面的代码即有绝对的鼠标移动,又有相对的:滚轮移动,所以在这里说一下《是怎么体现绝对与相对的》:
主要是看报表里面的输入和描述输入的属性(绝对值还是相对值)。
第18行:
0x81, 0x02, // Input (Data, Variable, Absolute) —— 输入数据,变量,绝对值(每个按钮单独描述)
第63行:
0x81, 0x06, // Input (Data, Array, Relative) —— 输入数据,数组,相对值(滚轮是相对滚动)
3.2.7主函数
void main() // 主函数入口
{UINT16 i; // 定义一个16位无符号变量i,用于循环计数UINT16 time1, time2; // 定义两个16位无符号变量time1和time2,用于计算坐标数据CfgFsys(); // 配置系统时钟(CH549特有函数),使系统时钟稳定mDelaymS(20); // 延时20毫秒,确保系统稳定后再继续初始化mInitSTDIO(); // 初始化标准输入输出(串口),用于打印调试信息printf("USB Mouse Test...\n"); // 向串口输出提示信息,说明程序开始运行// Timer2Init(); // 初始化定时器2(注释掉了),可用于节奏控制USBDeviceInit(); // 初始化USB设备,使其具备USB通信能力EA = 1; // 全局中断使能,允许中断触发memset(HIDMouse, 0, sizeof(HIDMouse)); // 将HIDMouse数组清零,防止初始脏数据while(1) // 无限循环,程序主循环{/*在此循环中执行鼠标移动动作,并将动作数据上传*//* 判断设备是否枚举完成(即主机已经识别此USB设备)*/if (UsbDevConfig != 0) // 如果USB设备已配置好{
#ifndef MouseAbs // 如果没有定义MouseAbs,则进入相对坐标模式(普通鼠标模式){ HIDMouse[0] = 1; // 左键按下HIDMouse[3] = 0; // 中间的滚轮不使用for(i = 0; i < 1000; i++) // 向右移动1000次{HIDMouse[2] = 0; // X轴增量为0HIDMouse[1] = 2; // Y轴增量为2(每次移动2个单位)while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK); // 等待上次数据发送完成EpIntIn(HIDMouse, 4); // 向USB主机发送4字节鼠标数据mDelaymS(5); // 延时5毫秒,控制移动速度}for(i = 0; i < 500; i++) // 向下移动500次{HIDMouse[2] = 2; // X轴增量为2HIDMouse[1] = 0; // Y轴增量为0while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 4);mDelaymS(5);}for(i = 0; i < 1000; i++) // 向左移动1000次{HIDMouse[2] = 0; // X轴增量为0HIDMouse[1] = -2; // Y轴增量为-2(向左)while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 4);mDelaymS(5);}for(i = 0; i < 500; i++) // 向上移动500次{HIDMouse[2] = -2; // X轴增量为-2(向上)HIDMouse[1] = 0; // Y轴增量为0while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 4);mDelaymS(5);}HIDMouse[0] = 0; // 报告ID置0,表示松开按键while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 4); // 发送清零数据mDelaymS(5); // 再次延时,保证动作完成}
#else // 如果定义了MouseAbs,则进入绝对坐标模式{ HIDMouse[0] = 1; // 报告ID设为1for(i = 0; i < 1000; i++) // 绘制第一段轨迹{time1 = 6000 + i * 5; // X坐标逐步递增time2 = 6000; // Y坐标固定HIDMouse[2] = time1 >> 8; // X轴高8位HIDMouse[1] = time1; // X轴低8位HIDMouse[4] = time2 >> 8; // Y轴高8位HIDMouse[3] = time2; // Y轴低8位while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 6); // 发送6字节数据(绝对坐标需要更多字节)mDelaymS(5);}for(i = 0; i < 1000; i++) // 绘制第二段轨迹{time1 = 6000 + 999*5; // X坐标保持最大值time2 = 6000 + i * 5; // Y坐标递增HIDMouse[2] = time1 >> 8;HIDMouse[1] = time1;HIDMouse[4] = time2 >> 8;HIDMouse[3] = time2;while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 6);mDelaymS(5);}for(i = 0; i < 1000; i++) // 绘制第三段轨迹{time1 = 6000 + 999*5 - i * 5; // X坐标递减time2 = 6000 + 999*5; // Y坐标保持最大值HIDMouse[2] = time1 >> 8;HIDMouse[1] = time1;HIDMouse[4] = time2 >> 8;HIDMouse[3] = time2;while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 6);mDelaymS(5);}for(i = 0; i < 1000; i++) // 绘制第四段轨迹{time1 = 6000; // X坐标固定time2 = 6000 + 999*5 - i * 5; // Y坐标递减HIDMouse[2] = time1 >> 8;HIDMouse[1] = time1;HIDMouse[4] = time2 >> 8;HIDMouse[3] = time2;while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 6);mDelaymS(5);}HIDMouse[0] = 0; // 报告ID置0,表示动作结束while (UEP1_CTRL & MASK_UEP_T_RES == UEP_T_RES_ACK);EpIntIn(HIDMouse, 6); // 发送清零数据mDelaymS(5); // 再次延时
#endif}}
}
3.2.8效果展示
相对鼠标绘制如下图:
绝对鼠标将3、4段轨迹注释掉了,效果如下图:
相对鼠标的移动方式是:相对上一个点慢慢的移动;绝对鼠标的移动方式是:点名坐标,让鼠标去哪个点就瞬间去那个点,所以针对绝对鼠标就必须提前分好屏幕的分辨率 。
3.3软件书写分析
在键盘的代码中已经了解到:USB的中断请求开了 3 个,分别是:挂起中断、传输中断、复位中断,其中挂起和复位往往在主线空闲和主机刚识别的时候会产生,传输中断会在发包的过程中频繁、不断产生的。
之间有提到:USB 以事务为最小单位,在下图中用红线分割了以下几个中断点,就划分出了一个又一个的事务。CH549 单片机在做 USB 传输的时候就是以事务为单位靠传输完成中断。

当主机下传 SETUP 包并附带数据 DATA0 的时候 ,设备会回应 ACK ,同时单片机会上报一个中断。当上报一个传输完成中断,其实就是一个事务完成,这时候根据单片机一些寄存器的状态知道当前是发生了什么事务(SETUP?IN?OUT?)以及对应的端点号。如上图中下传的是SETUP事务,对端点零发出的,就会匹配到下图中的 case 端点零。
这个时候提出 SETUP 里面对应数据的 8 个字节请求的命令是什么,准备下面的相应。
在产生中断期间,其实现在还是在中断标志(并没有撤销)。
先去判断接受长度,当前是不是这 8 个字节控制传输的 SETUP 包,另外下面判断它的命令类型以及相应的细节,一些标准请求还是类请求,以及标准请求中的具体命令码是什么。
(上图只是一小部分代码)
在这个过程中其实软件即使是再快,也是速度远远低于本身总线上的 USB 传输速度。
这个时候芯片有一个中断暂停,只要这个中断机制没有撤销它,这个 CH549 的 USB 控制器会自动应答主机 NAK ,也就是说在这个处理过程中主机下发 IN 包,设备还没有准备好的时候,控制器会自动帮我应答 NAK 。
若控制传输向设备获取设备描述符,设备准备好了,将数据填充进去。
如何进行上传?填充好以后设备会让 USB 控制器将它对应的 target 位置位好对应的数据 1 还是数据 0 , 以及应答状态置成 ACK ,然后等到主机下次再发 IN 包的时候,数据就可以被拿走。
也就是说上传数据的时候,不仅要设置数据上传长度,同时还要设置这个数据的应答状态是 ACK ,并且需要补充的是:外面需要清除掉中断标志,就相当于取消了这个中断暂停(下图中的第四条),不暂停数据就可以传走。
3.3.1总结
使用 CH549 控制需要注意以下几点:
(1)接收数据
接收数据包括 SETUP 包和 OUT 包都属于主机下传,对于设备来说都属于接收,只需要设置数据的应答,接收的时候是 ACK 数据就可以被控制器接受下来。如上图中的第一条。
(2)上传数据
对于上传数据需要设置上传数据的长度,同时就相当于写对应的长度寄存器。
同时设置这个数据的应答状态为 ACK ,就是相当于“期望主机的给我的应答是 ACK”,表示这个数据可以出去了。
这个时候只要处于非中断的情况下,相当于这个中断暂停机制不启用数据就可以传走。
如上图中的第 2 条。
(3)没有数据上传
当没有数据上传的时候,处于非中断的情况下,必须人为设置答应状态为 NAK ,就像本程序使用的是端点一上传主机数据。
但是只要每次上传成功以后,都会有中断点,如“软件书写分析图”,就相当于会有中断信号会爆出来。
本程序中如下图,使用中断 1 上传,一旦上传成功了,数据被主机拿走,(代码写好)就会产生中断,执行完后面的撤销中断标志。
但是若此时没有鼠标(或者键盘)的一些数据需要上传,需要提前将应答状态位 置成 NAK ,就相当于自动回应 NAK ,如下图。
在下图中可知,每一个事务后面都会产生一个中断,这样的好处是相当于将整个传输流程变缓了。
3.3.2与键盘的差异
除了上面说的差异之外,还有数据内容与鼠标的差异,在键盘的代码中填写的是 8 个字节,相对鼠标填写的是 4 个字节,绝对鼠标填写的是 6 个字节。
数据大小取决于设备本身特性的有效数据大小。
四、HID设备异同
这里的 HID 设备主要针对的是已经讲解过的键盘和鼠标。
4.1键盘和鼠标的相同点
(1)接口都属于 HID 类,遵循 HID 类规范。
HID 类设备有一个好处就是基本上可以认为是免驱,所有这个主机电脑,包括嵌入式平台基本上都嵌入了 HID 类的这个驱动,所以外面不需要安装任何额外的软件或者是相应的驱动包。
(2)速度上一般有低速、全速类产品。(全速12Mbps完全够用,完全没有必要做高速)
(3)通讯上一般包括中断传输和控制传输,其中控制管道和中断输入(上传)管道必须有,中断传输(下传)可以没有(键盘的点灯)。
因为所有的 USB 设备跟主机通讯的时候都会经过枚举,枚举就是完全由控制传输构成,所以控制传输肯定是要有的。不只是键鼠,任何 USB 设备都必须要有。
中断传输是一个周期性的传输,比较讲究的是这个时效性,所以键盘鼠标对于这种它本身是上传这种异步数据,它是需要额外去采。然后但是对于设备来说又不能主动上传数据,所以主机一般都需要定时去轮询,寻获设备现在的这些有效数据状态。
(4)报表概念:描述自身上传有效数据格式及含义。
(5)上传异步数据,传输数据少量,但要主机周期性来获取。
键盘鼠标就像 ADC 一样,它是去实时采样当前外部的这个传输的一个状态,一旦状态改变了,它才会上传数据。或者也可以通过主机设置它定时上定时进行数据的更新上传。
最多或最通用的还是属于那种有数据量改变的时候再上传。
4.2键盘和鼠标的差异
(1)在 HID 类中的接口协议不同
接口描述符中会将这个接口协议改动,键盘是01,鼠标是02。
(2)自身结构不同,报表描述不同
它自身的结构,由于键盘鼠标它本身从物理描述上的这个结构设计上不一样,键盘一般都是健码,按下与释放以及对应的键盘上指示灯。
鼠标是由比较少按键,最更多的是位移变化,滚轮滚动来表示鼠标特性,所以它相应的这个报表就是描述其本身这些结构的组成以及其相应的表达方式。
4.3HID类请求
完整的 HID 类请求有 6 个,所有的 HID 设备都必须支持,如下图所示。
HID 类请求都是主机采用控制传输发送,并且需要遵循完成的时间限制。
4.3.1 GetReport
主机可以通过该请求来接收 HID 设备通过控制管道发送的数据,它一般是在 HID 设备初始化以及主机获取特征数据项状态的时候使用。
一般建议不要用这条命令来获取设备的周期性数据,像键盘的键值数据,鼠标的移动数据,不要通过这条命令来获取,因为这些数据完全可以通过中断的 IN 上传通道得到,用它的话效率非常的低。
在它的 wValue 字段高字节位置定义的是报告类型(报告类型有:输入报告、输出报告、特征报告),在代码中描述符的 INPUT 就属于输入报告。
报告 ID 可以认为是当描述符里面有多个报表的时候,会以报告 ID 作为区分。在下图中红色框中的内容就是绝对鼠标的报告 ID ,但是由于只有一个绝对鼠标所以 ID 一般就是 0 ,可以不用写,如果将键盘合并到一个报表里一起上传数据的时候,就需要写不同的报告 ID 来区分两个描述的内容。
4.3.2 GetIdle
在 HID 设备上传数据报告时有一个概念是:空闲速率,指的是上传的一包有效报告距离下一个包报告的时间间隔,或者说上传一包报告维持的时间间隔。
GetIdle 就是主机用于读取当前 HID 设备的空闲速率,读取到的值一般在 GetIdle 下面控制传输的数据阶段会上传。
一般在 HID 初始化时,操作系统会将空闲速率设置为 0 ,主机可以通过 SetIdle 命令进行修改。
4.3.3 GetProtocol
该 HID 类请求是主机用来通过它读取 HID 设备当前哪个协议有效,协议分为引导协议和报告协议。在该请求控制传输进行的数据阶段,设备会返回当前的协议值,一般 0 代表就是引导协议,1 代表报告协议。
这条控制传输并不是所有的 HID 都必须支持的,但是支持引导协议的设备是一定要支持的。
4.3.4 SetReport
主机使用该请求向 HID 设备来发送数据。比如说设置它的输入报告、输出报告和特征报告的一些状态等等。
如果 HID 设备没有中断 OUT 端点,就像键盘需要点灯,但是它没有中断下传 OUT 端点,或者是一些只支持 HID 规范 1.0 协议的设备。
所以这条命令是向这些设备发送数据的唯一方式,但这条命令其实也并不是 HID 设备所必需的。
4.3.5 SetIdle
主机用该请求来设置这个 HID 设备的空闲速率。
对键盘来说, HID 规范推荐它的空闲速率应该是 500 毫秒 。在 Value 字段中,它的这个高字节就是用来指明当前主机设置的这个空闲速率,一般这个范围是 4 毫秒~ 1.02 毫秒,一般档位间隔是以 4 毫秒为一档。如果在这个时间间隔过去以后,要上传的这个报告数据仍然没有发生改变的话, HID 也会按照这个报告上传一包这个有效数据表示它将以当前的这个空闲速率进行等待,但如果主机SetIdle 的空闲速率填的是0,就表示设备可以进行无限期的空闲等待,直到当前他这个设备监测到数据状态发生改变了再进行上传。
所以之前我们的键盘枚举、鼠标枚举的时候,主机都会下传一包 set ldle,然后对应的这个位置填的是0。这样的话键盘和鼠标只有发生箭码按键改变或位移等改变才会上传一包数据。
4.3.6 SetProtocol
主机用该请求来设置当前 HID 设备所使用的这个协议类型。在这个 value 值会指明它当前需要使用的这个协议值, 0 代表引导协议,1代表报告协议。在 HID 设备初始化时,它一般被系统设置成为使用的是报告协议。
这条命令不是所有的 HID 需要支持的,但引导设备是必须要支持的。
本专栏说明:本人是USB的初学者,该专栏是我CSDN上第一个学习USB技术的专栏,笔记会比较口语,不是很精炼,后续有点基础后,争取“字字珠玑”。
本文参考:
《USB技术应用与开发》第四讲:实现USB鼠标_哔哩哔哩_bilibili