Here's a quick and easy shiftlight I made for an acquantance who needed something for his racecar.
Basically just an arduino using interrupt counting to determine RPM, and some overcomplicated code to drive a trio of 10mm 6000 MCD LED's.
Hardware is nothing interesting.. 3x TIP112 overkill transistors and a handful of resistors. Took me the best part of 2 weeks to put everything together in casings and such though.
I added some fade and blink code to make it less boring but in the end simple on/off functionality is all that is required.
Did learn how much difference there can be in LED brightness.. Had to drive my green LED at 8%, the red one at about 50%, and the yellow one at 100% to get them at a similar brightness.
The PCB with all the potmeters is a megasquirt diy efi stimulator board (JimStim) that can emulate the mazda's crank angle sensor for me (for rpm input).
The green led gradually comes on between 500-1000rpm and fades
off from 4000 to 4500 rpm when the powerband comes in.
The yellow led is the shiftpoint indicator, and the blinking red one indicates that the engine is at redline.
It's my first arduino project with hardware so I hope someone can appreciate it... :-[
I'll ask the guy to make some footage of it once it is mounted in the miata. I havent got a chance to test it yet, my mazda familia uses a different ignition system. Might try it on moms miata if the opportunity arises.
The little black box is approx. 5cm wide and 2,5cm deep. It is meant to be mounted on the steering column or wherever in the line of sight.
The bigger red box contains the Arduino and the interfacing PCB.
Could have tried to stuff the transistors in the little box, but it was nigh impossible since I had to assemble everything inside the box (because it was impossible to stuff the leds back into the holders when attached to the PCB).
For anyone interested in the supply part of things, I'm using some pcb's taken from DealExtreme car USB chargers (5V, 1A), and am feeding that directly into the 5V pin of the arduino. Cheapest way to get reliable power without overheating the linear regulator of the Arduino.
Anyway, this way I have room for expansion.. I'd like to tap into some important sensors like oil pressure and temp so I can use the red LED as a warning light.
For my own purposes I'm planning something a little more ambitious.
Busy putting together something that will allow me to log:
-sensor data
-rpm
-accelerometer data (wiichuck offcourse)
-gps
And then throw in an LCD or other type of display for eye-candy's sake. My brother is busy putting together some code in matlab that plots gps data onto google maps tiles (coming together nicely).
Would be nice if I could get a laptime display too.
Sort of poor mans datalogger, I'm guessing I will have to use my Arduino Mega for that project.
Small update: have some pics of the shiftlight mounted in position:
Sadly no race footage of it in action, there wasnt enough time to connect everything before race weekend.
On a happy note, the car grabbed first place on it's very first official race in our dutch Max5 cup (similar to US spec miata cup)
The electrical circuit is very straightforward for a mazda mx5. I use a 4N32 optocoupler with a pull-up resistor on the input side anode, that is connected to the tach wire with a protection diode thrown in for good measure. The MX5 has a switched-earth type tach signal, for cars with coil-negative style tach signals you need a different circuit.
Just have a look at the megasquirt (msefi.com) manuals, there is a whole manual dedicated to tach circuits and such for most types of cars.
My tach code is just an adapted version of the motorcycle tach someone here on the forum made, which keeps track of time between 10 pulses if I remember correctly. I used it because it has a running average filter that I thought would be useful back when my testsetup was very noisy. Does have a problem that it has some awkward behaviour when RPM drops to zero (it keeps outputting the last nonzero value until it's arrays are empty), but it was actually not a bad thing for the shiftlight.
BTW, you the guy that created the multidisplay project? Your nickname rings a bell.
Here you go... like I said before (I think): it's overly complicated for what it does, but let's just call it feature-rich
BTW, I have a clip of the thing in action. The guy I built it for has won the championship in his first year (nothing to do with the shift light though).
// RPM Calculation variables
unsigned long lasttachint; //time in millis of most recent tach interrupt
float insttachrpm=0, avgtachrpm=0; //tach instantaneous and rolling average rpm
volatile int ilog=0;
int maxlog=31;
volatile boolean logoflo; //index to log table, max entry #, log overflow flag
volatile byte lflag[32];
volatile unsigned long lmillis[32]; //log of interrupts
// Blink
int BlinkState = LOW;
long previousMillis = 0;
long interval = 500;
// Pin assignment
int LEDPinLow = 9;
int LEDPinHigh = 10;
int LEDPinAlert = 11;
// Calibration values for LED intensity (to even the intensity levels out)
double LEDIntensityGreen = 0.08;
double LEDIntensityOrange = 1;
double LEDIntensityRed = 0.4;
// RPM Setpoints
int RPMSetpointRunning = 800; // Engine running / shiftlight operational (green led on)
int RPMSetpointPowerband = 4000; // Bottom of powerband (green led off)
int RPMSetpointShift = 6800; // Shiftpoint (orange led on above this rpm)
int RPMSetpointAlert = 8200; // Overrev alert (red led blinking)
// Init function
void setup()
{
// Initialize serial comms
//Serial.begin(115200);
// Clear the interrupt table
clearlog();
// Set LED pinmodes to output
pinMode(LEDPinLow, OUTPUT);
pinMode(LEDPinHigh, OUTPUT);
pinMode(LEDPinAlert, OUTPUT);
// Attach interrupt from tachometer
attachInterrupt(0, tachmonitor, RISING);
}
// Main loop
void loop()
{
generateBlink();
checklog(); //make sure log is ok before processing
processlog(); //process and dump the log file to the PC
chkstale(); //check to see if tach readings are >3 seconds old (stopped!)
updatedisplay();
delay(50); //wait a while
}
// Blink function. This generates a blinking signal we can
// use everywhere we need to in the code.
void generateBlink()
{
unsigned long currentMillis = millis();
if( currentMillis - previousMillis > interval )
{
previousMillis = currentMillis;
if( BlinkState == LOW )
BlinkState = HIGH;
else
BlinkState = LOW;
}
}
// Fades a led on or off (iDirection) across a variable revrange
int fadeLED( float rpm, float initial, float range, int iDirection )
{
float result;
// Fade out
if( iDirection == 0 )
{
if( rpm > initial )
{
if( rpm > initial + range )
{
result = 0;
}
else
{
result = 100 - ( ( rpm - initial ) * ( 100 / range ) );
}
}
else
{
result = 100;
}
}
else // Fade in
{
if( rpm > initial )
{
if( rpm > initial + range )
{
result = 100;
}
else
{
result = 0 + ( ( rpm - initial ) * ( 100 / range ) );
}
}
else
{
result = 0;
}
}
return (int)result;
}
// This function returns a blink signal
// at a variable brightness level.
int blinkLED( int iPercentage )
{
return BlinkState ? iPercentage : 0;
}
// Output function. This function does the actual lighting
// of the LED's after applying some brightness calibration.
void lightLED( int LEDPin, int iPercentage )
{
int iResult;
int iMax = 255;
// Brightness adjustment
switch( LEDPin )
{
// GREEN
case 9:
iResult = (iPercentage / 100) * iMax * LEDIntensityGreen;
break;
// ORANGE
case 10:
iResult = (iPercentage / 100) * iMax * LEDIntensityOrange;
break;
// RED
case 11:
iResult = (iPercentage / 100) * iMax * LEDIntensityRed;
break;
}
analogWrite( LEDPin, iResult );
}
// Output function (controls LED behaviour)
void updatedisplay()
{
//Serial.print(int((avgtachrpm+50)/100)*100); //tach
// Green LED is regulated with fade functions
if( avgtachrpm < RPMSetpointRunning )
lightLED( LEDPinLow, fadeLED( avgtachrpm, 500, 500, 1 ) );
else
lightLED( LEDPinLow, fadeLED( avgtachrpm, RPMSetpointPowerband, 500, 0 ) );
// Orange LED is ON above shift RPM setpoint, OFF below it.
if( avgtachrpm < RPMSetpointShift ) lightLED( LEDPinHigh, 0 );
if( avgtachrpm >= RPMSetpointShift ) lightLED( LEDPinHigh, 100 );
// Red LED is blinked above alert RPM setpoint, OFF below it
if( avgtachrpm < RPMSetpointAlert ) lightLED( LEDPinAlert, 0 );
if( avgtachrpm >= RPMSetpointAlert ) lightLED( LEDPinAlert, blinkLED(100) );
// Serial output for debugging
/*
Serial.print((unsigned int)insttachrpm);
Serial.print(", avg: ");
Serial.print((unsigned int)avgtachrpm); //tach
Serial.print("\r\n");
*/
}
void chkstale()
{
if ((millis()> (lasttachint+3000)) && (insttachrpm>0)){
insttachrpm=0; avgtachrpm=0;
}
}
// Make sure log hasn't overflowed
void checklog()
{
if (logoflo==true)
{
noInterrupts();
clearlog();
interrupts();
Serial.print("log overflowed at ");
Serial.println(millis());
}
}
void processlog()
{
int dlog,i; //how many entries to copy and dump
unsigned long dmillis[32];
byte dflag[32]; //space for the log entries
noInterrupts(); //disable interrupts while we copy the log
dlog=ilog; //copy the count
if (dlog!=0) //if there's anything to copy
{
for(int i=0; i<dlog;i++)
{
dmillis[i]=lmillis[i];
dflag[i]=lflag[i];
}
ilog=0; //reset the count
}
interrupts(); //reenable interrupts
if (dlog!=0) //if there's anything to process
{
for (i=0;i<dlog;i++)
{
if (dflag[i]=='T')
{
processtach(dmillis[i]);
}
}
}
}
// RPM Calculation logic
void processtach(unsigned long lmillis) // input is interrupt time in millis
{
unsigned long tachtime; //elapsed time for 10 tach events
if (lasttachint!=0) //skip calculation on first time in
{
tachtime=lmillis-lasttachint; //time for last 10 events
if (tachtime>0) //just avoid divide by 0
{
insttachrpm=(60000/(float)tachtime)*10; //rpm based on time in ms for 10 tach pulses
avgtachrpm=(avgtachrpm*3+insttachrpm)/4; //rolling average rpm
}
}
lasttachint=lmillis;
}
// Tach interrupt function
void tachmonitor()
{
static int tachcount;
if (tachcount <9) //bulking up tach interrupts by a factor of 10
{
tachcount++;
}
else //every 10th tach pulse we spawn a log entry
{
if (ilog<=maxlog)
{
lflag[ilog]='T'; //pedal pass flag
lmillis[ilog]=millis();
ilog++;
}
else
{
logoflo=true; //we've overrun the log table
}
tachcount=0;
}
}
void clearlog() //clear the interrupt log
{
for(int i=0; i<=maxlog; i++)
{
lflag[i]=' ';
lmillis[i]=12345678;
}
ilog=0;
logoflo=false;
}
The red case was a generic one that I bought from Conrad (should be a household name for many european hobbyists).
It is relatively flimsy though, I would only use it for portable applications since it is all lightweight plastic. The little black housing I used for the led's is much better quality and also available in bigger sizes.