I’ve bought recently some cheap dual axis magnetic sensors at SureElectronics (http://www.sureelectronics.net/goods.php?id=944), I’ve tried first to use them as straight compass.

Annoying problem : it’s impossible to accurately compensate the tilt of the sensor without using a gyro sensor. On the other end, if you stay on the same horizontal plan, the measures are pretty accurate even if you consider their low price (6$), I was really surprised. Three axis sensor can simplify the problem but they are more expensive …
Usage of the sensor is pretty simple : the communication is based on I2C and you can read new data 50 times per second. Furthermore, there is a reset command that put the sensor back in its initial state, which is necessary when it has to handle strong magnetic fields.
After a few tries and some code, I was able to get the angle of the compass. For that, I had to calibrate it first to know the minimal and maximal values on each axis. The calibration procedure is easy : I’ve placed the sensor on the edge of my workbench, red the x and y values, then I’ve rotated it by 90°, then 180° and finally 270°. After that, I’ve taken the minimal and maximal values along both axis from the different orientations.
The calibrated values are computed this way :
cal_x=(raw_x-((max_x+min_x)/2))/((max_x-min_x)/2)
cal_y=(raw_y-((max_y+min_y)/2))/((max_y-min_y)/2)
The calibrated values varies more or less between -1 and 1, the angle can be determined thanks to some trigonometry :
if (( cal_x ==0) && ( cal_y <0))
angle=PI/2;
else if (( cal_x ==0) && ( cal_y >0))
angle=-PI/2;
else if (cal_x<0)
angle=PI-atan(cal_y/cal_x);
else if ((cal_x>0) && (cal_y<0))
angle=-atan( cal_y/cal_x );
else if (( cal_x >0) && ( cal_y >0))
angle=(2*PI)-atan( cal_y/cal_x );
While playing with some magnets borrowed from my fridge, I’ve noticed that the sensor can detect accurately the orientation of the magnet as well as its presence. The sensed values increase strongly but the angle calculus is still totally valid. The magnetic field intensity can be computed like this :
intensity=sqrt(cal_x*cal_x+cal_y*cal_y)
If there is no magnet, the intensity stays under 1.5 (unless I tilt the sensor) and it moves to higher values which depends on the distance between the magnet and the sensor and on the strenght of the magnet. Above a certain threshold, the sensor has to be reset as it gives totally random values. This is not a real problem for my specific usage as there will always be a minimal distance of an inch of wood between the sensor and the magnet, I just have to avoid strong magnets.
But there is a small data stability issue when you stay on the same orientation. To reduce that jitter, I’ve dampened the data depending on their variation : if the offset between the previous read and the new one is small, the data is slightly taken into account, on the other hand, if the offset is bigger, the data has more weight. This has the advantage of stabilyzing the data while ensuring a good reactivity when bigger changes are done.
Once all of this has been validated, I’ve prototyped a controller for an RGB led strip I’ve bought at SureElectronics as well (http://www.sureelectronics.net/goods.php?id=1223). That led strip runs on 12V and can pump up to 15w, so I’ve used IRLZ34N mosfets which can be driven directly from an Arduino pin without any extra components and can handle far more than my needs. The other advantage of using those mosfets is that they can handle the PWM mode, which allow me to control the intensity of each color channel of my strip. Here is the schematic of the project :

I’ve not made a dedicated PCB for that project, I’ve just used a round prototype pcb instead and build it as an Arduino shield.



I’ve powered the Arduino on 12V as it’s still in the accepted votlage range. By doing so, I was able to get a 12V line on my shield (Vin pin) and the usual 5v line. A bended connector is soldered on the side to plug the rgb led strip :
The magnet is hidden in the pedestal of an Eizo Auditore’s action figure (from Assassin’s Creed) :

As I was able to control independently each color channel, I’ve fixed the color balance which was far too blueish. A well balanced white was finally obtained by reducing the blue by 65%.
The light intensity depdends on the orientation of the action figure : the more it faces the outside, the higher the intensity is. When you place the pedestal over the sensor, there is a smooth transition to avoid going from off state to on state in a snap, and also when the light is switched off.
There is still the small problem of non linear answer of the leds : a PWM of 50% does not match an average intensity (right bewteen maximal intensity and black). This phenomenon is also visible on tv screens and monitors (http://en.wikipedia.org/wiki/Gamma_correction) and I’ve used the same ramp used on standard monitor (2.2) to have a far more linear answer from the leds.
One just need to put the value to the power 2.2 which will counter balance the led’s response curve :
To conclude, here is a video of the light control in action :
And the current full code :
// Led compass control
// By -Gil- (c)2012
// http://domoduino.tumblr.com/
// http://domoduino-world.tumblr.com/
//
#include "Arduino.h"
#include <Wire.h>
int calibration=-1;
int PreviousCompass_x=0;
int PreviousCompass_y=0;
int Compass_x=0;
int Compass_y=0;
float Compass_xcal=0;
float Compass_ycal=0;
int Compass_minx=1920;
int Compass_miny=1935;
int Compass_maxx=2131;
int Compass_maxy=2136;
float Compass_dist=0.0;
float ProximityFade=0.0;
int RotationValue=255;
float Compass_angle=0.0;
#define COMPASS_CARD_ADR B0110000
const byte COMPASS_comp_reg_addr = B00000000;
const byte COMPASS_read_comp = B00000001;
const byte COMPASS_reset_comp = B00000100;
const byte COMPASS_set_comp = B00000010;
void setCompass()
{
Wire.beginTransmission(COMPASS_CARD_ADR);
Wire.write((uint8_t)COMPASS_comp_reg_addr);
Wire.write((uint8_t)COMPASS_set_comp);
Wire.endTransmission();
delay(20);
}
void resetCompass()
{
Wire.beginTransmission(COMPASS_CARD_ADR);
Wire.write((uint8_t)COMPASS_comp_reg_addr);
Wire.write((uint8_t)COMPASS_reset_comp);
Wire.endTransmission();
delay(20);
}
void readCompass()
{
Wire.beginTransmission(COMPASS_CARD_ADR);
Wire.write((uint8_t)COMPASS_comp_reg_addr);
Wire.write((uint8_t)COMPASS_read_comp);
Wire.endTransmission();
delay(10);
byte x_msb = 0;
byte x_lsb = 0;
byte y_msb = 0;
byte y_lsb = 0;
// read data from the sensor
Wire.requestFrom(COMPASS_CARD_ADR, 5);
int avail = Wire.available();
if (avail == 5)
{
x_msb = Wire.read();
x_lsb = Wire.read();
y_msb = Wire.read();
y_lsb = Wire.read();
}
int newCompass_x=((int)x_msb)<<8 | (int)x_lsb;
int newCompass_y=((int)y_msb)<<8 | (int)y_lsb;
int deltax=abs(newCompass_x-PreviousCompass_x);
int deltay=abs(newCompass_y-PreviousCompass_y);
// Filter the result to avoid jitter
float deltaxFriction=deltax/10.0f;
float deltayFriction=deltay/10.0f;
if (deltaxFriction>1.0)
deltaxFriction=1.0f;
if (deltayFriction>1.0)
deltayFriction=1.0f;
Compass_x=(newCompass_x*deltaxFriction)
+(PreviousCompass_x*(1.0-deltaxFriction));
Compass_y=(newCompass_y*deltayFriction)
+(PreviousCompass_y*(1.0-deltayFriction));
PreviousCompass_x=Compass_x;
PreviousCompass_y=Compass_y;
// calibrate the values
Compass_xcal=(Compass_x
-((Compass_maxx+Compass_minx)*0.5))
/((Compass_maxx-Compass_minx)*0.5);
Compass_ycal=(Compass_y
-((Compass_maxy+Compass_miny)*0.5))
/((Compass_maxy-Compass_miny)*0.5);
// compute magnetic field intensity
Compass_dist=sqrt(Compass_xcal*Compass_xcal
+Compass_ycal*Compass_ycal);
// compute angle
if ((Compass_xcal==0) && (Compass_ycal<0))
Compass_angle=PI/2;
if ((Compass_xcal==0) && (Compass_ycal>0))
Compass_angle=-PI/2;
if (Compass_xcal<0)
Compass_angle=PI-atan(Compass_ycal/Compass_xcal);
if ((Compass_xcal>0) && (Compass_ycal<0))
Compass_angle=-atan(Compass_ycal/Compass_xcal);
if ((Compass_xcal>0) && (Compass_ycal>0))
Compass_angle=(2*PI)-atan(Compass_ycal/Compass_xcal);
}
void setup()
{
Wire.begin();
// reset the compass when starting/restarting
resetCompass();
setCompass();
}
void loop()
{
readCompass();
if (Compass_dist>1.5) // Is there a magnet ?
{
// Fade in
ProximityFade=ProximityFade+0.05;
if (ProximityFade>1.0)
ProximityFade=1.0f;
float intensity=(Compass_angle-PI/2)/(PI);
if (intensity<0)
intensity=0;
if (intensity>1)
intensity=1;
RotationValue=255*pow((float)intensity,2.2);
}
else
{
// Fade out
ProximityFade=ProximityFade-0.05;
if (ProximityFade<0.0)
ProximityFade=0.0f;
}
int finalFade=RotationValue*ProximityFade;
analogWrite(3, finalFade); //R
analogWrite(5, finalFade); //G
analogWrite(6, finalFade*0.35); //B (fixed)
delay(30);
}
You can also follow Domoduino on Google+ here. Don’t hesitate to leave comments!

























