A simple compass, using the HMC5883L chip, on a breadboard with an Arduino Nano and an OLED screen. Compass connection is over I2C, the screen uses SPI. I decided not to use a library for the HMC5883L but keep it simple as described here. That example code gives the X,Y,Z values, of which we need X and Y to calculate the heading (angle with the X axis, in the X,Y plane). See my sketch below.

IMG_20160322_110142937

I added the magnetic declination, which depends on your location. For Hong Kong, it is -2 degrees 44 minutes. I also added a buzzer sound (not connected on above picture) to indicate when the compass is pointed to North.

On the screen i show the heading in degrees, and also the general N-E-S-W direction. The arrow points North, the line crossing the circle is the X axis of the compass. The ‘char array’ and ‘String’ handling for printing the heading and direction at 90 degrees rotation gave me some headache but the solution below works fine.

Of course the compass is very sensitive to all kind of magnets: when i put the breadboard close to my laptop the arrow went all over the place.

My sketch:

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
* compass with HMC5883L 3 axle magnetometer via I2C
* based on https://www.sparkfun.com/tutorials/301
*
* OLED to show North
*
* buzzer on D3: increasing frequency closer to North
* silent within 2 degrees from North
* Tom Tobback – March 2016
*/
#include <Wire.h> //I2C Arduino Library

#define HMC5883_address 0x1E //0011110b, I2C 7bit HMC5883_address of HMC5883
#include “U8glib.h”
U8GLIB_SSD1306_128X64 u8g(10, 9); // OLED display: HW SPI CS = 10, A0/DC = 9 (Hardware Pins are SCK/D0 = 13 and MOSI/D1 = 11) + RST to Arduino D8

const int oled_rst = 8;
int angle;

void setup() {

pinMode(oled_rst, OUTPUT); // reset OLED display
digitalWrite(oled_rst, LOW);
delay(100);
digitalWrite(oled_rst, HIGH);

drawIntro();
delay(2000);

pinMode(3, OUTPUT); // buzzer
//Initialize Serial and I2C communications
Serial.begin(9600);
Wire.begin();

//Put the HMC5883 IC into the correct operating mode
Wire.beginTransmission(HMC5883_address); //open communication with HMC5883
Wire.write(0x02); //select mode register
Wire.write(0x00); //continuous measurement mode
Wire.endTransmission();
}

///////////////////////////////////////////////////////////////////////////////////////////

void loop() {

int x, y, z; //triple axis data

//Tell the HMC5883L where to begin reading data
Wire.beginTransmission(HMC5883_address);
Wire.write(0x03); //select register 3, X MSB register
Wire.endTransmission();
//Read data from each axis, 2 registers per axis
Wire.requestFrom(HMC5883_address, 6);
if (6 <= Wire.available()) {
x = Wire.read() << 8; //X msb
x |= Wire.read(); //X lsb
z = Wire.read() << 8; //Z msb
z |= Wire.read(); //Z lsb
y = Wire.read() << 8; //Y msb
y |= Wire.read(); //Y lsb
}

angle = atan2(-(double) y, -(double) x) * (180 / 3.14159265) + 180;
angle -= (2 + 44 / 60); // magnetic declination in HK= -2 44′
if (angle < 0) angle += 360;
Serial.println(angle);
Serial.print(“\t”);

int angle_freq = angle;
if (angle > 180) angle_freq = 360 – angle;
int freq = map(angle_freq, 180, 0, 1000, 2000);
if (angle_freq > 2)
tone(3, freq);
else
noTone(3);
Serial.println(freq);

drawCompass();

delay(50);
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

void drawCompass() {
float angle_rad = 3.14159265 * (float) angle / 180.0;
char angle_array [] = ”    “; // better to start with an empty string to avoid garbage
dtostrf(angle, 3, 0, angle_array);
angle_array [3] = char(176);
String corner;
if (angle > 337 || angle < 23) corner = “N “;
else if (angle > 22 && angle < 68) corner = “NE”;
else if (angle > 67 && angle < 113) corner = “E “;
else if (angle > 112 && angle < 158) corner = “SE”;
else if (angle > 157 && angle < 203) corner = “S “;
else if (angle > 202 && angle < 248) corner = “SW”;
else if (angle > 247 && angle < 293) corner = “W “;
else if (angle > 292 && angle < 338) corner = “NW”;
char corner_array [3];
corner.toCharArray(corner_array, 3);
Serial.print(“***”);
Serial.print(angle_array);
Serial.print(“***”);

int x0 = 30 + cos(angle_rad) * 25; // tip of the arrow on circle
int y0 = 30 – sin(angle_rad) * 25;
int x1 = 30 + cos(angle_rad + 0.2) * 15; // triangle point
int y1 = 30 – sin(angle_rad + 0.2) * 15;
int x2 = 30 + cos(angle_rad – 0.2) * 15; // triangle point
int y2 = 30 – sin(angle_rad – 0.2) * 15;
u8g.firstPage(); // draw compass
do {
u8g.setFont(u8g_font_fub14);
u8g.drawStr90(100, 10, angle_array);
u8g.drawStr90(75, 15, corner_array);
u8g.drawCircle(30, 30, 25);
u8g.drawDisc(30, 30, 2);
u8g.drawLine(3, 30, 57, 30);
u8g.drawLine(30, 30, x0, y0);
u8g.drawTriangle(x0, y0, x1, y1, x2, y2);
} while ( u8g.nextPage() );
}

///////////////////////////////////////////////////////////////////////////////////////////

void drawIntro() {
u8g.firstPage(); // draw intro
do {
u8g.setFont(u8g_font_fub14);
u8g.setPrintPos(10, 25);
u8g.print(“Compass”);
u8g.setPrintPos(2, 60);
u8g.print(“BuffaloLabs”);
} while ( u8g.nextPage() );
}

///////////////////////////////////////////////////////////////////////////////////////////

3 thoughts on “Arduino compass (HMC5883L) with OLED screen

  • 10/02/2018 at 03:06
    Permalink

    I like your project!
    And I want to repeat it too!
    I have an arduino nano, a 128×64 oled display, and an HNC5983!
    I copied the source code but it has errors!
    Is the source code good?
    I’ve found some bugs …. I’ve corrected them but others do not know!

    E.g:
    for the line:

    int y2 = 30 – sin (angle_rad – 0.2) * 15

    I have the STRAY ‘\ 342’ error in the program

    Do not I understand where the error is?

    if i comment the line with //
    do errors continue with the same error?

    Can you help me?

    Do you have a correct source code to download it too?

    Thank you

    Reply
    • 12/02/2018 at 08:37
      Permalink

      Hi, wordpress is very bad at inserting code, so it’s possible that the copy/paste introduces some errors. please go through the code line by line and i think it will be easy to spot the errors. the stray 342 is well documented on the arduino forum. if you can’t see the error, just re-type the line so the unknown/invisible characters will not be there.

      Reply
      • 26/02/2018 at 16:21
        Permalink

        Hallo zusammen
        Ich habe mir den Sketch geladen und ein paar Fehler beseitigt.-
        Als erstes muss mann die ( ” ) beachten,diese werden beim kopieren nicht richtig dargestellt.
        Und dann muss die Zeile :
        U8GLIB_SSD1306_128X64 u8g(10, 9);
        je nach OLED geändert werden in :
        U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0);
        Dann sollte es gehen.
        Grüsse Andre1a

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *