Ayant acheté récemment quelques capteurs magnétiques bon marché à deux axes chez SureElectronics (http://www.sureelectronics.net/goods.php?id=944), j’ai tenté dans un premier temps de les utiliser comme simples compas.

Problème gênant : il est impossible de compenser de manière précise l’inclinaison du capteur sans utiliser d’autres capteurs de type gyroscopique. Par contre, si l’on reste sur un plan horizontal, les mesures sont assez précises et cela malgré le faible coût des capteurs (6$), j’ai été agréablement surpris. Les capteurs à trois axes simplifient le problème mais sont beaucoup plus chers…
L’utilisation du capteur est simple : la communication passe par de l’I2C et il est possible de lire les données jusqu’à 50 fois par secondes. De plus, une commande de remise à zéro permet de remettre le capteur dans son état initial, ce qui est nécessaire quand il a été soumis à un champ magnétique trop intense.
Après quelques essais et un peu de code, je pouvais récupérer un angle correspondant à l’orientation du capteur. J’ai dû passer par une phase de calibration pour déterminer les valeurs minimales et maximales sur les deux axes. Pour cela, j’ai procédé à une calibration simple : J’ai placé le capteur le long de ma table de travail et j’ai récupéré les valeur x et y, j’ai fait de même en tournant à 90° puis à 180° puis à 270°. J’ai ensuite déterminé les valeurs minimales et maximales sur chaque axe sur l’ensemble des mesures.
Les valeurs calibrées sont calculées comme suit :
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)
Les valeurs calibrées varient alors approximativement dans une fourchette de -1 à 1, l’angle peut être obtenu grâce à un peu de trigonométrie:
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 );
En jouant avec des magnets empruntés à mon frigo, j’ai remarqué que le capteur permettait aussi de détecter précisément l’orientation d’un aimant ainsi que sa présence. Les valeurs captées augmentent fortement en intensité mais le calcul de l’angle est toujours valable. L’intensité du champ magnétique est calculé comme suit :
intensity=sqrt(cal_x*cal_x+cal_y*cal_y)
Tant qu’il n’y a pas d’aimant dans les environs, l’intensité reste en dessous de 1.5 (sauf si j’incline le capteur) pour passer à des valeurs bien plus élevées qui dépendent de la distance qui sépare le capteur de l’aimant et de l’intensité de l’aimant. Au delà d’une certaine intensité, le capteur doit être remis à zéro car il renvoie des valeurs totalement incorrectes. Ce n’est pas un réel problème pour l’utilisation que je veux en faire, il y aura toujours au minimum une plaque de 2cm de bois entre le capteur et l’aimant, je dois juste éviter d’avoir un aimant trop puissant.
Il y a cependant un léger problème de stabilité des données lorsque l’on reste dans une position supposée fixe. Pour limiter l’oscillation, j’ai amorti les données en fonction du déplacement : si la différence avec la mesure précédente est faible, alors je ne tiens que très peu compte de cette nouvelle valeur, par contre, si la différence est marquée, j’en tiens plus compte. Ceci a donc pour effet de stabiliser les données mais assure une bonne réactivité en cas de mouvements plus importants.
Une fois le code validé, l’utilisation pratique a été assez évidente : j’ai prototypé un contrôleur pour un rail de leds RGB acheté aussi chez SureElectronics (http://www.sureelectronics.net/goods.php?id=1223). Ce rail fonctionne en 12V et peut tirer jusqu’à 15w, j’ai donc utilisé des Mosfets IRLZ34N qui peuvent être contrôlés directement par un port de l’arduino et supportent des tensions nettement plus élevées que mes besoins réels. L’autre avantage de ces Mosfets est de supporter les modes PWM ce qui me permet de régler l’intensité de chacun des canaux de couleur. Le schéma global est le suivant :

Je n’ai pas pris la peine de graver un circuit mais j’ai plutôt utilisé une platine d’essai circulaire pouvant être montée comme un shield Arduino.



J’ai alimenté l’arduino en 12V, sachant que cela reste dans la zone acceptée, ce qui m’a permis de récupérer sur le shield le 12V (pin Vin) et le 5V. J’ai soudé un connecteur coudé directement sur le shield pour me brancher sur le rail de leds :
L’aimant, lui, est camouflé dans le pied d’une figurine d’Eizo Auditore (Assassin’s Creed) :

J’ai profité du contrôle indépendant que j’avais sur les canaux de couleur pour corriger la couleur de ma rampe de leds qui tirait sur le bleu. J’ai obtenu un blanc bien plus pur en réduisant le canal bleu de 65%.
L’intensité globale de la rampe de leds dépend de l’orientation de la figurine: plus elle fait face, plus l’intensité de la rampe est forte. Lorsque l’on pose la figurine, une transition douce évite de passer directement de l’état éteint à l’état allumé, il en est de même lorsqu’on l’enlève.
Il reste juste le problème de la réponse non linéaire des leds à régler : une intensité de 50% au niveau du PWM ne correspond pas à une couleur moyenne située entre l’intensité maximale et le noir. C’est un phénomène qui est aussi présent sur les écrans/téléviseurs et j’ai d’ailleurs utilisé la même correction que celle utilisée en vidéo (2.2) pour obtenir un rampe de variation bien plus correcte (http://fr.wikipedia.org/wiki/Correction_gamma).
Le calcul consiste à élever l’intensité entrante à la puissance 2.2 pour obtenir la valeur corrigée qui contrebalancera la courbe de réponse des leds :
Pour conclure, voici une vidéo du système en action :
Et voici le code complet dans sa version actuelle :
// 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);
}
Vous pouvez aussi suivre Domoduino sur Google+ ici. N’hésitez pas à laisser des commentaires!




































