Introduction: How to Make an Arduino-controlled Bike-light

Microcontrollers are very powerful tools for exploration into the electrical world. In this tutorial we will be using Arduino's Uno board, and we will show you how to program a number of basic functions into a breadboard bike light. To allow for switching between the various function of the bike light we will show you how to physically debounce a button and use an interrupt in Arduino code. Also we will show you how to set up a basic voltage divider to measure the value of a photoresistor, and how to power your Arduino externally so that it could be removed from your computer.

Step 1: The Materials

For this instructable you will require the following in addition to your arduino board:

1. A breadboard
2. 5 LEDs
3. A standard pushbutton
4. An inverting shmidt trigger
5. A 10 uF Capacitor
6. A 10k Ohm Resistor
7. A bunch of wires (not pictured)
8. Some kind of power supply from 5V-9V that doesn't need a whole lot of current. (optional)

Step 2: Setting Up Your Arduino

Firstly download the Arduino IDE and set up your arduino to work with your computer following the instructions found here: http://arduino.cc/en/main/software

Once it's set up there should be an icon on your computer that looks like the arduino logo, and once you open it, the program should look like this, but without the code. That part comes later.

Step 3: The Pins on the Arduino

The arduino uno comes equipped with a huge amount of pins. There are a few important kinds of pins we will deal with for our project:

1. Digital Pins:
            These pins give out, or read in a digital signal. They are located on the right side of the board while the power source is facing upwards. Of these, the ones that can use Pulse Width Modulation to emulate an analog output for, say and LED, are pins 3, 5, 6, 9, 10, and 11. If you want to be able to dim your leds you should only use these pins. Additionally, pins 2 and 3 are the two interrupt pins the Arduino has, which will be needed for our button. 

2. Analog Pins:
            These pins deal with analog signals, and we will be using an analog port to talk to our photoresistor later on.

To use these pins in an arduino, we first must declare that we will use them and set their modes. In Arduino code this is done as follows. Luckily an arduino is smart enough to know that when we talk about an integer we mean a pin.

#define NUMBER_OF_LEDS 5
#define NUMBER_OF_PROGRAMS 6

//set up LEDs
int ledList[NUMBER_OF_LEDS] = {11, 10, 9, 6, 5};
int currentLed;
int bounceDirection = 0;
int ledsOn[NUMBER_OF_LEDS] = {0,0,0,0,0};

//set up button
int buttonInt = 0; //digital pin 2 is interrupt 0

//for switching between programs
volatile int currentProgram = 0;

//set up the read from the photoresistor
int photoresistor = 0; //analog port 0

void setup(){
  int i;
  for (i = 0; i < NUMBER_OF_LEDS; i++){
    pinMode(ledList[i], OUTPUT);
  }
 
  attachInterrupt(buttonInt, incrementProgram, RISING);
 
  Serial.begin(9600);
}

You probably noticed that there are a few extra constants put in there. Their purpose might be confusing now, but soon they should make sense. Additionally, it's probably a good idea to wire the Ground and 5V pins to opposite rails of the bread board, or to whichever rails make sense for you.

Step 4: Setting Up the Physical Debouncer

As we showed in the previous bit of code, we want to use a button coming on digital port 2, which is interrupt port 0 (yes, it's confusing) as an interrupt, which basically means that it will stop the arduino in its tracks if a certain condition is met.

//set up button
int buttonInt = 0; //pin 2 is interrupt 0

attachInterrupt(buttonInt, incrementProgram, RISING);

That line of code means we want the pin indicated by buttonInt(digital pin 2) to function as an interrupt. We want it to call the function incrementProgram, whenver it experiences a rising edge. A rising edge simply means a place where the voltage jumps up very suddenly (such as when a button completes a connection to 5 volts)

We are going to use the incrementProgram function (shown below) to change the current program the Arduino is running.

void incrementProgram(){
  Serial.print("Pressed!");
  if (currentProgram == (NUMBER_OF_PROGRAMS - 1)){
    currentProgram = 0;
  }
  else{
    currentProgram = currentProgram + 1;
  }
}

Remember these lines?
#define NUMBER_OF_PROGRAMS 6

//for switching between programs
volatile int currentProgram = 0;

Do they make a little bit more sense now? the reason we have to add the keyword volatile to current program is because we want to let the arduino know that this number will change regularly and that this is a normal thing that it should not freak out about.

The problem with this however, is that when a button gets pressed, it actually bounced up an down a bit (extremely quickly) before settling on it's final value. To remove a lot of this bouncing and smooth out the transition we are going to use an RC circuit as a lowpass filter that will largely screen out the very rapid "bounces". The time constant of this particular RC circuit is about 10 ms, which is generally slower than a human can press a button, and so those signals should be unaffected.

Since most Schmidt Triggers are inverting, the left half of the circuit diagram shows a button that actually will go from 5 volts to 0 volts when pressed. However, we have this interrupt on a digital pin, and the exact nature of the bouncing in unpredictable. To fix this problem we will use a Schmidt Trigger as a basic Analog to Digital converter, taking our slow moving change from high to low into a jump from low to high, sending out a rising edge, which should trigger our interrupt.

The circuit diagram uses a very particular Schmidt trigger, and yours might be different. Check the data sheet to see exactly how it should work, but the set up shown is very common.

Vout should be connected to digital pin 2.



Step 5: Setting Up the Photoresistor

Secondly, we would like to be able to read off the changes in resistance that the photoresistor experiences so that we might be able to change some feature of the LEDs in accordance (like making the LEDs dimmer when it's bright out. Shown below is a simple voltage divider that should allow for a fairly accurate reading of the resistance in the photoresistor. The resistor opposite was chosen to be a 10k resistor because this particular photoresistor had a a range of about 5k to 20k ohms and picking a resistor in a similar range will give you the largest changes in measured voltage for a given resistance change.

The Vout here should be connected to A0, the first analog pin of the arduino.

Step 6: Extending the Code

Hooking up the LEDs varies a lot depending on your specific LEDs, but an example set up is shown in the figure. Choosing the exact resistance for your LEDs also varies, depending on which LEDs you use, and which power source you want to use, but a good guide to it is found here: 

https://www.instructables.com/id/Choosing-The-Resistor-To-Use-With-LEDs

The code shown here should all be fairly straightforward if you have some familiarity with code.

//Simple method to turn all of the LEDs on
void allOn(){
  int i;
  for (i = 0; i<NUMBER_OF_LEDS; i++){
    digitalWrite(ledList[i], HIGH);
    ledsOn[i] = 1;
  }
}

//Simple method to turn all of the LEDs off
void allOff(){
  int i;
  for (i = 0; i<NUMBER_OF_LEDS; i++){
    digitalWrite(ledList[i], LOW);
    ledsOn[i] = 0;
  }
}

//helper function to sum an array
int summation(int array[]){
  int sum = 0;
  int length = sizeof(array)/sizeof(int);
  int i;
  for (i = 0; i<length; i++){
    sum = sum + array[i];
  }
  return sum;
}

//flashes all of the LEDs. If more than 2 leds are currently on the cycle starts by turning them all off
//otherwise the cycle starts by turning them all on.
void allFlashing(int delayLength){
  if (summation(ledsOn) > 2) {
    allOff();
    delay(delayLength);
    allOn();
    delay(delayLength);
  } else {
    allOn();
    delay(delayLength);
    allOff();
    delay(delayLength);
  }
}
 
// a method that isn't actually used, but could be useful in the future. This method will toggle whichever LED is currently
//selected.
void toggleCurrentLed(){
  if (ledsOn[currentLed] == 1){
    digitalWrite(ledList[currentLed], LOW);
    ledsOn[currentLed] = 0;
  } else if (ledsOn[currentLed] == 0){
    digitalWrite(ledList[currentLed], HIGH);
    ledsOn[currentLed] = 1;
  }
}

//turns on the current LED
void currentLedOn(){
  digitalWrite(ledList[currentLed], HIGH);
  ledsOn[currentLed] = 1;
}

//turns off the current LED
void currentLedOff(){
  digitalWrite(ledList[currentLed], LOW);
  ledsOn[currentLed] = 0;
}

//this is a bouncing method that seems to send a light up and down
//your LEDs. Pretty cool if you ask me. Keeps track of a direction variable
//and whatever the current Led being lit is to make sure that the switch is always in the
//right direction.
void bounce(int delayLength){
  if (bounceDirection == 0){
    if (currentLed < NUMBER_OF_LEDS - 1) {
      currentLedOff();
      currentLed++;
      currentLedOn();
      delay(delayLength);
    } else if (currentLed == NUMBER_OF_LEDS - 1) {
      currentLedOff();
      currentLed--;
      bounceDirection = 1;
      currentLedOn();
      delay(delayLength);
    }
  } else if (bounceDirection == 1){
    if (currentLed > 0) {
      currentLedOff();
      currentLed--;
      currentLedOn();
      delay(delayLength);
    } else if (currentLed == 0) {
      currentLedOff();
      currentLed++;
      currentLedOn();
      bounceDirection = 0;
      delay(delayLength);
    }
  }
}

//This method currently is meant to get dimmer as the surroundings get dimmer.
//The reason for this is mostly to reduce interference, as in the breadboard set up
//the light from the LEDs is very easily seen by the photoresistor, which would lead to
//some strange behavior.
//These constants should change a bit for your specific circuit. Use the function
//Serial.println(analogRead(photoresistor)); to print out the current value, and
//determine approximate maximums and minimums.
void allFading(){
  int brightness = map(analogRead(photoresistor), 100, 450, 0, 255);
  int i;
  for (i = 0; i<NUMBER_OF_LEDS; i++){
    analogWrite(ledList[i], brightness);
  }
}

//This method transforms the bike light into a measuring unit for light intensity.
//The number of LEDs that light up at any given point are indicative of how bright it is.
//The constants shown will generally cover standard room lighting with all 5 LEDs
//These constants should change a bit for your specific circuit. Use the function
//Serial.println(analogRead(photoresistor)); to print out the current value, and
//determine approximate maximums and minimums.
void photometer(){
  int brightness = map(analogRead(photoresistor), 75, 450, 0, NUMBER_OF_LEDS);
  int i;
  for (i = 0; i<NUMBER_OF_LEDS; i++){
    if (i < brightness){
      digitalWrite(ledList[i], HIGH);
    } else {
      digitalWrite(ledList[i], LOW);
    }
  }
}
   

void loop(){
  if (currentProgram == 0){
   allOff();
  } else if (currentProgram == 1) {
    allOn();
  } else if (currentProgram == 2) {
    allFlashing(250);
  } else if (currentProgram == 3) {
    bounce(100);
  } else if (currentProgram == 4) {
    allFading();
  } else if (currentProgram == 5) {
    photometer();
  }
}

Step 7: Using an External Power Supply

So now you've got your brand new nifty bike light and learned something about debouncing buttons, setting up LEDs and coding for an arduino. If you want to go around now and show off your fancy new toy it would probably help for it to not be hooked p to your computer at all times huh? Well those ports on the Uno board just so happen to support external power supplies! These are the same kind of ports that are on many power supplies you may already own. To check if an owned power supply is appropriate check the back. It's VDC should be between 5 and 9 volts, and the polarity should be positive. Other than that you should generally be fine. Good luck and happy hacking!