
Since I built my Bartop Arcade I have been wanting to play Duck Hunt. I knew the original Light Gun could not work with the Raspberry Pi on RetroPie. I came across that you can use a mouse, which wouldn’t be fun. I knew the Arduino Pro Micro (and a couple others) could emulate a Keyboard or Mouse. There was a video about a year ago on the Element 14 Presents Youtube channel “NES Zapper on Retropie”. It is the same concept with a little different components. The Pro Micro board is a cheaper option than the board he used there. The boards based on the ATmega32U4 have a built in USB inferface on the ATmega chip, where the more common Uno Nano, and Pro Mini do not support being used in this way.
Using that as the base idea, I found various info on making an Air Mouse using a GY-521/MPU-6050 module attached to the Pro Micro board. I had found a video by “Asiq’s Theory” on Youtube with a basic Air Mouse using this setup along with code. I can no longer find the channel or video though.
I didn’t have either the GY-521/MPU-6050 board or a Arduino Pro Micro so I had to get them in to try this project out. I have plenty of Arduino Unos and Nanos around, but I hadn’t purchased any Pro Micros. The boards are similar but I usually use an Uno for prototyping then I can directly do that to a Nano without any code changes, where the Pro Micro with the different ATmega model is slightly different as well as slightly more expensive. I had been wanting some Pro Micro boards for keyboard projects in the past, but I never got into anything with them to this point. Once they arrived I took built it up on breadboard to test operation.
I ended up looking for a decent 3d model to use as a light gun model. I couldn’t find something that I could print reasonably for the amount of effort I wanted to put into this project. Really I don’t see this getting much use, mostly a little Duck Hunt now and then, maybe a few other games. I did end up finding a model I could use as a basis that had a completely different intended use. https://www.thingiverse.com/thing:2560577 I can print it as two parts now without any support. It feels pretty good to hold it. I went with some larger buttons I had in my stock with snap on caps.
I did my initial modifications included taking away parts from the model and slicing it in half. I also cut an opening for the usb cable as well as areas inside to hold the electronics. I did that work in Tinkercad. It works well enough for modifying things, but I wouldn’t want to use it to start it from scratch. I am looking for another program to use that I can get familiar enough to make items from scratch though. I first made a blue printed prototype half. It worked out fairly well, the Pro Micro fit in the area I made for it and the MPU-6050 board fit great as well. I put the 6050 forward in the gun expecting better readings of the motion, I am not sure if it makes much difference. That first half is basically a hollow shell, then I had to go on and make the other half “hold” everything. For the buttons I used my calipers to measure the button dimensions and built a placeholder model that I was able to use to give proper placement in the frame. It let me build up the back support and I slightly over sized the button “caps” so I could use them as holes to cut into the model. I also made a mock up of the MPU-6050 board so I could model in posts to hold it in place. I cut in recessed screw holes in the right side as well. For the prototype gun also had 3 opens in the shell that were from the original model that I then had to fill in so I printed them as extensions off of the right side on it. The screws I used used pointed screws to go into the left side making holes as I put them into it. That is holding that prototype together quite well so far.
After the first one was done, I decided I would make another for the second cabinet in Grey. For this one I reworked the left side with the holes filled in. I also setup to put in threaded inserts into it. This was the first time I used them, and I wasn’t sure what size to make the holes. They went in, but not very well and I ended up giving them more support and hold with glue so they hopefully won’t pull out. Otherwise the inserts are working well. They are very cheap and not well made though. For the Grey gun I tried to line up the usb cable coming in more to the center of the Pro Micro holding area. I placed it in a cut out to hold a zip tie as strain relief in the cord area. I didn’t make enough room for the “head” of the zip tie though, so when I put it in I cut out a smaller square deeper into the shell so that it would slot the whole way in and I could properly close the gun. It worked out very well. Still I have had to make my own custom cables to get enough room in there for the cable and the Pro Micro. I may rework the model a bit more to see if I can get a standard MicroUSB cable inside instead of soldering up a minimal one of my own.



The 3d model is posted here: https://www.thingiverse.com/thing:5805908
The electronics are mostly the Pro Micro and MPU-6050 board. The additions are the two momentary switches with pull down resistors wired to ground to prevent false presses. As an added feature I put in a green 5mm LED with an appropriate current limiting resistor in the tip that glows when pressing the trigger button. The device is a motion sensitive mouse using the accelerometer feature of the GY-521/MPU-6050 module. The Trigger button is the Left Mouse Button, and the other button is the Right Mouse button. So it isn’t like the old Light Gun where the game knows where the gun is pointing. It is based on relative motion, and so Duck Hunt has an onscreen cursor. It isn’t the same, but it still is fun. Those Ducks are tricky even with a cursor. To keep the gun relatively lined up, between rounds I point the gun to the upper right corner of the screen making sure the cursor has moved the whole way up there.
For the base code and initial wiring I used info from a post and YouTube video by “Asiq’s Theory”. I think it was under 50 lines of code. It was about the bare minimum to get one of these MPU-6050s working and that was very helpful to me to break down what changes I needed to make.
The exact code for one gun doesn’t work for the second one as each MPU-6050s seems to be slightly different. That is as far as “standing still” status. If that is wrong, then the cursor will constantly drift in either up or down or left or right. So a couple numbers are changed to kill the drift. I think for the one I had to do a +3 to the X (which is left/right) and the other a +4.
Asiq’s code had been based on a different orientation of the MPU module, so I adjusted for that and the code also included pauses and would freeze the cursor when pressing the button. It made it pretty good for an air mouse on a computer, but bad for tracking Ducks on the screen. This means these are not very good mice,



The Air Mouse can be pulled into about any pc and used, but as it is constantly tracking the action of pressing the buttons tends to move the cursor even if you are quite careful. You could make a more proper Air Mouse with the same components and a bit different code on the Arduino board. I have thought of making it so that if one of the buttons was held down as it was plugged in that it would make it behave differently. Still this is was to be a relatively quick project that I don’t expect to use all that heavily.
Parts:
This project uses an Arduino Pro Micro and a GY-521 Accelerometer Gyroscope Module. There are two buttons with 10k pull down resistors on them. There is also a single LED with a 1k Resistor to limit the current on it. Those bits of information are on the Code below as to what they are and what pins they goto. The resistor values aren’t critical. You could use 20k for the pull downs and possibly higher, you could use something a bit lower than 10k for the pull downs but the lower you go the more current that is drawn when pressing the button. For the LED resistor you can go as low as 330 Ohm for the LED and be safe with most any standard LED, I also commonly use 1.5k and sometimes higher for LED current limiting resistors.
Arduino Pro Micro clone: https://www.amazon.com/HiLetgo-Atmega32U4-Bootloadered-Development-Microcontroller/dp/B01MTU9GOB/
GY-521 Module: https://www.amazon.com/HiLetgo-MPU-6050-Accelerometer-Gyroscope-Converter/dp/B00LP25V1A/
12x12x7.3mm Tactile Push Button: https://www.amazon.com/TWTADE-Momentary-Tactile-Button-12x12x12mm/dp/B07CG7VTGD/
Resistors, 1k an 10k: https://www.amazon.com/EDGELEC-Resistor-Tolerance-Multiple-Resistance/dp/B07QJB31M7/
LED, hopefully you have a couple, or you can pick them up on Amazon etc too.
All links are current as of posting this in 2/4/23 none are affiliate links or anything.
Below is the code for the Arduino Pro Micro Line 78 and 79 are where the drift adjustment numbers are Currently the “+7” on Line 78 and the “-1” on 79. I am referencing “Test code” that Ashiq mentioned, again I can’t find that source to get it. I guess Ashiq’s video was deleted or channel on Youtube or something, if someone finds it let me know so I can reference it properly. That code would return numbers from what are the gx and gz values. Using those values as a starting point, I guess I had something around a constant 400 and 100 respectively out of the 6050. The point of Line 78 and 79 are to get us a result of 0 if the Air Mouse is not in motion. Changing the divide by 150 will change the speed at which the mouse will track and possibly throw off your adjustment values at the end by a number or two. You may notice the Serial.print at the end there, that returns the vx and vy values that are created on line 78 and 79. So if you open the Arduino (or other) serial monitor set to the 9600 and to the serial port the Pro Micro is showing up as and look at those numbers you can use that as your Test code to get an idea of what changes you need to make be it to the 400 or 100 or the minor fine tuning numbers at the end of those lines. The goal is to get the values to 0 when the gun is not being moved. If you make the “dead zone” area to large you can’t make fine movements, which makes aiming even more difficult. These motions are relative, and not to “scale”, so at the start of a round you point at the screen, but to get the cursor to move the way you want you likely end up pointing in a very different place by the end. You can quickly move to aim at the center again and over correct to get the cursor more inline with where you are pointing. It is hard to describe. I enjoy it for a few rounds of Duck Hunt, for longer round games it may not be as much fun. I don’t know for sure as I haven’t played other games with it to this point.
Air Mouse Code for the Pro Micro is below:
#include <Wire.h>
#include <I2Cdev.h>
#include <MPU6050.h>
#include <Mouse.h>
/*
Name: Prototype Arduino Pro Micro AirMouse
Author: Markeno
Created: 3/7/2020
Version .99
Based on a sketch posted by "Asiq's Theory".
Feature: USB 2 Button AirMouse for RetroPie Gun
+-----+
+------------| USB |-------------+
| +-----+ |
D1 | [ ]1/TX/INT3 RAW[X] |
D0 | [ ]0/RX/INT2 GND[X] |
| [ ]GND RST[ ] |
| [ ]GND VCC[X] |
SDA | [X]2/INT1 ___ 21[ ] | A3
SCL |~[X]3/INT0 / \ 20[ ] | A2
A6 | [X]4 / PRO \ 19[ ] | A1
|~[X]5 \Micro/ 18[ ] | A0
A7 |~[x]6 \___/ SCLK/15[ ] |
| [ ]7/INT6 MISO/14[ ] |
A8 | [ ]8 MOSI/16[ ]~|
A9 |~[ ]9 SS/10[ ]~| A10
| |
+--------------------------------+
Based on: http://busyducks.com/ascii-art-arduinos
Gy521 2=SDA 3=SCL, RAW to GY521 VCC in (didn't want to work from the PRO Micro VCC pin), GND to GND
4 Left Mouse Button, 10k Pulldown to Ground, press to VCC
5 Right Mouse Button, 10k Pulldown to Ground, press to VCC
6 Additional Wire, for an LED in the tip when the main trigger was pressed.
*/
MPU6050 mpu;
int16_t ax, ay, az, gx, gy, gz;//Variable for the Accel and Gyro Data
int vx, vy;// X and Y axis variables
const int button1 = 4; //For Left Mouse Button
const int button2 = 5; //For Right Mouse Button
const int fled1 = 6; // For Tip LED
int responseDelay = 10;
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time Button was updated
// constants won't change:
const long interval = 100; // interval at which to change button states (milliseconds)
// Variables will change:
int btn1State = LOW; // ledState used to set the LED
int btn2State = LOW; // ledState used to set the LED
void setup() {
pinMode(button1, INPUT);
pinMode(button2, INPUT);
pinMode(fled1, OUTPUT);
Serial.begin(9600);
Wire.begin();
Mouse.begin();
mpu.initialize();
if (!mpu.testConnection()) {
while (1);
}
}
void loop() {
//Get the Acel and Gyro Data as 6 values
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
vx = ((gx - 400) / 150)+7 ; // (Grey Gun +3) (Blue Gun +4)"-400" because the X-Axis Left/Right of gyroscope give values about -400 while it's not moving. Change this value if you get something different using the TEST code, checking if there are values far from zero. "+3" added due to value around -3..
vy = (-(gz - 100) / 150)-1 ; // (Grey Gun 0) (Blue Gun -1)"- 100" same here for the Y-Axis Up/Down. may need to "-1" "+1" etc due to some drift still.
vx = vx / 2;
vy = vy /2;
//Create a small -1,0,1 DeadZone for the Y-Axis
if (vy>=-1 && vy <= 1)
{
vy=0;
}
//Create a small -1,0,1 DeadZone for the X-Axis
if (vx>=-1 && vx <= 1)
{
vx=0;
}
int buttonState1 = digitalRead(button1);
int buttonState2 = digitalRead(button2);
// check to see if it's time to blink the LED; that is, if the difference
// between the current time and last time you blinked the LED is bigger than
// the interval at which you want to blink the LED.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
if (buttonState1 != btn1State) {
if (buttonState1 == HIGH) {
Mouse.press(MOUSE_LEFT); //5
digitalWrite(fled1,1);// Light LED
}
else
{
Mouse.release(MOUSE_LEFT);
digitalWrite(fled1,0);// Light LED Off
}
btn1State = buttonState1;
}
if (buttonState2 != btn2State) {
if (buttonState2 == HIGH) {
Mouse.press(MOUSE_RIGHT);
}
else
{
Mouse.release(MOUSE_RIGHT);
}
btn2State = buttonState2;
}
/*
if (buttonState2 == HIGH) {
btn2State = HIGH;
Mouse.press(MOUSE_RIGHT);
delay(100);
btn2State = LOW;
Mouse.release(MOUSE_RIGHT);
}
*/
}
Serial.print(-vx);
Serial.print(-vy);
Serial.print("\n");
if (millis() - previousMillis >= 20) {
Mouse.move(-vx, -vy);
}
}
Note: the “X” in the Pro Micro Ascii diagram denotes something is wired to that pin, below the diagram is described what is going to those various pins.
There are far better options out there. You could use a regular mouse, but that certainly wasn’t remotely the same. You could buy an Air Mouse, the feel is not the same either. There are units you can get similar to the Wii motion bar that will work with Retropi and use a Wiimote in a gun holder. Those should track much better because it is real tracking and not relative acceleration. With the Air Mouse solution it just knows it went right or left or up or down, you don’t even have to be facing the right direction. Still it is not a very expensive solution.
Addendum: MPU-6050 Test code. This is some Test code I had on file for the MPU-6050 this is NOT the Air Mouse code, that code is the Section Above here. I believe this is the code I used to get my baseline “stationary” values to put in the Air Mouse Code The 400 and 100 in the Air Mouse Code above. To use the code, again open the Arduino or other Serial Monitor set to 9600 and the Serial Port that the Pro Micro is connected to. I could be wrong and that may not be what this is. It has been sitting there a long time.
// MPU-6050 Test Code. This is NOT the Air Mouse Code!
// (c) Michael Schoeffler 2017, http://www.mschoeffler.de
#include "Wire.h" // This library allows you to communicate with I2C devices.
const int MPU_ADDR = 0x68; // I2C address of the MPU-6050. If AD0 pin is set to HIGH, the I2C address will be 0x69.
int16_t accelerometer_x, accelerometer_y, accelerometer_z; // variables for accelerometer raw data
int16_t gyro_x, gyro_y, gyro_z; // variables for gyro raw data
int16_t temperature; // variables for temperature data
char tmp_str[7]; // temporary variable used in convert function
char* convert_int16_to_str(int16_t i) { // converts int16 to string. Moreover, resulting strings will have the same length in the debug monitor.
sprintf(tmp_str, "%6d", i);
return tmp_str;
}
void setup() {
Serial.begin(9600);
Wire.begin();
Wire.beginTransmission(MPU_ADDR); // Begins a transmission to the I2C slave (GY-521 board)
Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // set to zero (wakes up the MPU-6050)
Wire.endTransmission(true);
}
void loop() {
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40]
Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active.
Wire.requestFrom(MPU_ADDR, 7*2, true); // request a total of 7*2=14 registers
// "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable
accelerometer_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L)
accelerometer_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L)
accelerometer_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L)
temperature = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L)
gyro_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L)
gyro_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L)
gyro_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L)
// print out data
// Serial.print("aX = "); Serial.print(convert_int16_to_str(accelerometer_x));
// Serial.print(" | aY = "); Serial.print(convert_int16_to_str(accelerometer_y));
// Serial.print(" | aZ = "); Serial.print(convert_int16_to_str(accelerometer_z));
// the following equation was taken from the documentation [MPU-6000/MPU-6050 Register Map and Description, p.30]
// Serial.print(" | tmp = "); Serial.print(temperature/340.00+36.53);
Serial.print(" | gX = "); Serial.print(convert_int16_to_str(gyro_x));
Serial.print(" | gY = "); Serial.print(convert_int16_to_str(gyro_y));
Serial.print(" | gZ = "); Serial.print(convert_int16_to_str(gyro_z));
Serial.println();
// delay
delay(1000);
}





















































































































































