Projects / Navigation
Digital Compass & Inclinometer
Tilt-compensated heading and an angle gauge on a bright LED display.
Overview
A magnetometer alone only gives a correct heading when it's held perfectly flat. Combining the ET-S64 QMC5883L compass with the ET-S63 MPU-6500 accelerometer corrects for tilt, so the heading stays right when you hold it at an angle.
The same accelerometer turns it into an inclinometer for setting up shelves, solar panels or antennas. The ET-A20 button pad switches modes and runs calibration, and the ET-D01 display shows the result.
- Tilt-compensated compass heading (0–359°)
- Pitch and roll angle gauge
- Hard-iron calibration saved in flash
- Large 4-digit LED readout
What you need
ElectroTrical boards
You'll also need
- 3 Qwiic cables
- Jumper wires
- USB power bank or USB-C supply
- A rigid base to mount the sensors flat and aligned
Wiring
Addresses: QMC5883L 0x0D, MPU-6500 0x69, HT16K33 0x70. Mount the ET-S64 and ET-S63 flat, side by side, with their printed X arrows pointing the same way.
Qwiic chain
| From | To | Note |
|---|---|---|
| ET-M02 Qwiic | ET-S64 Qwiic | |
| ET-S64 Qwiic | ET-S63 Qwiic | |
| ET-S63 Qwiic | ET-D01 Qwiic |
ET-A20 → ET-M02
| From | To | Note |
|---|---|---|
| 3V3 | VIN | |
| GND | GND | |
| IO4 | B1 | Mode: heading / pitch / roll |
| IO5 | B2 | Hold 2 s: calibrate |
| IO6 | B3 | Zero the angle |
Build it
- 01
Mount the sensors
Fix both sensor boards flat on the same base with their X arrows aligned and forward. Keep them at least 10 cm from the USB power bank, magnets, speakers and steel screws.
- 02
Set up the Arduino IDE
Install the Espressif ESP32 package, select “ESP32S3 Dev Module” and set USB CDC On Boot to Enabled. Install the Adafruit LED Backpack library.
- 03
Calibrate
Hold B2 for 2 seconds; the display shows “CAL”. Slowly rotate the device through every direction — a figure-eight, then roll it over — for 20 seconds. The calibration is saved.
- 04
Use the compass
In heading mode the display shows degrees from magnetic north. Add your local declination (about +2° in the UAE) for true north.
- 05
Use the inclinometer
Press B1 for pitch, again for roll. Place it on a reference surface and press B3 to zero, then measure relative angles.
Arduino code
Written for the ET-M02 ESP32-S3 Dev Board. Install these libraries from the Arduino Library Manager:
- Adafruit LED Backpack Library
- Adafruit GFX Library
#include <Wire.h>
#include <Preferences.h>
#include <Adafruit_LEDBackpack.h>
const uint8_t QMC = 0x0D, MPU = 0x69;
const int B1_PIN = 4, B2_PIN = 5, B3_PIN = 6;
const float DECLINATION = 2.0; // degrees, UAE ≈ +2
Adafruit_7segment disp;
Preferences prefs;
float offX, offY, offZ, zeroPitch = 0, zeroRoll = 0;
int mode = 0; // 0 heading, 1 pitch, 2 roll
void w8(uint8_t a, uint8_t r, uint8_t v) { Wire.beginTransmission(a); Wire.write(r); Wire.write(v); Wire.endTransmission(); }
void rd(uint8_t a, uint8_t r, uint8_t* d, int n) {
Wire.beginTransmission(a); Wire.write(r); Wire.endTransmission(false);
Wire.requestFrom(a, (uint8_t)n); for (int i = 0; i < n; i++) d[i] = Wire.read();
}
void readMag(float& x, float& y, float& z) {
uint8_t d[6]; rd(QMC, 0x00, d, 6); // little-endian X, Y, Z
x = (int16_t)(d[0] | d[1] << 8); y = (int16_t)(d[2] | d[3] << 8); z = (int16_t)(d[4] | d[5] << 8);
}
void readAcc(float& x, float& y, float& z) {
uint8_t d[6]; rd(MPU, 0x3B, d, 6); // big-endian X, Y, Z
x = (int16_t)(d[0] << 8 | d[1]); y = (int16_t)(d[2] << 8 | d[3]); z = (int16_t)(d[4] << 8 | d[5]);
}
void calibrate() {
disp.println("CAL"); disp.writeDisplay();
float mn[3] = {1e6, 1e6, 1e6}, mx[3] = {-1e6, -1e6, -1e6}, v[3];
unsigned long t0 = millis();
while (millis() - t0 < 20000) {
readMag(v[0], v[1], v[2]);
for (int i = 0; i < 3; i++) { mn[i] = min(mn[i], v[i]); mx[i] = max(mx[i], v[i]); }
delay(20);
}
offX = (mn[0] + mx[0]) / 2; offY = (mn[1] + mx[1]) / 2; offZ = (mn[2] + mx[2]) / 2;
prefs.putFloat("x", offX); prefs.putFloat("y", offY); prefs.putFloat("z", offZ);
}
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
pinMode(B1_PIN, INPUT_PULLUP); pinMode(B2_PIN, INPUT_PULLUP); pinMode(B3_PIN, INPUT_PULLUP);
w8(QMC, 0x0B, 0x01); // set/reset period
w8(QMC, 0x09, 0x1D); // continuous, 200 Hz, 8 G, OSR 512
w8(MPU, 0x6B, 0x00); // wake MPU-6500
disp.begin(0x70);
prefs.begin("compass");
offX = prefs.getFloat("x", 0); offY = prefs.getFloat("y", 0); offZ = prefs.getFloat("z", 0);
}
void loop() {
// buttons
static bool last1 = HIGH; bool b1 = digitalRead(B1_PIN);
if (b1 == LOW && last1 == HIGH) mode = (mode + 1) % 3;
last1 = b1;
static unsigned long b2Down = 0;
if (digitalRead(B2_PIN) == LOW) { if (!b2Down) b2Down = millis(); else if (millis() - b2Down > 2000) { calibrate(); b2Down = 0; } }
else b2Down = 0;
float ax, ay, az, mx, my, mz;
readAcc(ax, ay, az);
readMag(mx, my, mz);
mx -= offX; my -= offY; mz -= offZ;
float roll = atan2(ay, az);
float pitch = atan2(-ax, sqrt(ay * ay + az * az));
if (digitalRead(B3_PIN) == LOW) { zeroPitch = pitch; zeroRoll = roll; }
// tilt-compensated heading
float xh = mx * cos(pitch) + mz * sin(pitch);
float yh = mx * sin(roll) * sin(pitch) + my * cos(roll) - mz * sin(roll) * cos(pitch);
float heading = atan2(-yh, xh) * RAD_TO_DEG + DECLINATION;
if (heading < 0) heading += 360;
if (heading >= 360) heading -= 360;
float value = mode == 0 ? heading
: mode == 1 ? (pitch - zeroPitch) * RAD_TO_DEG
: (roll - zeroRoll) * RAD_TO_DEG;
if (mode == 0) disp.print((int)heading, DEC);
else disp.printFloat(value, 1);
disp.writeDisplay();
Serial.printf("heading %.0f pitch %.1f roll %.1f\n", heading,
(pitch - zeroPitch) * RAD_TO_DEG, (roll - zeroRoll) * RAD_TO_DEG);
delay(100);
}
Troubleshooting
Heading turns the wrong way
The two boards aren't aligned. Make sure both X arrows point forward; if one board is rotated, change the sign of the matching axis in readMag().
Heading changes when you tilt it
Recalibrate away from metal, and include rolling the device over during calibration, not just turning it flat.
Reading jumps near the power bank
Batteries and USB cables carry current that disturbs the field. Keep them at least 10 cm from the ET-S64.
Take it further
- Add the ET-S41 GNSS receiver for course and position
- Show a level bubble on an ET-D08 8×8 LED matrix
- Log heading and tilt for vehicle or boat orientation
Turning this into a product?
We design custom boards that combine these sensors on one PCB, with firmware and production.