Thiết kế chuột máy tính trên không của riêng bạn

Mô tả dự án: 

Hiện nay, những dự án chuột máy tính trên không đã sớm không còn xa lạ với mọi người. Tuy nhiên việc sở hữu chúng chưa phải là thông dụng, bài viết này sẽ hướng dẫn bạn tự mình thiết kế một chú chuột như thế ứng dụng để chơi game The Legend of Korra. Và tuyệt vời hơn nữa là sau bài viết này các bạn sẽ có một chiếc tay cầm chơi game độc đáo và sang chảnh hết sức! laugh

Chuẩn bị

  1. Mạch phát​​
  2.  Mạch thu

Làm thôi!

Mạch phát

Mình sử dụng arduino nhận tín hiệu từ con cảm biến gia tốc rồi qua con led phát hồng ngoại để thực hiện giao tiếp từ xa giữa hai board arduino. Các bạn cũng có thể sử dụng thêm nút bấm để tăng thêm chức năng nhá

     

Xử lý MPU6050

Khi làm việc với con cảm biến gia tốc này vấn đề khó nhất là đọc tín hiệu hắn trả về bởi vì hắn trôi rất nhiều. Nhưng sẽ rất là đơn giản khi sử dụng bộ lọc Kaiman, các bạn tải thư viện trên về sẽ dễ dàng đọc được các giá trị góc quay của MPU6050

Xử lý led phát hồng ngoại

Vấn đề này tương đối dễ dàng, chỉ cần tải thư viện IRLremote.h  là có thể sử dụng được rồi. Các bạn tham khảo thêm về cách sử dụng led thu phát hồng ngoại tại bài viết Infrared remote control nha.

Lập trình

Code này mình viết cho mạch phát  

Ý tưởng là khi thay đổi góc các trục x,y của MPU6050 theo ý đã định sẽ gửi một mã hồng ngoại xác định tới mạch thu      

#include <Wire.h>
#include "Kalman.h"
#include "IRLremote.h"
const int pinSendIR = 3;
int len=11;
int xuong=12;
#define RESTRICT_PITCH // Comment out to restrict roll to ±90deg instead - please read: http://www.freescale.com/files/sensors/doc/app_note/AN3461.pdf
Kalman kalmanX;
Kalman kalmanY;
double accX, accY, accZ;
double gyroX, gyroY, gyroZ;
int16_t tempRaw;
double gyroXangle, gyroYangle; // Angle calculate using the gyro only
double compAngleX, compAngleY; // Calculated angle using a complementary filter
double kalAngleX, kalAngleY; // Calculated angle using a Kalman filter
uint32_t timer;
uint8_t i2cData[14];
void setup() {
  //Serial.begin(115200);
  pinMode(len, INPUT_PULLUP);
  pinMode(xuong, INPUT_PULLUP);
  Wire.begin();
  TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz
  i2cData[0] = 7; // Set the sample rate to 1000Hz - 8kHz/(7+1) = 1000Hz
  i2cData[1] = 0x00; // Disable FSYNC and set 260 Hz Acc filtering, 256 Hz Gyro filtering, 8 KHz sampling
  i2cData[2] = 0x00; // Set Gyro Full Scale Range to ±250deg/s
  i2cData[3] = 0x00; // Set Accelerometer Full Scale Range to ±2g
  while (i2cWrite(0x19, i2cData, 4, false)); // Write to all four registers at once
  while (i2cWrite(0x6B, 0x01, true)); // PLL with X axis gyroscope reference and disable sleep mode
  while (i2cRead(0x75, i2cData, 1));
  if (i2cData[0] != 0x68) { // Read "WHO_AM_I" register
    //Serial.print(F("Error reading sensor"));
    while (1);
  }

  delay(100); // Wait for sensor to stabilize

  /* Set kalman and gyro starting angle */
  while (i2cRead(0x3B, i2cData, 6));
  accX = (i2cData[0] << 8) | i2cData[1];
  accY = (i2cData[2] << 8) | i2cData[3];
  accZ = (i2cData[4] << 8) | i2cData[5];
#ifdef RESTRICT_PITCH // Eq. 25 and 26
  double roll  = atan2(accY, accZ) * RAD_TO_DEG;
  double pitch = atan(-accX / sqrt(accY * accY + accZ * accZ)) * RAD_TO_DEG;
#else // Eq. 28 and 29
  double roll  = atan(accY / sqrt(accX * accX + accZ * accZ)) * RAD_TO_DEG;
  double pitch = atan2(-accX, accZ) * RAD_TO_DEG;
#endif

  kalmanX.setAngle(roll);
  kalmanY.setAngle(pitch);
  gyroXangle = roll;
  gyroYangle = pitch;
  compAngleX = roll;
  compAngleY = pitch;

  timer = micros();
}

void loop() {
  while (i2cRead(0x3B, i2cData, 14));
  accX = ((i2cData[0] << 8) | i2cData[1]);
  accY = ((i2cData[2] << 8) | i2cData[3]);
  accZ = ((i2cData[4] << 8) | i2cData[5]);
  tempRaw = (i2cData[6] << 8) | i2cData[7];
  gyroX = (i2cData[8] << 8) | i2cData[9];
  gyroY = (i2cData[10] << 8) | i2cData[11];
  gyroZ = (i2cData[12] << 8) | i2cData[13];

  double dt = (double)(micros() - timer) / 1000000; // Calculate delta time
  timer = micros();
#ifdef RESTRICT_PITCH // Eq. 25 and 26
  double roll  = atan2(accY, accZ) * RAD_TO_DEG;
  double pitch = atan(-accX / sqrt(accY * accY + accZ * accZ)) * RAD_TO_DEG;
#else // Eq. 28 and 29
  double roll  = atan(accY / sqrt(accX * accX + accZ * accZ)) * RAD_TO_DEG;
  double pitch = atan2(-accX, accZ) * RAD_TO_DEG;
#endif

  double gyroXrate = gyroX / 131.0; // Convert to deg/s
  double gyroYrate = gyroY / 131.0; // Convert to deg/s

#ifdef RESTRICT_PITCH
  // This fixes the transition problem when the accelerometer angle jumps between -180 and 180 degrees
  if ((roll < -90 && kalAngleX > 90) || (roll > 90 && kalAngleX < -90)) {
    kalmanX.setAngle(roll);
    compAngleX = roll;
    kalAngleX = roll;
    gyroXangle = roll;
  } else
    kalAngleX = kalmanX.getAngle(roll, gyroXrate, dt); // Calculate the angle using a Kalman filter

  if (abs(kalAngleX) > 90)
    gyroYrate = -gyroYrate; // Invert rate, so it fits the restriced accelerometer reading
  kalAngleY = kalmanY.getAngle(pitch, gyroYrate, dt);
#else
  // This fixes the transition problem when the accelerometer angle jumps between -180 and 180 degrees
  if ((pitch < -90 && kalAngleY > 90) || (pitch > 90 && kalAngleY < -90)) {
    kalmanY.setAngle(pitch);
    compAngleY = pitch;
    kalAngleY = pitch;
    gyroYangle = pitch;
  } else
    kalAngleY = kalmanY.getAngle(pitch, gyroYrate, dt); // Calculate the angle using a Kalman filter

  if (abs(kalAngleY) > 90)
    gyroXrate = -gyroXrate; // Invert rate, so it fits the restriced accelerometer reading
  kalAngleX = kalmanX.getAngle(roll, gyroXrate, dt); // Calculate the angle using a Kalman filter
#endif

  gyroXangle += gyroXrate * dt; // Calculate gyro angle without any filter
  gyroYangle += gyroYrate * dt;
  compAngleX = 0.93 * (compAngleX + gyroXrate * dt) + 0.07 * roll; // Calculate the angle using a Complimentary filter
  compAngleY = 0.93 * (compAngleY + gyroYrate * dt) + 0.07 * pitch;
  if (gyroXangle < -180 || gyroXangle > 180)
    gyroXangle = kalAngleX;
  if (gyroYangle < -180 || gyroYangle > 180)
    gyroYangle = kalAngleY;
/*
  Serial.print(kalAngleX); Serial.print("\t");

  Serial.print("\t");

  Serial.print(kalAngleY); Serial.print("\t");

  double temperature = (double)tempRaw / 340.0 + 36.53;
  Serial.print(temperature); Serial.print("\t");
  Serial.print("\r\n"); */
  if((kalAngleX>=15)&&(kalAngleY>=-5)&&(kalAngleY<=5))
  {
    uint16_t address = 0x6361;
    uint32_t command = 0xFE01;
    IRLwrite<IR_NEC>(pinSendIR, address, command);
    delay(50);
  } else
  if((kalAngleX<=-15)&&(kalAngleY>=-5)&&(kalAngleY<=5))
  {
    uint16_t address = 0x6300;
    uint32_t command = 0xFE01;
    IRLwrite<IR_NEC>(pinSendIR, address, command);
    delay(50);
  } else
  if((kalAngleY>=15)&&(kalAngleX<=5)&&(kalAngleX>=-5))
  {
    uint16_t address = 0x6200;
    uint32_t command = 0xFE01;
    IRLwrite<IR_NEC>(pinSendIR, address, command);
    delay(50);
  } else
  if((kalAngleY<=-15)&&(kalAngleX<=5)&&(kalAngleX>=-5))
  {
    uint16_t address = 0x6100;
    uint32_t command = 0xFE01;
    IRLwrite<IR_NEC>(pinSendIR, address, command);
    delay(50);
  } else
  if(digitalRead(len)==LOW)
  {
    uint16_t address = 0x6000;
    uint32_t command = 0xFE01;
    IRLwrite<IR_NEC>(pinSendIR, address, command);
    delay(50);
  } else
  if(digitalRead(xuong)==LOW)
  {
    uint16_t address = 0x6400;
    uint32_t command = 0xFE01;
    IRLwrite<IR_NEC>(pinSendIR, address, command);
    delay(50);
  }
}

Mạch thu

Mục đích của mình là sử dụng arduino để giả lập chuột và bàn phím điều khiển máy tính, nhưng bản thân con Uno không có khả năng này nên chúng ta sẽ nâng cấp nó lên Hoodloader2 để có thể sử dụng thư viện Keyboard.h và Mouse.h của Leonardo hay micro. Sẽ dễ dàng hơn rất nhiều nếu bạn có sẵn một con Leonardo hay Micro rồi, nhưng mà nâng cấp con Uno lên đi các bạn sẽ thấy điều tuyệt vời mà nó có thể làm được.wink

Sơ đồ nguyên lý mạch thu

Code này mình viết cho mạch thu

Ý tưởng là khi nhận được mã hồng ngoại từ mạch phát thì sẽ ấn giữ phím nào đó trên bàn phím hoạc chuột, cứ thế tùy vào từng trò chơi cần sử dụng những

phím gì thì code cho phù hợp.

#include "Keyboard.h"
#include "IRLremote.h"
#include "PinChangeInterrupt.h"
#include "Mouse.h"
#define IRL_BLOCKING true
#define pinIR 2
uint8_t IRProtocol = 0;
uint16_t IRAddress = 0;
uint32_t IRCommand = 0;

void setup() {
  pinMode(3,OUTPUT);
  Keyboard.begin();
  Mouse.begin();
  attachPCINT(digitalPinToPCINT(pinIR), IRLinterrupt<IR_ALL>, CHANGE);
}

void loop() 
{
  if(IRAddress==0x6361)
  {
      Keyboard.press('w');
      Mouse.move(10,0, 0);
      delay(50);
    Keyboard.release('w');
    Mouse.move(0,0, 0);
  }
  else  if(IRAddress==0x6300)
  {
      Keyboard.press('s');
      Mouse.move(-10,0, 0);
      delay(50);
    Keyboard.release('s');
    Mouse.move(0,0, 0);
  }
  else  if(IRAddress==0x6200)
  {
      Keyboard.press('d');
      Mouse.move(0,10, 0);
      delay(50);
    Keyboard.release('d');
    Mouse.move(0,0, 0);
  }
  else  if(IRAddress==0x6100)
  {
      Keyboard.press('a');
      Mouse.move(0,-10, 0);
      delay(50);
    Keyboard.release('a');
    Mouse.move(0,0, 0);
  }
  else  if(IRAddress==0x6000)
  {
  Keyboard.releaseAll();
  Mouse.move(0,0, 0);
  Mouse.release(MOUSE_LEFT);
  }
  else  if(IRAddress==0x6400)
  {
  if (!Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.press(MOUSE_LEFT);
    }
    else
    if (Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.release(MOUSE_LEFT);
    }
  }
  uint8_t oldSREG = SREG;
  cli();
  if (IRProtocol) 
    IRProtocol = 0;
  SREG = oldSREG;
  
}
void IREvent(uint8_t protocol, uint16_t address, uint32_t command) {
  if (IRL_BLOCKING && !IRProtocol) {
    IRProtocol = protocol;
    IRAddress = address;
    IRCommand = command;
  }
}
Đấy, xong mất rồi ^^.

Kết luận

Cảm ơn các bạn đã đọc bài viết, mình chắc rằng sau bài viết này với trí tưởng tượng và sự sáng tạo tuyệt vời các bạn sẽ có những sản phẩm độc đáo của riêng mình. Chúc các bạn thành côngcool!

Dưới đây là đoạn video test sản phẩm của mìnhcheeky.

Những hình ảnh về dự án: 
Youtube: 
lên
17 thành viên đã đánh giá bài viết này hữu ích.
Chuyên mục: 
Các dự án được truyền cảm hứng

Select any filter and click on Apply to see results