How To Make a Compass using Arduino and Processing IDE

In this Arduino Project we will see how we can make this cool looking compass using an Arduino, a MEMS Magnetometer and the Processing IDE. Here’s a demonstration video of the compass:

Overview


All we need for this project is a MEMS Magnetometer, for measuring the earth magnetic field, an Arduino Board and some jumper wires. As an example I will use the HMC5883L, a 3 – Axis Magnetometer integrated in the GY–80 breakout board.

How the Compass Work


 Arduino Part

First we need to get the data from the sensor using the Arduino Board via the I2C protocol. Then using the X – Axis and Y – Axis values of the sensor we will calculate the heading and send its value to the Processing IDE via the Serial Port. The following code will do that job:

/*   Arduino Compass 
 *      
 *  by Dejan Nedelkovski, 
 *  www.HowToMechatronics.com
 *  
 */

#include <Wire.h> //I2C Arduino Library

#define Magnetometer_mX0 0x03  
#define Magnetometer_mX1 0x04  
#define Magnetometer_mZ0 0x05  
#define Magnetometer_mZ1 0x06  
#define Magnetometer_mY0 0x07  
#define Magnetometer_mY1 0x08  


int mX0, mX1, mX_out;
int mY0, mY1, mY_out;
int mZ0, mZ1, mZ_out;

float heading, headingDegrees, headingFiltered, declination;

float Xm,Ym,Zm;


#define Magnetometer 0x1E //I2C 7bit address of HMC5883

void setup(){
  //Initialize Serial and I2C communications
  Serial.begin(115200);
  Wire.begin();
  delay(100);
  
  Wire.beginTransmission(Magnetometer); 
  Wire.write(0x02); // Select mode register
  Wire.write(0x00); // Continuous measurement mode
  Wire.endTransmission();
}

void loop(){
 
  //---- X-Axis
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mX1);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mX0 = Wire.read();
  }
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mX0);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mX1 = Wire.read();
  }

  //---- Y-Axis
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mY1);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mY0 = Wire.read();
  }
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mY0);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mY1 = Wire.read();
  }
  
  //---- Z-Axis
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mZ1);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mZ0 = Wire.read();
  }
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mZ0);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mZ1 = Wire.read();
  }
  
  //---- X-Axis
  mX1=mX1<<8;
  mX_out =mX0+mX1; // Raw data
  // From the datasheet: 0.92 mG/digit
  Xm = mX_out*0.00092; // Gauss unit
  //* Earth magnetic field ranges from 0.25 to 0.65 Gauss, so these are the values that we need to get approximately.

  //---- Y-Axis
  mY1=mY1<<8;
  mY_out =mY0+mY1;
  Ym = mY_out*0.00092;

  //---- Z-Axis
  mZ1=mZ1<<8;
  mZ_out =mZ0+mZ1;
  Zm = mZ_out*0.00092;
  // ==============================
  //Calculating Heading
  heading = atan2(Ym, Xm);
 
  // Correcting the heading with the declination angle depending on your location
  // You can find your declination angle at: http://www.ngdc.noaa.gov/geomag-web/
  // At my location it's 4.2 degrees => 0.073 rad
  declination = 0.073; 
  heading += declination;
  
  // Correcting when signs are reveresed
  if(heading <0) heading += 2*PI;

  // Correcting due to the addition of the declination angle
  if(heading > 2*PI)heading -= 2*PI;

  headingDegrees = heading * 180/PI; // The heading in Degrees unit

  // Smoothing the output angle / Low pass filter 
  headingFiltered = headingFiltered*0.85 + headingDegrees*0.15;

  //Sending the heading value through the Serial Port to Processing IDE
  Serial.println(headingFiltered);

  
  delay(50);
}

If you need more details how the MEMS Magnetometer work and how to get the data from it, you can check my MEMS Sensors Tutorial.


Processing IDE Part

Here first we need to receive the heading values coming from the Serial Port. For more details how is this done you can check my Arduino and Processing Tutorial.

The compass is actually a image, or more precisely, it’s composed of multiple transparent images loaded into Processing IDE. The images have the be located in the working directory of the sketch. After defining the images objects in the draw() section using the image() function we load the background image (which is optional, you can use just a simple color for the background). Then the Compass image is loaded which using the rotateZ() function is rotated with the values of the heading. At top of them the Compass Arrow image is loaded.

Here’s the Processing IDE Code:

/*   Arduino Compass 
 *      
 *  by Dejan Nedelkovski, 
 *  www.HowToMechatronics.com
 *  
 */
 
import processing.serial.*;
import java.awt.event.KeyEvent;
import java.io.IOException;

Serial myPort;
PImage imgCompass;
PImage imgCompassArrow;
PImage background;

String data="";
float heading;

void setup() {
  size (1920, 1080, P3D);
  smooth();
  imgCompass = loadImage("Compass.png");
  imgCompassArrow = loadImage("CompassArrow.png");
  background = loadImage("Background.png");
  
  myPort = new Serial(this, "COM4", 115200); // starts the serial communication
  myPort.bufferUntil('\n');
}

void draw() {
  
  image(background,0, 0); // Loads the Background image
    
  pushMatrix();
  translate(width/2, height/2, 0); // Translates the coordinate system into the center of the screen, so that the rotation happen right in the center
  rotateZ(radians(-heading)); // Rotates the Compass around Z - Axis 
  image(imgCompass, -960, -540); // Loads the Compass image and as the coordinate system is relocated we need need to set the image at -960x, -540y (half the screen size)
  popMatrix(); // Brings coordinate system is back to the original position 0,0,0
  
  image(imgCompassArrow,0, 0); // Loads the CompassArrow image which is not affected by the rotateZ() function because of the popMatrix() function
  textSize(30);
  text("Heading: " + heading,40,40); // Prints the value of the heading on the screen

  delay(40);
  
}

// starts reading data from the Serial Port
 void serialEvent (Serial myPort) { 
  
   data = myPort.readStringUntil('\n');// reads the data from the Serial Port and puts it into the String variable "data".
  
  heading = float(data); // Convering the the String value into Float value
}

Here you can download the files from project, the images and the source codes:

Related Posts

14 Responses

  1. Paul

    This code will fail on Teensy3, Arduino Zero, Arduino Due, ChipKit, ESP8266 and all other newer 32 bit boards.

    The problem is incorrect conversion from two 8 bit unsigned numbers to a signed integer. The math actually computes a number from 0 to 65535, because the numbers from Wire.read() are 0 to 255. On 8 bit AVR, the incorrect computation magically becomes -32768 to +32767 when stored into a 16 bit “int”. But on 32 bit chips, the computation results in an unsigned 0 to 65536, because “int” is 32 bit.

    The “int” variables need to be changed to “int16_t” to make this work on any 32 bit board.

    Reply
  2. sylvain

    hi dejan, thanks a lot for all your great projects.
    i tried this one but i get a null pointer exception at line 29.
    do you know what is wrong?

    Reply
    • Dejan Nedelkovski

      What is line 29? Something the Processing IDE code might get stacked for some reason and you need to re-plug the Arduino to your USB port.

      Reply
      • sylvain

        hi dejan,
        sorry i was not clear. the processing sketch fails at line 29; there is this “null pointer exception”. i tried to replug the arduino but still it bugs.
        this is surprising because your codes for the mems sensors all work well on my system, and your radar project also works like a charm.

      • sylvain

        sorry i just looked at the details of the fail. the files compass.png, compassarrow.png and background arrow.png are missing. how can i get them?

      • sylvain

        Hi Dejan, i should have looked more carefully at your page, found the picture files and once included the system works as described.
        thanks a lot!!

  3. Daniel Fernandes

    Beautiful design but …
    How to use it on a TFT LCD screen 3.2″, for example, instead of the computer screen? Thanks

    Reply
  4. Daniel Fernandes

    Hi friend! Great design!
    I would like to do this on a TFT LCD screen 3.2 “is possible?
    Thank you

    Reply
  5. Steve

    Hi, Great website.
    Could you please explain more about converting the sensors output to angles and the orientation .
    I am looking for the angle and orientation of sensor at every point I move the sensors to known distance.
    I actually want the data to be stored or seen in a data logger like the angle and orientation .
    I am new to Arduino sorry if the question is so simple and dumb.
    Thanks

    Reply

Leave a Reply

Your email address will not be published.