DIY Dashboard Compass Project

You’re right to ask. The short version: I made real progress, but the work got interrupted multiple times by execution stops, so it likely looked like I was spinning.
Here’s what actually happened:

Table


AreaWhat I didCurrent state
Project setupRenamed the project and updated the description to match the FJ Cruiser solar compass conceptDone
Architecture notesCreated Design Notes for the system intentDone
Major componentsAdded the MCU, magnetometer, LED driver, charger, LDO, displays, dimmer/switch, connectors, protection, and passivesDone
Power wiringWired solar input through fuse, Schottky reverse protection, TVS clamp, charger input, battery, charger SYS output, switched rail, and 3.3V LDOMostly done
Logic wiringWired I2C between U1, U2, and U3, with pullupsDone
Display wiringWired the three 7-segment displays to the LED driver segment/common netsDone
Dimmer/offWired RV1 as both ADC dimmer input and hard load-disconnect switchDone
Debug/resetWired SWD/debug and reset support around U1 and J3Done
Display replacementReplaced the earlier placeholder displays with DISP1, DISP2, and DISP3 using LTS-5503AE orange/yellow displays, then rewired themDone
Board setupSet the layout to a 120 mm x 55 mm rounded rectangle, 4-layer stackup, 0.2 mm keepout, and 0.25 mm trace widthDone
PCB placement/routingStarted moving toward layout work, but placement and routing were interrupted before completionNot done
The biggest reason it took so long is that the execution agent was stopped several times, including by “insufficient credits” earlier and later by “stopped by user.” I did not get all the way through PCB placement, routing, DRC review, or manufacturability review.
Most important unfinished items
  • Run a schematic review/ERC to catch floating or incorrectly configured pins.
  • Verify charger/programming resistor values against the actual solar panel and battery you want to use.
  • Place components on the PCB.
  • Route the board.
  • Run DRC and review footprints/MPNs.
I should have surfaced the interruptions more clearly instead of leaving you guessing.
Recent activity log
Check recent changes
Run design checks
Modified components
Export BOM
RV1
U4
U1 PA22 - U2 SDA/SDI/SDO
U3 C_FILT - C13 P1
U3 CA7 - DISP1 ANODE_G
U5 VO - C6 P1
U5 VO - C6 P1
U3 CA5 - DISP1 ANODE_E
U3 CB2 - DISP2 COMMON_CATHODE_1
U3 CA8 - DISP1 ANODE_D.P.
U1 VDDCORE - C11 P1
U1 PA02 - RV1 R1_2
U3 CA4 - DISP1 ANODE_D
U4 SYS - C8 P1
U2 Vdd_IO - U2 ~{CS}
U1 ~RESETN - J3 10
U3 CB2 - DISP2 COMMON_CATHODE_1
U3 R_EXT - R8 P1
U1 VDDIN - U2 Vdd
U2 Vdd_IO - U2 ~{CS}
U3 CA2 - DISP1 ANODE_B
RV1 SW_2 - U5 VI
U1 PA06 - U4 STAT1
J1 1 - F1 P1
U4 BAT - J2 1
U2 Vdd_IO - U2 ~{CS}
U3 CA2 - DISP1 ANODE_B
U2 Vdd_IO - U2 ~{CS}
U3 CA1 - DISP1 ANODE_A
U1 PA07 - U4 STAT2
U3 CA3 - DISP1 ANODE_C
U1 ~RESETN - J3 10
U2 Vdd_IO - U2 ~{CS}
U3 C_FILT - C13 P1
U3 CA5 - DISP1 ANODE_E
U3 CA8 - DISP1 ANODE_D.P.
U3 CA8 - DISP1 ANODE_D.P.
U5 VO - C6 P1
F1 P2 - D2 A
U1 PA22 - U2 SDA/SDI/SDO
U4 TS/MR - R11 P2
U3 CB2 - DISP2 COMMON_CATHODE_1
U1 PA04 - U2 INT
U3 CA4 - DISP1 ANODE_D
U3 CA4 - DISP1 ANODE_D
U4 BAT - J2 1
U3 CA5 - DISP1 ANODE_E
U2 C1 - C14 P1
U1 PA23 - U2 SCL/SPC
U1 VDDIN - U2 Vdd
U3 CA7 - DISP1 ANODE_G
U3 CB3 - DISP3 COMMON_CATHODE_1
U3 CA5 - DISP1 ANODE_E
U2 Vdd_IO - U2 ~{CS}
U3 CA8 - DISP1 ANODE_D.P.
U3 CA1 - DISP1 ANODE_A
U1 PA02 - RV1 R1_2
U1 VDDCORE - C11 P1
F1 P2 - D2 A
U3 CB1 - DISP1 COMMON_CATHODE_1
U3 R_EXT - R8 P1
U3 CA3 - DISP1 ANODE_C
D2 K - D1 K
U2 Vdd_IO - U2 ~{CS}
U2 Vdd_IO - U2 ~{CS}
U1 PA30 - J3 4
D2 K - D1 K
U5 VO - C6 P1
U1 PA06 - U4 STAT1
U1 PA05 - U2 DRDY
U2 Vdd_IO - U2 ~{CS}
U3 CA6 - DISP1 ANODE_F
U3 CA3 - DISP1 ANODE_C
U1 PA22 - U2 SDA/SDI/SDO
U3 CA7 - DISP1 ANODE_G
U3 CA2 - DISP1 ANODE_B
U1 PA31 - J3 2
U1 PA30 - J3 4
U1 PA07 - U4 STAT2
U1 PA05 - U2 DRDY
U1 ~RESETN - J3 10
U4 SYS - C8 P1
U1 PA23 - U2 SCL/SPC
U3 CA6 - DISP1 ANODE_F
U2 Vdd_IO - U2 ~{CS}
D2 K - D1 K
U4 ISET - R3 P1
U3 CA2 - DISP1 ANODE_B
U3 CA1 - DISP1 ANODE_A
U3 CB3 - DISP3 COMMON_CATHODE_1
U1 PA06 - U4 STAT1
RV1 SW_2 - U5 VI
U4 ILIM/VSET - R4 P1
U1 PA04 - U2 INT
J1 1 - F1 P1
U3 CA4 - DISP1 ANODE_D
U3 CB3 - DISP3 COMMON_CATHODE_1
U1 PA31 - J3 2
U4 BAT - J2 1
U3 CB1 - DISP1 COMMON_CATHODE_1
U4 ILIM/VSET - R4 P1
U2 Vdd_IO - U2 ~{CS}
U1 PA23 - U2 SCL/SPC
U4 TS/MR - R11 P2
D2 K - D1 K
U2 Vdd_IO - U2 ~{CS}
U4 SYS - C8 P1
U1 PA31 - J3 2
U3 CB1 - DISP1 COMMON_CATHODE_1
U2 Vdd_IO - U2 ~{CS}
U3 CA6 - DISP1 ANODE_F
U3 CA3 - DISP1 ANODE_C
U1 PA22 - U2 SDA/SDI/SDO
U3 CA6 - DISP1 ANODE_F
U2 C1 - C14 P1
U5 VO - C6 P1
U3 CA1 - DISP1 ANODE_A
U5 VO - C6 P1
U4 ISET - R3 P1
U2 Vdd_IO - U2 ~{CS}
U1 ~RESETN - J3 10
U3 CA7 - DISP1 ANODE_G
U1 PA07 - U4 STAT2
U1 PA30 - J3 4
U1 PA23 - U2 SCL/SPC
D2 K - D1 K
U5 VO - C6 P1
C2
Capacitance
100nF
C6
Capacitance
100nF
C3
Capacitance
100nF
C5
Capacitance
100nF
C8
Capacitance
10uF
C10
Capacitance
100nF
C13
Capacitance
100nF
C11
Capacitance
1uF
C12
Capacitance
1uF
C7
Capacitance
10uF
C14
Capacitance
100nF
C9
Capacitance
10uF
C4
Capacitance
100nF
U1
R11
Resistance
10kΩ
GND
U2 GND - U2 SDO/SA1
R2
Resistance
4.7kΩ
J3
U2
J3 9 - U4 ~CE
U4 EP - U5 GND
U2 GND - U2 SDO/SA1
U2 GND - U2 SDO/SA1
GND
U2 GND - U2 SDO/SA1
GND
U4 EP - U5 GND
U2 GND - U2 SDO/SA1
U2 GND - U2 SDO/SA1
GND
J3 9 - U4 ~CE
J1
U4 EP - U5 GND
GND
U4 EP - U5 GND
R1
Resistance
4.7kΩ
R4
Resistance
18kΩ
R3
Resistance
6kΩ
U2 GND - U2 SDO/SA1
R5
Resistance
10kΩ
R9
Resistance
10kΩ
U2 GND - U2 SDO/SA1
U1 GND3 - U2 GND
U4 EP - U5 GND
R10
Resistance
10kΩ
U4 EP - U5 GND
U2 GND - U2 SDO/SA1
U2 GND - U2 SDO/SA1
U4 EP - U5 GND
U4 EP - U5 GND
R6
Resistance
10kΩ
U4 EP - U5 GND
U4 EP - U5 GND
U1 GND3 - U2 GND
R7
Resistance
10kΩ
U4 EP - U5 GND
GND
GND
U4 EP - U5 GND
R8
Resistance
20kΩ
GND
DISP2
D2
U5
F1
Not Recommended for New Designs
U3
DISP3
D1
J2
DISP1

Refine this doc
Ask about this doc
Firmware Starter - ATSAMD21G18A-AU
Platform & Toolchain
  • MCU: ATSAMD21G18A-AU
  • Framework: Arduino SAMD core
  • Build system: PlatformIO
  • Purpose: Read LIS3MDL heading data, read dimmer ADC, monitor charger status, and drive IS31FL3731 display matrix.
Pin Mapping

Table


FunctionMCU PinNetConnected ToDirectionNotes
I2C SDAPA22I2C_SDALIS3MDL SDA, IS31FL3731 SDA, R1Bidir4.7k pull-up to 3.3V
I2C SCLPA23I2C_SCLLIS3MDL SCL, IS31FL3731 SCL, R2Output4.7k pull-up to 3.3V
Dimmer ADCPA02DIMMER_ADCRV1 wiperInput0V to 3.3V brightness control
Magnetometer INTPA04MAG_INTLIS3MDL INTInputOptional interrupt
Magnetometer DRDYPA05MAG_DRDYLIS3MDL DRDYInputData-ready input
Charger STAT1PA06CHG_STAT1BQ25185 STAT1InputOpen-drain with 10k pull-up
Charger STAT2PA07CHG_STAT2BQ25185 STAT2InputOpen-drain with 10k pull-up
SWDIOPA31SWDIOJ3 pin 2DebugReserved for programming
SWCLKPA30SWCLKJ3 pin 4DebugReserved for programming
ResetRESETNRESETJ3 pin 10InputPull-up plus filter cap
Dependencies & Project Setup
Create platformio.ini:

Ini


[env:zeroUSB]
platform = atmelsam
board = zeroUSB
framework = arduino
monitor_speed = 115200
lib_deps =
Complete Firmware Source
Create src/main.cpp:

Cpp


#include <Arduino.h>
#include <Wire.h>
#include <math.h>

static constexpr uint8_t LIS3MDL_ADDR = 0x1C;
static constexpr uint8_t IS31_ADDR = 0x74;

static constexpr uint8_t PIN_DIMMER_ADC = A0;
static constexpr uint8_t PIN_MAG_INT = 4;
static constexpr uint8_t PIN_MAG_DRDY = 5;
static constexpr uint8_t PIN_CHG_STAT1 = 6;
static constexpr uint8_t PIN_CHG_STAT2 = 7;

static uint8_t displayFrame[3] = {0, 0, 0};

static void i2cWrite8(uint8_t addr, uint8_t reg, uint8_t value) {
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
}

static uint8_t i2cRead8(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(addr, (uint8_t)1);
  if (Wire.available()) return Wire.read();
  return 0;
}

static int16_t i2cRead16LE(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire.write(reg | 0x80);
  Wire.endTransmission(false);
  Wire.requestFrom(addr, (uint8_t)2);
  uint8_t lo = Wire.available() ? Wire.read() : 0;
  uint8_t hi = Wire.available() ? Wire.read() : 0;
  return (int16_t)((hi << 8) | lo);
}

static bool initLIS3MDL() {
  uint8_t who = i2cRead8(LIS3MDL_ADDR, 0x0F);
  if (who != 0x3D) return false;
  i2cWrite8(LIS3MDL_ADDR, 0x20, 0x70); // CTRL_REG1: 80 Hz, high-performance XY
  i2cWrite8(LIS3MDL_ADDR, 0x21, 0x00); // CTRL_REG2: +/-4 gauss
  i2cWrite8(LIS3MDL_ADDR, 0x22, 0x00); // CTRL_REG3: continuous conversion
  i2cWrite8(LIS3MDL_ADDR, 0x23, 0x0C); // CTRL_REG4: high-performance Z
  i2cWrite8(LIS3MDL_ADDR, 0x24, 0x40); // CTRL_REG5: block data update
  return true;
}

static void initIS31FL3731() {
  i2cWrite8(IS31_ADDR, 0xFD, 0x0B); // Function register page
  i2cWrite8(IS31_ADDR, 0x0A, 0x00); // Audio sync off
  i2cWrite8(IS31_ADDR, 0x00, 0x00); // Picture mode
  i2cWrite8(IS31_ADDR, 0x01, 0x00); // Display frame 0
  i2cWrite8(IS31_ADDR, 0x0B, 0x00); // Shutdown normal operation

  i2cWrite8(IS31_ADDR, 0xFD, 0x00); // Frame 0 page
  for (uint8_t r = 0x00; r <= 0x11; r++) i2cWrite8(IS31_ADDR, r, 0x00);
  for (uint8_t r = 0x24; r <= 0xB3; r++) i2cWrite8(IS31_ADDR, r, 0x20);
}

static uint8_t sevenSegForChar(char c) {
  switch (c) {
    case 'N': return 0b00110111;
    case 'E': return 0b01111001;
    case 'S': return 0b01101101;
    case 'W': return 0b00111110;
    case ' ': return 0;
    default: return 0;
  }
}

static void setDisplayText(const char text[3], uint8_t brightness) {
  i2cWrite8(IS31_ADDR, 0xFD, 0x00);
  for (uint8_t digit = 0; digit < 3; digit++) {
    displayFrame[digit] = sevenSegForChar(text[digit]);
    for (uint8_t seg = 0; seg < 8; seg++) {
      bool on = displayFrame[digit] & (1 << seg);
      uint8_t pwmReg = 0x24 + digit * 16 + seg;
      i2cWrite8(IS31_ADDR, pwmReg, on ? brightness : 0);
    }
  }
}

static float readHeadingDegrees() {
  int16_t x = i2cRead16LE(LIS3MDL_ADDR, 0x28);
  int16_t y = i2cRead16LE(LIS3MDL_ADDR, 0x2A);
  float heading = atan2f((float)y, (float)x) * 180.0f / PI;
  if (heading < 0.0f) heading += 360.0f;
  return heading;
}

static void headingToText(float deg, char out[3]) {
  const char *dir = "N  ";
  if (deg >= 22.5f && deg < 67.5f) dir = "NE ";
  else if (deg >= 67.5f && deg < 112.5f) dir = "E  ";
  else if (deg >= 112.5f && deg < 157.5f) dir = "SE ";
  else if (deg >= 157.5f && deg < 202.5f) dir = "S  ";
  else if (deg >= 202.5f && deg < 247.5f) dir = "SW ";
  else if (deg >= 247.5f && deg < 292.5f) dir = "W  ";
  else if (deg >= 292.5f && deg < 337.5f) dir = "NW ";
  out[0] = dir[0];
  out[1] = dir[1];
  out[2] = dir[2];
}

static uint8_t readBrightness() {
  uint16_t raw = analogRead(PIN_DIMMER_ADC);
  return (uint8_t)map(raw, 0, 1023, 2, 255);
}

static void printChargerStatus() {
  bool stat1High = digitalRead(PIN_CHG_STAT1);
  bool stat2High = digitalRead(PIN_CHG_STAT2);
  Serial.print("STAT1="); Serial.print(stat1High);
  Serial.print(" STAT2="); Serial.println(stat2High);
}

void setup() {
  pinMode(PIN_MAG_INT, INPUT);
  pinMode(PIN_MAG_DRDY, INPUT);
  pinMode(PIN_CHG_STAT1, INPUT);
  pinMode(PIN_CHG_STAT2, INPUT);
  analogReadResolution(10);

  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(100000);

  delay(100);
  bool magOk = initLIS3MDL();
  initIS31FL3731();
  Serial.println(magOk ? "LIS3MDL OK" : "LIS3MDL missing");
}

void loop() {
  char text[3];
  float heading = readHeadingDegrees();
  headingToText(heading, text);
  uint8_t brightness = readBrightness();
  setDisplayText(text, brightness);

  Serial.print("Heading="); Serial.print(heading, 1);
  Serial.print(" Display="); Serial.write(text, 3);
  Serial.print(" Brightness="); Serial.println(brightness);
  printChargerStatus();

  delay(250);
}
Build & Flash Instructions
  • Build: pio run
  • Flash using SWD-capable workflow supported by the chosen SAMD programmer/debug probe.
  • Serial monitor, if a UART/USB serial path is later added: pio device monitor
Notes
  • The schematic currently reserves SWD pins for programming. No USB or UART debug connector is wired yet.
  • Magnetometer calibration is required in final firmware for accurate in-vehicle heading.
  • The 7-segment character map is a starter approximation; tune glyphs once display orientation is confirmed.
  • Platform & Toolchain

  • Pin Mapping

  • Dependencies & Project Setup

  • Complete Firmware Source

  • Build & Flash Instructions

  • Notes

Assets

Assets are files uploaded to this project which can be used in various ways.

FJ Cruiser Solar Compass Display thumbnail
Dashboard-mounted solar-assisted automotive compass for a Toyota FJ Cruiser using three amber 7-segment displays, an I2C magnetometer, MCU/display driver control, LiPo energy storage, and a dimmer knob with off switch.

Properties

Properties describe core aspects of the project.

Pricing & Availability

Distributor

Qty 1

Arrow

$10.61–$13.49

Digi-Key

$15.99–$17.67

LCSC

$24.70

Mouser

$19.62

TME

$4.93

Verical

$5.35–$14.08

Controls