PVD:绝对值鼠标

我们通常使用的鼠标发送的都是相对坐标,比如:输出 X=100,Y=200, 意思是通知系统将鼠标从目前的位置移动(100,200)个单位。此外,Windows 还有一个鼠标优化机制,比如,你的鼠标输出范围是 +/- 127, 如果你的屏幕宽度是 1920, 经过优化之后无需滑动 1920/127 次才能将鼠标指针从最左边移动到最右边。

这次带来的PVD (Physical Virtual Device)是一款绝对值鼠标,它模拟了一个绝对值鼠标,能够将从USB串口输入的数据转化为绝对值鼠标数据发送出来。这样,可以方便的实现鼠标点击屏幕上的任意位置。

硬件部分使用和上一次设计相同【参考1】,核心是 WCH出品的 CH554e单片机。

代码是 Arduino 来实现的,屏幕左上角是 (0,0), 输出坐标为 0-32767。实践表明:送出来的数据和屏幕分辨率存在定比例关系。比如,在 1920*1024屏幕上,如果想让鼠标移动到 (300, 200)的位置,那么需要设备输出(300/1920 *32768, 200/1024 *32768)这样的数据。

主程序代码如下:

#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif

#include <WS2812.h>
#include "src/CdcHidCombo/USBCDC.h"
#include "src/CdcHidCombo/USBHIDKeyboardMouse.h"

#define NUM_LEDS 1
#define COLOR_PER_LEDS 3
#define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)

__xdata uint8_t ledData[NUM_BYTES];

#define  KeyboardReportID  0x01
#define  rMouseReportID    0x02
#define  aMouseReportID    0x03
#define  OnBoardLED        0x04

// Data format
// Keyboard(Total 9 bytes): 01(ReportID 01) + Keyboard data (8 Bytes)
// Mouse(Total 5 bytes): 02(ReportID 02) + Mouse Data (4 Bytes)
uint8_t recvStr[9];
uint8_t recvStrPtr = 0;
unsigned long Elsp;

void setup() {
  USBInit();
  Serial0_begin(115200);
  delay(1000);
  Serial0_print("start");
  Elsp=0;
}

void loop() {
  while (USBSerial_available()) {
    uint8_t serialChar = USBSerial_read();
    recvStr[recvStrPtr++] = serialChar;
    if (recvStrPtr == 10) {
      for (uint8_t i = 0; i < 9; i++) {
        Serial0_write(recvStr[i]);
      }      
      if (recvStr[0] == KeyboardReportID) { // Keyboard
        USB_EP3_send(recvStr, 9);
      }
      if (recvStr[0] == rMouseReportID) {
        USB_EP3_send(recvStr, 5); // Relative Mouse
      }
      if (recvStr[0] == aMouseReportID) {
        USB_EP3_send(recvStr, 7); // Absolute Mouse
      }      
      if (recvStr[0] == OnBoardLED) {
        set_pixel_for_GRB_LED(ledData, 0, recvStr[1], recvStr[2], recvStr[3]); 
        neopixel_show_P1_5(ledData, NUM_BYTES);  
      }



      recvStrPtr = 0;
    }
    Elsp=millis();
  }
  // If there is no data in 100ms, clear the receive buffer
  if (millis()-Elsp>100) {
      recvStrPtr = 0;
      Elsp=millis();
    }
}

如果USB CDC 收到以 aMouseReportID 起始的9个字节,那么会将数据直接转到绝对值鼠标端点然后发送给主机。

VS2019 编写一个上位机程序进行测试,代码如下:

// CDC_vKBMSTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <windows.h>
#include <SetupAPI.h>
#include <tchar.h>
#include <iostream>
#include <cstring>
#include <atlstr.h>

#pragma comment(lib, "Setupapi.lib")

#define MY_USB_PID_VID	_T("VID_1209&PID_C55C")
#define SCREENWIDTH 1920
#define SCREENHEIGHT 1200
int Port;

class ComPortException : public std::exception {
public:
	ComPortException(DWORD errorCode) : errorCode(errorCode) {}

	DWORD getErrorCode() const {
		return errorCode;
	}

private:
	DWORD errorCode;
};

// 对串口portName 发送数据
// 无法打开串口返回 1
// 无法设置串口参数 2
void SendToComPort(const int port, const char* data) {
	int Result = 0;
	HANDLE hCom = INVALID_HANDLE_VALUE;
	TCHAR portName[10]; // 用于存储 "COMp" 字符串

// 将整数 p 转换为 "COMp" 字符串
#ifdef UNICODE
	swprintf(portName, 10, _T("\\\\.\\COM%d"), port);
#else
	sprintf(portName, "COM%d", port);
#endif
	try {
		// 打开串口
		hCom = CreateFile(portName,
			GENERIC_READ | GENERIC_WRITE,
			0,
			NULL,
			OPEN_EXISTING,
			0,
			NULL);

		if (hCom == INVALID_HANDLE_VALUE) {
			throw ComPortException(GetLastError());
		}

		// 设置串口参数
		DCB dcb;
		SecureZeroMemory(&dcb, sizeof(DCB));
		dcb.DCBlength = sizeof(DCB);

		if (!GetCommState(hCom, &dcb)) {
			throw ComPortException(GetLastError());
		}

		dcb.BaudRate = CBR_115200;  // 波特率
		dcb.ByteSize = 8;         // 数据位
		dcb.StopBits = ONESTOPBIT; // 停止位
		dcb.Parity = NOPARITY;    // 校验位

		if (!SetCommState(hCom, &dcb)) {
			throw ComPortException(GetLastError());
		}

		// 设置超时参数
		COMMTIMEOUTS timeouts;
		timeouts.ReadIntervalTimeout = 50;
		timeouts.ReadTotalTimeoutConstant = 50;
		timeouts.ReadTotalTimeoutMultiplier = 10;
		timeouts.WriteTotalTimeoutConstant = 50;
		timeouts.WriteTotalTimeoutMultiplier = 10;

		if (!SetCommTimeouts(hCom, &timeouts)) {
			throw ComPortException(GetLastError());
		}

		// 发送数据
		DWORD bytesWritten;
		if (!WriteFile(hCom, data, 10, &bytesWritten, NULL)) {
			std::cerr << "Failed to write to COM port." << std::endl;
		}
		else {
			std::cout << "Successfully sent data to COM port: [" << port << "]" << std::endl;
		}

		for (int i = 0; i < 10; i++) {
			printf("%02x ", data[i]&0xFF);
		}
		printf("\n");
		// 关闭串口
		if (hCom != INVALID_HANDLE_VALUE) {
			CloseHandle(hCom);
		}
	}
	catch (const ComPortException& ex) {
		std::cerr << "Error: " << ex.getErrorCode() << std::endl;
		if (hCom != INVALID_HANDLE_VALUE) {
			CloseHandle(hCom);
		}
	}

}

/************************************************************************/
/* 根据USB描述信息字符串中读取
/************************************************************************/
int MTGetPortFromVidPid(CString strVidPid)
{
	// 获取当前系统所有使用的设备
	int					nPort = -1;
	int					nStart = -1;
	int					nEnd = -1;
	int					i = 0;
	CString				strTemp, strName;
	DWORD				dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT);
	HDEVINFO			hDevInfo = INVALID_HANDLE_VALUE;
	SP_DEVINFO_DATA		sDevInfoData;
	TCHAR				szDis[2048] = { 0x00 };// 存储设备实例ID
	TCHAR				szFN[MAX_PATH] = { 0x00 };// 存储设备实例属性
	DWORD				nSize = 0;

	// 准备遍历所有设备查找USB
	hDevInfo = SetupDiGetClassDevs(NULL, L"USB", NULL, dwFlag);
	if (INVALID_HANDLE_VALUE == hDevInfo)
		goto STEP_END;

	// 开始遍历所有设备
	memset(&sDevInfoData, 0x00, sizeof(SP_DEVICE_INTERFACE_DATA));
	sDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
	for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &sDevInfoData); i++)
	{
		nSize = 0;

		// 无效设备
		if (!SetupDiGetDeviceInstanceId(hDevInfo, &sDevInfoData, szDis, sizeof(szDis), &nSize))
			goto STEP_END;

		// 根据设备信息寻找VID PID一致的设备
		strTemp.Format(_T("%s"), szDis);
		strTemp.MakeUpper();
		if (strTemp.Find(strVidPid, 0) == -1)
			continue;

		// 查找设备属性
		nSize = 0;
		SetupDiGetDeviceRegistryProperty(hDevInfo, &sDevInfoData,
			SPDRP_FRIENDLYNAME,
			0, (PBYTE)szFN,
			sizeof(szFN),
			&nSize);

		// "XXX Virtual Com Port (COM7)"
		strName.Format(_T("%s"), szFN);
		_tprintf(_T("%s"), szFN);
		if (strName.IsEmpty())
			//goto STEP_END;
			continue;

		// 寻找串口信息
		nStart = strName.Find(_T("(COM"), 0);
		nEnd = strName.Find(_T(")"), 0);
		if (nStart == -1 || nEnd == -1)
			//goto STEP_END;
			continue;

		strTemp = strName.Mid(nStart + 4, nEnd - nStart - 4);
		nPort = _ttoi(strTemp);

	}
STEP_END:

	// 关闭设备信息集句柄
	if (hDevInfo != INVALID_HANDLE_VALUE)
	{
		SetupDiDestroyDeviceInfoList(hDevInfo);
		hDevInfo = INVALID_HANDLE_VALUE;
	}

	return nPort;
}

int ScreenX2Abs(int X) {
	return (X* 32768 / SCREENWIDTH);
}
int ScreenY2Abs(int Y) {
	return (Y  * 32768 / SCREENHEIGHT);
}

void ChangeColor(
	int X,
	int Y
) {
	char Data[10];

	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(X) & 0xFF;
	Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(Y) & 0xFF;
	Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 点击
	Data[2] = ScreenX2Abs(X) & 0xFF;
	Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(Y) & 0xFF;
	Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);
}

void LedColor(
	int Color
) {
	char Data[10];

	memset(Data, 0, sizeof(Data));
	Data[0] = 4; // LED
	Data[1] = Color&0xFF; // 点击
	Data[2] = (Color>>8) & 0xFFF;
	Data[3] = (Color >>16) & 0xFF;

	SendToComPort(Port, (char*)&Data);
}

void DrawRect(
	int StartX,
	int StartY,
	int Width,
	int Height,
	int Dx,
	int Dy
) {
	char Data[10];

	//从左到右横线
	//line(StartX, StartY, StartX + Width, StartY);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX+Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	//从上到下,竖线
	//line(StartX + Width, StartY,StartX+Width,StartY+Height);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	//从右到左,横线
	//line(StartX + Width, StartY + Height,StartX+Dx,StartY+Height);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	//从下到上竖线
	//line(StartX + Dx, StartY + Height, StartX + Dx, StartY + Dy);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);
}

int main()
{

	printf("%d[%x]  %d[%x]\n", ScreenX2Abs(100), ScreenX2Abs(100), ScreenY2Abs(300), ScreenY2Abs(300));
	printf("Virutal KB MS Test\n");
	Port = MTGetPortFromVidPid(MY_USB_PID_VID);
	if (Port == -1) {
		printf("No device is found\n");
		goto EndProgram;
	}
	else {
		printf("Found COM%d\n",Port);
	}
	Sleep(5000);
	for (int i = 0; i < 8; i++) {
		DrawRect(100+20*i, 300+20*i, SCREENWIDTH - 200-20*i*2, SCREENHEIGHT - 500-20*i*2, 20, 20);
		if ((i % 3) == 0) {
			LedColor(0x01);
		}
		if ((i % 3) == 1) {
			LedColor(0x0100);
		}
		if ((i % 3) == 2) {
			LedColor(0x010000);
		}
	}
	
	ChangeColor(1329,125);
	for (int i = 8; i < 18; i++) {
		DrawRect(100 + 20 * i, 300 + 20 * i, SCREENWIDTH - 200 - 20 * i * 2, SCREENHEIGHT - 500 - 20 * i * 2, 20, 20);
		if ((i % 3) == 0) {
			LedColor(0x01);
		}
		if ((i % 3) == 1) {
			LedColor(0x0100);
		}
		if ((i % 3) == 2) {
			LedColor(0x010000);
		}
	}


EndProgram:
	printf("End\n");
	getchar();
}

这个代码的作用是在画笔中绘制蛇形线,在绘制过程中还有更换颜色的动作。

工作的视频: 【PVD 计划:绝对值鼠标测试】

参考:

1. https://mc.dfrobot.com.cn/thread-323494-1-1.html

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注