STANNUM
Open the simulator

Tutorials / Sensors

Magnetometer (HMC5883L compass)

Three magnetic axes on I²C; atan2 of X and Y is the heading — the electronic compass, without a library.

Parts in the example:
  • Magnetometer
  • RGB LED
The Magnetometer block as it appears on the canvas.

What it is

The HMC5883L measures the magnetic field along three axes. Earth's field is weak — around 23 µT horizontally in Brazil, 50 µT in Europe — and the heading is the angle of the horizontal component: atan2(y, x). The chip has a fixed address, 0x1E, and one famous trap: its data registers come in the order X, Z, Y.

In the simulator

The block has a Hdg slider, the compass heading in degrees. Settings hold the field strength in µT (it changes the raw counts, like moving to another latitude — or bringing a magnet close, which saturates the sensor).

Pins

PinWhat it is
SDAI²C data — to a GPIO
SCLI²C clock — to a GPIO
3V3power — to a 3V3 symbol
GNDground — to a GND symbol

Wiring

The circuit below is the example Compass with a magnetometer from the lab — open it with Project → Open example… and it comes ready to run.

The wired circuit, as the lab draws it.
Magnetometer pinGoes to
SDAD21 on the board (GPIO 21)
SCLD22 on the board (GPIO 22)
3V3a 3V3 symbol
GNDa GND symbol

Settings

Double-click the block's title bar to open its card, then the Settings tab. Changes apply to the running simulation right away.

The Settings tab of the Magnetometer.
SettingAcceptsDefaultNotes
I²C address0x1E (fixed on the HMC5883L)0x1E (fixed on the HMC5883L)This chip does not allow changing the address.
Field strength5 to 65 µT23 µTEarth's field changes with latitude: ~23 µT in Brazil, ~50 µT in Europe. A magnet nearby saturates it.

Code

sketch.cpp
#include <Arduino.h>
#include <Wire.h>

const uint8_t ADDR = 0x1E;         // HMC5883L
const int R = 25, G = 26, B = 27;

int16_t lerEixo(uint8_t reg) {
  Wire.beginTransmission(ADDR); Wire.write(reg); Wire.endTransmission();
  Wire.requestFrom(ADDR, (uint8_t)2);
  int hi = Wire.read(), lo = Wire.read();
  return (int16_t)((hi << 8) | lo);
}

void setup() {
  Serial.begin(115200);
  delay(300);
  Wire.begin(21, 22);
  pinMode(R, OUTPUT); pinMode(G, OUTPUT); pinMode(B, OUTPUT);
}

void loop() {
  // WATCH the register order: X, Z, Y — not X, Y, Z
  int16_t x = lerEixo(0x03);
  int16_t z = lerEixo(0x05);
  int16_t y = lerEixo(0x07);

  float rumo = atan2((float)y, (float)x) * 180.0 / PI;
  if (rumo < 0) rumo += 360;

  const char *ponto = "?";
  if (rumo < 45 || rumo >= 315)      ponto = "N";
  else if (rumo < 135)               ponto = "E";
  else if (rumo < 225)               ponto = "S";
  else                               ponto = "W";

  digitalWrite(G, ponto[0] == 'N' ? HIGH : LOW);
  digitalWrite(R, ponto[0] == 'S' ? HIGH : LOW);
  digitalWrite(B, (ponto[0] == 'E' || ponto[0] == 'W') ? HIGH : LOW);

  Serial.printf("x=%d y=%d z=%d  heading=%.0f degrees  %s\n", x, y, z, rumo, ponto);
  delay(500);
}

No library: the sketch talks to the chip with Wire directly — a register address, then two bytes per axis, read in the chip's X, Z, Y order. atan2 gives the heading, and the RGB LED shows the quadrant: north green, south red, east and west blue.

Run it

Press Build and run. The first build of a project takes a while; after that, only what changed is rebuilt.

The simulation running: the canvas reacts and the serial monitor shows what the code prints.

Drag the heading around the circle: the serial monitor prints the raw axes, the heading and the cardinal point, and the LED changes color at 45°, 135°, 225° and 315°.

How the simulation models it

Try this

  1. Print the heading only when it changes by more than 5°.
  2. Set the field to 60 µT in Settings and check that the heading does not change — only the magnitude of the axes does.

See also