Tachometer: Control board


With the face plate working, it was time to move on to the difficult part: Actually interfacing it with the car.

Under the assumption that the tachometer signal was a 12-ish volt square wave, I decided that using a high speed optocoupler would work well. If I tossed in a few diodes and a schmitt trigger to filter out any noise (There is a lot of noise on ignition components), I should be good to go.

Another person I found who made a similar project who goes by the name of Seanauff was kind enough to share his tachometer interface circuit with me:



Tachometer interface


Apparently there are optocouplers with built in schmitt trigger outputs in a nice 6-SMD package perfect for what I’m trying to do.


So, I built the circuit on a breadboard to test it out:




Annnd it didn’t work. Nothing. No output.

The Zener diode was regulating, the input to the optocoupler should have been functioning but I couldn’t get any output whatsoever.

Two hours later I decided to read the datasheet and immediately saw the words “Open collector” followed by “pullup resistor”.


Always read the datasheet to avoid moments like these.


With that out of the way, it seemed to be working properly. Input was inputting, schmitt trigger was schmitting AND triggering.

Time to test it out.



If I’m going to be honest, I had to fix a number of bugs in the code before it worked as intended. At first it was counting up but would never go back down…then I realized I was comparing a variable to itself.


Always remember to double check your code to avoid moments like these.


Anyway, the point is that it works and works well: If I blip the throttle, the leds are counting down with the engine speed while the stock needle gauge is still moving up. I’d call that an improvement.

With it working it was time to turn the breadboard mess into a circuit board that the face plate could be stacked on:




The first attempt didn’t turn out so well. I forgot to specify any design rules in Eagle, so the autorouter decided to make everything as close as possible. Combine that with misaligning the transparency sheets and you get a bunch of broken traces. Oh, and I forgot the i2c connector.


Eventually I got it etched and all soldered up, where something amazing happened.

I plugged it into the AVR programmer and it worked, first time.

No broken traces, no jumpers forgotten.

It just worked.







I think I’m starting to get good at this.


In case anyone else is attempting this, here’s the code I’m using (Arduino IDE):


#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#define LEDOUT 5 //Digital pin connected to neopixel data line, DO NOT USE PIN 2 OR 3 THEY BE FOR INTERRUPTS
#define numberOfRingLED 24 //Number of neopixels chained together
Adafruit_NeoPixel ring = Adafruit_NeoPixel(numberOfRingLED, LEDOUT, NEO_GRB + NEO_KHZ800); //Setup for neopixel ring
volatile unsigned int newRPM = 0; //New RPM value. Modified in interrupt so volatile. Will never be negative so unsigned int.
unsigned int oldRPM = 0; //Old RPM value. Will never be negative so unsigned int.
int RPMcurrentNumLED = 0; //Number of leds to be lit for the display (This is used on line 50, math is done with the RPM value and the increment value)
byte currentBrightness = 255; //Brightness for the LEDs. Valid values are 0 – 255. Could be used for a dimmer, or to automatically dim if you turn the headlights on
int incrementRPM = 250; //RPM increment. One LED being lit is equal to the increment number in RPM. Mine is set to 250, for 250RPM per LED. Note that all values are rounded DOWN, meaning at 499 RPM, only one led is lit. At 500-749 two are lit, etc.
volatile unsigned long LastPulseTime = 0; //previous pulse time, used to calculate RPM. Stored between interrupts, changed so volatile. Also, unsigned. If, for some reason, time goes negative, you’ve got bigger problems than the code not working.

void setup()

attachInterrupt(1, RPMPulse, RISING); //Upon detecting a rising edge on interrupt 1, it calls function RPMPulse
//Wire.begin(2); //For communicating with the information display via i2c
//Wire.onRequest(requestEvent); //Function to call when information is requested from this device

for (int i = 0; i < numberOfRingLED; i++) // This just lights up all leds in sequence and turns them off in sequence upon startup. Looks neat.
ring.setPixelColor(i, 0, 255, 0);
for( int i = numberOfRingLED; i > -1; i– )
ring.setPixelColor( i, 0, 0, 0 );


void loop()
if( newRPM != oldRPM ) //If the RPM has changed
// turn everything off
for( int i = 0; i < numberOfRingLED; i++ )
ring.setPixelColor( i, 0, 0, 0 );

RPMcurrentNumLED = newRPM / incrementRPM; //calculate how many leds to light up

for( int i = 0; i < RPMcurrentNumLED; i++ )
ring.setPixelColor( i, 0, currentBrightness, 0 ); //Tells the leds to light up, how bright, what color. My default is green.

ring.show(); //Turns the leds on
oldRPM = newRPM; //Changes oldRPM to the current value


void RPMPulse() //Upon detecting a pulse…
unsigned long t=micros(); //make a local variable, t, which is the time in microseconds since the program has been running

//if ( ( t-LastPulseTime)< 50 ) //Original code, //250 x 24 = 6,000 RPM so we can safely assume: Anything over 600,000 RPM is bounce
if ( ( t-LastPulseTime)< 100 ) //Modified debounce. If it’s over 300,000 it’s either bounce or you have a seriously high revving engine.

unsigned long PulseTime = t – LastPulseTime; // Gets pulse duration, finds the elapsed time between the current pulse and the last pulse.
newRPM = 30000000/PulseTime;       // Calculates RPM, for my 1993 Saab 900 the magic number was 30000000. Experement with this to get the value you need.
LastPulseTime=t; //Sets the last pulse time to the current pulse time.