North Star

A small, smart compass that could be embedded in some jewellery or a toy. I personally like the idea of embedding this in the head of a wizard's staff.

The build

This project combines the HMC5883L 3 axis magnetometer with an LPC810 to drive a blue LED when the project boards points north. It was inspired by a comment on a show I heard over at The circuit layout is as follows:

As you can see, the LPC810 communicates with the magnetometer over I2C. Just like in previous projects, the LPC810 is programmed using In-System-Programming (ISP) mode using the very useful lpc21isp program. The assembled circuit is quite small and fits on a micro breadboard as shown below:

The code

Full source code is available here. The main function is listed below. It begins by configuring the I/O pins for I2C sets up GPIO0 BIT1 as an output. If the symbol DEBUG is defined it also initializes the UART allowing debug data to be relayed back to the development PC. The I2C interface is configured next and it runs at 333kHz (approx). The system timer is then configured to generate an interrupt every 100ms next. This is the period between reads of the magnetometer. For the rest of the time the LPC810 is asleep to save power. The magnetometer is configured with a 7.5Hz sampling rate and an initial single-shot sample is triggered. The main while loop again contains some debugging output (if DEBUG is defined). Apart from that, the main loop simply issues a WFI (wait for interrupt command) repeatedly putting the CPU to sleep between timer interrupts.
void main()
#ifdef DEBUG
	I2CWrite(0x1e,0,0x0c);		// set data rate
	I2CWrite(0x1e,0x2,0x1); 	// Trigger a measurement
#ifdef DEBUG	
The Code that reads the magnetometer is shown next. It checks the magnetometer status register to see if a valid reading is available. If there is one, then the X,Y,Z values are read and the results exteneded to 32 bit integers (the original readings are 16 bits in size). An angle must now be computed from the X and Y readings. The result of Y/X is the tan of the angle between the board and magnetic north. X is premultiplied by 100 to improve resolution of this (integer) calculation. Using a lookup table the LUTAtan function converts this value to an angle in the rang 0 to 89 degrees. This is returned to the caller.
const int TanTable[]={ 0,1,3,5,6,8,10,12,14,15,17,19,21,23,24,26,28,30,32,34,36,38,40,42,44,46,48,50,53,55,57,60, \
	62,64,67,70,72,75,78,80,83,86,90,93,96,99,103,107,111,115,119,123,127,132,137,142,148,153,160,166,173,180,188, \
	196,205,214,224,235,247,260,274,290,307,327,348,373,401,433,470,514,567,631,711,814,951,1143,1430,1908,2863 };
// Approximate integer arctan
int LUTAtan(int TanBy100)
	int Angle = 0;
	if (TanBy100 < 0) 
		TanBy100 = -TanBy100;
	for (Angle = 0; Angle < 90;Angle++)
		if (TanTable[Angle] >= TanBy100)
	return Angle;

int GetHeading()
	int X,Y,Z;
	int TanTheta;
	int RValue=-1;
	int data;
	short Converter;
	static int Angle = 90;
	// read status register
	RValue = I2CRead(0x1e,02,&data);		
	if (RValue < 0)
	if (data & 1) // if there is new data
		// The next few lines do sign extension from short to int
		Converter = X;
		X = Converter;
		Converter = Y;
		Converter = Z;
		TanTheta = 100*abs(Y)/abs(X);
		Angle = LUTAtan(TanTheta);			
	// Trigger next reading
	RValue = I2CWrite(0x1e,0x2,0x1);	
	return Angle;
A decision must now be made as to whether the LED should be turned on or not. This is done in the System Timer (SysTick) interrupt service routine (shown below). If the measured angle is less than 5 degrees off north then the blue LED is turned on, otherwise it is turned off.
void SysTick(void)
	Heading = GetHeading();
	if (Heading < 5) 
		GPIO_B1 = 1; // pointing north (approx)
		GPIO_B1 = 0;
If you have any suggested improvements, please mail me. Contact information is here