Sure, you can buy after-market lighting kits.   What fun is that?!? … especially for us Arduino-heads  :-)     If you aren't an Arduino-head,  I could be persuaded to put together a kit..    Of course you'll still will be able to program it any way you want.   

On the surface this is a pretty simple project.   It reads 3 on/off switches and drives 2 LED strips.  That’s all there is to it.   However, this isn’t your typical stand-alone Arduino project.   There are some subtle nuances of integrating it into an existing electrical system.   Also, Pixel animation  can be ... tedious, but hopefully my encapsulation can shield you from that. 

Here are the skills and the items you will need to build this project. 

Notes on the schematic

Building the circuit board

Building a test fixture

If you are modifying the code, you are going to want to build a test fixture so that you can develop and test at your desk rather than working on this out in the garage attached to your bike.   I built a 3 button project box that feeds 9v to each of the three relays (I lucked out that 9v can trigger the 12V relays).   The three buttons represent: left, right, and brake signals and you can test all combinations.

Arduino Projects

I learned early on in my Arduino adventures that it’s best to encapsulate  LED animations in class libraries.   That makes the animation reusable and cleans up your main Arduino sketch from all gory details allowing you to focus on what you want to do, not how to do it.    Compiling this Arduino sketch requires 4 downloads:

The method to my madness is to create instances of the LED animation classes specifying the LEDs to light, and the frequency.   Then you simply call the instance on each control loop and tell the instance if it should be on or off.   The instance keeps track of its blinking frequency and decides what to do.   With the animation fully encapsulated like this, there are only a handful of lines of code in the sketch itself (excluding the instantiation code). 

BlinkLed class library

Look in the header file of the class library for the details of the call sequence.  You can also find examples in the sketch for instantiating and calling it for the brake lights and running lights. Basically, you pass in an array of pixels to light and the frequency.   

example: 

byte  maxstop  = 25;
byte StopPix[MAXSINGLEROW] =  {
  3,  4, 5, 6, 7,
  11, 12, 13, 14, 15,
  19, 20, 21, 22, 23,
  27, 28, 29, 30, 31,
  35, 36, 37, 38, 39
};
BlinkLed LStopLed = BlinkLed(&lstrip, &StopPix, maxstop, R, G, B, 19);
BlinkLed RStopLed = BlinkLed(&rstrip, &StopPix, maxstop, R, G, B, 19);

CascadeLed class library

Look in the header file of the class library for the details of the call sequence.  You can also find examples in the sketch for instantiating and calling the turn signal signals.   Basically, you pass in a two dimensional array of pixels to light and the frequency.    The class will then cycle through the rows of pixels and light them in that order. 

WARNING:   For simplicity I have chosen to create constants to define the array sizes.   This burns some memory for the sake of ease of use.   If you choose to drive more than 40 pixels per bank, you may need to alter the constants defined in: CascadeLed\ArrayConstants.h.  

example:

const byte maxrow = 5;
const byte maxcol = 8;
byte Rightpix[MAXROW][MAXCOL] = {
  { 0,  1,  2,  3,  4,  5,  6,  7},
  { 8,  9, 10, 11, 12, 13, 14, 15},
  {16, 17, 18, 19, 20, 21, 22, 23},
  {24, 25, 26, 27, 28, 29, 30, 31},
  {32, 33, 34, 35, 36, 37, 38, 39}
};
CascadeLed RightLed = CascadeLed(&rstrip, &Rightpix, maxrow, maxcol, RT, GT, BT, 80, 900);

TailLight.ino Arduino Sketch

Not much to say here.   The control loop is REALLY simple:

void loop() {
  byte leftpin = (analogRead(LEFTPIN) > 50);
  byte brakepin = (analogRead(BRAKEPIN) > 50);
  byte rightpin = (analogRead(RIGHTPIN) > 50);

  // animate or clear the turn signals
  // PsudoOn is true when pin is high or between turn signal blinks (pin is actually low)
  bool lPsudoOn = LeftLed.Blink(leftpin);
  bool rPsudoOn = RightLed.Blink(rightpin);

  // animate or clear the brake lights
  LStopLed.Blink(brakepin);
  RStopLed.Blink(brakepin);

  // only turn on running lights.  Let the overlays turn it off
  // Clear will interfere with overlapping lights
  if (!lPsudoOn && !brakepin) LRunLed.Set(true);
  if (!rPsudoOn && !brakepin) RRunLed.Set(true);

}

Side notes:

Turn signals switch on and off and will disrupt the pixel animation because the blinking is faster than one animation sequence.   The CascadeLed class has a timeout value to absorb the “off” part of the blink and pretend that the signal is still on.   This pseudo on is passed back to the main control loop so it knows if the turn signal should be considered on or off instead of using the current pin value.  The only minor consequence is that the turn signals don’t immediately turn off when the turn signal is turned off.  

The pixel assignment between turn/brake/running lights overlap.   That means turning off one LED animation may turn off the LEDs of another animation.    This is only temporary until the current animation is on long enough to repaint its corrupted pixels.  I’ve only seen this problem between the turn signal and running light animations.   If you choose another running light pixel assignment, this may cause you problems.   I’ll consider fixing it if it becomes an annoyance for someone else.    For me I’m happy with current way it works.