Built-in project · Arduino Uno R3

Hiking compass on an Arduino Uno

An LSM303AGR gives the compass heading and the tilt, and an LTR-390 the sun’s UV index, with a warning at 3. The whole build, an Arduino Uno and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $42. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Compass heading from two magnetic axes
  • Tilt from an accelerometer
  • Reading raw sensor registers over I2C
  • A UV index threshold as a simple warning
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 4

How it works

An LSM303AGR holds an accelerometer and a magnetometer. The sketch reads the magnetometer's two level axes directly over I2C and turns them into a compass heading with atan2. The accelerometer's reading gives the tilt. This heading is only right while the board is flat.

An LTR-390 on A4 and A5 reads the sun's UV index. Every half second or so the Uno prints the heading, tilt and UV index to the serial monitor.

When the UV index reaches 3 or more, it also prints "Sun cream".

Parts list

4 parts, plus the jumper wires. They retail for about $42; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Accelerometer and compass, LSM303AGR pin SCLArduino Uno R3 pin SCL
Accelerometer and compass, LSM303AGR pin SDAArduino Uno R3 pin SDA
Accelerometer and compass, LSM303AGR pin GNDArduino Uno R3 pin GND
UV and light, LTR-390 pin VINArduino Uno R3 pin 5V
UV and light, LTR-390 pin GNDArduino Uno R3 pin GND
UV and light, LTR-390 pin SDAArduino Uno R3 pin A4
UV and light, LTR-390 pin SCLArduino Uno R3 pin A5
Accelerometer and compass, LSM303AGR pin VIN5 V

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Hiking compass: an LSM303AGR reads which way is down and which way is
// north, and an LTR-390 reads the sun's UV index, two things worth knowing
// on a hill.
//
//   LSM303AGR   SDA -> SDA   SCL -> SCL
//   LTR-390     SDA -> A4    SCL -> A5
//
// The compass heading comes from the magnetometer's two level axes, so it is
// only right while the board is flat; the accelerometer says how far it is
// tipped. A UV index of 3 or more wants sun cream.
//
// Things to try. Drag the heading slider round. Tip the board with the
// roll slider. Drag the UV slider past 3.

#include <Wire.h>
#include <Adafruit_LSM303_Accel.h>
#include "Adafruit_LTR390.h"

const uint8_t MAG = 0x1E;

Adafruit_LSM303_Accel_Unified accel(1);
Adafruit_LTR390 uv;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  if (!accel.begin()) {
    Serial.println("No LSM303");
  }
  // The magnetometer half: continuous at 10 Hz.
  Wire.beginTransmission(MAG);
  Wire.write(0x60);
  Wire.write(0x00);
  Wire.endTransmission();
  uv.begin();
  uv.setMode(LTR390_MODE_UVS);
  uv.setGain(LTR390_GAIN_18);
  uv.setResolution(LTR390_RESOLUTION_18BIT);
  Serial.println("Hiking compass");
}

void loop() {
  delay(200);
  int16_t m[3];
  Wire.beginTransmission(MAG);
  Wire.write(0x68);
  Wire.endTransmission(false);
  Wire.requestFrom(MAG, (uint8_t)6);
  for (uint8_t i = 0; i < 3; i++) {
    uint8_t lo = Wire.read();
    m[i] = (int16_t)(Wire.read() << 8 | lo);
  }
  float heading = atan2((float)m[1], (float)m[0]) * 180 / PI;
  if (heading < 0) heading += 360;
  sensors_event_t event;
  accel.getEvent(&event);
  float uvi = uv.getUVI();
  Serial.print("heading ");
  Serial.print(heading, 0);
  Serial.print("  tilt ");
  Serial.print(atan2(event.acceleration.y, event.acceleration.z) * 180 / PI, 0);
  Serial.print("  UV ");
  Serial.println(uvi, 1);
  if (uvi >= 3) {
    Serial.println("Sun cream");
  }
  delay(300);
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Starts serial and I2C and begins the accelerometer. It then writes a configuration to the magnetometer at address 0x1E to run continuously at 10 Hz. The UV sensor is set to UV mode with gain 18 and 18-bit resolution.

void setup() {
  Serial.begin(9600);
  Wire.begin();
  if (!accel.begin()) {
    Serial.println("No LSM303");
  }
  // The magnetometer half: continuous at 10 Hz.
  Wire.beginTransmission(MAG);
  Wire.write(0x60);
  Wire.write(0x00);
  Wire.endTransmission();
  uv.begin();
  uv.setMode(LTR390_MODE_UVS);
  uv.setGain(LTR390_GAIN_18);
  uv.setResolution(LTR390_RESOLUTION_18BIT);
  Serial.println("Hiking compass");
}

loop sketch.ino

Reads six bytes from the magnetometer starting at register 0x68 into three values. It takes atan2 of the second and first, turns radians into degrees, and adds 360 if the result is negative. Then it reads the accelerometer and UV index, prints all three, and prints "Sun cream" at a UV index of 3 or more.

void loop() {
  delay(200);
  int16_t m[3];
  Wire.beginTransmission(MAG);
  Wire.write(0x68);
  Wire.endTransmission(false);
  Wire.requestFrom(MAG, (uint8_t)6);
  for (uint8_t i = 0; i < 3; i++) {
    uint8_t lo = Wire.read();
    m[i] = (int16_t)(Wire.read() << 8 | lo);
  }
  float heading = atan2((float)m[1], (float)m[0]) * 180 / PI;
  if (heading < 0) heading += 360;
  sensors_event_t event;
  accel.getEvent(&event);
  float uvi = uv.getUVI();
  Serial.print("heading ");
  Serial.print(heading, 0);
  Serial.print("  tilt ");
  Serial.print(atan2(event.acceleration.y, event.acceleration.z) * 180 / PI, 0);
  Serial.print("  UV ");
  Serial.println(uvi, 1);
  if (uvi >= 3) {
    Serial.println("Sun cream");
  }
  delay(300);
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The heading is wrong when the board is tilted This project
    The sketch uses only the two level magnetic axes, so keep the board flat. It does not correct for tilt.
  • The serial monitor says No LSM303 This project
    Check the compass chip's SDA and SCL, VIN and GND.
  • The chip does not respond. Accelerometer and compass
    It is two devices in one: the accelerometer is at 0x19 and the magnetometer at 0x1E. Scan the I2C bus and use a library for the AGR, not older LSM303 versions. Run it at 3.3 V.
  • The compass heading is wrong. Accelerometer and compass
    Calibrate the magnetometer by turning it through all directions. Keep it away from magnets, motors and metal. Use the accelerometer to tilt-compensate the heading.
  • The sensor is not found, or UV reads zero indoors. UV and light
    The I2C address is 0x53, and it is a 3.3 V part, so do not feed the bus 5 V. UV needs real sunlight, since glass and most indoor lights give almost none.
  • UV readings look wrong. UV and light
    Choose the UV mode or the ambient light mode, because the sensor measures only one at a time. The UV index is raw counts divided by sensitivity for the gain and resolution you set. Change gain for the light level.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.