使用 Teensy 的 USB HID 键盘、鼠标、触摸屏模拟器
本文介绍如何使用 PJRC.com 的 Teensy 3.1 开发板同时模拟键盘、鼠标和触摸屏 USB HID 设备。这允许通过一根 USB 电缆远程控制计算机。
Teensy 3.1
Teensy 3.1 是一款微型 PCB 板(35 x 18 毫米),具有许多硬件功能。它可以使用与 Arduino 板相同的开发环境进行编程。Teensy 的众多特性之一是其微处理器能够模拟 USB 设备。
编程
要对 Teensy 的处理器进行编程,您需要一根 micro USB 数据线,并且必须安装:
源代码必须用 C 语言编写,您为该板编写的固件被称为“Sketch”。
您必须像这张截图一样配置编译器
您也可以使用“Serial + Keyboard + Mouse + Joystick”(串行+键盘+鼠标+游戏手柄)选项,这将在键盘、鼠标和游戏手柄设备之外再增加一个 USB 虚拟 COM 端口。
当前版本 TeensyDuino 1.27 的问题
安装并配置好编译器后,您就可以开始编写模拟键盘、鼠标和游戏手柄的代码了。您会发现硬盘上已经安装了几个示例项目(请参见菜单“文件”->“示例”)。
但是 TeensyDuino 1.27 的原始代码存在一个严重问题
鼠标模拟在 Linux 上无法工作。
原因是 Linux X11 服务器存在严重的设计错误,它充满了 bug 和不合理的设计。
X11 服务器不接受发送绝对坐标的 USB 鼠标设备。
但如果您想将鼠标定位在屏幕上的确切位置,使用相对坐标进行鼠标定位是无用的,原因有以下几点:
- 由于您不知道连接到 Teensy 的计算机上鼠标指针的当前位置,您必须首先将鼠标移动到屏幕的左上角,然后从那里开始相对移动,这很不优雅。
- 当 Teensy USB 设备告诉远程计算机鼠标移动了 100 个单位时,鼠标在屏幕上实际移动的距离取决于控制面板中的设置和操作系统。它可能移动 53 个像素,也可能移动 144 个像素!
- 当在控制面板中启用“鼠标指针精准度”时,情况会变得更糟:移动的距离还将取决于移动的速度。
- 由于 X11 中的 bug/设计缺陷,相对移动在 Linux 上只有在步长为 1 时才能正常工作。
- 总结:如果您想通过相对移动来精确定位鼠标指针:别想了!
因此,PJRC 在 TeensyDuino 1.17 和 1.18 版本中实现了绝对鼠标定位。这种方式效果更好,但随后一些用户抱怨说,他们现在无法相对移动鼠标了,而他们的项目需要这个功能。
所以在 TeensyDuino 的 1.19 到 1.27 版本中,PJRC 实现了一个允许在同一个 HID 设备中进行相对和绝对移动的 USB 设备。但 Linux 不接受这个 HID 设备,鼠标也无法再使用绝对坐标移动。
总结
无论您使用哪个版本的 TeensyDuino,您都永远无法拥有一个能满足所有用户和所有操作系统需求的代码。
我的修改
我的项目来了。您可以在上面的 ZIP 文件中下载的代码是对 Teensyduino 1.27 代码的修改。将 ZIP 中的 5 个文件复制到以下文件夹:
我对原始代码做了以下更改:
- 鼠标设备只使用相对坐标,这将在所有操作系统上正常工作(就像一个“普通”的真实鼠标一样)。
- 我添加了一个新的触摸屏设备,用于绝对指针移动,它同样适用于所有操作系统。
- 我没有更改您当前 sketch 中已经使用的 Mouse 类的命令,所以您只需替换上面 ZIP 文件中的文件并重新编译您的 sketch,无需任何更改。
- 我完全重写了 Mouse 类,清理了代码并添加了更多功能。
- 我为之前注释稀疏的代码添加了大量注释。
- 我注释掉了我不需要的游戏手柄设备。如果您需要它,请阅读我在 usb_desc.h 文件中的注释,了解如何重新启用它。
- 我移除了我不需要的 SerialEmu 设备。如果您需要它,只需在 usb_desc.h 文件中更改几行代码即可。
我希望 PJRC 能将我的代码集成到未来版本的 TeensyDuino 中,但目前 USB 相关功能处于特性冻结状态。
以防 PJRC 不采纳我的代码,您也可以在 ZIP 文件中找到 1.27 版的原始代码。这样您就可以比较我修改了什么,也可以将原始的 1.27 代码与任何未来版本进行比较,看看 PJRC 做了哪些更改。(我推荐使用 Araxis Merge 来比较文本文件)
应用我的更改并将编程后的 Teensy 连接到 Windows 计算机后,您将在控制面板中看到 3 个新的 USB 设备
USB 设备描述符
要在 Teensy 中创建一个新的 HID 设备,我首先必须学习很多关于 USB 描述符的知识。互联网上没有关于这个复杂东西的真正写得好的手册。如果您阅读 USB.org 的文档,您会对其糟糕的写作质量感到震惊。您可能什么也看不懂。
您可以在 Beyondlogic 和 Eleccelerator 找到一些更用户友好的教程,但即使是这些教程对初学者来说仍然很晦涩。
对于 Windows 用户来说,USBlyzer 程序是一个非常有用的工具,它提供功能齐全的 33 天试用版。这款 USB 嗅探器可以显示所有已连接 USB 设备的所有设备描述符以及通过 USB 电缆发送的数据包。
为了确保我做的是正确的,我复制了一个真实的 Genius 光学鼠标和一个真实的触摸屏:ELO TouchSystems CarrollTouch 4500U 的设备描述符。键盘设备保持不变。
以下是我最终实现的 HID 设备描述符
|
|
如您所见,现在的鼠标允许 16 位(-32767 到 32767)的相对移动,而不是原始代码中的 8 位(-127 到 127),后者范围太小了。
触摸屏使用的坐标范围是 0 到 10000,这允许以屏幕宽度/高度的百分比传递值,精度到小数点后两位。因此,要将鼠标定位到屏幕右下角,Mouse 类会发送 X 和 Y 的值 100.00%,这对应于通过 USB 电缆发送的值 10000。要将鼠标定位在屏幕中央,Mouse 类会发送 5000, 5000。
请注意,实际显示器的分辨率是多少像素完全无关紧要。这种方法适用于任何尺寸的显示器,因为接收触摸屏坐标的操作系统会将它们转换为以像素为单位的屏幕坐标。所有触摸屏都是这样工作的。
我在 Windows XP、7、8、10 和 Linux (Suse, Ubuntu, Knoppix) 上测试了我的项目,它都能无缝工作。
第一个备用触摸屏描述符
2016年2月更新
有人在 Codeproject 上报告说,上述触摸屏描述符在 OSX 和 Android 上无法工作。因此我创建了一个备用描述符,您必须在 usb_desc.h
文件中通过将这一行中的一个零改为一来启用它
#define TOUCH_DEVICE 1
这将用以下描述符替换上述描述符
触摸屏 (版本 '单点触摸') |
---|
用法页 (数字化仪) |
用法 (笔) |
集合 (应用) |
用法 (手写笔) |
集合 (物理) |
用法 (笔尖开关) |
用法 (在范围内) |
逻辑最小值 (0) |
逻辑最大值 (1) |
报告大小 (1) |
报告计数 (2) |
输入 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
报告大小 (1) |
报告计数 (6) |
输入 (常量, 数组, 绝对值) |
用法页 (通用桌面) |
用法 (指针) |
集合 (物理) |
用法 (X) |
用法 (Y) |
逻辑最小值 (0) |
逻辑最大值 (10000) |
物理最小值 (0) |
物理最大值 (10000) |
单位 (无) |
报告大小 (16) |
报告计数 (2) |
输入 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
结束集合 |
结束集合 |
结束集合 |
我在 Windows XP, 7, 8, 10 以及 Ubuntu 和 Knoppix 上成功测试了此描述符。
一位 Codeproject 用户报告说,此描述符在 Android 上也能正常工作。
请报告在 OSX 上的测试结果!
第二个备用触摸屏描述符
您可以在 usb_desc.h
文件中使用以下代码启用第三个触摸屏描述符:
#define TOUCH_DEVICE 2
此描述符是多点触摸描述符,但它只使用一根手指。
触摸屏 (版本 '多点触摸') |
---|
用法页 (数字化仪) |
用法 (触摸屏) |
集合 (应用) |
用法 (最大接触点数) |
逻辑最大值 (1) |
功能 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 非易失, 位) |
用法 (接触点数) |
报告计数 (1) |
报告大小 (8) |
输入 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
用法 (手指) |
集合 (逻辑) |
用法 (接触点标识符) |
报告大小 (8) |
报告计数 (1) |
输入 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
用法 (笔尖开关) |
用法 (在范围内) |
逻辑最小值 (0) |
逻辑最大值 (1) |
报告大小 (1) |
报告计数 (2) |
输入 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
报告计数 (6) |
输入 (常量, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
用法页 (通用桌面) |
用法 (X) |
用法 (Y) |
逻辑最小值 (0) |
逻辑最大值 (10000) |
物理最小值 (0) |
物理最大值 (10000) |
单位 (无) |
报告大小 (16) |
报告计数 (2) |
输入 (数据, 变量, 绝对值, 非换行, 线性, 首选状态, 非空, 位) |
结束集合 |
结束集合 |
注意
这个描述符的行为与前两个描述符大相径庭。
当您调用 Mouse.moveTo()
时,鼠标指针不会在屏幕上移动。
(唯一能看到鼠标指针移动的地方是在 Windows 8 或 10 上的 MSPAINT.EXE 的绘图区域内。)
移动鼠标指针时,它甚至可能会消失(在 Linux 和 Windows 上)。原因是在真实的触摸屏上,您不需要鼠标指针,因为您能看到自己触摸的位置,所以操作系统会隐藏鼠标指针。
当您用模拟的手指触摸时,鼠标指针会跳转到新的位置并执行一次点击。
此描述符在 Windows XP, 8, 10 以及 Ubuntu 和 Knoppix 上可立即工作。
但在 Windows 7 上,如果您不先启用它,将不会有任何反应。请前往控制面板 -> 笔和触摸
在首次安装后(Windows 提示:“设备已准备就绪”),可能需要断开并重新连接一次 Teensy 才能使其工作。
通过将 TOUCH_DEVICE
设置为 0、1 或 2,尝试哪个触摸屏描述符适合您。
编程鼠标/触摸屏移动
您 sketch 中的命令可以保持不变,但您会获得扩展的功能
移动鼠标指针relative坐标(-32767 到 32767),通过鼠标设备
Mouse.move(x, y);
移动鼠标指针absolute百分比坐标(0 到 10000),通过触摸屏设备
Mouse.moveTo(x, y); Example: Mouse.moveTo(5000, 5000); moves the mouse to the center of the screen (50.00%) Mouse.moveTo(10000, 10000); moves the mouse to the bottom/right corner of the screen (100.00%)
移动鼠标指针absolute在 1024 x 768 像素分辨率屏幕上的像素坐标,通过触摸屏设备
Mouse.screenSize(1024, 768); Mouse.moveTo(x, y); Example: Mouse.moveTo(1024/2, 768/2); moves the mouse to the center of the screen Mouse.moveTo(1024, 768); moves the mouse to the bottom/right corner of the screen
如您所见
调用 Mouse.screenSize()
后,坐标会自动从百分比变为像素。
您只需调用一次 Mouse.screenSize()
(或者从不调用)。
特殊情况:苹果 Macintosh
PJRC 之前的代码对苹果 Macintosh 有一个变通方案,它在鼠标移动的可用区域周围设置了 7.5% 的固定边距,从而得到 85% 的屏幕区域。请参阅此帖子。
我不知道 MAC 上的触摸屏是否也需要这个鼠标变通方案,因为我没有 MAC 来测试。
以防万一需要,我为 Mouse.screenSize()
实现了四个可选参数,允许定义这个边距,但比原始(非常晦涩的)PJRC 代码更灵活
Mouse.screenSize(widthPixel, heightPixel, // in pixel marginLeft, marginTop, marginRight, marginBottom); // in percent Example for MAC: Mouse.screenSize(1024, 768, 750, 750, 9250, 9250);
这行代码定义了 1024 x 768 像素的屏幕尺寸,并在可用区域周围设置了 7.50% 的边距。
此信息基于 PJRC。
是否有 MAC 用户可以确认这个变通方案确实是必需的?
点击
由于现在有一个拥有 3 个鼠标按钮(左、中、右)的鼠标设备和一个只有一个“按钮”(即用户的手指)的触摸屏,我添加了使用鼠标设备或触摸屏设备进行点击的选项
Mouse.click(button); Or you can also use Mouse.press(button); and Mouse.release(button); where button may be MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT or TOUCH_FINGER
如果您想按住一个按钮不放(例如用于拖放操作)
Mouse.set_buttons(1, 0, 0, 0); // left mouse button down Mouse.set_buttons(0, 1, 0, 0); // middle mouse button down Mouse.set_buttons(0, 0, 1, 0); // right mouse button down Mouse.set_buttons(0, 0, 0, 1); // finger touching Mouse.set_buttons(0, 0, 0, 0); // release all buttons and the finger
使用触摸屏设备执行拖放操作
Mouse.moveTo(startX, startY); // drag coordinates delay(10); Mouse.set_buttons(0, 0, 0, 1); // finger down delay(10); Mouse.moveTo(endX, endY); // drop coordinates delay(10); Mouse.set_buttons(0, 0, 0, 0); // finger up
这将导致一个跳跃式的拖动操作,而不是一个缓慢的拖动操作。
为了获得更真实的模拟效果,您应该编写一个循环,以更小的步长发送坐标。
例如,从坐标 0,0 移动到 100,100
Mouse.moveTo(10,10); delay(5); Mouse.moveTo(20,20); delay(5); Mouse.moveTo(30,30); delay(5); Mouse.moveTo(40,40); etc..
鼠标滚动
最后,您可以使用鼠标滚轮(-127 到 127),通过鼠标设备
Mouse.scroll(movement);
请阅读我在 usb_mouse.h 中添加的大量注释。
更多使用 Teensy 的项目
这是我用 Teensy 制作并发布在 Codeproject 上的第三个电子项目。您可能也对以下项目感兴趣:
- DIY 电子 RFID 门锁带电池备份
它使用带有 DES / AES 加密的 Desfire RFID 卡来实现电子门锁。 - 红外遥控您的电脑
它使用一个 Teensy 和一个红外接收器来控制您电脑上的音乐播放器/视频播放器。