Automotive dashboard display

Here is code for an automotive display - so far only AFR / Lambda and RPM. Plan to include GPS speed & odo & running fuel consumption, rpm based on actual scoped frequency from an aftermarket ecu, narrowband AFR formula courtesy of Google. Criticism, comments and improvements welcome.

#include <LiquidCrystal.h>

// ---------------- LCD ----------------
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// ---------------- PINS ----------------
const byte RPM_PIN = 2;   // ECU RPM output (INT0)
const int afrPin = A0;    // AFR analog input

// ---------------- RPM ----------------
volatile unsigned long lastPulseMicros = 0;
volatile unsigned long periodMicros = 0;
volatile bool newPeriod = false;
unsigned int rpm = 0;
unsigned long lastRPMUpdate = 0;
const unsigned long RPM_TIMEOUT = 300000; // 300 ms no pulses → RPM = 0
const unsigned long LCD_REFRESH = 200; // ms (5 Hz)
unsigned long lastLCDUpdate = 0;
unsigned int lastDisplayedRPM = 65535;
// ---------------- AFR / Lambda ----------------
float afr = 14.7;
float lambda = 1.0;

// ---------------- Warm-up ----------------
const unsigned long WARMUP_TIME = 180000;  // 3 minutes
unsigned long startMillis;

void rpmISR() {
  unsigned long now = micros();
  periodMicros = now - lastPulseMicros;
  lastPulseMicros = now;
  newPeriod = true;
}

void setup() {
  lcd.begin(16, 2);
  lcd.clear();

  lcd.setCursor(0, 0);
  lcd.print("RPM:");

  lcd.setCursor(0, 1);
  lcd.print("AFR:");
  lcd.setCursor(10, 1);
  lcd.print("L:");

  pinMode(RPM_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(RPM_PIN), rpmISR, FALLING);

  startMillis = millis();
}

void loop() {
  unsigned long nowMillis = millis();

  // -------- RPM FROM FREQUENCY --------
  if (newPeriod) {
    noInterrupts();
    unsigned long p = periodMicros;
    newPeriod = false;
    interrupts();

    if (p > 0) {
      float frequency = 1000000.0 / p;   // Hz
      rpm = (unsigned int)(frequency * 40.0);
      lastRPMUpdate = micros();
    }
  }

  // RPM timeout → engine stopped
  if (micros() - lastRPMUpdate > RPM_TIMEOUT) {
    rpm = 0;
  }

 // -------- DISPLAY RPM (SLOW REFRESH) --------
if (millis() - lastLCDUpdate >= LCD_REFRESH) {
  lastLCDUpdate = millis();

  if (rpm != lastDisplayedRPM) {
    lcd.setCursor(4, 0);
    lcd.print("     ");
    lcd.setCursor(4, 0);
    lcd.print(rpm);

    lastDisplayedRPM = rpm;
  }
}

Welcome to the forum

All that on a 16 by 2 LCD display ?

1 Like
  1. Do not use single-characters for variables. Use descriptive words.
  2. Why are you assigning periodMicros to p? Just use periodMicros
  3. Why are you using both millis() and micros()? Just use micros()

Thanks for advice - still learning. Using a 162 lcd as thats what I had, but for full project will use 2 320480 TFT displays - 1 for speed, odo, Temp, 2nd for RPM. AFR, fuel gauge & fuel consumption(amt of fuel added entered via keypad). This is for an old project car being built with aftermarket ecu, so AFR needed.