Digital Car Compass and Thermometer

My car doesn’t have a compass which I wish to have one. So I started making one using my JeonLab mini v1.3 (minimalist Arduino compatible board), popular 16×2 LCD panel with a back light LED, and a 3 axis magnetometer.  Here is the part list and pictures of the LCD, JeonLab mini v1.3 and the prototyping board.

  • LCD: 16×2 HD44780 LCD (white text on blue background)
  • JeonLab mini v1.3
  • Digital compass: MAG3110 (bought an assembled from ebay)
  • Temperature sensor: TMP36
  • Phototransistor: LTR-4206E
  • 7805 regulator
  • resistors and capacitors (see schematic diagram)
  • Car battery jack
  • prototyping board
  • push button switch
  • solid copper wire (1mm in diameter) for bracket
  • cable ties

parts photo 20130203_162415.jpg

The JeonLab mini v1.3 is so small that can be attached to the back of the LCD.

compass & LCD test photo 20121228_174420.jpg

First of all, the LCD, JeonLab mini and the magnetometer, MAG3110 have been assembled on a breadboard and tested. The magnetometer has 3 axis sensor, but because, fortunately, the most roads where I live and commute to work are relatively level. So I didn’t bother to use complicated equations, but decided to calculate simply the heading angle using ATAN from X and Y readings. And it really works just good enough. Take a look at the source Arduino code below.
/*
JeonLab Car Digital Compass & Thermometer

Functions:
LCD display
Digital compass (MAG3110)
Interior temperature
LCD backlight brightness automatic adjust

MAG3110 read: Original code from Sparkfun example code
(https://www.sparkfun.com/products/10619)
*/

#include
#include #include <avr/eeprom.h>

#define MAG_ADDR 0x0E
int avgX, avgY;

struct settings_t
{
long maxX, minX, maxY, minY;
}
settings;

LiquidCrystal lcd(2,3,6,7,8,9);

void setup()
{
Wire.begin();
config(); //configuration of the magnetometer, MAG3110
pinMode(10,INPUT); //temperature sensor
pinMode(5,OUTPUT); //LCD backlight LED
analogWrite(5, 100);

//read previously stored calibration data from EEPROM
eeprom_read_block((void*)&settings, (void*)0, sizeof(settings));
avgX=(settings.maxX+settings.minX)/2;
avgY=(settings.maxY+settings.minY)/2;

lcd.begin(16, 2);
}

void loop()
{
//read the magnetometer and calculate heading
float heading = (atan2(readx()-avgX, ready()-avgY))*180/PI;

//read and calculate the interior temperature in celcius
float temp = (analogRead(0)/1024.0*5000.0-500)/10;

//read ambient brightness from the photo-transistor
int brightness = map(analogRead(1), 900, 1023, 50, 255);
analogWrite(5,brightness); //LCD backlight LED voltage control by PWM

//diaplay compass bearing
lcd.setCursor(0,0);
lcd.print(” “);
lcd.setCursor(0,0);
if (abs(heading) <= 22.5) lcd.print(“N “); if (abs(heading) >= 157.5) lcd.print(“S “);
if (heading >= 67.5 && heading <= 112.5) lcd.print(“E “);
if (heading <= -67.5 && heading >= -112.5) lcd.print(“W “);
if (heading > 22.5 && heading < 67.5) lcd.print(“NE”);
if (heading < -22.5 && heading > -67.5) lcd.print(“NW”);
if (heading > 112.5 && heading < 157.5) lcd.print(“SE”);
if (heading < -112.5 && heading > -157.5) lcd.print(“SW”);

if (heading < 0) heading += 360.0; //display heading lcd.setCursor(3,0); lcd.print(int(heading)); lcd.setCursor(7,0); lcd.print(int(temp)); lcd.print(“\337C”); lcd.setCursor(0,1); lcd.print(” “); lcd.setCursor(0,1); lcd.print(“Brightness: “); //show PWM value for a reference lcd.print(brightness); if (digitalRead(10) == HIGH) //monitor calibration button status { analogWrite(5,200); lcd.setCursor(0,1); lcd.print(“calibrating…..”); delay(1000); calXY(); } delay(1000); } void calXY() //magnetometer calibration: finding max and min of X, Y axis { int tempXmax, tempXmin, tempYmax, tempYmin, newX, newY; lcd.setCursor(0,1); lcd.print(“Rotate the car “); delay(1000); lcd.setCursor(0,1); lcd.print(“and press button”); delay(1000); lcd.setCursor(0,1); lcd.print(“Now, begin! “); delay(500); tempXmax = tempXmin = readx(); tempYmax = tempYmin = ready(); while(digitalRead(10) == LOW) { newX = readx(); newY = ready(); if (newX > tempXmax) tempXmax = newX;
if (newX < tempXmin) tempXmin = newX; if (newY > tempYmax) tempYmax = newY;
if (newY < tempYmin) tempYmin = newY;
}
settings.maxX = tempXmax;
settings.minX = tempXmin;
settings.maxY = tempYmax;
settings.minY = tempYmin;

//store new X, Y values in the EEPROM
eeprom_write_block((const void*)&settings, (void*)0, sizeof(settings));

avgX=(settings.maxX+settings.minX)/2;
avgY=(settings.maxY+settings.minY)/2;

lcd.setCursor(0,1);
lcd.print(“Calibration done”);
delay(3000);
lcd.setCursor(0,1);
lcd.print(” “);

}

void config(void) //MAG3110 config taken from Sparkfun example
{
Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x10); // cntrl register1
Wire.write(0); // send 0x00, standby mode
Wire.endTransmission(); // stop transmitting

delay(15);

Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x11); // cntrl register2
Wire.write(0x80); // send 0x80, enable auto resets
Wire.endTransmission(); // stop transmitting

delay(15);

Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x10); // cntrl register1
Wire.write(0x19); // send 0x01, active mode
Wire.endTransmission(); // stop transmitting

delay(15);
}

void print_values(void)
{
}

int readx(void) //MAG3110 read X value taken from Sparkfun example
{
int xl, xh; //define the MSB and LSB

Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x01); // x MSB reg
Wire.endTransmission(); // stop transmitting

delayMicroseconds(2); //needs at least 1.3us free time between start and stop

Wire.requestFrom(MAG_ADDR, 1); // request 1 byte
while(Wire.available()) // slave may send less than requested
{
xh = Wire.read(); // receive the byte
}

delayMicroseconds(2); //needs at least 1.3us free time between start and stop

Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x02); // x LSB reg
Wire.endTransmission(); // stop transmitting

delayMicroseconds(2); //needs at least 1.3us free time between start and stop

Wire.requestFrom(MAG_ADDR, 1); // request 1 byte
while(Wire.available()) // slave may send less than requested
{
xl = Wire.read(); // receive the byte
}

int xout = (xl|(xh << 8)); //concatenate the MSB and LSB
return abs(xout);
}

int ready(void) //MAG3110 read Y value taken from Sparkfun example
{
int yl, yh; //define the MSB and LSB

Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x03); // y MSB reg
Wire.endTransmission(); // stop transmitting

delayMicroseconds(2); //needs at least 1.3us free time between start and stop

Wire.requestFrom(MAG_ADDR, 1); // request 1 byte
while(Wire.available()) // slave may send less than requested
{
yh = Wire.read(); // receive the byte
}

delayMicroseconds(2); //needs at least 1.3us free time between start and stop

Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
Wire.write(0x04); // y LSB reg
Wire.endTransmission(); // stop transmitting

delayMicroseconds(2); //needs at least 1.3us free time between start and stop

Wire.requestFrom(MAG_ADDR, 1); // request 1 byte
while(Wire.available()) // slave may send less than requested
{
yl = Wire.read(); // receive the byte
}

int yout = (yl|(yh << 8)); //concatenate the MSB and LSB
return abs(yout);
}

And the schematic diagram is below.
Schematic photo JeonLabCarDigitalCompassThermometer-1.png

I have introduced a step-by-step assembling procedure on Instructable, and here are some pictures of assembling and mounting on top of the interior mirror of my car.

assembling JeonLab mini v1.3 photo 20130203_165903.jpg

Note that there is no LED attached. A 6-pin header is attached upwards for the FTDI USB interface and 3 single header pins are attached at the bottom of the JeonLab mini to support on the prototyping board which will be attached to the back of the LCD.

JeonLab mini on the back of the LCD photo 20130209_122723.jpg

 photo 20130217_124514.jpg
The temperature sensor (TO-92 package), a voltage regulator, and a calibration switch are assembled.

finished assembly photo 20130317_163954.jpg
Next to the temperature sensor, the phototransistor (looks like a 3mm LED in black) is added to adjust the LED backlight of the LCD automatically.

wrapping heat shrink tube photo 20130409_193307.jpg
A thick solid copper wire (appx. 1mm in diameter) is used to form a simple bracket on top of the interior mirror.

finalizing bracket photo 20130409_194316.jpg
A heat shrink tube is used to insulate the copper wire bracket where it touches the LCD PCB.

attched to the mirror photo 20130409_194552.jpg
Two short cable ties are used to fix the assembled LCD and bracket on top of the mirror.

magnetometer fix photo 20130413_153144.jpg
The magnetometer is held by a small suction cup on the wind shield at the back of the mirror.

in my car photo 20130410_174041.jpg
Finally, it works!