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() );
}

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

Leave a Reply

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