最近我在玩一个安卓的射击游戏。 偶然发现可以使用鼠标进行游戏,但是使用鼠标有下面两条: 1. 鼠标右键会导致游戏闪退 2. 不同武器射击方式有差别。例如:狙击枪长按鼠标左键打开瞄准镜,速成“开镜”,瞄准目标后抬起左键立即射击;机关枪最好用三连发点射。
为此,我使用 Teensy 搭配 USB Host Mini 做了一个转接器,将USB鼠标操作解析过滤之后再送入手机。
我使用的是微软 InteliMouse Optical1.1A 鼠标。这款鼠标有5个按键,使用到的只有左键和右键。在设计上,可以其中前者用来发射,后者用来切换不同的射击模式。
首先需要解析这个鼠标的数据。使用之前介绍过很多次的 USBlyzer进行分析,第一步查看描述符了解鼠标数据的格式: Microsoft USBIntelliMouse Optical Connection Status | Device connected | Current Configuration | 1 | Speed | Low (1.5 Mbit/s) | Device Address | 5 | Number Of Open Pipes | 1 |
Device Descriptor Microsoft5-Button Mouse with IntelliEye(TM) Offset | Field | Size | Value | Description | 0 | bLength | 1 | 12h | | 1 | bDescriptorType | 1 | 01h | Device | 2 | bcdUSB | 2 | 0110h | USB Spec 1.1 | 4 | bDeviceClass | 1 | 00h | Class info in Ifc Descriptors | 5 | bDeviceSubClass | 1 | 00h | | 6 | bDeviceProtocol | 1 | 00h | | 7 | bMaxPacketSize0 | 1 | 08h | 8 bytes | 8 | idVendor | 2 | 045Eh | | 10 | idProduct | 2 | 0039h | | 12 | bcdDevice | 2 | 0300h | 3.00 | 14 | iManufacturer | 1 | 01h | "Microsoft" | 15 | iProduct | 1 | 03h | "Microsoft 5-Button Mouse with IntelliEye(TM)" | 16 | iSerialNumber | 1 | 00h | | 17 | bNumConfigurations | 1 | 01h | |
Configuration Descriptor1 Bus Powered, 100 mA Offset | Field | Size | Value | Description | 0 | bLength | 1 | 09h | | 1 | bDescriptorType | 1 | 02h | Configuration | 2 | wTotalLength | 2 | 0022h | | 4 | bNumInterfaces | 1 | 01h | | 5 | bConfigurationValue | 1 | 01h | | 6 | iConfiguration | 1 | 00h | | 7 | bmAttributes | 1 | A0h | Bus Powered, Remote Wakeup | | 4..0: Reserved | | ...00000 | | | 5: Remote Wakeup | | ..1..... | Yes | | 6: Self Powered | | .0...... | No, Bus Powered | | 7: Reserved (set to one)
(bus-powered for 1.0) | | 1....... | | 8 | bMaxPower | 1 | 32h | 100 mA |
Interface Descriptor 0/0 HID,1 Endpoint Offset | Field | Size | Value | Description | 0 | bLength | 1 | 09h | | 1 | bDescriptorType | 1 | 04h | Interface | 2 | bInterfaceNumber | 1 | 00h | | 3 | bAlternateSetting | 1 | 00h | | 4 | bNumEndpoints | 1 | 01h | | 5 | bInterfaceClass | 1 | 03h | HID | 6 | bInterfaceSubClass | 1 | 01h | Boot Interface | 7 | bInterfaceProtocol | 1 | 02h | Mouse | 8 | iInterface | 1 | 00h | |
HID Descriptor Offset | Field | Size | Value | Description | 0 | bLength | 1 | 09h | | 1 | bDescriptorType | 1 | 21h | HID | 2 | bcdHID | 2 | 0110h | 1.10 | 4 | bCountryCode | 1 | 00h | | 5 | bNumDescriptors | 1 | 01h | | 6 | bDescriptorType | 1 | 22h | Report | 7 | wDescriptorLength | 2 | 0048h | 72 bytes |
Endpoint Descriptor 81 1In, Interrupt, 10 ms Offset | Field | Size | Value | Description | 0 | bLength | 1 | 07h | | 1 | bDescriptorType | 1 | 05h | Endpoint | 2 | bEndpointAddress | 1 | 81h | 1 In | 3 | bmAttributes | 1 | 03h | Interrupt | | 1..0: Transfer Type | | ......11 | Interrupt | | 7..2: Reserved | | 000000.. | | 4 | wMaxPacketSize | 2 | 0004h | 4 bytes | 6 | bInterval | 1 | 0Ah | 10 ms |
Interface 0 HID ReportDescriptor Mouse Item Tag (Value) | Raw Data | Usage Page (Generic Desktop) | 05 01 | Usage (Mouse) | 09 02 | Collection (Application) | A1 01 | Usage (Pointer) | 09 01 | Collection (Physical) | A1 00 | Usage Page (Button) | 05 09 | Usage Minimum (Button 1) | 19 01 | Usage Maximum (Button 5) | 29 05 | Logical Minimum (0) | 15 00 | Logical Maximum (1) | 25 01 | Report Size (1) | 75 01 | Report Count (5) | 95 05 | Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) | 81 02 | Report Size (3) | 75 03 | Report Count (1) | 95 01 | Input (Cnst,Ary,Abs) | 81 01 | Usage Page (Generic Desktop) | 05 01 | Usage (X) | 09 30 | Usage (Y) | 09 31 | Usage (Wheel) | 09 38 | Logical Minimum (-127) | 15 81 | Logical Maximum (127) | 25 7F | Report Size (8) | 75 08 | Report Count (3) | 95 03 | Input (Data,Var,Rel,NWrp,Lin,Pref,NNul,Bit) | 81 06 | End Collection | C0 | Usage Page | 05 FF | Usage | 09 02 | Logical Minimum (0) | 15 00 | Logical Maximum (1) | 25 01 | Report Size (1) | 75 01 | Report Count (1) | 95 01 | Feature (Data,Var,Abs,NWrp,Lin,NPrf,NNul,NVol,Bit) | B1 22 | Report Size (7) | 75 07 | Report Count (1) | 95 01 | Feature (Cnst,Ary,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) | B1 01 | End Collection | C0 |
关注点在 HID Descriptor上。同时,移动鼠标按下按键结合抓取数据的功能:
最终确定鼠标发出来的数据格式如下: 偏移 | | | | | | | | 按键信息 BIT0:左键 BIT1:右键 BIT2:中键 BIT3:左下键 BIT4:右下键 | | | | | | | | | |
有了上面的数据格式,配合USB Host Shield可以得知鼠标的操作,比如:移动多少距离,是否按下鼠标左键。和之前的解析方式类似,这次选择的是USBHost Mini,它和之前使用的USBHOST Shield都是同一个作者设计的,使用相同的核心芯片(MAX3421E),因此使用的库和调用方法是相同的。具体的 rework 方法可以在之前的帖子中看到。
相比键盘鼠标对于时间非常敏感,高速的读取和解析是必要的,为了保证游戏体验这次使用 Teensy3.2。这是一款Arduino 兼容板,和Arduino相比,它速度更快(Teensy3.2,72Mhz,64K 内存),同时和Arduino代码100%兼容,无需专门修改 Arduino 代码即可烧写运行。 此外,Teensy3.2 引脚都是3.3V 的,因此,可以直接将它 USB Host 相连。为了方便使用笔者又设计了一块不包含任何元件的转接板,使用时只要将Teensy和 Shield一同插到转接板即可工作。
拿到PCB之后焊接排插,将Teensy 3.2 和 USBHOST MINI 插接在一起即可工作: 此外,使用 D2/D3/D4 Pin连接3个LED,分别表示:正常射击,三连发射击和模拟长按射击,不同模式使用鼠标右键顺序切换。正常射击模式
完整代码
msmouse.ino [mw_shl_code=c,true]#include <SPI.h>
#include "msParser.h"
USB Usb;
MSPARSER msparser(&Usb);
bool printTilt;
void setup() {
Serial.begin(115200);
//3个状态指示灯用来指示状态
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
if (Usb.Init() == -1) {
Serial.print(F("\r\nOSC did not start"));
while (1); // Halt
}
Serial.println(F("\r\nSteelSeries SRW-S1 Steering Wheel example started"));
msparser.SetMode(0);
}
void loop() {
Usb.Task();
if (msparser.connected()) {
if (printTilt) { // Show tilt angle using the LEDs
}
}
}
[/mw_shl_code]
msParser.h
[mw_shl_code=c,true]#ifndef __srws1_h__
#define __srws1_h__
#include <hiduniversal.h>
//鼠标的PID和VID
#define STEELSERIES_VID 0x045E
#define STEELSERIES_SRWS1_PID 0x0039
class MSPARSER : public HIDUniversal {
public:
MSPARSER(USB*p) : HIDUniversal(p) {};
voidSetMode(int Current);
int GetMode();
boolconnected() {
returnHIDUniversal::isReady() && HIDUniversal::VID == STEELSERIES_VID&& HIDUniversal::PID == STEELSERIES_SRWS1_PID;
};
private:
voidParseHIDData(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf); // Calledby the HIDUniversal library
uint8_tOnInitSuccessful() { // Called by the HIDUniversal library on success
if(HIDUniversal::VID != STEELSERIES_VID || HIDUniversal::PID !=STEELSERIES_SRWS1_PID) // Make sure the right device is actually connected
return 1;
return0;
};
int Mode=0;
};
#endif[/mw_shl_code]
这是完成主要工作的文件 msParser.cpp [mw_shl_code=c,true]#include "msParser.h"
#include <Mouse.h>
//设置当前的射击模式
void MSPARSER::SetMode(int Current)
{
digitalWrite(2,LOW);
digitalWrite(3,LOW);
digitalWrite(4,LOW);
Serial.print("Current");
Serial.println(Current);
switch (Current) {
case 0: //Mode 0 正常模式,鼠标信息只是Bypass
digitalWrite(2,HIGH);
break;
case 1: //Mode 1 速射模式,模拟快速按键
digitalWrite(3,HIGH);
break;
case 2: //Mode 2 狙击枪模式,模拟按下一段时间设计
digitalWrite(4,HIGH);
break;
default:
break;
}
Mode=Current;
}
//返回当前的射击模式
int MSPARSER::GetMode()
{
return Mode;
}
//解析USB鼠标的数据
void MSPARSER::ParseHIDData(USBHID *hid, bool is_rpt_id,uint8_t len, uint8_t *buf) {
if(HIDUniversal::VID != STEELSERIES_VID || HIDUniversal::PID !=STEELSERIES_SRWS1_PID)
return;
if (len&& buf) {
//输出收到的数据
for(uint8_t i = 0; i < len; i++) {
if (buf<0x10) {Serial.print("0");}
Serial.print(buf,HEX);
Serial.print(" ");
}
Serial.println();
//如果鼠标移动了,那么也让模拟鼠标相同的移动
if((buf[1]!=0)||(buf[2]!=0)) {
Mouse.move(buf[1],buf[2]);
}
if(buf[0]&1!=0) { //如果按下鼠标左键
if (GetMode()==0) { //正产模式
Mouse.press(MOUSE_LEFT);
}
if(GetMode()==1) { //速射模式,打三发
Mouse.press(MOUSE_LEFT);
delay(10);
Mouse.release(MOUSE_LEFT);
delay(10);
Mouse.press(MOUSE_LEFT);
delay(10);
Mouse.release(MOUSE_LEFT);
delay(10);
Mouse.press(MOUSE_LEFT);
delay(10);
Mouse.release(MOUSE_LEFT);
}
if(GetMode()==2) { //狙击模式,长按然后马上发射
Mouse.press(MOUSE_LEFT);
delay(500);
Mouse.release(MOUSE_LEFT);
}
}
elseif (Mouse.isPressed(MOUSE_LEFT)){
Mouse.release(MOUSE_LEFT);
}
if((buf[0]&2)!=0) {
Serial.println("Fired");
SetMode((GetMode()+1)%3);
}
}
}[/mw_shl_code]
最后,将鼠标接在USB Host Mini 上,然后再将 Teensy 连接到手机上就可以快乐的进行游戏了。除了打得准,我相信鼠标能让你的手更轻松……… 游戏视频可以在 https://zhuanlan.zhihu.com/p/43195205 看到 |