DIY Dashboard Compass Project
Table
| Area | What I did | Current state |
|---|---|---|
| Project setup | Renamed the project and updated the description to match the FJ Cruiser solar compass concept | Done |
| Architecture notes | Created Design Notes for the system intent | Done |
| Major components | Added the MCU, magnetometer, LED driver, charger, LDO, displays, dimmer/switch, connectors, protection, and passives | Done |
| Power wiring | Wired solar input through fuse, Schottky reverse protection, TVS clamp, charger input, battery, charger SYS output, switched rail, and 3.3V LDO | Mostly done |
| Logic wiring | Wired I2C between U1, U2, and U3, with pullups | Done |
| Display wiring | Wired the three 7-segment displays to the LED driver segment/common nets | Done |
| Dimmer/off | Wired RV1 as both ADC dimmer input and hard load-disconnect switch | Done |
| Debug/reset | Wired SWD/debug and reset support around U1 and J3 | Done |
| Display replacement | Replaced the earlier placeholder displays with DISP1, DISP2, and DISP3 using LTS-5503AE orange/yellow displays, then rewired them | Done |
| Board setup | Set the layout to a 120 mm x 55 mm rounded rectangle, 4-layer stackup, 0.2 mm keepout, and 0.25 mm trace width | Done |
| PCB placement/routing | Started moving toward layout work, but placement and routing were interrupted before completion | Not done |
Table
| Function | MCU Pin | Net | Connected To | Direction | Notes |
|---|---|---|---|---|---|
| I2C SDA | PA22 | I2C_SDA | LIS3MDL SDA, IS31FL3731 SDA, R1 | Bidir | 4.7k pull-up to 3.3V |
| I2C SCL | PA23 | I2C_SCL | LIS3MDL SCL, IS31FL3731 SCL, R2 | Output | 4.7k pull-up to 3.3V |
| Dimmer ADC | PA02 | DIMMER_ADC | RV1 wiper | Input | 0V to 3.3V brightness control |
| Magnetometer INT | PA04 | MAG_INT | LIS3MDL INT | Input | Optional interrupt |
| Magnetometer DRDY | PA05 | MAG_DRDY | LIS3MDL DRDY | Input | Data-ready input |
| Charger STAT1 | PA06 | CHG_STAT1 | BQ25185 STAT1 | Input | Open-drain with 10k pull-up |
| Charger STAT2 | PA07 | CHG_STAT2 | BQ25185 STAT2 | Input | Open-drain with 10k pull-up |
| SWDIO | PA31 | SWDIO | J3 pin 2 | Debug | Reserved for programming |
| SWCLK | PA30 | SWCLK | J3 pin 4 | Debug | Reserved for programming |
| Reset | RESETN | RESET | J3 pin 10 | Input | Pull-up plus filter cap |
platformio.ini:Ini
[env:zeroUSB] platform = atmelsam board = zeroUSB framework = arduino monitor_speed = 115200 lib_deps =
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); }
pio runpio device monitorPlatform & Toolchain
Pin Mapping
Dependencies & Project Setup
Complete Firmware Source
Build & Flash Instructions
Notes