Community project

Isolated ESP32 Motorcycle Tachometer

ESP32
Photo of Isolated ESP32 Motorcycle Tachometer
Generated with AI

Cyril Stephan

Published October 14, 2026

This project turns an ESP32 into a motorcycle tachometer by reading ignition pulses from the engine and converting them to RPM. The system uses an optocoupler for electrical isolation between the motorcycle's ignition circuit and the microcontroller, protecting the ESP32 from voltage spikes and noise. Builders will receive a complete wiring diagram showing the optocoupler connection, a parts list with all components needed, and firmware with debouncing logic to filter electrical noise.

The guide includes step-by-step assembly instructions for connecting the isolated input to the ESP32, uploading the interrupt-driven firmware, and verifying RPM readings via serial output. The firmware uses hardware interrupts and atomic operations to accurately count ignition pulses in real time, making it suitable for 2-stroke engines where one spark equals one revolution.

Wiring diagram

Deploy the firmware

schematik_esp32.inoOpen in Schematik
#include <Arduino.h>

const byte PIN_RPM = 22; // Entrée signal optocoupleur avec pull-up externe de 10k

volatile uint32_t impulsions = 0;
volatile uint32_t derniereImpulsionUs = 0;

const unsigned long intervalle = 500;        // Fenêtre de calcul de 500ms
const uint32_t DELAI_MIN_IMPULSION_US = 3000; // Filtre anti-parasite (3ms)
uint32_t rpm = 0;                             
unsigned long precedentMillis = 0;

// Routine d'interruption avec filtre anti-parasite
void IRAM_ATTR compterImpulsion() {
  uint32_t maintenant = micros();
  if (maintenant - derniereImpulsionUs >= DELAI_MIN_IMPULSION_US) {
    impulsions++;
    derniereImpulsionUs = maintenant;
  }
}

void setup() {
  Serial.begin(115200);
  
  // Configuration de l'entrée RPM
  pinMode(PIN_RPM, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_RPM), compterImpulsion, FALLING);
  
  Serial.println("Systeme pret. En attente du signal de la DTR 125...");
}

void loop() {
  unsigned long actuelMillis = millis();
  
  if (actuelMillis - precedentMillis >= intervalle) {
    uint32_t impulsionsMesurees;
    
    // Section critique protégée (lecture atomique)
    noInterrupts();
    impulsionsMesurees = impulsions;
    impulsions = 0;
    interrupts();
    
    precedentMillis = actuelMillis;
    
    // Calcul du régime (1 étincelle = 1 tour pour moteur 2 Temps)
    rpm = (impulsionsMesurees * 60000UL) / intervalle; 
    
    // Envoi des données sur le port USB
    Serial.print("Impulsions: ");
    Serial.print(impulsionsMesurees);
    Serial.print(" -> RPM: ");
    Serial.println(rpm);
  }
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik