19浏览
查看: 19|回复: 1

[进阶] PVD(Physical Virtual Device) :绝对值鼠标设计

[复制链接]
我们通常使用的鼠标发送的都是相对坐标,比如:输出X=100,Y=200 意思是通知系统将鼠标从目前的位置移动(100,200)个单位。此外,Windows 还有一个鼠标优化机制,比如,你的鼠标输出范围是 +/- 127, 如果你的屏幕宽度是 1920 经过优化之后无需滑动 1920/127 次才能将鼠标指针从最左边移动到最右边。
这次带来的PVD(Physical Virtual Device)是一款绝对值鼠标,它模拟了一个绝对值鼠标,能够将从USB串口输入的数据转化为绝对值鼠标数据发送出来。这样,可以方便的实现鼠标点击屏幕上的任意位置。
硬件部分使用和上一次设计相同【参考1】,核心是 WCH出品的 CH554e单片机。
PVD(Physical Virtual Device) :绝对值鼠标设计图1
代码是Arduino 来实现的,屏幕左上角是(0,0), 输出坐标为 0-32767。实践表明:送出来的数据和屏幕分辨率存在定比例关系。比如,在1920*1024屏幕上,如果想让鼠标移动到(300, 200)的位置,那么需要设备输出(300/1920*32768, 200/1024 *32768)这样的数据。
主程序代码如下:
  1. #ifndef USER_USB_RAM
  2. #error "This example needs to be compiled with a USER USB setting"
  3. #endif
  4. #include <WS2812.h>
  5. #include "src/CdcHidCombo/USBCDC.h"
  6. #include "src/CdcHidCombo/USBHIDKeyboardMouse.h"
  7. #define NUM_LEDS 1
  8. #define COLOR_PER_LEDS 3
  9. #define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)
  10. __xdata uint8_t ledData[NUM_BYTES];
  11. #define  KeyboardReportID  0x01
  12. #define  rMouseReportID    0x02
  13. #define  aMouseReportID    0x03
  14. #define  OnBoardLED        0x04
  15. // Data format
  16. // Keyboard(Total 9 bytes): 01(ReportID 01) + Keyboard data (8 Bytes)
  17. // Mouse(Total 5 bytes): 02(ReportID 02) + Mouse Data (4 Bytes)
  18. uint8_t recvStr[9];
  19. uint8_t recvStrPtr = 0;
  20. unsigned long Elsp;
  21. void setup() {
  22.   USBInit();
  23.   Serial0_begin(115200);
  24.   delay(1000);
  25.   Serial0_print("start");
  26.   Elsp=0;
  27. }
  28. void loop() {
  29.   while (USBSerial_available()) {
  30.     uint8_t serialChar = USBSerial_read();
  31.     recvStr[recvStrPtr++] = serialChar;
  32.     if (recvStrPtr == 10) {
  33.       for (uint8_t i = 0; i < 9; i++) {
  34.         Serial0_write(recvStr[i]);
  35.       }      
  36.       if (recvStr[0] == KeyboardReportID) { // Keyboard
  37.         USB_EP3_send(recvStr, 9);
  38.       }
  39.       if (recvStr[0] == rMouseReportID) {
  40.         USB_EP3_send(recvStr, 5); // Relative Mouse
  41.       }
  42.       if (recvStr[0] == aMouseReportID) {
  43.         USB_EP3_send(recvStr, 7); // Absolute Mouse
  44.       }      
  45.       if (recvStr[0] == OnBoardLED) {
  46.         set_pixel_for_GRB_LED(ledData, 0, recvStr[1], recvStr[2], recvStr[3]);
  47.         neopixel_show_P1_5(ledData, NUM_BYTES);  
  48.       }
  49.       recvStrPtr = 0;
  50.     }
  51.     Elsp=millis();
  52.   }
  53.   // If there is no data in 100ms, clear the receive buffer
  54.   if (millis()-Elsp>100) {
  55.       recvStrPtr = 0;
  56.       Elsp=millis();
  57.     }
  58. }
复制代码
如果USB CDC 收到以 aMouseReportID起始的9个字节,那么会将数据直接转到绝对值鼠标端点然后发送给主机。
VS2019 编写一个上位机程序进行测试,代码如下:
  1. // CDC_vKBMSTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
  2. //
  3. #include <windows.h>
  4. #include <SetupAPI.h>
  5. #include <tchar.h>
  6. #include <iostream>
  7. #include <cstring>
  8. #include <atlstr.h>
  9. #pragma comment(lib, "Setupapi.lib")
  10. #define MY_USB_PID_VID        _T("VID_1209&PID_C55C")
  11. #define SCREENWIDTH 1920
  12. #define SCREENHEIGHT 1200
  13. int Port;
  14. class ComPortException : public std::exception {
  15. public:
  16.         ComPortException(DWORD errorCode) : errorCode(errorCode) {}
  17.         DWORD getErrorCode() const {
  18.                 return errorCode;
  19.         }
  20. private:
  21.         DWORD errorCode;
  22. };
  23. // 对串口portName 发送数据
  24. // 无法打开串口返回 1
  25. // 无法设置串口参数 2
  26. void SendToComPort(const int port, const char* data) {
  27.         int Result = 0;
  28.         HANDLE hCom = INVALID_HANDLE_VALUE;
  29.         TCHAR portName[10]; // 用于存储 "COMp" 字符串
  30. // 将整数 p 转换为 "COMp" 字符串
  31. #ifdef UNICODE
  32.         swprintf(portName, 10, _T("\\\\.\\COM%d"), port);
  33. #else
  34.         sprintf(portName, "COM%d", port);
  35. #endif
  36.         try {
  37.                 // 打开串口
  38.                 hCom = CreateFile(portName,
  39.                         GENERIC_READ | GENERIC_WRITE,
  40.                         0,
  41.                         NULL,
  42.                         OPEN_EXISTING,
  43.                         0,
  44.                         NULL);
  45.                 if (hCom == INVALID_HANDLE_VALUE) {
  46.                         throw ComPortException(GetLastError());
  47.                 }
  48.                 // 设置串口参数
  49.                 DCB dcb;
  50.                 SecureZeroMemory(&dcb, sizeof(DCB));
  51.                 dcb.DCBlength = sizeof(DCB);
  52.                 if (!GetCommState(hCom, &dcb)) {
  53.                         throw ComPortException(GetLastError());
  54.                 }
  55.                 dcb.BaudRate = CBR_115200;  // 波特率
  56.                 dcb.ByteSize = 8;         // 数据位
  57.                 dcb.StopBits = ONESTOPBIT; // 停止位
  58.                 dcb.Parity = NOPARITY;    // 校验位
  59.                 if (!SetCommState(hCom, &dcb)) {
  60.                         throw ComPortException(GetLastError());
  61.                 }
  62.                 // 设置超时参数
  63.                 COMMTIMEOUTS timeouts;
  64.                 timeouts.ReadIntervalTimeout = 50;
  65.                 timeouts.ReadTotalTimeoutConstant = 50;
  66.                 timeouts.ReadTotalTimeoutMultiplier = 10;
  67.                 timeouts.WriteTotalTimeoutConstant = 50;
  68.                 timeouts.WriteTotalTimeoutMultiplier = 10;
  69.                 if (!SetCommTimeouts(hCom, &timeouts)) {
  70.                         throw ComPortException(GetLastError());
  71.                 }
  72.                 // 发送数据
  73.                 DWORD bytesWritten;
  74.                 if (!WriteFile(hCom, data, 10, &bytesWritten, NULL)) {
  75.                         std::cerr << "Failed to write to COM port." << std::endl;
  76.                 }
  77.                 else {
  78.                         std::cout << "Successfully sent data to COM port: [" << port << "]" << std::endl;
  79.                 }
  80.                 for (int i = 0; i < 10; i++) {
  81.                         printf("%02x ", data[i]&0xFF);
  82.                 }
  83.                 printf("\n");
  84.                 // 关闭串口
  85.                 if (hCom != INVALID_HANDLE_VALUE) {
  86.                         CloseHandle(hCom);
  87.                 }
  88.         }
  89.         catch (const ComPortException& ex) {
  90.                 std::cerr << "Error: " << ex.getErrorCode() << std::endl;
  91.                 if (hCom != INVALID_HANDLE_VALUE) {
  92.                         CloseHandle(hCom);
  93.                 }
  94.         }
  95. }
  96. /************************************************************************/
  97. /* 根据USB描述信息字符串中读取
  98. /************************************************************************/
  99. int MTGetPortFromVidPid(CString strVidPid)
  100. {
  101.         // 获取当前系统所有使用的设备
  102.         int                                        nPort = -1;
  103.         int                                        nStart = -1;
  104.         int                                        nEnd = -1;
  105.         int                                        i = 0;
  106.         CString                                strTemp, strName;
  107.         DWORD                                dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT);
  108.         HDEVINFO                        hDevInfo = INVALID_HANDLE_VALUE;
  109.         SP_DEVINFO_DATA                sDevInfoData;
  110.         TCHAR                                szDis[2048] = { 0x00 };// 存储设备实例ID
  111.         TCHAR                                szFN[MAX_PATH] = { 0x00 };// 存储设备实例属性
  112.         DWORD                                nSize = 0;
  113.         // 准备遍历所有设备查找USB
  114.         hDevInfo = SetupDiGetClassDevs(NULL, L"USB", NULL, dwFlag);
  115.         if (INVALID_HANDLE_VALUE == hDevInfo)
  116.                 goto STEP_END;
  117.         // 开始遍历所有设备
  118.         memset(&sDevInfoData, 0x00, sizeof(SP_DEVICE_INTERFACE_DATA));
  119.         sDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
  120.         for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &sDevInfoData); i++)
  121.         {
  122.                 nSize = 0;
  123.                 // 无效设备
  124.                 if (!SetupDiGetDeviceInstanceId(hDevInfo, &sDevInfoData, szDis, sizeof(szDis), &nSize))
  125.                         goto STEP_END;
  126.                 // 根据设备信息寻找VID PID一致的设备
  127.                 strTemp.Format(_T("%s"), szDis);
  128.                 strTemp.MakeUpper();
  129.                 if (strTemp.Find(strVidPid, 0) == -1)
  130.                         continue;
  131.                 // 查找设备属性
  132.                 nSize = 0;
  133.                 SetupDiGetDeviceRegistryProperty(hDevInfo, &sDevInfoData,
  134.                         SPDRP_FRIENDLYNAME,
  135.                         0, (PBYTE)szFN,
  136.                         sizeof(szFN),
  137.                         &nSize);
  138.                 // "XXX Virtual Com Port (COM7)"
  139.                 strName.Format(_T("%s"), szFN);
  140.                 _tprintf(_T("%s"), szFN);
  141.                 if (strName.IsEmpty())
  142.                         //goto STEP_END;
  143.                         continue;
  144.                 // 寻找串口信息
  145.                 nStart = strName.Find(_T("(COM"), 0);
  146.                 nEnd = strName.Find(_T(")"), 0);
  147.                 if (nStart == -1 || nEnd == -1)
  148.                         //goto STEP_END;
  149.                         continue;
  150.                 strTemp = strName.Mid(nStart + 4, nEnd - nStart - 4);
  151.                 nPort = _ttoi(strTemp);
  152.         }
  153. STEP_END:
  154.         // 关闭设备信息集句柄
  155.         if (hDevInfo != INVALID_HANDLE_VALUE)
  156.         {
  157.                 SetupDiDestroyDeviceInfoList(hDevInfo);
  158.                 hDevInfo = INVALID_HANDLE_VALUE;
  159.         }
  160.         return nPort;
  161. }
  162. int ScreenX2Abs(int X) {
  163.         return (X* 32768 / SCREENWIDTH);
  164. }
  165. int ScreenY2Abs(int Y) {
  166.         return (Y  * 32768 / SCREENHEIGHT);
  167. }
  168. void ChangeColor(
  169.         int X,
  170.         int Y
  171. ) {
  172.         char Data[10];
  173.         memset(Data, 0, sizeof(Data));
  174.         Data[0] = 3; // 绝对值鼠标
  175.         Data[1] = 1; // 点击
  176.         Data[2] = ScreenX2Abs(X) & 0xFF;
  177.         Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
  178.         Data[4] = ScreenY2Abs(Y) & 0xFF;
  179.         Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
  180.         SendToComPort(Port, (char*)&Data);
  181.         Sleep(100);
  182.         memset(Data, 0, sizeof(Data));
  183.         Data[0] = 3; // 绝对值鼠标
  184.         Data[1] = 0; // 点击
  185.         Data[2] = ScreenX2Abs(X) & 0xFF;
  186.         Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
  187.         Data[4] = ScreenY2Abs(Y) & 0xFF;
  188.         Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
  189.         SendToComPort(Port, (char*)&Data);
  190.         Sleep(100);
  191. }
  192. void LedColor(
  193.         int Color
  194. ) {
  195.         char Data[10];
  196.         memset(Data, 0, sizeof(Data));
  197.         Data[0] = 4; // LED
  198.         Data[1] = Color&0xFF; // 点击
  199.         Data[2] = (Color>>8) & 0xFFF;
  200.         Data[3] = (Color >>16) & 0xFF;
  201.         SendToComPort(Port, (char*)&Data);
  202. }
  203. void DrawRect(
  204.         int StartX,
  205.         int StartY,
  206.         int Width,
  207.         int Height,
  208.         int Dx,
  209.         int Dy
  210. ) {
  211.         char Data[10];
  212.         //从左到右横线
  213.         //line(StartX, StartY, StartX + Width, StartY);
  214.         memset(Data, 0, sizeof(Data));
  215.         Data[0] = 3; // 绝对值鼠标
  216.         Data[1] = 1; // 点击
  217.         Data[2] = ScreenX2Abs(StartX) & 0xFF;
  218.         Data[3] = (ScreenX2Abs(StartX) >> 8) & 0xFF;
  219.         Data[4] = ScreenY2Abs(StartY) & 0xFF;
  220.         Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
  221.         SendToComPort(Port, (char*)&Data);
  222.         Sleep(100);
  223.         Data[0] = 3; // 绝对值鼠标
  224.         Data[1] = 1; // 点击
  225.         Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
  226.         Data[3] = (ScreenX2Abs(StartX+Width) >> 8) & 0xFF;
  227.         Data[4] = ScreenY2Abs(StartY) & 0xFF;
  228.         Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
  229.         SendToComPort(Port, (char*)&Data);
  230.         Sleep(100);
  231.         Data[0] = 3; // 绝对值鼠标
  232.         Data[1] = 0; // 抬起
  233.         Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
  234.         Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
  235.         Data[4] = ScreenY2Abs(StartY) & 0xFF;
  236.         Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
  237.         SendToComPort(Port, (char*)&Data);
  238.         Sleep(100);
  239.         //从上到下,竖线
  240.         //line(StartX + Width, StartY,StartX+Width,StartY+Height);
  241.         memset(Data, 0, sizeof(Data));
  242.         Data[0] = 3; // 绝对值鼠标
  243.         Data[1] = 1; // 点击
  244.         Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
  245.         Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
  246.         Data[4] = ScreenY2Abs(StartY) & 0xFF;
  247.         Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
  248.         SendToComPort(Port, (char*)&Data);
  249.         Sleep(100);
  250.         Data[0] = 3; // 绝对值鼠标
  251.         Data[1] = 1; // 点击
  252.         Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
  253.         Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
  254.         Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
  255.         Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
  256.         SendToComPort(Port, (char*)&Data);
  257.         Sleep(100);
  258.         Data[0] = 3; // 绝对值鼠标
  259.         Data[1] = 0; // 抬起
  260.         Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
  261.         Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
  262.         Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
  263.         Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
  264.         SendToComPort(Port, (char*)&Data);
  265.         Sleep(100);
  266.         //从右到左,横线
  267.         //line(StartX + Width, StartY + Height,StartX+Dx,StartY+Height);
  268.         memset(Data, 0, sizeof(Data));
  269.         Data[0] = 3; // 绝对值鼠标
  270.         Data[1] = 1; // 点击
  271.         Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
  272.         Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
  273.         Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
  274.         Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
  275.         SendToComPort(Port, (char*)&Data);
  276.         Sleep(100);
  277.         Data[0] = 3; // 绝对值鼠标
  278.         Data[1] = 1; // 点击
  279.         Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
  280.         Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
  281.         Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
  282.         Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
  283.         SendToComPort(Port, (char*)&Data);
  284.         Sleep(100);
  285.         Data[0] = 3; // 绝对值鼠标
  286.         Data[1] = 0; // 抬起
  287.         Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
  288.         Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
  289.         Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
  290.         Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
  291.         SendToComPort(Port, (char*)&Data);
  292.         Sleep(100);
  293.         //从下到上竖线
  294.         //line(StartX + Dx, StartY + Height, StartX + Dx, StartY + Dy);
  295.         memset(Data, 0, sizeof(Data));
  296.         Data[0] = 3; // 绝对值鼠标
  297.         Data[1] = 1; // 点击
  298.         Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
  299.         Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
  300.         Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
  301.         Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
  302.         SendToComPort(Port, (char*)&Data);
  303.         Sleep(100);
  304.         Data[0] = 3; // 绝对值鼠标
  305.         Data[1] = 1; // 点击
  306.         Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
  307.         Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
  308.         Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
  309.         Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
  310.         SendToComPort(Port, (char*)&Data);
  311.         Sleep(100);
  312.         Data[0] = 3; // 绝对值鼠标
  313.         Data[1] = 0; // 抬起
  314.         Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
  315.         Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
  316.         Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
  317.         Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
  318.         SendToComPort(Port, (char*)&Data);
  319.         Sleep(100);
  320. }
  321. int main()
  322. {
  323.         printf("%d[%x]  %d[%x]\n", ScreenX2Abs(100), ScreenX2Abs(100), ScreenY2Abs(300), ScreenY2Abs(300));
  324.         printf("Virutal KB MS Test\n");
  325.         Port = MTGetPortFromVidPid(MY_USB_PID_VID);
  326.         if (Port == -1) {
  327.                 printf("No device is found\n");
  328.                 goto EndProgram;
  329.         }
  330.         else {
  331.                 printf("Found COM%d\n",Port);
  332.         }
  333.         Sleep(5000);
  334.         for (int i = 0; i < 8; i++) {
  335.                 DrawRect(100+20*i, 300+20*i, SCREENWIDTH - 200-20*i*2, SCREENHEIGHT - 500-20*i*2, 20, 20);
  336.                 if ((i % 3) == 0) {
  337.                         LedColor(0x01);
  338.                 }
  339.                 if ((i % 3) == 1) {
  340.                         LedColor(0x0100);
  341.                 }
  342.                 if ((i % 3) == 2) {
  343.                         LedColor(0x010000);
  344.                 }
  345.         }
  346.         
  347.         ChangeColor(1329,125);
  348.         for (int i = 8; i < 18; i++) {
  349.                 DrawRect(100 + 20 * i, 300 + 20 * i, SCREENWIDTH - 200 - 20 * i * 2, SCREENHEIGHT - 500 - 20 * i * 2, 20, 20);
  350.                 if ((i % 3) == 0) {
  351.                         LedColor(0x01);
  352.                 }
  353.                 if ((i % 3) == 1) {
  354.                         LedColor(0x0100);
  355.                 }
  356.                 if ((i % 3) == 2) {
  357.                         LedColor(0x010000);
  358.                 }
  359.         }
  360. EndProgram:
  361.         printf("End\n");
  362.         getchar();
  363. }
复制代码
这个代码的作用是在画笔中绘制蛇形线,在绘制过程中还有更换颜色的动作。
工作的视频:
PVD 计划:绝对值鼠标测试】

参考:





zoologist  高级技匠
 楼主|

发表于 昨天 19:07

本文提到的Arduino 代码:下载附件CdcVKBMS2.zip
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail