/////////////////////////////////////////
// //
// SHIFT LIGHT CONTROL CODE //
// BY BEAVIS MOTORSPORT //
// //
/////////////////////////////////////////
// Credits/References:
// https://www.reddit.com/r/arduino/comments/515vsd/my_progressive_shift_light_because_racecar/
// https://github.com/MickTheMechanic/Shiftlight
// VERSION HISTORY
// 1.0 - 07 JAN 2023
// 1.1 - 06 FEB 2024 - BY BrokenAgain_Mini
// Added Headlight Dimming
// Added Warning Light for ECU
#include <Adafruit_NeoPixel.h> //NEO PIXEL LIBRARY TO DRIVE OUR LED STRIP
/////////////////////
// Pins and Vars //
/////////////////////
bool DEMO = true;
#define LedOutput 6 // LED Data Pin
#define LedLength 9 // Number of LEDs connected
#define RpmIn 2 // RPM SENSOR WIRE. 5V SQUARE WAVE 1 REVOLUTION = 2 HIGH INPUTS
#define DIM 3 // Headlight input
#define ECU 4 // ECU Warning input
int DIMState = 0;
int ECUState = 0;
Adafruit_NeoPixel LEDS = Adafruit_NeoPixel(LedLength, LedOutput, NEO_GRB + NEO_KHZ800);
unsigned long pulseTimeout = 1000000; // Wait for RPM pulse timeout, debug: 5000, default: 1000000
unsigned long currentRPM = 0; // Calulated RPM to ouptut (RpmIn * freqMultiplier)
unsigned int offRPM = 300; // RPM below which point we assume engine is off, or we have no RPM signal
const unsigned int startRPM = 3000; // Start RPM to begin turning on LEDs
const unsigned int endRPM = 6500; // Max RPM at which point all LEDs are illuminated, above this 'Shift-up' flash will occur
const unsigned int freqMultiplier = 60; // Base frequency multiplier, change this to suit your car. Miata fires twice per cycle, so to convert 'seconds' to 'minutes' we x30 instead of x60.
const unsigned int RPMperLED = (endRPM-startRPM)/LedLength; // Calulated RPM range per LED
//String(LEDstate) = "ledColour"; // Defines the LED pattern to use
const uint32_t startupColour = Adafruit_NeoPixel::Color(100, 100, 100); // white
const uint32_t shiftUpColour = Adafruit_NeoPixel::Color(255, 0, 0); //Red
const uint32_t shiftUpColourNight = Adafruit_NeoPixel::Color(50, 0, 0); //Red
const uint32_t offColour = Adafruit_NeoPixel::Color(10, 0, 25); //dark purple
const uint32_t ECUwarnColour = Adafruit_NeoPixel::Color(130, 150, 0); //Yellow
//////////////////////
// START THE CODE //
//////////////////////
//Define default LED colours, we have 8 LEDs so 8 colours.
//Color and Brightness is defined as an RGB value, 0 to 255 for each of R,G and B.
const uint32_t ledColour[LedLength] = {
Adafruit_NeoPixel::Color(0, 15, 150),//beavis-blue
Adafruit_NeoPixel::Color(0, 15, 150),//beavis-blue
Adafruit_NeoPixel::Color(0, 15, 150),//beavis-blue
Adafruit_NeoPixel::Color(0, 15, 150),//beavis-blue
Adafruit_NeoPixel::Color(0, 15, 150),//beavis-blue
Adafruit_NeoPixel::Color(175, 0, 0),//red
Adafruit_NeoPixel::Color(175, 0, 0),//red
Adafruit_NeoPixel::Color(175, 0, 0),//red
Adafruit_NeoPixel::Color(175, 0, 0),//red
};
// sets LED colour when headlights are on
const uint32_t ledColourNight[LedLength] = {
Adafruit_NeoPixel::Color(0, 0, 5),//dim blue
Adafruit_NeoPixel::Color(0, 0, 5),//dim blue
Adafruit_NeoPixel::Color(0, 0, 5),//dim blue
Adafruit_NeoPixel::Color(0, 0, 5),//dim blue
Adafruit_NeoPixel::Color(0, 0, 5),//dim blue
Adafruit_NeoPixel::Color(5, 0, 0),//red
Adafruit_NeoPixel::Color(5, 0, 0),//red
Adafruit_NeoPixel::Color(5, 0, 0),//red
Adafruit_NeoPixel::Color(5, 0, 0),//red
};
//Calculate the RPM when each LED should turn on
//First LED turns on at startRPM then each subsequent LED is an increment of RPMperLED
const unsigned int ledSetPoint[LedLength] = {
startRPM,
startRPM+(RPMperLED*1),
startRPM+(RPMperLED*2),
startRPM+(RPMperLED*3),
startRPM+(RPMperLED*4),
startRPM+(RPMperLED*5),
startRPM+(RPMperLED*6),
startRPM+(RPMperLED*7),
startRPM+(RPMperLED*8)
};
void setup() {
pinMode(DIM, INPUT);
pinMode(ECU, INPUT);
LEDS.begin(); // This initializes the NeoPixel library.
//run start sequence
for ( int i = 0; i < LedLength; ++i) {
LEDS.setPixelColor(i, startupColour);
LEDS.show();
delay(100);
}
//then reverse startup sequence
for ( int i = LedLength-1; i >= 0; --i) {
LEDS.setPixelColor(i, LEDS.Color(0, 0, 0));
LEDS.show();
delay(100);
}
if(DEMO){
offRPM = startRPM-500;
currentRPM = startRPM-1000;
}
}
unsigned long getRpm(){
float pulseHigh, pulseLow;
unsigned long rpmCalc[2] = {0,0};
//to get a better/cleaner result, we take two measurements to average.
for(int i=0; i < 2; i++){
//measure tach signal
pulseHigh = pulseIn(RpmIn, HIGH, pulseTimeout);
pulseLow = pulseIn(RpmIn, LOW, pulseTimeout);
rpmCalc[i] = 1000 / ((pulseHigh / 1000) + (pulseLow / 1000));
}
//to filter out noise we only consider our measurements valid if they are within a reasonable range of each other
if((rpmCalc[0] - rpmCalc[1]) < 8){
// then we use the average and mutiply the frequency to get RPM.
return ((rpmCalc[0] + rpmCalc[1]) / 2)*freqMultiplier;
}
return currentRPM; // otherwise, RPM is unchanged
}
void loop() {
//READS THE INPUTS
DIMState = digitalRead(DIM);
ECUState = digitalRead(ECU);
if(DEMO){
currentRPM = currentRPM+10;
delay(10);
}
else{
currentRPM = getRpm();
}
//ECU Warning flash
if (ECUState == HIGH){
LEDS.fill(ECUwarnColour);
LEDS.show();
}
else{
// Headlight dim
if (DIMState == HIGH){
if (currentRPM < offRPM) {
LEDS.setPixelColor(0, offColour);
LEDS.setPixelColor(LedLength-1, offColour);
LEDS.show();
}
else if (currentRPM < endRPM) {
for ( int i = 0; i < LedLength; ++i) {
if (currentRPM > ledSetPoint[i]) {
LEDS.setPixelColor(i, ledColourNight[i]);
}
else {
LEDS.setPixelColor(i, LEDS.Color(0, 0, 0));
}
}
LEDS.show();
}
else {
//Shift-Up flash
LEDS.fill(shiftUpColourNight);
LEDS.show();
delay(50);
LEDS.fill(LEDS.Color(0, 0, 0));
LEDS.show();
delay(50);
}
}
else{
// Illuminate LEDs based on current RPM value
if (currentRPM < offRPM) {
LEDS.setPixelColor(0, offColour);
LEDS.setPixelColor(LedLength-1, offColour);
LEDS.show();
}
else if (currentRPM < endRPM) {
for ( int i = 0; i < LedLength; ++i) {
if (currentRPM > ledSetPoint[i]) {
LEDS.setPixelColor(i, ledColour[i]);
}
else {
LEDS.setPixelColor(i, LEDS.Color(0, 0, 0));
}
}
LEDS.show();
}
else {
//Shift-Up flash
LEDS.fill(shiftUpColour);
LEDS.show();
delay(50);
LEDS.fill(LEDS.Color(0, 0, 0));
LEDS.show();
delay(50);
}
}}
}