Easier, Better, Arduino IMU Head Tracker

mainI’ve recently been immersed in a space sim called Elite:Dangerous. (It’s in Gamma and will be out shortly 12/16/2014.) I play with a small casual group that’s not about to build a ship cockpit in our living rooms or all splurge for a dev kit VR Oculus Rift. Some of us have played Elite on the Oculus and the first thing you miss is “head look”. The game is designed for it, and once you use it dog-fighting in an asteroid field, watching your enemy turn sharp high above you while you cut power and rotate at the same time whilst avoiding giant floating rocks, you don’t want to give it up. This is one of those games (much like a flight simulator) that takes 30 minutes just to map your controller(s).

My brother linked me to a UK group that was doing head tracking with an Arduino (SparkFun Pro Micro) and a Gyroscope / Accelerometer (MPU-6050) over at edtracker.org.uk, “Can you build this?” he asked.

“I can build a better one.” and you can too.

  • No drift – Use hardware that incorporates a magnetometer (compass)
    (The new edtracker 9150 version uses a magnetometer to remove drift)
  • No calibration GUI – Place flat on table when powering on
  • No PCB – Four connections. SCL, SDA, +5, GND

Here’s what makes up the easier better IMU head tracker:

The first thing I noticed was the drift problem. The EDTracker guys have since put out a second version with magnetometer compensation, but they didn’t have it from the start and the hardware difference is around $6. I picked an IMU hardware package that has a great tested library for it. The calibration and angle calculation built into the Pololu libraries – specifically the code from Michael Baker Pololu_Open_IMU (Inertial Measurement Unit) as it uses the Madgwick algorithm is particularly brilliant. It outputs pitch, yaw, and roll angles. Watch a video of the Madgwick algorithm in action.

joy_cplI knew from past experience that the Teensy 2.0 can emulate many types of USB devices right out of the bag. “Joystick.X(value);” was simple to integrate and the device needs no emulation software or additional CPU to work. It just shows up in joy.cpl as a Joystick. You wouldn’t think that an Arduino could handle complex Euler angles and lots of float math on its own, but it has no problem. With the Teensy Joystick, the X,Y, and Z can be directly mapped 0 – 1023.

Lastly there are PCB boards being built for this, and  I’m really not sure why. These are not PCB’s with components on them like an ATMEGA32U4’s and the 9 Degree of Freedom hardware already built in. These are PCB’s just to connect a 9-DOF board with an Arduino. This is all of 4 connections, and the two pins on the Teensy 2.0 are D0 (SCL) & D1 (SDA) and they line up fine on both the Adafruit and Pololu breakouts for an above mount setup.

Yes, I cover the Teensy reset button. The PJRS software has a great auto-loader and you just don’t need it. You also don’t need a fancy PCB – two pins will do.

With the hardware finalized, I mounted it in a small box with a dab of hot glue and a cut-out for  the mini-usb. Here’s the test:

For the head tracking code I used Fscale to scale map the angles to the joystick. This is the part where you amplify a tiny amount of head movement into a larger amount of in-game head-look movement.  I settled on a 50° angle for pitch and roll, and an 80° angle for left and right (yaw). You’re more than welcome to try a different scale by changing the low and high Fscale numbers (-25 & 25 = 50° of movement translates into -90° & 90° of game movement) Everything else is directly from Michael Baker mikeshub/Pololu_Open_IMU. Such a tiny amount of code here is from me that I don’t even want credit for it. 😛 I’m just a lowly hardware guy that can smash out some C#. Example:

if (pitch < 0){pitch = fscale(-25, 0, 0, 512, pitch,0);} else {pitch = fscale(0, 25, 512, 1023, pitch,0);}

Here’s a list of what you will need for software:

The device calibrates when it it powered on, make sure it’s flat and motionless. After that it takes an initial heading reading and starts to blink. 20 more seconds of being on, and that heading is locked in. I commented out all of the serial outputs and zip tied it to my headset:

Results: After playing for 3 hours I had zero drift. It stays pointed at the direction of your monitor forever. I considered adding a curve to the scaling to provide a “dead zone”, but the game has a dead zone setting built-in and I’d rather just output 100% and let the game / user control the settings. I also mapped the roll axis and one could use it to fire the roll thrusters in the game. For ~$45 you now have a cool little IMU that you can experiment with!


Update: If you would like to configure it to work with Opentrack (supports TrackIR games), you will need to map the axis 1:1 with the joystick output and then set all your curves and config up in Opentrack.

fscale(-90, 0, 0, 512, your_axis,0)
fscale(0, 90, 512, 1023, your_axis,0)

Update 1/16/2016: Uploaded code and working with Grégory Paul over on hackaday.io

36 thoughts on “Easier, Better, Arduino IMU Head Tracker

  1. How to build this/get the parts? Do you have an instructional, or is there one out there? Is it basically EDTrackers build but with the different partS?

  2. Kris ignore my previous post of stupidity. I am not that “electronic” savy so I found the parts by (take a guess how?) reading your post again! LOL I do want to ask. Do you have instructions on flashing this thing if it needs it? I know the ED tracker has to be flashed. So wondering if this does as well. I will read your post again LOL and see if you mention it of course after I post this message. 🙂

  3. Kris I am going to attempt to build this. I have ordered the Pololu (I noticed you used the IMU breakout in the pictures), and the Teensy. I see where you soldered the 2 pins in the picture, but can you provide me a little more detail on the wiring I see in the very top picture of this page? I am new to doing all of this stuff so I just want to make sure I do this right. also do you upload all the different instructions before assembling the two boards?


  4. I’ve put this together best I can. I’ve got everything needed. The problem is when I cycle the power after loading the sketch it halts at “IMUinit()”. Can’t seem to get past this point. Any direction would be appreciated!

    1. Got it working! Turns out you have to actually power the sensors. HA. =)

      I’m getting a weird behavior though. After moving around for a bit (can be seconds) the yaw will continuously pulse to one direction.

      I installed the Pololu_Open_IMU sketch and watched it do it there as well. Could my board be bad? I’m not sure how to start debugging this.

  5. Hello! Thanks for the brilliant instructions! You were right about the zero drift!

    I went ahead and built one and have been using it for a few weeks now. I built mine using a bluetooth module and an arduino pro micro clone from china (about 2 dollars). Whole thing powered by a gigantic 18650 3.7v lithium battery. The power consumption is incredibly low, I don’t seem to be able to drain the battery 😀

    To get the data to Elite I had to make a few modifications to the Pololu Open IMU code to send the raw yaw, pitch, roll in Hatire (Head Arduino Tracker) format. The hatire plugin is currently only supported by FreeTrackNoIR (as far as I know).

    So the orientation data is flowing like this now:
    Device -> Bluetooth Serial -> FreeTrackNoIR -> Freetrack2 -> Elite Dangerous

    FreeTrackNoIR allows shaping the data with filters and curves, and using the old Accela filter the motion is much smoother than using just raw data.

    However FreeTrackNoIR is getting a bit dated and there is a much newer Opentrack project going on which supports a newer Accela Filter with a really fast response and smooth tracking. To get this to work my data flow needed to be set up as follows:

    Device -> Bluetooth Serial -> FreeTrackNoIR (Reset all curves and NO filters configured, just a pipe) -> VJoy virtual Joystick -> Opentrack (source set to VJoy) -> FreeTrack2 output -> Elite Dangerous

    That’s a lot of steps to get things to work. I’m now working on just getting the serial data directly to opentrack since opentrack is IMHO much much better. I’ll post my results here when I finish with coding something to reduce the tedium of settting all of this up.

    The port of my Arduino Open IMU code is currently at:

  6. I’ve been working on trying to replicate this. I’m pretty new to all of this but I am learning. I bought the Pololu IMU. Using the L3G and LSM303 libraries you mention above the code will compile but it gets locked up at the lime IMUinit(). If i use the new libraries, things like the calibrate codes work and the gyro, compass, and accel all write to the script monitor but i can’t compile your code. Here are my errors.

    HeadTracking.ino: In function ‘void loop()’:
    HeadTracking:105: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:106: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:107: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking.ino: In function ‘void IMUinit()’:
    HeadTracking:179: error: ‘LSM303_CTRL_REG1_A’ was not declared in this scope
    HeadTracking:180: error: ‘LSM303_CTRL_REG4_A’ was not declared in this scope
    HeadTracking:182: error: ‘LSM303_CRA_REG_M’ was not declared in this scope
    HeadTracking:183: error: ‘LSM303_CRB_REG_M’ was not declared in this scope
    HeadTracking:184: error: ‘LSM303_MR_REG_M’ was not declared in this scope
    HeadTracking:193: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:194: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:195: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:204: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:205: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’
    HeadTracking:206: error: cannot convert ‘int*’ to ‘float*’ for argument ‘1’ to ‘void Smoothing(float*, float*)’

    Any help would be great!


  7. Heyas, Kris.

    Found this via your Reddit post. I am copying your design here exactly to the part, the last of which arrived today.

    My only trouble: I have never soldered or handled one of these wonderful bits of micro electronics before. In fact, I’ve never soldered anything at all. That’s not going to stop me, though: I’ve got everything I need to build it but the experience and the confidence.

    I know it is probably long since sealed up in that enclosure, but if it isn’t, and it isn’t too much trouble, can you take a few more photos from different angles so I can be confident I am putting/soldering all the right bits into all the right areas?

    Thanks again!

    1. Tim,

      /Other than the Teensy which board did you purchase? The Pololu or the Adafruit? I tried both, had better luck with the Adafruit. That might have been because of my terrible soldering skills and I thinkI burned out the pololu. Anyway I am going to build myself a second one if you just need to know which part of board A needs to be soldered to board B I might can help.

  8. Hey guys!

    For those of you who have built this: How did it turn out? I’m having all sorts of trouble, lol. For instance I’m not sure if the Pololu IMU is functioning at all. The sketch that Kris supplied doesn’t do much. The led will come on steady, and that’s it. Also, even when I uncomment the serial lines, I’m not getting anything in the serial monitor. The only thing I CAN get working is the example in the L3G library. I get all sorts of serial data there. Any help would be greatly appreciated. Thanks!

      1. Thanks for the reply! I ran the calibration sketch, but it isn’t giving me much. “-1” for all mins and “0” for all maxes. It would be helpful to know if this is what is supposed to happen, or if the values should be changing as I move the device… still pretty noobish. I’m guessing that I should get some values and plug them in to the Pololu sketch, but I’m just not getting much. Any more help would be greatly appreciated as I try to navigate this little demon, lol. Thanks!

        1. I think I may have started to zero in on my problems… has anyone else been using the Pololu Minimu-9 v3? I’m starting to think that this newer version of the board is causing the problems. It doesn’t seem to respond to the older libraries for LSM303 and L3G, but it responds fine to the newer ones which the Pololu-open_IMU nor Kris’ code works with.

          Just to double check everything I have tho:

          Teensy 2.0
          Pololu Minimu-9 v3
          GND -> GND
          VCC -> VIN
          D0 -> SCL
          D1 -> SDA

          Arduino software v1.0.6
          Teensy Loader
          L3G v1.2.2 in library folder
          LSM303 v1.4.4 in library folder

          If you guys can’t see anything wrong with this setup, I’ll just order the Adafruit board and hope. Thanks for looking, and thanks for taking your time. =]

          1. Hello! I got the Pololu Minimu-9 v3 as well, and I have exactly the same problem as you. The new LSM303 library works with it but not the older one. Something is different with addressing the registers I guess. With my very basic programming skills it would take ages until I could figure out a solution for this (if ever), so for now I’m back to infra leds + camera…

  9. Hey guys, I’m in the same boat with the Pololu Minimu-9 v3! Anyone worked out how to get it working with the Pololu_Open_IMU sketches? Nothing but -1s and 0s from the calibration sketch so far…

    1. Unfortunately, it’s going to take an almost total rewrite for the newer version to work. I’ve spent too much money on this so far, so I’m just going to wait for a wireless version of EDTracker until I throw more at it. Back to strapping a phone to my head for now.

  10. Hi There
    I’m trying to use something like this for controlling my “look” in fps games. I have a teensy with buttons and analog joystick connected to it and running standard joystick script from teensy site. Do you think it is possible to add LSM303 onto my setup and use it as mouse in FPS game? Obviously it will only have to work on X and Y axis. I’m guessing it’s as simple as connecting it to second analog input but how would I go about it as far as script goes?
    Is it something that can be done?


  11. I’m following the tutorial to build one device (Teensy 2.0 with Adafruit IMU Breakout).

    As people above, I’ve also got compiling errors like :

    headtracking:119: error: ‘fscale’ was not declared in this scope

    which I fixed by putting fscale declaration on a single line.

    Then, I’ve got errors like :
    headtracking:173: error: ‘L3G_CTRL_REG1’ was not declared in this scope
    gyro.writeReg(L3G_CTRL_REG1, 0xCF);

    That I fixed by replacing L3G_CTRL_REG1-5 by direct values as defined in https://github.com/pololu/l3g-arduino/blob/b561c4fb6861dad42f878ec68adaff014bc43dbd/L3G/L3G.h#L23
    So that code
    gyro.writeReg(L3G_CTRL_REG1, 0xCF);
    gyro.writeReg(L3G_CTRL_REG2, 0x00);
    gyro.writeReg(L3G_CTRL_REG3, 0x00);
    gyro.writeReg(L3G_CTRL_REG4, 0x20);
    gyro.writeReg(L3G_CTRL_REG5, 0x02);
    become this :
    gyro.writeReg(0x20, 0xCF);
    gyro.writeReg(0x21, 0x00);
    gyro.writeReg(0x22, 0x00);
    gyro.writeReg(0x23, 0x20);
    gyro.writeReg(0x24, 0x02);

    Output then only generates some warnings but is uploaded to Teensy.

    I not able to test for now (my gaming PC is at home, I’m on winter holidays) but I’ll try to find a way to test the device under linux in following days (any idea ?).

    Hope it helps some people 🙂

    Thanks again for sharing that head tracking hack.

    If you don’t mind, I’m thinking to upload the fixed code on github with credits to you and link to that page.

    1. Hey yeah ive been messing with this code trying to fix that fscale compiling error! Im a EE student and we dont have much work with C/C++, we learn it but barly touch it! Thanks for the code you rock! Im using projects like these to better my C/C++ skills

  12. I published more detailed instructions on hackaday : https://hackaday.io/project/8952-elite-dangerous-headtracker with credits to you and that post. I do not want to steal any credits from you but I thought it can help some people to have more detailed instructions.

    Also, I have some problem with the code : when looking left or right, “head” is slowly returning to center, even when keeping the head on left or right.
    Playing with betaDef variable tempers that but I wasn’t able to fix the problem.

    I started another code “from scratch” : https://github.com/paulgreg/headtracker but also had some problem with that (when looking up or down, head is jumping a little bit on left or right…).

  13. Does anyone have experience or instructions on adding a Bluetooth and lipo battery to this to make a wireless unit?

    1. My thoughts exactly! I think it should be as simple as adding the bluetooth serial board and setting up your software to listen on a com port.

  14. You’re only using 10 bit joystick axes, is that really enough to map the entire range of the IMU 1:1? The IMU AHRS pitch roll and yaw values are 16 bits at least. But if you map that to a 10 bit joystick axis (0-1024), you’ll be jumping around every 0.3 degrees – isn’t that kinda choppy?

    You can make the joystick axes as many bits as you want, up to USB limitations, if you know how to work with HID code. I know of a guy that has written a 16 bit joystick axis library for Arduino.

  15. Does anybody had any luck with the Teensy2.0 and the Pololu IMU-9 v3. I need much help.
    How to setup so that opentrack can recognize as the USB joystick or Serial. Please send me your code or modified one that worked to kwings714@gmail.com. Please more instructions are needed.

  16. Hi Guys,
    I can’t seem to get the Opentrack to recognize my Teensy2.0 and Pololu.
    I managed to the PC as a Serial Joystick, Mouse, GamePad. Couldn’t get the calibration going even thought in the control panel see is a USB game controller. Help

  17. Ok folks,

    I am managed to get the Teensy2.0 and Pololu MinIMU-9v3 worked with both OpenTrack and FaceTracknoIR using the Pololu code AHRS with minor modification. However, My IMU unit is unstable, due the wiring method I used. Connecting the IMU to the Micro-Controller via 7′ long cable is a bad for I2C communication. For the sketch modification, see the link.

    P.S I still couldn’t get the joystick X,Y & Z axis to recognize the IMU data.
    I am in the process to shorten my connection by soldering the pins to the IMU and mount together on the breadboard. I2C communication length optimization is 2cm long
    (around 5/8″-3/7″)

Leave a Reply