All projects

Automotive Electronics · Embedded Systems · Hardware / PCB Design

NINA — Digital Instrument Cluster

A replacement instrument cluster for a 1990 Yugo 55 that reads the car's original sensors and signals. It measures engine RPM and vehicle speed with interrupts, converts the stock fuel and coolant senders into calibrated readings, and drives an LED tachometer, a seven-segment speedometer, warning lights and two OLED gauges.

Prototype· vehicle-tested, ~30 km2025 – 2026
Source on GitHub(opens in a new tab)
NINA / INSTRUMENT CLUSTER
ESP32DASH CONTROLLERIGNITIONSENDERS12V INPUTSRPM BARSPEEDOOLED GAUGES
Hardware → firmware → softwareConcept diagram
Front-panel artwork for the NINA cluster: curved RPM scale from 0 to 8, a km/h speed readout, fuel and temperature bars and a row of warning-light icons.
Early front-panel artwork iteration (design file, not a photo of the hardware).

Problem and motivation

The Yugo 55 uses a simple analogue cluster driven directly by the car's senders and wiring. I wanted a digital cluster that keeps the factory sensors and harness, so the car stays original underneath, while giving clearer readouts and a platform I fully understand and can extend.
It was also a deliberate exercise in the parts of embedded work that only show up in a vehicle: noisy 12 V signals, non-linear resistive senders and an ignition signal that is far from a clean square wave.

Requirements

  • Read engine RPM from the ignition signal and road speed from a pulse sensor.
  • Convert the stock coolant-temperature and fuel-level senders into calibrated values.
  • Accept 12 V warning inputs (brake, oil pressure, indicators, lights, high beam, fog) without exposing the MCU to vehicle transients.
  • Display RPM, speed, fuel, temperature and warnings clearly while driving.
  • Draw no meaningful current with the ignition off, and keep the odometer across power cycles.

Hardware architecture

The system is split across two custom PCBs designed in KiCad: an input/controller board carrying the ESP32 and all signal conditioning, and a display board carrying the indicators.
  • Digital 12 V inputs pass through optocouplers and are read active-low; the ignition-on signal uses a 4N35 and doubles as the deep-sleep wake source.
  • The coolant and fuel senders form voltage dividers with known pull-up resistors (510 Ω and 330 Ω) on ADC1 pins, which stay usable while Wi-Fi is active.
  • The display board is a 4-layer, 1.6 mm, 228 × 110 mm PCB with a 29-LED RPM bar, a three-digit seven-segment speedometer, 12 warning LEDs and two 128×32 SSD1306 OLEDs for fuel and temperature.
  • LEDs are driven through 74HC595 shift registers, so dozens of outputs need only a few GPIOs per group.
  • The supply input has a fuse, an SMCJ36A TVS diode, reverse-polarity protection and a common-mode choke, with TE automotive-style connectors to the harness.

Firmware architecture

Firmware is C++ on the Arduino framework, built with PlatformIO and split into small libraries with one job each: RPM input, speed input, analogue sensors, warning lights, display multiplexing, odometer, UI screens and OTA updates. A main loop polls each module; time-critical work happens in interrupts.
  • RPM: a falling-edge ISR in IRAM timestamps ignition pulses. Edges arriving faster than physically possible are rejected without moving the reference timestamp, so ignition ringing cannot corrupt the next measurement. An adaptive window then rejects pulses arriving earlier than 80 % of the previous valid period (60 % at low RPM, to follow throttle blips). After 500 ms without a valid pulse the engine is treated as stopped.
  • The RPM output is smoothed with an adaptive filter: heavy smoothing when steady, a faster response when RPM changes by more than 300, plus a slew limiter for the LED bar.
  • Speed: an ISR counts sensor pulses, which are converted to km/h and distance using a calibrated distance-per-pulse constant.
  • Senders: the firmware computes each sender's resistance from the divider and interpolates a lookup table, instead of mapping raw ADC counts, so a hardware change to the pull-up does not break calibration. Implausible readings caused by connection glitches are rejected.
  • Power: when the ignition-on input goes inactive, the odometer is saved and the ESP32 enters deep sleep with an external wake-up on that input.
  • OTA firmware updates over Wi-Fi with a captive-portal setup, so the cluster can be updated without being removed from the car.

Engineering decisions

  • Galvanic isolation on every 12 V digital input, rather than dividers alone, to keep alternator and ignition transients away from the MCU.
  • Resistance-based sender calibration, so the calibration describes the sensor rather than one specific circuit.
  • Shift-register multiplexing to keep the GPIO count manageable on the ESP32, at the cost of refresh timing that has to be handled carefully in firmware.
  • Two boards, so the conditioning and protection circuitry can change without re-spinning the large display board.

Problems and challenges

  • Ignition ringing produced spurious edges on the RPM input. The firmware rejects them using the timing rules described above.
  • The fuel and temperature senders are non-linear and differ from car to car. They were characterised with multi-point tables rather than a single formula.
  • Several ESP32 boot-strapping pins (GPIO 2, 12 and 15) are used for outputs and inputs. Their default states had to be considered so the board still boots reliably.

Testing and validation

  • Fuel calibration points were taken across the tank range (0, 25, 50, 75 and 100 %), and the temperature curve uses sender resistance points between 20 °C and 100 °C.
  • Installed in the car and driven for approximately 30 km with no observed problems.

Results

A complete, working prototype that has been installed and tested in the vehicle. It is a personal engineering project, not a production-certified automotive product, and has not been through automotive EMC or environmental qualification.

Potential improvements

  • Longer road testing across temperature extremes, with logged data instead of observation only.
  • Bench transient testing of the supply input against representative automotive pulses.
  • Validate speed accuracy against GPS over a measured distance.
  • An enclosure and mounting designed for long-term vibration resistance.