目錄
一.?qt界面實現
二.?虛擬設備模擬模擬鼠標實現體感鼠標
2.1 函數聲明
2.2 虛擬鼠標實現
2.2.1 虛擬鼠標創建函數
2.2.2?鼠標移動函數
2.2.3 鼠標點擊函數
2.3 mpu6050相關函數實現
2.3.1 i2c設備初始化
2.3.2 mpu6050寄存器寫入
2.3.3 mpu6050寄存器讀取
2.3.4 mpu6050初始化
2.3.5 read_accel 加速度數據獲取及處理函數
2.4 按鍵模擬鼠標點擊功能實現
2.4.1?導出export按鍵對應的gpio
2.4.2?將gpio方向direction設置為輸入
2.4.3 讀取按鍵gpio狀態,來控制鼠標按鍵
2.5 資源清理函數
2.6?主函數實現(偽代碼)
2.7 源代碼及執行步驟
????????使用uinput虛擬設備實現體感鼠標,通過用戶空間 input 設備測試程序讀取/dev/event*文件獲取鼠標狀態,鼠標移動數據能隨開發板傾角運動改變。
????????鼠標按鍵可以使用 Key_DOWN、Key_RIGHT、Key_LEFT、Key_UP 四個按鍵中任選兩個 實現,四個按鍵的 GPIO 編號為 960 – 963.(本實驗使用Key_RIGHT、Key_LEFT分別實現鼠標的右鍵和左鍵)
一.?qt界面實現
????????為了測試虛擬鼠標的功能,使用qt軟件畫了一個窗口,通過以下指令將 .ui文件 轉換成python可執行文件。
pyuic5 -x windows_mouse.ui -o windows_mouse.py
? ? ? ? ?qt完整python代碼如下
# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'windows_mouse.ui'
#
# Created by: PyQt5 UI code generator 5.10.1
#
# WARNING! All changes made in this file will be lost!
# windows_mouse.pyfrom PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_Form(object):def setupUi(self, Form):Form.setObjectName("Form")Form.resize(480, 272) # 調整窗口高度以容納新控件self.verticalLayout = QtWidgets.QVBoxLayout(Form)self.verticalLayout.setObjectName("verticalLayout")# 原有的 right 按鈕和進度條self.right = QtWidgets.QPushButton(Form)self.right.setObjectName("right")self.verticalLayout.addWidget(self.right)self.progressBarRight = QtWidgets.QProgressBar(Form)self.progressBarRight.setProperty("value", 0) # 初始值設為 0self.progressBarRight.setObjectName("progressBarRight")self.verticalLayout.addWidget(self.progressBarRight)# 新增的 left 按鈕和進度條self.left = QtWidgets.QPushButton(Form)self.left.setObjectName("left")self.verticalLayout.addWidget(self.left)self.progressBarLeft = QtWidgets.QProgressBar(Form)self.progressBarLeft.setProperty("value", 0) # 初始值設為 0self.progressBarLeft.setObjectName("progressBarLeft")self.verticalLayout.addWidget(self.progressBarLeft)self.Exit = QtWidgets.QPushButton(Form)self.Exit.setObjectName("Exit")self.verticalLayout.addWidget(self.Exit)self.retranslateUi(Form)self.Exit.clicked.connect(Form.close)# 設置 right 按鈕右鍵菜單self.right.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)self.right.customContextMenuRequested.connect(self.right_button_clicked)# 設置 left 按鈕左鍵點擊事件self.left.clicked.connect(self.left_button_clicked)QtCore.QMetaObject.connectSlotsByName(Form)def retranslateUi(self, Form):_translate = QtCore.QCoreApplication.translateForm.setWindowTitle(_translate("Form", "Form"))self.right.setText(_translate("Form", "right"))self.left.setText(_translate("Form", "left"))self.Exit.setText(_translate("Form", "left - Exit"))def right_button_clicked(self, pos):# right 按鈕右鍵點擊事件處理if self.progressBarRight.value() == 0:self.progressBarRight.setValue(100) # 進度條加滿else:self.progressBarRight.setValue(0) # 進度條變為 0def left_button_clicked(self):# left 按鈕左鍵點擊事件處理if self.progressBarLeft.value() == 0:self.progressBarLeft.setValue(100) # 進度條加滿else:self.progressBarLeft.setValue(0) # 進度條變為 0if __name__ == "__main__":import sysapp = QtWidgets.QApplication(sys.argv)Form = QtWidgets.QWidget()ui = Ui_Form()ui.setupUi(Form)Form.show()sys.exit(app.exec_())
? ? ? ? 運行python windows_mouse.py出現以下界面。其中right按鍵只能鼠標右鍵點擊,left按鍵只能鼠標左鍵點擊,left-Exit為鼠標左鍵點擊的退出按鍵。
二.?虛擬設備模擬模擬鼠標實現體感鼠標
2.1 函數聲明
????????總共分為四個部分,分別是mpu6050設備初始化及信息獲取、虛擬鼠標實現、gpio設備初始化及按鍵gpio信息獲取、資源清理clean。
/* 函數聲明 */
// mpu6050和i2c的初始化和功能函數
int i2c_init(const char* i2c_dev);
void mpu6050_write(int fd, uint8_t reg, uint8_t val);
void mpu6050_read(int fd, uint8_t reg, uint8_t* buf, int len);
void mpu6050_init(int fd);
void read_accel(int fd, float* accel);
// 清理
void cleanup_handler(int sig);
// 虛擬鼠標創建及移動、點擊
int create_virtual_mouse(const char* dev_name);
void send_mouse_move(int fd, int dx, int dy);
int send_mouse_click(int fd, int button_code);// 按鍵對應鼠標點擊
void set_gpio_direction(int gpio, const char *direction);
char read_gpio_value(int gpio);
void export_gpio(int gpio);
void unexport_gpio(int gpio);
2.2 虛擬鼠標實現
2.2.1 虛擬鼠標創建函數
????????1. 創建虛擬鼠標需要:配置鼠標移動和按鍵事件、初始化設備信息、提交設備配置 三個步驟。
int create_virtual_mouse(const char* dev_name) {struct uinput_user_dev uidev;int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);/* 配置基礎事件類型 */ // 此處沒有錯誤處理,代碼里有// 鼠標移動事件ioctl(fd, UI_SET_EVBIT, EV_REL);ioctl(fd, UI_SET_RELBIT, REL_X);ioctl(fd, UI_SET_RELBIT, REL_Y);// 鼠標按鍵事件ioctl(fd, UI_SET_EVBIT, EV_KEY);ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);/* 初始化設備信息 */memset(&uidev, 0, sizeof(uidev));snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "%s", dev_name);uidev.id.bustype = BUS_USB;uidev.id.version = 4;/* 提交設備配置 */ioctl(fd, UI_DEV_SETUP, &uidev); // 提交設備配置ioctl(fd, UI_DEV_CREATE); // 創建設備
}
2.2.2?鼠標移動函數
void send_mouse_move(int fd, int dx, int dy) {struct input_event ev[3];struct timeval tv;gettimeofday(&tv, NULL); // 此函數是獲取時間戳// X軸移動memset(&ev[0], 0, sizeof(ev[0])); // 初始化ev[0]的所有成員為0ev[0].type = EV_REL;ev[0].code = REL_X;ev[0].value = dx;ev[0].time = tv;// Y軸移動memset(&ev[1], 0, sizeof(ev[1]));ev[1].type = EV_REL;ev[1].code = REL_Y;ev[1].value = dy;ev[1].time = tv;// 同步事件memset(&ev[2], 0, sizeof(ev[2]));ev[2].type = EV_SYN;ev[2].code = SYN_REPORT;ev[2].value = 0;ev[2].time = tv;
}
? ? ? ? 1.?使用到的結構體?input_event 結構體介紹
#include <linux/input.h>struct input_event {struct timeval time; // 時間戳__u16 type; // 事件類型__u16 code; // 事件代碼__s32 value; // 事件值
};
變量名 | 類型 | 功能 |
---|---|---|
time | struct timeval | 記錄輸入事件發生的時間戳,包含秒(tv_sec)和微秒(tv_usec),通常用于事件排序或性能分析 |
type | __u16 | 表示輸入事件的類型,如按鍵事件(EV_KEY)、相對位置事件(EV_REL) 、(EV_SYN)同步事件(標記批次結束) |
code | __u16 | 與 type 結合,具體化事件含義,如具體按鍵、軸或輸入元素 EV_KEY 類型: BTN_LEFT? // 鼠標左鍵(0x110)、 BTN_RIGHT // 鼠標右鍵(0x111)、 KEY_ENTER ?// 回車鍵(0x1c) EV_REL 類型: REL_X? // X軸相對移動、 (0x00)REL_Y ?// Y軸相對移動(0x01)、 REL_WHEEL? // 滾輪滾動(0x08) EV_ABS 類型: ABS_X ?// X軸絕對坐標、( 0x00)ABS_Y? // Y軸絕對坐標(0x01) |
value | __s32 | 表示與事件相關的具體值,含義取決于 event 的 type 和 code EV_KEY 事件: value = 1:按鍵按下 value = 0:按鍵釋放 value = 2:按鍵長按(部分設備支持) EV_REL 事件: value = 10:X軸向右移動10像素 value = -5:Y軸向下移動5像素 EV_ABS 事件: value = 500:觸摸屏X軸坐標為500 |
? ? ? ? ?2. 注意:每次事件完成都要調用同步事件,將鼠標位置和按鍵狀態更新。
2.2.3 鼠標點擊函數
? ? ? ? 和鼠標移動函數類似,只是將鼠標移動函數中的input_event結構體的參數值給鼠標按鍵相關的參數即可。
2.3 mpu6050相關函數實現
2.3.1 i2c設備初始化
/* I2C設備初始化 */
int i2c_init(const char* i2c_dev) {int fd = open(i2c_dev, O_RDWR);if (fd < 0) {perror("Failed to open I2C device");return -1;}// 因為ioctl的數據結構體有“從機地址”參數,所以不用單獨設置從機地址return fd;
}
2.3.2 mpu6050寄存器寫入
/* MPU6050寄存器寫操作 */
void mpu6050_write(int fd, uint8_t reg, uint8_t val) {struct i2c_msg msg;uint8_t buf[2] = {reg, val};msg.addr = MPU6050_ADDR; // 設備地址msg.flags = 0; // 寫操作msg.len = 2; // 數據長度(寄存器地址 + 值)msg.buf = buf; // 數據緩沖區struct i2c_rdwr_ioctl_data ioctl_data;ioctl_data.msgs = &msg; // 消息數組ioctl_data.nmsgs = 1; // 消息數量if (ioctl(fd, I2C_RDWR, &ioctl_data) < 0)perror("MPU6050 write failed");
}
? ? ? ? 1. struct i2c_rdwr_ioctl_data結構體詳解
struct i2c_rdwr_ioctl_data {struct i2c_msg* msgs; /* I2C 消息數組的指針 */int nmsgs; /* 消息數組的元素個數 */
};
參數名 | 類型 | 功能 |
---|---|---|
msgs | struct i2c_msg* | 指向一個?i2c_msg ?結構體數組的指針,每個數組元素描述了一次 I2C 傳輸操作(消息)。 |
nmsgs | int | 表示?msgs ?數組中元素的個數,即要執行的 I2C 消息傳輸操作的次數。 |
? ? ? ? 2.??struct i2c_msg結構體詳解
struct i2c_msg {__u16 addr; /* I2C從設備地址 */__u16 flags; /* 消息標志 */__u16 len; /* 數據緩沖區長度 */__u8 *buf; /* 數據緩沖區指針 */
};
參數名 | 類型 | 功能說明 |
---|---|---|
addr | __u16 | I2C從設備地址,用于指定要通信的從設備的7位或10位地址 |
flags | __u16 | 消息標志,用于指定傳輸方向等信息,如I2C_M_RD(讀取操作),flags=0(寫入操作)等 |
len | __u16 | 要傳輸的數據長度,即buf 數組中數據的字節數 |
buf | __u8 * | 指向數據緩沖區的指針,用于存儲要發送的數據或接收到的數據 |
2.3.3 mpu6050寄存器讀取
? ? ? ? 將 i2c_msg 的flags標志換成讀取操作即可。
? ? ? ? 注意:在讀取數據之前,主設備(linux開發板)需要先指定要讀取的從設備(mpu6050)寄存器地址。所以在讀取數據之前需要先進行一次寫入。這個寫入操作的作用是將寄存器地址發送給 MPU6050,告訴它接下來要從哪個寄存器開始讀取數據。
2.3.4 mpu6050初始化
/* MPU6050初始化 (關閉睡眠模式)*/
void mpu6050_init(int fd) {mpu6050_write(fd, ACCEL_CONFIG, 0x00); // ±2g量程mpu6050_write(fd, PWR_MGMT_1, 0x00); // 退出睡眠模式usleep(100000); // 等待穩定100ms
}
2.3.5 read_accel 加速度數據獲取及處理函數
? ? ? ? 調用mpu6050_read函數讀取加速度數據,并進行處理,轉換為m/s2的加速度。
/* 讀取加速度數據 (XYZ 三軸)*/
void read_accel(int fd, float* accel) {uint8_t buf[6];mpu6050_read(fd, ACCEL_XOUT_H, buf, 6);// 合并原始數據并轉換單位int16_t raw_x = (int16_t)((buf[0] << 8) | buf[1]); // X 軸int16_t raw_y = (int16_t)((buf[2] << 8) | buf[3]); // Y 軸int16_t raw_z = (int16_t)((buf[4] << 8) | buf[5]); // Z 軸// 轉換為 m/s2accel[0] = (raw_x / ACCEL_SCALE_2G) * GRAVITY_CM_S2;accel[1] = (raw_y / ACCEL_SCALE_2G) * GRAVITY_CM_S2;accel[2] = (raw_z / ACCEL_SCALE_2G) * GRAVITY_CM_S2;
}
2.4 按鍵模擬鼠標點擊功能實現
2.4.1?導出export按鍵對應的gpio
????????還有對應的撤銷導出unexport函數同理。
// 導出gpio
void export_gpio(int gpio) {FILE *fp = fopen(GPIO_EXPORT, "w");if (fp == NULL) {perror("Failed to open export file");fprintf(stderr, "Error code: %d\n", errno);exit(EXIT_FAILURE);}fprintf(fp, "%d", gpio);fclose(fp);
}
2.4.2?將gpio方向direction設置為輸入
// 設置gpio方向為輸入
void set_gpio_direction(int gpio, const char *direction) {char path[64];snprintf(path, sizeof(path), GPIO_DIRECTION, gpio);FILE *fp = fopen(path, "w");if (fp == NULL) {perror("Failed to open direction file");fprintf(stderr, "Error code: %d\n", errno);exit(EXIT_FAILURE);}fprintf(fp, "%s", direction);fclose(fp);
}
2.4.3 讀取按鍵gpio狀態,來控制鼠標按鍵
char read_gpio_value(int gpio) {char path[64];snprintf(path, sizeof(path), GPIO_VALUE, gpio);FILE *fp = fopen(path, "r");if (fp == NULL) {perror("Failed to open value file");fprintf(stderr, "Error code: %d\n", errno);exit(EXIT_FAILURE);}char value[2];fgets(value, sizeof(value), fp);fclose(fp);return value[0] == '1' ? '1' : '0';
}
2.5 資源清理函數
? ? ? ? 銷毀虛擬設備、關閉i2c、撤銷導出按鍵gpio。
void cleanup_handler(int sig) {if (uinput_fd >= 0) {// 銷毀虛擬設備ioctl(uinput_fd, UI_DEV_DESTROY);close(uinput_fd);printf("\n[Cleanup] Virtual mouse device destroyed\n");}if (i2c_fd >= 0) { // 新增:關閉I2C設備close(i2c_fd);printf("[Cleanup] I2C device closed\n");i2c_fd = -1; // 重置描述符}int gpios[] = {960, 961, 962, 963, 964};int num_gpios = sizeof(gpios) / sizeof(gpios[0]);for (int i = 0; i < num_gpios; i++) {unexport_gpio(gpios[i]);}printf("all gpio unexported\n");fflush(stdout); // 強制刷新標準輸出exit(EXIT_SUCCESS);
}
2.6?主函數實現(偽代碼)
int main() {// 1. 注冊信號處理(Ctrl+C)/* 2. 創建虛擬鼠標 *//* 3. 初始化MPU6050 */// 4. 導出五個按鍵的gpio,并設置方向為輸入while (1) {// 調整方向// 發送給虛擬鼠標使其移動// 讀取當前按鈕狀態// 鍵盤左鍵對應鼠標左鍵// 鍵盤右鍵對應鼠標右鍵// 按鍵消抖}//資源清理
}
2.7 源代碼及執行步驟
? ? ? ? 源代碼:用戶空間MPU6050體感虛擬鼠標驅動程序資源-CSDN文庫
# 代碼編譯 -lm 是因為使用了數學公式
gcc mpu_mouse.c -o mpu_mouse.out -lm# 功能實現1:添加需要的環境變量,屏幕和鼠標# Qt-embedded需要的環境變量
# ? 指定顯示設備
export QT_QPA_PLATFORM=linuxfb
# ? 指定輸入設備(觸摸屏)
export QT_QPA_GENERIC_PLUGINS=evdevtouch:/dev/input/event0
export QWS_MOUSE_PROTO=evdevtouch:/dev/input/event0
# ? 指定輸入設備(鼠標)
export QT_QPA_GENERIC_PLUGINS=evdevmouse:/dev/input/event0
export QWS_MOUSE_PROTO=evdevmouse:/dev/input/event0# 功能實現2 # "&"符號是因為要放在后臺運行,然后運行鼠標控制程序
python windows_mouse.py &
./mpu_mouse.c