fbuser367 gửi vào
- 13662 lượt xem
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!
Chuẩn bị
-
Mạch phát
- Arduino (Uno hay bất kì bo mạch arduino nào khác)
- cảm biến gia tốc MPU6050
- led phát hồng ngoại
-
Mạch thu
-
Arduino (Uno, Leonardo hay micro)
-
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.
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ông!
Dưới đây là đoạn video test sản phẩm của mình.