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
embedded.fm. 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()
{
ConfigPins();
#ifdef DEBUG
initUART();
#endif
initI2C();
initSysTick();
enable_interrupts();
I2CWrite(0x1e,0,0x0c); // set data rate
I2CWrite(0x1e,0x2,0x1); // Trigger a measurement
while(1)
{
#ifdef DEBUG
printHex(Heading);
printString("\r\n");
#endif
sleep();
}
}
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)
break;
}
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)
initI2C();
if (data & 1) // if there is new data
{
ReadMagneticFields(&X,&Y,&Z);
// The next few lines do sign extension from short to int
Converter = X;
X = Converter;
Converter = Y;
Y=Converter;
Converter = Z;
Z=Converter;
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)
else
GPIO_B1 = 0;
}
If you have any suggested improvements, please mail me. Contact information is here