You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Arduino Pro Micro自制HID游戏手柄开发求助:矩阵按键与触控板代码问题

DIY HID游戏手柄开发问题(Arduino Pro Micro)

我是Arduino/MCU开发新手,正在用Arduino Pro Micro制作一款可后续集成到更大项目的DIY HID游戏手柄。硬件包含一个4x4按键矩阵和两块Azoteq TPS43-201A-S电容触控板(内置Azoteq IQS570电容触摸控制器)。目前遇到两个问题:

  • 不确定自己编写的矩阵按键代码是否正确
  • 完全不知道如何编写触控板的Arduino驱动代码

以下是我目前完成的代码:

#include <Keypad.h>
#include <Gamepad.h>

// Define the 4x4 button matrix
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
  {'Y', 'X', 'B', 'A'},
  {'LEFT', 'RIGHT', 'UP', 'DOWN'},
  {'LB', 'RB', 'SE', 'MU'},
  {'JL', 'JR', 'E1', 'E2'}
};

// Define the pin numbers for the rows and columns of the matrix
byte rowPins[ROWS] = {9, 8, 7, 6}; // Row pins are connected to Arduino pins 9, 8, 7, 6
byte colPins[COLS] = {5, 4, 3, 2}; // Column pins are connected to Arduino pins 5, 4, 3, 2

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
Gamepad gp;

void setup() {
  for (byte i = 0; i < ROWS; i++) {
    pinMode(rowPins[i], INPUT_PULLUP);
  }

  for (byte i = 0; i < COLS; i++) {
    pinMode(colPins[i], INPUT_PULLUP);
  }

  pinMode(A0, INPUT); //right stick y-axis
  pinMode(A1, INPUT); //right stick x-axis
  pinMode(A2, INPUT); //left stick y-axis
  pinMode(A3, INPUT); //left stick x-axis
  pinMode(A4, INPUT); //left trigger
  pinMode(A5, INPUT); //right trigger

  calibrate();
}

void loop() {
  int rt, lt, lx, ly, rx, ry;

  rt = analogRead(A5);
  lt = analogRead(A4);
  lx = analogRead(A3);
  ly = analogRead(A2);
  rx = analogRead(A1);
  ry = analogRead(A0);

  // Convert 0-1000 to -127 - 127
  rt = floor((rt - rightTcenter) * multiplierRT);
  lt = floor((lt - leftTcenter) * multiplierLT);
  lx = floor((lx - leftXcenter) * multiplierLX);
  ly = floor((ly - leftYcenter) * multiplierLY);
  rx = floor((rx - rightXcenter) * multiplierRX);
  ry = floor((ry - rightYcenter) * multiplierRY);

  if (rt > 127) rt = 127;
  if (lt > 127) lt = 127;
  if (lx > 127) lx = 127;
  if (ly > 127) ly = 127;
  if (rx > 127) rx = 127;
  if (ry > 127) ry = 127;

  gp.setRightTaxis(rt);
  gp.setLeftTaxis(lt);
  gp.setLeftXaxis(lx);
  gp.setRightXaxis(rx);
  gp.setLeftYaxis(ly);
  gp.setRightYaxis(ry);

  char key = keypad.getKey();

  if (key) {
    handleButtonPress(key);
  }

  delay(20);
}

void handleButtonPress(char button) {
  for (byte row = 0; row < ROWS; row++) {
    for (byte col = 0; col < COLS; col++) {
      if (button == keys[row][col]) {
        if (digitalRead(rowPins[row]) == LOW && digitalRead(colPins[col]) == LOW) {
          gp.setButtonState(row * COLS + col, true);
        } else {
          gp.setButtonState(row * COLS + col, false);
        }
      }
    }
  }
}

int rightTcenter = 500;
int leftTcenter = 500;
int rightXcenter = 500;
int rightYcenter = 500;
int leftXcenter = 500;
int leftYcenter = 500;
double multiplierRT = 0.254; //127 / 500
double multiplierLT = 0.254;
double multiplierRX = 0.254;
double multiplierRY = 0.254;
double multiplierLX = 0.254;
double multiplierLY = 0.254;

void calibrate() {
  int rt, lt, lx, ly, rx, ry;
  int i = 0;
  while (i < 13) {
    rt = analogRead(A5);
    lt = analogRead(A4);
    lx = analogRead(A3);
    ly = analogRead(A2);
    rx = analogRead(A1);
    ry = analogRead(A0);
    bool validRT = rt > (rightTcenter - 100) && rt < (rightTcenter + 100);
    bool validLT = lt > (leftTcenter - 100) && lt < (leftTcenter + 100);
    bool validLX = lx > (leftXcenter - 100) && lx < (leftXcenter + 100);
    bool validLY = ly > (leftYcenter - 100) && ly < (leftYcenter + 100);
    bool validRX = rx > (rightXcenter - 100) && rx < (rightXcenter + 100);
    bool validRY = ry > (rightYcenter - 100) && ry < (rightYcenter + 100);
    if (validRT && validLT && validLX && validLY && validRX && validRY) {
      i++;
      //nothing to do here!
    } else
      i = 0;
    delay(20);
  }
  rightTcenter = rt;
  leftTcenter = lt;
  leftXcenter = lx;
  leftYcenter = ly;
  rightXcenter = rx;
  rightYcenter = ry;
  multiplierRT = (double)127 / (double)rt;
  multiplierLT = (double)127 / (double)lt;
  multiplierLX = (double)127 / (double)lx;
  multiplierLY = (double)127 / (double)ly;
  multiplierRX = (double)127 / (double)rx;
  multiplierRY = (double)127 / (double)ry;
}

我曾尝试给每个按键单独分配引脚,但引脚数量不足,因此改用矩阵方案。也尝试在网上查找TPS43-201A-S或IQS570的相关资料,但信息极少,仅在Reddit上看到一位用户分享了Linux下的使用经验,而我需要的是Arduino Pro Micro上的实现方法。


一、矩阵按键代码修正与优化

你的代码框架没问题,但存在几个关键错误,以下是修正说明和优化后的代码:

主要问题点

  1. 按键定义错误:keys数组中使用LEFT、RIGHT这类多字符常量,而char类型只能存储单个字符,会导致编译失败。需改为单字符标识。
  2. handleButtonPress逻辑冗余:Keypad库的getKey()已经能返回当前按键的按下/释放状态,无需再通过digitalRead判断引脚电平,该判断逻辑还会导致状态识别错误。
  3. 重复设置引脚模式:Keypad库初始化时会自动配置行列引脚为INPUT_PULLUP,setup中的手动设置属于重复操作,可删除。

优化后的矩阵按键核心代码

#include <Keypad.h>
#include <Gamepad.h>

const byte ROWS = 4;
const byte COLS = 4;
// 改用单字符标识按键,避免编译错误
char keys[ROWS][COLS] = {
  {'Y', 'X', 'B', 'A'},
  {'L', 'R', 'U', 'D'},
  {'1', '2', 'S', 'M'},
  {'J', 'K', 'E', 'F'}
};

byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
Gamepad gp;

void setup() {
  pinMode(A0, INPUT); //right stick y-axis
  pinMode(A1, INPUT); //right stick x-axis
  pinMode(A2, INPUT); //left stick y-axis
  pinMode(A3, INPUT); //left stick x-axis
  pinMode(A4, INPUT); //left trigger
  pinMode(A5, INPUT); //right trigger

  calibrate();
}

void loop() {
  // 摇杆和扳机处理逻辑保留...

  char key = keypad.getKey();
  if (key != NO_KEY) {
    // 判断按键是按下还是释放
    bool isPressed = (keypad.getState() == PRESSED);
    // 根据按键位置计算按钮索引
    int btnIndex = -1;
    for (byte row = 0; row < ROWS; row++) {
      for (byte col = 0; col < COLS; col++) {
        if (keys[row][col] == key) {
          btnIndex = row * COLS + col;
          break;
        }
      }
      if (btnIndex != -1) break;
    }
    if (btnIndex != -1) {
      gp.setButtonState(btnIndex, isPressed);
    }
  }

  delay(20);
}

// 其余calibrate等函数保留...

二、Azoteq IQS570触控板的Arduino驱动实现

IQS570支持I2C通信协议,以下是Arduino Pro Micro上的实现方案:

1. 硬件连接调整

  • IQS570是3.3V器件,TPS43-201A-S的VCC必须接3.3V,不能接5V
  • 两个触控板需要设置不同的I2C地址(通过硬件引脚ADDR调整,默认地址为0x44)
  • 若当前A4/A5引脚用于扳机输入,需改用软件I2C(模拟I2C),选择其他空闲引脚(比如A6、A7)

2. 基础驱动代码框架

#include <Wire.h>
// 若用软件I2C,替换为#include <SoftwareWire.h>,并初始化SoftwareWire对象

#define IQS570_ADDR_1 0x44 // 第一个触控板地址
#define IQS570_ADDR_2 0x45 // 第二个触控板地址

void setup() {
  Wire.begin(); // 硬件I2C初始化,SDA=A4, SCL=A5
  // 软件I2C示例:SoftwareWire wire(A6, A7); wire.begin();
  Serial.begin(9600);
}

// 读取IQS570寄存器
uint8_t iqs570ReadReg(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(addr, 1);
  return Wire.read();
}

// 读取触摸坐标(X/Y)
void iqs570ReadTouch(uint8_t addr, uint16_t &x, uint16_t &y) {
  // 读取X坐标(寄存器0x03-0x04)
  uint8_t x_low = iqs570ReadReg(addr, 0x03);
  uint8_t x_high = iqs570ReadReg(addr, 0x04);
  x = (x_high << 8) | x_low;
  
  // 读取Y坐标(寄存器0x05-0x06)
  uint8_t y_low = iqs570ReadReg(addr, 0x05);
  uint8_t y_high = iqs570ReadReg(addr, 0x06);
  y = (y_high << 8) | y_low;
}

// 读取触摸状态(是否有触摸)
bool iqs570IsTouched(uint8_t addr) {
  uint8_t status = iqs570ReadReg(addr, 0x01);
  return (status & 0x01) == 1; // 第0位表示触摸状态
}

void loop() {
  uint16_t x1, y1, x2, y2;
  bool touch1 = iqs570IsTouched(IQS570_ADDR_1);
  bool touch2 = iqs570IsTouched(IQS570_ADDR_2);
  
  if (touch1) {
    iqs570ReadTouch(IQS570_ADDR_1, x1, y1);
    // 将坐标转换为游戏手柄摇杆的-127~127范围
    int joyX = map(x1, 0, 4095, -127, 127);
    int joyY = map(y1, 0, 4095, -127, 127);
    gp.setLeftXaxis(joyX);
    gp.setLeftYaxis(joyY);
  }
  
  if (touch2) {
    iqs570ReadTouch(IQS570_ADDR_2, x2, y2);
    int joyX = map(x2, 0, 4095, -127, 127);
    int joyY = map(y2, 0, 4095, -127, 127);
    gp.setRightXaxis(joyX);
    gp.setRightYaxis(joyY);
  }
  
  delay(20);
}

关键注意事项

  • 必须参考Azoteq官方的IQS570 datasheet,里面有完整的寄存器定义和通信时序
  • 若出现I2C通信失败,检查电平转换(5V转3.3V)和接线是否正确
  • 两个触控板的ADDR引脚需分别接GND或VCC,设置不同的I2C地址,避免冲突

内容的提问来源于stack exchange,提问作者BONIIXSAbonion 1500

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 05:32:03