DIY Headtracker (Easy build, No drift, OpenSource) - RC Groups


Time for a new DIY and OpenSource-project

A couple of fellow FPV-friends complained about their headtrackers - mainly drift problems and bad resolution. One showed a video with the problem, and my comment "that looks really bad, even I can make that a lot better", ended up costing me a bit of time.

I have choosen to use 3 axis- accelerometer, gyro and magnetometer, as that's the only way to account for drift in all directions.

Flight video from Gabek

Quick video from absolute-zero:

The hardware used is pretty simple, you just need an Arduino and a sensorboard.

Arduino Nano:
11.99$ shipped:
12.28$ shipped:
16.90$ shipped:

Sensor board (9-axis IMU):
18.30$ shipped:

33.49$ shipped:
29.51$ shipped:

Perhaps this one is the same?
19.67$ shipped:

That's about 30$ total

Code and GUI can be found at google code

And that's it.

The project is made as user-friendly as possible, and only little technical knowledge should be necessary. All necessary settings can be changed from a graphical user interface - making it easy for everyone to use.

All good ideas for improvement is more than welcome

Connection diagram/pinout:

Connection diagram for Pro mini
(made by typicalaimster)

The hardware used:

Assembled hardware - just missing connections to RC-transmitter

Early test-version. Plug-n-play in FF9 transmitter.

Graphical user interface

- GUI showing advanced settings too:

GUI for magnetometer calibration:

Tests with PPM expansion (this part is not done):
PPM in from FF9 (red), PPM out from tracker (blue):

8 channel PPM in, 12 channel PPM out (this part is only :

Constant frame-rate (red: FF9 PPM in, blue headtracker PPM out)

Headtracker sensor plots, raw, filtered etc:

A quick test-video can be found here. It's hard to show if a headtracker works well or not, so it's just a quick test-video.

Short description of the more technical stuff
The sensors are sampled with 128 samples/sec. As we have 9 sensors, we get 128 * 9 = 1152 measurements/samples every second (more than plenty for a headtracker). The sampling-rate is controlled by timer0, that will set a flag high - indicating that the sensors should be read and new angles etc. calculated (timer0 running in CTC-mode, using compare-register A). While it should be possible to make all the calculations etc. within the interrupt, it's not recommended. A lot of heavy/slow calculations are done, and in case of timing-problems, we just want it to skip a sample and continue (should never happen, but just in case).
If you turn on DEBUG you will get a lot of extra information, be able to see timing-problems etc. (please note, using serial.print is time comsuming and will in mast cases give timing-problems. Rarely a real problem when debugging, but be aware that it will change the samplerate a bit.)

The accelerometer data is converted to G-force. Not really necessary, but more convenient to work with. The total G-vector is calculated, and the angle calculated by using the total vector-length and the G-force in each direction.

The gyro indicates the angular change in degrees/sec. All axes accounts for tilt etc and data is continously integrated, to give an estimation of the angle.

The magnetometer is compensated for tilt (x- y-direction), and the final x and y values used to calculate the magnetic heading.

The filter is a basic complementary filter, and an optional lowpass filter. The weight for gyro/accelerometer/magnetometer can be sat in the GUI, and lowpass filter changed.

Please note that only PPM-out is activated as default. PPM-mixing etc. need some work.
The PPM in/out is a bit tricky, as we can only do one thing at a time. Oh - and we only have one 16 bit timer available. Timer1 (16 bit timer) is used to generate the PPM output. It's set to change pin at interrupt to get an accurate hardware-timed pin-change. The compare-interrupt will set the next compare-match.

PPM-in is detected by pin-change interrupt (higher priority than timer1 compare interrupt). It will use the time from timer1, and detect if the timer have been reset.
A few notes:

Use of Serial output
The headtracker uses a lot of slow calculations with floating points, cos, sin atan etc. Furthermore the sampling frequency is pretty high. Using Serial.print is great for getting info, data etc. - but it requires time. As it's running "on the edge", you can only print a few characters without loosing a sample. It's programmed to just skip a sample in case of timing problems, but just be sure to note this when testing.

Updating variables
All variables stored in EEPROM have to be changed in the GUI or by changing this:
// If the device have just been programmed, write initial config/values to EEPROM:
if ( != 42) {

But as all settings saved in EEPROM can be changed from GUI I'll suggest that, as it's a lot easier.
Using the GUI:

The GUI have been changed quite a bit since first versions, and is hopefully pretty intuitive.

PPM channels
Used to set the PPM-channel pan/tilt and roll is assigned t0

The center position of the servo

Servo travel end.

The gain determines how much the servo should move for a given head-movement. Default is 170 which pretty much gives 1:1 (90 degrees head-movement = 90 degrees servo movement).

All the "advanced settings"
- - - -
LP filter at tilt/roll [%]
Lowpass-filter of the final tilt/roll output.
1 = max lowpass/time constant. Will give very smooth, but also very slow change.
100 = lowpass off.

LP filter at pan [%]
Lowpass-filter of the final pan output.
1 = max lowpass/time constant. Will give very smooth, but also very slow change.
100 = lowpass off.

Gyro weight on tilt/roll [%]
How much do we thrust the gyro compared to the accelerometer on the tilt/roll axis? Accelerometer is noisy and should only be used to slowly compensate for drift.
100 = only use gyro.
0 = only use accelerometer

Gyro weight on pan [%]
How much do we thrust the gyro compared to the magnetometer on the pan-axis? The gyro is a lot more accurate, so magnetometer should only be used to slowly compensate drift.
100 = only use gyro
0 = only use magnetometer

Servo pulse variation
In short: Servo travel adjustment.

Used to set the max variation of the pulse-width in the PPM signal. The unit is microseconds*2 - matching the Atmega timer

Servo center
In short: Servo neutral position

Used to set the center/default length of the pulse-width in the PPM signal. The unit is microseconds*2 - matching the Atmega timer

Tilt and roll gain
Gain of the servo. You decide if you want to turn the head 10 degrees or 360 degrees to get full travel.

Pan gain
Gain of the servo. You decide if you want to turn the head 10 degrees or 360 degrees to get full travel.

Reverse (Tilt, roll and Pan)
used to inverse the servo direction.

General questions:

Can I use other Arduino versions?
Sure, all Arduino boards should have the necessary pins available (as far as I know). I have choosen Arduino Nano as it's cheap, small, have FTDI onboard and the regulator can handle a bit higher current, compared to Arduino Pro etc. But pick whatever you prefer/have available.

Can I use other sensors?
It should be pretty easy to use other sensors, as long as it's I2C. Update the sensor-init, sensor-reading and the sensor-configuration and you should more or less be good to go. I'll maybe add support for another IMU later.

Can I buy a headtracker from you?
No, it's meant as a DIY project. It's easy to make, and it should be pretty straight forward. I like to develope, but care little about sale etc.

Can I sell this?
Feel free. I can imagine a few people would like to buy a nice ready-made unit, but it would be great to know if you use my software/work. But in general it's uploaded with no strings attached.

What about copyright/license etc?
It's uploaded as OpenSource, and I expect it to be used and abused - don't really see much sense in "protecting" my work. So feel free to use it for whatever purpose.

Quick guide
Okay, quick little "start guide" for those not familiar with Arduino

You should have:
1 x Arduino Nano or similar
1 x IMU/sensor board

  1. First, lets assemble some hardware. It's pretty straight forward.

    The connections are:
    Sensorboard Arduino
    Vcc_in -> 5 volt
    GND -> Ground/GND
    SCL -> A5
    SDA -> A4

    Transmitter Arduino
    PPM_IN -> D9
    V_out -> V_in
    Ground -> Ground/GND

    And that's it.

    The placement of the board is pretty much up to you. But please remember that the magnetometer is pretty sensitive. Even small currents nearby will cause a magnetic field. If it's a constant field, chances are you can calibrate it, but please be aware of this problem.

    The board can be placed on top of Arduino, on buttom, on the side or just where-ever you want by using long wires.

    The pins match pretty good, so you can just connect it directly. The only pin that doesn't match is ground.

    Quick pictures of the easy configuration. Please note that I haven't tested if the positions close to the electronic is a problem.

    Placed underneath the Arduino board:

    On the side:

    On top:

    The only wire needed for the sensor-board is ground:
    (Here just shown as a quick test with nothing soldered but fully working)

    Choose your preferred location and solder the sensorboard.

  2. Download Arduino IDE from (should be version 1 or higher)
  3. Connect your Arduino board
  4. If the driver is installed automatic, just continue. If not, select the folder called "Driver" inside the Arduino IDE folder
  5. Download the latest headtracker-software from
  6. Open Arduino IDE (the program), Select file -> Open, find the headtracker software downloaded and open the file DIY_headtracker.ino
  7. You should now have a window looking pretty much like this:

  8. Go to tools -> Board and select "Arduino Nano w/Atmega328
  9. Go to tools -> Serial port -> Select the com-port used by Arduino (you should remember the port-number for later use in the GUI).
  10. Press the left-arrow in Arduino IDE to upload, or select file -> Upload, or press Ctrl + u.
  11. Hopefully it starts to upload the firmware. It should flash the LED's quickly for
    a while, and stop when the upload/programming is complete.

    Magnetometer calibration

  12. Now let's start with calibrating the magnetometer. Open the file Magnetometer_cal_v0_02 included in the folder.
    - Set the com-port to match the one found/used in Arduino IDE. Press connect. If the program crash, you have must likely selected a wrong com-port or the com-port is used by another program such as Arduino IDE.
    - Press "Start plot" and you should see live-data being plotted.
    - Everytime you do a calibration, you have to press "Set offset to 0".
    - Now place the headtracker as shown on the pictures, and press "set" when the headtracker is on the position as shown.
    - When all 3 positions have been set, you should have the offset
    - Press "Save" to upload the offset-settings to the headtracker.

    And that should be the magnetometer calibration, and hopefully you will never have to see it again

  13. Now find the file Headtracker_GUI_v0_04.exe in the file downloaded from google-code and open it.
  14. Set the com-port to match the one found/used in Arduino IDE. Press connect. If the program crash, you have must likely selected a wrong com-port or the com-port is used by another program such as Arduino IDE.
  15. Press the button "start plot". If everything works, you will see 3 live graphs with pan/tilt/roll.
  16. Set the necessary settings - remember to press "calibrate gyro".

Hopefully that's it, and you are ready to go.

Some known limitations:

  • The sensorboard must be orientated with the components up.
  • Nothing in the GUI is "idiot proof". It will accept more or less everything and just do it.
  • (If you look at the code) The sensorboard's magnetometer have 2 axis swapped. This is a hardware problem. Not anything to worry about, but the magnetometer z/y axis is swapped.
    Support for PPM-in etc. is not done. Only PPM-output is supported atm.

Few pictures from other peoples build:






Donations kindly received from:
- Guy1a
- Gabek
- typicalaimster
- ChilternFlyer
- Quadzimodo