Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Motorcycle Signal Lights

4.98/5 (23 votes)
30 Jun 2018CPOL7 min read 35.9K   454  
Using an Arduino Nano to control all the working lights on an old 1978 Suzuki GS500

Introduction

So a few years ago, I started building custom motorcycles in my free time as a way to relax after work. The thing about custom bikes is that they are usually in someway original in looks. The first two bikes built did have an Arduino running the turn signals and working with the brake lights, but this one I wanted to be different. You can see the build here if you are curious.

I wanted all the lighting to be LED ran, but online options were pretty poor, so I had to build all my own marker lights to fit with the look of the bike.

Although the "finished" product is still going through revisions, it's stable enough to use with my summer daily driver.

Background

The Arduino code is fairly straight forward; it is not optimized yet, uses no external libraries other than <avr/wdt.h> for a watchdog timer.

The circuit is a little more complicated, but not too bad to wire up. The reasons all the extra parts had to be added, instead of using the Arduino to run the lights directly; each pin can only handle about 20ma @ 5vdc. The super bright white LEDs I sourced from Digi-key consumed about 20ma apiece. even if ran in series to reduce the amp load, you still drop around 2vdc per LED, limiting each output pin to 2 LEDs, and that was not enough for what I needed. So some PNP Darlington transistors were added to the mix so I could pump out a total of 1.0A per output if I needed to.

Here's a rough sketch of what was built.

Image 1

The above drawing ended up looking like the following below:

Image 2

Image 3

Image 4

Image 5

Here a snippit of the full electrical (there is a full version of it in the zip file as a PDF and PNG file)

Image 6

Using the Code

First off button de-bouncing! There are actually 2 versions of the button de-bouncing done: one for the brake switches and the other for turn signals and high/low beam selector. All of the inputs are set HIGH, by setting this pin to ground (through the frame chassis), this tells the code that the brake is pushed.

But, you can get a lot of false positives as the switch is almost activated or the bike goes over a bump. So there needs to be a delay time where we have to see the input in a constant state for x amount of time; I've found around 100ms works just fine.

The code below will set a time stamp every time the input switches state, if the timestamp exceeds the 100ms threshold, we know this is likely a solid state. if the state now also matches _on (LOW) then the brake lights should come on.

C++
bool isBrake() {
  static long lasttm;
  static int last;
  int state = digitalRead(switchB);
  bool ret = false;

  if (last != state) {
    lasttm = millis();
    last = state;
  }

  if ((millis() - lasttm) > 100) {
    ret = last == _on;
  }

  return ret;
}

Letting go of the brake switch will flip the state and return false, no time delay.

The turn signal buttons have a slightly different flavor. The code is similar to the brake logic, but now I only want the one instance when the button is initially pressed to trigger some other code; this is handled by the triggered boolean and the code will only return true one time for each time it is pressed, kind of like pressing the power button on your computer.

C++
bool isLeftTurn() {
  static long lasttm;
  static int last;
  static bool triggered;
  int state = digitalRead(switchL);
  bool ret = false;

  if (last != state) {
    lasttm = millis();
    last = state;
    triggered = true;
  }

  if ((millis() - lasttm) > 100 && triggered) {
    ret = last == _on;
    triggered = false;
  }
  return ret;
}

The setHiLowBeam works pretty much the same except it handles the output side also.

C++
void setHiLowBeam() {
  static long lasttm;
  static int last;
  static bool triggered;
  int state = digitalRead(switchH);

  if (last != state ) {
    lasttm = millis();
    last = state;
    triggered = true;
  }

  if ((millis() - lasttm) > 100 && triggered && last==_on) {
    digitalWrite(HILOW, digitalRead(HILOW) == LOW ? HIGH : LOW);
    triggered = false;
  }
}

Turn Signal Timers

From the main loop, it will call a simple function taking the current millisecond time stamp and switching back and forth the ison variable, the calling code just wants to know what state the blinker should be in, even if the turn signal is not currently running.

C++
int isBlinkTime(unsigned long curr){
  static unsigned long fut_time;
  static int ison;
   if (curr > fut_time) {
    ison = !ison;
    fut_time = curr+_blinktime;
  }
  return(ison ? _off : _on);
}

If you have ever ridden a Motorcycle on the road or been behind one, you know that sometimes, we forget to turn off those pesky blinkers, sometimes for miles and miles, so this next function will check for a 3 minute timeout and weather to start a turnsignal sequence.

The L & R (left & right) are gotten from asking the momentary isLeftTurn and isRightTurn functions from the main loop. If the blinker exceeds the fut_time time stamp, it will set currTS to none and cancel the signal.

If either the left or right was pressed and we are not in sequence, then start a new one with the next timeout. pressing either button in sequence also cancels the sequence.

C++
void CheckTurnSignals(unsigned long curr, bool L, bool R){
  static unsigned long fut_time; 
  if (curr >= fut_time) {
    currTS = none;
  }

  if (L || R) {
    if (currTS == none) {
      //the turn signal needs to start
      //and run for a max of 3 minutes
      fut_time = curr + _timeout;
      currTS = L ? left : right;
    } else {
      //the turn signal is being cancelled, because of a
      //secondary push to either button
      currTS = none;
    }
  }
}

Getting the Driver's Attention Behind You

Flash them brakes obnoxiously, at least 10 times to clue them in that you are stopping, and then go solid brake. This is another timer/ counter function to determine the state of the brake lights. The function gets the current millisecond timestamp, if the brakes are pressed and a pointer to a on/off value for flashing, and return is we are still in flash state.

C++
bool isBrakeFlashTime(unsigned long curr, bool B,int *state){
  static unsigned long fut_time;
  static int flshcntr;
  static int flserstate;
    
  if (curr >=fut_time){
    flserstate = !flserstate;
    if(B && flserstate)flshcntr++;
    fut_time=curr+_flshSleapTime;  
  }
 
  *state = flserstate;
 
  if (!B)
    flshcntr = 0; //no brake, reset the counter
  else if(flshcntr <= _flshCntrMax)
    return true; //has brake and still has flashes

  return false; //fall though, not in flash
}

Multiuse Brake Light

The brake light is split up into 4 sections: outer ring, inner ring, left half, right half (Rear Left Turn signal, Rear Left Brake, Rear Right Brake and Rear Right Turn signal), so with the 4 sections I can:

  • solid full brake (all lights on)
  • marker lights (outer ring on)
  • pulsing brake (inner and outer flash back and forth)
  • Animated (more on this later)
  • left turn (RLTS flashing, RRTS solid)
  • left turn with brake (RLTS flashing, RRB & RRTS on solid)
  • right turn (RRTS flashing, RLTS solid)
  • right turn with brake (RRTS flashing, RLB & RLTS on solid)

The animated turn signal "walks" the lights left or right across all 4 sets (unless the brake is pressed), it's kinda showy but it gets attention.

C++
byte updateRearTurnBits(unsigned long curr){
  static unsigned long fut_time;
  static byte scrcnt;
  static byte leftscroll;
  static byte rightscroll;
  if(curr>=fut_time){
    fut_time = curr+_scrollTime;
    scrcnt =++scrcnt % 4;
    leftscroll = (leftscroll<<1) | (!scrcnt & 1);
    rightscroll = (rightscroll >>1) | (!scrcnt<<7);
  }
  if(currTS == left)
    return leftscroll;//scroll from right to left
  else
    return rightscroll; //scroll from left to right
}

The code updates two bytes, one that travels left and one that travels right. Each time the fut_time exceeds, it updates the line:

C++
scrcnt =++scrcnt % 4;

That basically resets the count to 0 on the fourth count. I use this as a marker to flip the bits !scrcnt and if the bit I'm looking for (0x1 for left or 0x80 and pushing it into each sequence to make it the bytes scroll. Then return the left scroll or right scroll depending on the current turn signal state.

The Loop

The main loop is probably the simplest of all the code:

C++
void loop() {
  //====reset the watchdog on each loop===
  wdt_reset();

  //=====Get the statuses=====
  bool B = isBrake();
  bool L = isLeftTurn();
  bool R = isRightTurn();
  setHiLowBeam();
 
  //====update the timers====
  unsigned long curr = millis();
  int blnk = isBlinkTime(curr);
  int rearflashstate;
  bool brkflsh = isBrakeFlashTime(curr,B,&rearflashstate);
  byte rtb = updateRearTurnBits(curr);
  CheckTurnSignals(curr,L,R);

//update all lights depending on the current state.
}

The top chunk will reset the watchdog timer and get the state of the inputs. the second part will get the current millisecond time and figure out the rest of the current state. I'm not going to bother showing the digital outputs triggering as it's very basic, but if you want to see the rest download the code and try it out.

Points of Interest

One of the main reasons I wanted to share this is the hidden gotchas that happen when adding a mini computer to an old electronically noisy bike, where everything from the coils to the points make a lot of EM interference:

  • Optically isolate all your inputs and outputs if they are leaving the safety of the project box
  • Project box should be metal and grounded to the chassis.
  • De-bouncing inputs is critical when in the live world, so many false positives happen when just driving down the road.
  • Don't rely on an Arduino voltage regulator alone, they are good, but not hefty enough to work with a lot of external circuits. Put a pre-regulator 7805 that can really handle the crazy mod swings of a motorcycle charging system.
  • Always use a watchdog timer. You might concede that your code might be perfect, but outside forces like bad EM can lock up the little computer. The watchdog will at least reboot the computer and get it back up an running so the running lights will still work.

The watchdog timer code I changed a little came from https://forum.arduino.cc/index.php?topic=63651.0 by za_nic for credit.

If I'm happy with this version of the hardware, it will be reworked one more time to exclude the Arduino board and use a single ATmega 168A-PU chip as the next challenge, because I just bought a bunch and now they need a good use.

If anyone is interested, I can also add the parts list purchased from Digi-key and Amazon. A text file is now included in the zip file with all the parts purchased from Digi-Key and Amazon, (although some of it is stock for my workshop).  I will try to get a detailed electrical diagram posted in PDF form as soon as I have time. Let me know what you think There is now a full electrical diagram included in the zip file as a PDF and PNG file.

History

  • 1.0: Initial post
  • 1.1: fixed spelling and added parts text file to Bike_lights_v3b.zip file
  • 1.2: added filnal electrical diagram to zip file and includded a snippit of it in the text

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)