一、标准鼠标分析与演示
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相对鼠标
相应的描述符信息和键盘的差异不是很大:
(1)设备描述符
没有改动,因为设备描述符没有像主机上报任何关于当前设备类型的信息。
(2)接口描述符
该鼠标属于接口 HID 类,在对应的接口描述符处需要改成 02 ,对应的是鼠标协议。(之前是 01 ,代表的是键盘协议。)
(3)HID 类描述符
描述鼠标报表的总长度和键盘报表的总长度不一样,所以下面红框处发生的改变。
(4)端点描述符
在上将中描述的是一个低速的键盘设备,低速设备限定了端点的长度必须是 8 个字节长度,但是本讲中,描述绝对鼠标的时候上传的数据长度会比较大,所以为了方便描述全速鼠标将端点描述符改大了,这样可以将一个报表完整的以一包的形式上传。
(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 —— 结束应用集合
};
(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) —— 输入数据,数组,相对值(滚轮是相对滚动)
(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; // 设置报告ID为1HIDMouse[3] = 0; // 第四个字节置0(在4字节报告中,不使用)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}}
}
剩下争取在明天(4.28号)更新完!
本专栏说明:本人是USB的初学者,该专栏是我CSDN上第一个学习USB技术的专栏,笔记会比较口语,不是很精炼,后续有点基础后,争取“字字珠玑”。
本文参考:
《USB技术应用与开发》第四讲:实现USB鼠标_哔哩哔哩_bilibili