STM32模拟鼠标绝对坐标的设置

stm32模拟鼠标绝对坐标的实现方法

在一次进行stm32模拟鼠标的开发中,要实现用绝对坐标来控制鼠标位置的情况。在通常的开发中,都是使用相对坐标,模拟鼠标在上下左右几个方向移动多少像素,从而实现鼠标的功能。在这一次使用绝对坐标的过程中,遇到了几个坑,在这里记录一下。

实现过程中参考了以下几位博主的文章,在这里一并表示感谢:

  1. USB鼠标HID描述符以及数据格式_usb鼠标数据格式-CSDN博客

  2. usb gaghet hid 模拟鼠标键盘的绝对值描述_hid鼠标绝对坐标-CSDN博客

  3. 使用stm32配置自定义的HID设备_stm32 库函数实现自定义hid-CSDN博客

1 前置知识

1.1 STM32CUBEMX配置鼠标模式

设置debugeSerialWire

时钟为外部高速时钟

开启USB模式

设置HID模式

自动配置时钟频率 主频率为 72MHz USB为48MHz

如果不是,可以手动输入。

最后生成工程文件。

1.2 HID鼠标协议

如果只是实现简单的功能,比如说我这个就是鼠标在屏幕指定位置点击,这个HID协议要掌握的不多,重点是知道绝对坐标、相对坐标HID协议的不同之处即可。下面是相关协议,不懂也没问题,按照步骤来就行。下面是直接复制博主wingceltis-c文章里面的USB鼠标HID描述符以及数据格式_usb鼠标数据格式-CSDN博客

  1. 相对坐标HID描述符
c 复制代码
//相对坐标格式
//每行开始的第一字节为该条目的前缀,前缀的格式为:
//D7~D4:bTag。D3~D2:bType;D1~D0:bSize。以下分别对每个条目注释。
0x05,0x01, // 是一个全部条目。表示用途页为通用桌面设备 
0x09,0x02, // 是一个局部条目。表示用途为鼠标
0xa1,0x01, // 表示应用集合,必须要以END_COLLECTION来结束它,见最后的END_COLLECTION  
0x09,0x01, // 是一个局部条目。说明用途为指针集合
0xa1,0x00, // 这是一个主条目,开集合,后面跟的数据0x00表示该集合是一个物理集合,用途由前面的局部条目定义为指针集合。
0x95,0x03, // 这是一个全局条目,说明数据域的数量为三个。
0x75,0x01, // 这是一个全局条目,说明每个数据域的长度为1个bit。
0x05,0x09, // 这是一个全局条目,选择用途页为按键(Button Page(0x09))
0x19,0x01, // 这是一个局部条目,说明用途的最小值为1。实际上是鼠标左键。
0x29,0x03, // 这是一个局部条目,说明用途的最大值为3。实际上是鼠标中键。
0x15,0x00, // 这是一个全局条目,说明返回的数据的逻辑值(就是我们返回的数据域的值啦)最小为0。因为我们这里用Bit来表示一个数据域,因此最小为0。 MAXMIN
0x25,0x01, // 最大为1  MAXNUM
0x81,0x02, // 这是一个主条目,标识上面的3个bits是独立的。
0x95,0x01, // 这是一个全局条目,说明数据域数量为1个
0x75,0x05, // 这是一个全局条目,说明每个数据域的长度为5bit。
0x81,0x03, // //这是一个主条目,输入用,由前面两个全局条目可知,长度为5bit,数量为1个。它的属性为常量(即返回的数据一直是0)。这个只是为了凑齐一个字节(前面用了3个bit)而填充的一些数据而已,所以它是没有实际用途的。
0x95,0x03, // 这是一个全局条目,说明数据域的个数为3个。
0x75,0x08, // 这是一个全局条目,说明数据域的长度为8bit。
0x05,0x01, // 这是一个全局条目,选择用途页为普通桌面Generic Desktop Page(0x01)
0x09,0x30, // 这是一个局部条目,说明用途为X轴
0x09,0x31, // 这是一个局部条目,说明用途为Y轴
0x09,0x38, // 这是一个局部条目,说明用途为滚轴
0x15,0x81, // 这是一个全局条目,说明返回的逻辑最小为-128。
0x25,0x7f, // 这是一个全局条目,说明返回的逻辑最大为127。
0x81,0x06, // 这是一个主条目。标识上面的3个数据是绝对值。
0xc0,      // 我们开了两个集合,所以要关两次。bSize为0,所以后面没数据。
0xc0       // END_COLLECTION  
  1. 相对坐标发送数据格式

鼠标发送给PC的数据每次4个字节

BYTE1 BYTE2 BYTE3 BYTE4

定义分别是:

BYTE1 --

|--bit7: 1 表示 Y 坐标的变化量超出-256 ~ 255的范围,0表示没有溢出

|--bit6: 1 表示 X 坐标的变化量超出-256 ~ 255的范围,0表示没有溢出

|--bit5: Y 坐标变化的符号位,1表示负数,即鼠标向下移动

|--bit4: X 坐标变化的符号位,1表示负数,即鼠标向左移动

|--bit3: 恒为1

|--bit2: 1表示中键按下

|--bit1: 1表示右键按下

|--bit0: 1表示左键按下

BYTE2 -- X坐标变化量,与byte的bit4组成9位符号数,负数表示向左移,正数表右移。用补码表示变化量

BYTE3 -- Y坐标变化量,与byte的bit5组成9位符号数,负数表示向下移,正数表上移。用补码表示变化量

BYTE4 -- 滚轮变化。

BYTE1高5位是可以不用关注的,一般这5bit 在HID描述符中都是作为填充位使用,置0即可。

  1. 绝对坐标HID描述符

    //每行开始的第一字节为该条目的前缀,前缀的格式为:
    //D7~D4:bTag。D3~D2:bType;D1~D0:bSize。以下分别对每个条目注释。
    //这是一个全局(bType为1)条目,选择用途页为普通桌面Generic Desktop Page(0x01)
    //后面跟一字节数据(bSize为1),后面的字节数就不注释了,
    //自己根据bSize来判断。
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)

    //这是一个局部(bType为2)条目,说明接下来的应用集合用途用于鼠标
    0x09, 0x02, // USAGE (Mouse)

    //这是一个主(bType为0)条目,开集合,后面跟的数据0x01表示
    //该集合是一个应用集合。它的性质在前面由用途页和用途定义为
    //普通桌面用的鼠标。
    0xa1, 0x01, // COLLECTION (Application) //1byte报告ID +按键(3bit)+填充行(5bit)=1byte ; 坐标(16bits*2个)=4bytes;所以上报数据就是6bytes;
    0x85,0x02, // 报告ID(报告ID 0是保留的),多个设备需要添加。
    //这是一个局部条目。说明用途为指针集合
    0x09, 0x01, // USAGE (Pointer)

    //这是一个主条目,开集合,后面跟的数据0x00表示该集合是一个
    //物理集合,用途由前面的局部条目定义为指针集合。
    0xa1, 0x00, // COLLECTION (Physical)

    //这是一个全局条目,选择用途页为按键(Button Page(0x09))
    0x05, 0x09, // USAGE_PAGE (Button)

    //这是一个局部条目,说明用途的最小值为1。实际上是鼠标左键。
    0x19, 0x01, // USAGE_MINIMUM (Button 1)

    //这是一个局部条目,说明用途的最大值为3。实际上是鼠标中键。
    0x29, 0x03, // USAGE_MAXIMUM (Button 3)

    //这是一个全局条目,说明返回的数据的逻辑值(就是我们返回的数据域的值啦)
    //最小为0。因为我们这里用Bit来表示一个数据域,因此最小为0,最大为1。
    0x15, 0x00, // LOGICAL_MINIMUM (0)

    //这是一个全局条目,说明逻辑值最大为1。
    0x25, 0x01, // LOGICAL_MAXIMUM (1)

    //这是一个全局条目,说明数据域的数量为三个。
    0x95, 0x03, // REPORT_COUNT (3)

    //这是一个全局条目,说明每个数据域的长度为1个bit。
    0x75, 0x01, // REPORT_SIZE (1)

    //这是一个主条目,说明有3个长度为1bit的数据域(数量和长度
    //由前面的两个全局条目所定义)用来做为输入,
    //属性为:Data,Var,Abs。Data表示这些数据可以变动,Var表示 //Var可以任意大小数据但是最小值要从0开始 Abs只能一个字节
    //这些数据域是独立的,每个域表示一个意思。Abs表示绝对值。
    //这样定义的结果就是,第一个数据域bit0表示按键1(左键)是否按下,
    //第二个数据域bit1表示按键2(右键)是否按下,第三个数据域bit2表示
    //按键3(中键)是否按下。
    0x81, 0x02, // INPUT (Data,Var,Abs)

    //这是一个全局条目,说明数据域数量为1个
    0x95, 0x01, // REPORT_COUNT (1)

    //这是一个全局条目,说明每个数据域的长度为5bit。
    0x75, 0x05, // REPORT_SIZE (5)

    //这是一个主条目,输入用,由前面两个全局条目可知,长度为5bit,
    //数量为1个。它的属性为常量(即返回的数据一直是0)。
    //这个只是为了凑齐一个字节(前面用了3个bit)而填充的一些数据
    //而已,所以它是没有实际用途的。
    0x81, 0x03, // INPUT (Cnst,Var,Abs)

    //这是一个全局条目,选择用途页为普通桌面Generic Desktop Page(0x01)
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)

    //这是一个局部条目,说明用途为X轴
    0x09, 0x30, // USAGE (X)
    //下面两个为全局条目,说明返回的逻辑最小和最大值。 屏幕大小 1920*1080
    //这里定义X的逻辑最小值为0,即坐标原点
    //X的逻辑最大值为1919,即屏幕x的坐标为(0,1919)。
    //由于1920超过了一字节的范围,所以需要用2字节的格式表示最大值
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x26, 0x7f, 0x07, // LOGICAL_MAXIMUM (1919)

    //下面两个为全局条目,说明返回的物理最小和最大值。
    //这里定义X的物理最小值为0,即坐标原点
    //X的物理最大值为1919,即屏幕x的坐标为(0,1919)。
    //由于1920超过了一字节的范围,所以需要用2字节的格式表示最大值
    0x35, 0x00, //Physical Minimum (0)
    0x46, 0x7f, 0x07, //Physical Maximum(1919)

    //这是一个全局条目,说明数据域的长度为16bit。
    0x75, 0x10, // REPORT_SIZE (16)

    //这是一个全局条目,说明数据域的个数为1个。
    0x95, 0x01, // REPORT_COUNT (1)

    //这是一个主条目。它说明这两个16bit的数据域是输入用的,
    //属性为:Data,Var,Abs。Data说明数据是可以变的,Var说明
    //这些数据域是独立的,Abs表示这些值是绝对值。
    0x81, 0x02, // INPUT (Data,Var,Abs)

    //这是一个局部条目,说明用途为Y轴
    0x09, 0x31, // USAGE (Y)

    //下面两个为全局条目,说明返回的逻辑最小和最大值。
    //这里定义Y的逻辑最小值为0,即坐标原点
    //Y的逻辑最大值为1079,即屏幕Y的坐标为(0,1079)。
    //由于1080超过了一字节的范围,所以需要用2字节的格式表示最大值
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x26, 0x37, 0x04, // LOGICAL_MAXIMUM (1079)

    //下面两个为全局条目,说明返回的物理最小和最大值。
    //这里定义Y的物理最小值为0,即坐标原点
    //Y的物理最大值为1079,即屏幕Y的坐标为(0,1079)。
    //由于1080超过了一字节的范围,所以需要用2字节的格式表示最大值
    0x35, 0x00, //Physical Minimum (0)
    0x46, 0x37, 0x04, //Physical Maximum(1079)

    //这是一个全局条目,说明数据域的长度为16bit。
    0x75, 0x10, // REPORT_SIZE (16)

    //这是一个全局条目,说明数据域的个数为1个。
    0x95, 0x01, // REPORT_COUNT (1)

    //这是一个主条目。它说明这两个16bit的数据域是输入用的,
    //属性为:Data,Var,Abs。Data说明数据是可以变的,Var说明
    //这些数据域是独立的,Abs表示这些值是绝对值。
    0x81, 0x02, // INPUT (Data,Var,Abs)

    //下面这两个主条目用来关闭前面的集合用。
    //我们开了两个集合,所以要关两次。bSize为0,所以后面没数据。
    0xc0, // END_COLLECTION
    0xc0 , // END_COLLECTION

  2. 绝对坐标发送数据格式

触摸设备根据HID设置发送给PC的数据每次6个字节

BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 BYTE6

定义分别是:

BYTE1 -- 报告ID(报告ID 0是保留的),多个设备需要添加。

BYTE2 --

|--bit7: 保留 默认值为 0

|--bit6: 保留 默认值为 0

|--bit5: 保留 默认值为 0

|--bit4: 保留 默认值为 0

|--bit3: 保留 默认值为 0

|--bit2: 1表示中键按下

|--bit1: 1表示右键按下

|--bit0: 1表示左键按下

BYTE3 -- X坐标变化量低8位,BYTE3 与 BYTE4组合后最大值不能操作描述符中设定的值

BYTE4 -- X坐标变化量高8位

BYTE5 -- Y坐标变化量低8位,BYTE3 与 BYTE4组合后最大值不能操作描述符中设定的值

BYTE6 -- Y坐标变化量高8位

这里要注意,绝对坐标和相对坐标,发送数据格式是有区别的

相对坐标中BYTE1是按键状态

绝对坐标中BYTE2是按键状态,BYTE1是报告ID,这个不要设置为0,否则电脑不识别。

2 配置绝对坐标模式

  1. 添加头文件
c 复制代码
#include "usbd_hid.h"
extern USBD_HandleTypeDef hUsbDeviceFS;
  1. usbd_hid.c文件中,替换HID描述符
  1. usbd_hid.h中修改==#define HID_EPIN_SIZE 0x04U为#define HID_EPIN_SIZE 0x06U==

3 几个注意的地方

3.1 时钟频率为72MHz和48MHz

3.2 绝对坐标和相对坐标数据长度不同,绝对坐标表示方式

3.3 HID协议数据格式每一位表示的含义

3.4 电脑屏幕像素与绝对坐标HID描述符中参数要对应

3.5 如果中途修改CUBEMX配置,生成修改后的工程,一定要再看一下第2步修改过的配置还在不在

4 在手机上实现

上面的方法实现的鼠标,只能在我的电脑上使用,接在手机上不识别,于是我又参考:USB鼠标HID描述符以及数据格式_usb鼠标数据格式-CSDN博客

实现手机能识别,不过电脑识别不了。原理是把stm32模拟成触摸设备。

4.1 修改描述符

c 复制代码
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
{
    0x05, 0x0d,                    // USAGE_PAGE (Digitizers)
    0x09, 0x04,                    // USAGE (Touch Screen)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x22,                    //   USAGE (Finger)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x42,                    //     USAGE (Tip Switch)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
	0x09 , 0x32, 
	0x15 , 0x00 ,
	0x25 , 0x01 ,
	0x81 , 0x02 ,
	0x09 , 0x51 ,
	0x75 , 0x05,
	0x95 , 0x01 ,
	0x16 , 0x00 , 0x00,
	0x26 , 0x10 , 0x00,
	0x81 , 0x02,
	0x09 , 0x47 ,
	0x75 , 0x01,
	0x95 , 0x01 ,
	0x15 , 0x00,
	0x25 , 0x01 ,
	0x81 , 0x02 ,
	0x05 , 0x01 ,
	0x09 , 0x30 ,
	0x75 , 0x10 ,
	0x95 , 0x01 ,
	0x55 , 0x0D,
	0x65 , 0x33,
	0x35 , 0x00,
	0x46 , 0x37, 0x04 ,     //逻辑分辨率 1079
	0x26 , 0x37, 0x04 ,     //物理分辨率
	0x81 , 0x02 ,
	0x09 , 0x31 ,
	0x75 , 0x10 ,
	0x95 , 0x01 ,
	0x55 , 0x0D ,
	0x65 , 0x33 ,
	0x35 , 0x00 ,
	0x46 , 0x5f, 0x09,     //逻辑分辨率2399
	0x26 , 0x5f, 0x09 ,    //物理分辨率
	0x81 , 0x02 ,
	0x05 , 0x0D ,
	0x09 , 0x55 ,
	0x25 , 0x08 ,
	0x75 , 0x08 ,
	0x95 , 0x01 ,
	0xB1 , 0x02 ,
	0xC0 , 
	0xC0 , 
};

修改

#define HID_EPIN_SIZE                 0x05U

#define HID_MOUSE_REPORT_DESC_SIZE    112U//74U

4.2 发送数据格式

鼠标发送给手机的数据每次5个字节
BYTE0 BYTE1 BYTE2 BYTE3 BYTE4
定义分别是:
BYTE0  0x83表示按下  0x82表示松开
BYTE1 -- X坐标低8位
BYTE2 - X坐标高8位
BYTE3 -- Y坐标低8位
BYTE4 -- Y坐标高8位。
相关推荐
枯无穷肉9 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名6779 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普10 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
云山工作室10 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
艺术家天选12 小时前
STM32点亮LED灯
stm32·单片机·嵌入式硬件
向阳逐梦12 小时前
基于STM32F4单片机实现ROS机器人主板
stm32·单片机·机器人
小猪写代码13 小时前
STM32 水质水位检测项目 (调试模块)和(延时模块)
stm32·单片机·嵌入式硬件
御风_2114 小时前
STM32单片机使用CAN协议进行通信
stm32·单片机·嵌入式硬件
逝灮15 小时前
【蓝桥杯——物联网设计与开发】拓展模块3 - 温度传感器模块
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·温度传感器
Wallace Zhang16 小时前
STM32F407 | Embedded IDE01 - vscode搭建Embedded IDE开发环境(支持JLINK、STLINK、DAPLINK)
ide·vscode·stm32