基于STM32的触控USB鼠标设计

基于STM32的触控USB鼠标设计

一、系统概述与核心功能

1. 系统定位

基于STM32的触控USB鼠标以“触摸输入采集-坐标转换-USB HID协议封装-即插即用”为核心,将触摸传感器(电容/电阻式)的触摸位置、手势动作转换为标准USB鼠标事件(移动、点击、滚动),实现无需驱动的即插即用触控输入设备,适用于触摸板替代、嵌入式人机交互、创意控制器等场景。

2. 核心功能模块

模块 功能描述
触摸传感 电容触摸板(I2C接口)或电阻触摸屏(ADC接口),检测单点/多点触摸坐标(X,Y)及触摸状态(按下/释放)
坐标处理 原始坐标滤波(滑动平均/卡尔曼滤波)、坐标映射(将触摸区域映射到屏幕分辨率)、手势识别(单击、双击、拖拽、双指滚动)
USB HID STM32内置USB FS(全速)外设配置为HID鼠标设备,定义报告描述符(标准鼠标:X/Y相对移动、左/中/右键、滚轮)
事件转换 触摸坐标→鼠标移动(相对位移)、触摸按下→鼠标点击、双指滑动→滚轮滚动
低功耗设计 无触摸时进入STOP模式(触摸传感器中断唤醒),USB挂起时进入SUSPEND模式,待机电流<1mA

二、硬件设计方案

1. 核心硬件选型

模块 型号 关键参数 接口方式
主控MCU STM32F103C8T6 72MHz Cortex-M3,64KB Flash,20KB RAM,全速USB 2.0(PA11/PA12),12位ADC,2个I2C 核心控制器(USB HID设备)
电容触摸板 GT911/FT6236 I2C接口,支持最多5点触摸,分辨率320×240,中断输出(INT) I2C1(PB6=SCL,PB7=SDA)+ INT(PA0)
电阻触摸屏 4线电阻屏+XPT2046 SPI接口,12位ADC,压力检测,需校准(4点校准法) SPI1(PA5=SCK,PA6=MISO,PA7=MOSI,PA4=CS)+ IRQ(PA1)
USB接口 Micro-USB插座 USB 2.0全速,D+(PA12)、D-(PA11),内置1.5kΩ上拉电阻(通过软件控制) USB DP/DM直接连接MCU
电源管理 AMS1117-3.3V 5V USB供电→3.3V给MCU/触摸传感器,最大电流500mA 供电一体化(带ESD保护)
状态指示 WS2812 RGB LED 单线控制,显示触摸状态(红:触摸中,绿:USB已连接,蓝:手势识别) GPIO(PB0,单线时序)

2. 硬件电路设计要点

2.1 核心电路连接(以电容触摸板GT911为例)

2.2 备选方案:电阻触摸屏+XPT2046

三、软件设计与核心代码

1. 系统架构(FreeRTOS多任务)

采用FreeRTOS实时操作系统,划分3个核心任务(优先级从高到低):

  1. 触摸采集任务(优先级3):周期性读取触摸坐标(I2C/SPI),滤波后发送至消息队列。
  2. 手势识别任务(优先级2):解析触摸序列(单击、双击、拖拽、双指滑动),生成鼠标事件(移动、点击、滚动)。
  3. USB HID任务(优先级1):将鼠标事件封装为HID报告,通过USB发送给主机。

2. STM32CubeMX配置(关键步骤)

  1. 时钟配置:HSE=8MHz,PLL输出72MHz,USB时钟需为48MHz(PLL输出72MHz后经1.5分频)。

  2. USB配置

    • 模式:Device Only;
    • Class:Human Interface Device Class(HID);
    • HID参数:Vendor ID=0x0483(ST),Product ID=0x5750,Polling Interval=10ms。
  3. I2C/SPI配置:根据触摸传感器选择I2C1(400kHz)或SPI1(8MHz)。

  4. GPIO配置:触摸中断引脚(PA0)设为外部中断下降沿触发,USB DP(PA12)上拉通过软件控制。

3. 核心代码实现(基于HAL库+电容触摸板GT911)

3.1 USB HID报告描述符(usbd_hid.c中修改)

// 标准鼠标报告描述符(8字节)
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[] __ALIGN_END = {
  0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
  0x09, 0x02,        // USAGE (Mouse)
  0xA1, 0x01,        // COLLECTION (Application)
  0x09, 0x01,        //   USAGE (Pointer)
  0xA1, 0x00,        //   COLLECTION (Physical)
  // 按钮(1字节,8个按钮位)
  0x05, 0x09,        //     USAGE_PAGE (Button)
  0x19, 0x01,        //     USAGE_MINIMUM (Button 1)
  0x29, 0x03,        //     USAGE_MAXIMUM (Button 3)
  0x15, 0x00,        //     LOGICAL_MINIMUM (0)
  0x25, 0x01,        //     LOGICAL_MAXIMUM (1)
  0x95, 0x03,        //     REPORT_COUNT (3)
  0x75, 0x01,        //     REPORT_SIZE (1)
  0x81, 0x02,        //     INPUT (Data,Var,Abs)
  0x95, 0x01,        //     REPORT_COUNT (1)
  0x75, 0x05,        //     REPORT_SIZE (5)
  0x81, 0x03,        //     INPUT (Cnst,Var,Abs)
  // X/Y相对移动(2字节,-127~127)
  0x05, 0x01,        //     USAGE_PAGE (Generic Desktop)
  0x09, 0x30,        //     USAGE (X)
  0x09, 0x31,        //     USAGE (Y)
  0x15, 0x81,        //     LOGICAL_MINIMUM (-127)
  0x25, 0x7F,        //     LOGICAL_MAXIMUM (127)
  0x75, 0x08,        //     REPORT_SIZE (8)
  0x95, 0x02,        //     REPORT_COUNT (2)
  0x81, 0x06,        //     INPUT (Data,Var,Rel)
  // 滚轮(1字节,-127~127)
  0x09, 0x38,        //     USAGE (Wheel)
  0x15, 0x81,        //     LOGICAL_MINIMUM (-127)
  0x25, 0x7F,        //     LOGICAL_MAXIMUM (127)
  0x75, 0x08,        //     REPORT_SIZE (8)
  0x95, 0x01,        //     REPORT_COUNT (1)
  0x81, 0x06,        //     INPUT (Data,Var,Rel)
  0xC0,              //   END_COLLECTION
  0xC0               // END_COLLECTION
};

3.2 触摸采集任务(GT911驱动)

#include "gt911.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

QueueHandle_t touch_queue;  // 触摸数据队列

// 触摸数据结构体
typedef struct {
  uint16_t x;        // X坐标(0~320)
  uint16_t y;        // Y坐标(0~240)
  uint8_t  touch_num; // 触摸点数(0~5)
  uint8_t  event;    // 事件:0=释放,1=按下,2=移动
} TouchPoint_t;

void GT911_Task(void *pvParameters) {
  TouchPoint_t tp;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = pdMS_TO_TICKS(10);  // 10ms采集一次(100Hz)
  
  GT911_Init();  // 初始化GT911(I2C地址0x14/0x5D)
  
  while (1) {
    // 读取触摸状态(通过I2C读取GT911寄存器)
    uint8_t status = GT911_ReadReg(GT911_STATUS_REG);
    if (status & 0x80) {  // 有新数据
      tp.touch_num = status & 0x0F;  // 触摸点数
      if (tp.touch_num > 0) {
        // 读取第一个触摸点坐标(支持多点,此处简化取第一个)
        GT911_ReadTouchPoint(&tp.x, &tp.y, 0);
        tp.event = (tp.touch_num > 0) ? 1 : 0;  // 按下/移动
        xQueueSend(touch_queue, &tp, 0);  // 发送至队列
      }
      GT911_WriteReg(GT911_STATUS_REG, 0x00);  // 清除状态
    }
    vTaskDelayUntil(&xLastWakeTime, xFrequency);
  }
}

3.3 手势识别与鼠标事件转换

// 鼠标事件结构体(对应HID报告)
typedef struct {
  int8_t  x_rel;   // X相对移动(-127~127)
  int8_t  y_rel;   // Y相对移动(-127~127)
  uint8_t buttons; // 按钮状态(bit0=左键,bit1=右键,bit2=中键)
  int8_t  wheel;   // 滚轮(-127~127)
} MouseReport_t;

void Gesture_Task(void *pvParameters) {
  TouchPoint_t tp;
  MouseReport_t mouse = {0};
  uint16_t last_x = 0, last_y = 0;
  uint32_t last_touch_time = 0;
  uint8_t click_count = 0;
  
  while (1) {
    if (xQueueReceive(touch_queue, &tp, portMAX_DELAY) == pdPASS) {
      // 1. 坐标映射(触摸区域320×240 → 屏幕分辨率)
      int16_t dx = 0, dy = 0;
      if (tp.event == 1) {  // 按下/移动
        if (last_x != 0 || last_y != 0) {
          dx = (tp.x - last_x) * 127 / 320;  // 缩放为相对移动(-127~127)
          dy = (tp.y - last_y) * 127 / 240;
          // 限制范围
          dx = (dx < -127) ? -127 : (dx > 127) ? 127 : dx;
          dy = (dy < -127) ? -127 : (dy > 127) ? 127 : dy;
        }
        last_x = tp.x;
        last_y = tp.y;
        mouse.buttons = 0x01;  // 左键按下
      } else {  // 释放
        last_x = last_y = 0;
        mouse.buttons = 0x00;  // 所有按钮释放
        // 单击/双击判断(时间阈值300ms)
        if (HAL_GetTick() - last_touch_time < 300) click_count++;
        else click_count = 1;
        last_touch_time = HAL_GetTick();
        if (click_count == 2) {  // 双击
          mouse.buttons = 0x01;  // 模拟左键双击(快速按下释放两次)
          click_count = 0;
        }
      }
      
      // 2. 双指手势判断(滚动)
      if (tp.touch_num == 2) {
        // 读取第二个触摸点坐标(简化:假设垂直滑动为滚轮)
        uint16_t x2, y2;
        GT911_ReadTouchPoint(&x2, &y2, 1);
        int16_t dy2 = y2 - tp.y;
        mouse.wheel = (dy2 > 10) ? -10 : (dy2 < -10) ? 10 : 0;  // 滚动步长
      } else {
        mouse.wheel = 0;
      }
      
      // 3. 更新鼠标报告
      mouse.x_rel = dx;
      mouse.y_rel = dy;
      
      // 4. 发送至USB HID任务
      xQueueSend(mouse_queue, &mouse, 0);
    }
  }
}

3.4 USB HID发送任务

#include "usbd_hid.h"
extern USBD_HandleTypeDef hUsbDeviceFS;

void USB_HID_Task(void *pvParameters) {
  MouseReport_t mouse;
  uint8_t hid_report[4] = {0};  // 标准鼠标报告:buttons, x, y, wheel
  
  while (1) {
    if (xQueueReceive(mouse_queue, &mouse, portMAX_DELAY) == pdPASS) {
      // 封装HID报告(标准鼠标报告格式)
      hid_report[0] = mouse.buttons;  // 按钮状态
      hid_report[1] = mouse.x_rel;    // X相对移动
      hid_report[2] = mouse.y_rel;    // Y相对移动
      hid_report[3] = mouse.wheel;    // 滚轮
      
      // 发送USB HID报告(阻塞式,超时10ms)
      USBD_HID_SendReport(&hUsbDeviceFS, hid_report, 4);
    }
    vTaskDelay(pdMS_TO_TICKS(10));  // 10ms发送间隔(与HID描述符一致)
  }
}

四、关键技术与优化

1. 触摸坐标处理

2. USB HID优化

3. 低功耗设计

参考代码 STM32 触控USB鼠标 www.youwenfan.com/contentcnt/133749.html

五、系统调试与扩展

1. 调试步骤

阶段 操作 工具
USB枚举测试 连接电脑,查看设备管理器是否识别为“HID-compliant mouse” USBlyzer(抓包工具)、设备管理器
触摸采集测试 打印触摸原始坐标(串口调试),验证坐标范围与响应速度 串口助手、逻辑分析仪(I2C波形)
手势识别测试 模拟单击、拖拽、双指滑动,观察鼠标光标动作 屏幕录像、鼠标光标跟踪软件
性能测试 测量触摸到光标移动的延迟(目标<20ms) 高速摄像机、延时测试工具

2. 扩展功能

六、总结

基于STM32的触控USB鼠标通过电容/电阻触摸传感器采集坐标,经手势识别算法转换为鼠标事件,再通过USB HID协议上报主机,实现低成本、高定制化的触控输入方案。

 

专注于matlab/simulink,电子电路,编程