Home‎ > ‎Projects‎ > ‎POE‎ > ‎

Bike light

The assignment

In this lab you will build a bike light. Your goal is to set up LEDs such that pressing a button will switch the light between several different modes: all off, all flashing; all on; and “bouncing” lights. There should minimally be four modes, and three LEDs.

The circuit

Hook up 5 LEDs to pins 9 through 13.  Hook up a pushbutton to pin 8.

The code

This code is meant to demonstrate several elements of good coding style:
  • DRY: don't repeat yourself!  Each idea should only appear once, so if you have to change it, you only have to change it in one place.
  • Don't leave arbitrary numbers floating around in the code.  If they don't change, make them constants; if they do change, make them variables.
  • Don't copy and paste chunks of code; instead, pull common code out into a function with appropriate parameters.
This code does not demonstrate good internal documentation (comments).  Credit: Most of this code originally came from Getting Started with Arduino, although at this point there's not much left of it.

These constants keep track of which pins are which.

const int FROM = 9;
const int TO = 13;
const int BUTTON = 8;

These variables keep track of the button and the which mode we're in:

int val = 0; // stores the current state of the input pin
int old_val = 0; // stores the previous value of the input pin
int state = 0; // which mode we're in

These variables are used in the modes that maintain state from one iteration of the main loop to the next.

int i = 0;
int incr = 1;

Here's something you might not have seen before: an array of pointers to functions.  This array makes it possible to replace a switch statement with an array access (less code, more DRY).

typedef void (*mode_func)();
mode_func func_array[] = {all_on, all_off, fast_blink, slow_blink, marquee,
                          cylon, binary_counter, random_binary};
int num_modes = 8;

The setup function is straightforward.

void setup() {
  for (int pin=FROM; pin<=TO; pin++) {
    pinMode(pin, OUTPUT);
  }
  pinMode(BUTTON, INPUT);
}

loop() reads the button (with a delay for debouncing), updates the state, and invokes the right mode function.  Notice the modulus operator (%) as a quick way to cycle through the states.

void loop() {
  val = digitalRead(BUTTON); 
  
  // check if there was a transition 
  if ((val == HIGH) && (old_val == LOW)){ 
    state = (state + 1) % num_modes;
    delay(10);
  } 
  old_val = val;
  
  func_array[state]();
  return;
}

Here are some helper functions that will be used by several mode functions.  set_all() and set_one() are straightforward:

void set_all(int setting) {
  // set all output pins to the given setting
  for (int pin=FROM; pin<=TO; pin++) {
    digitalWrite(pin, setting);
  }
}

void set_one(int pin) {
  // set all pins LOW, except the given pin
  set_all(LOW);
  digitalWrite(pin, HIGH);
}

binary_display() sets the LEDs to show the binary code for the given number.  It uses the bit shift operator, >>.

void binary_display(int i){
  // display a binary number; i should be in the range 0--31.
  int shifter = i;
  for (int pin=FROM; pin<=TO; pin++) {
    if (shifter % 2) {
      digitalWrite(pin, HIGH);
    } else {
      digitalWrite(pin, LOW);
    }
    shifter >>= 1;
  }
  delay(100);
}

var_blink() blinks all of the LEDs.  msecs is the delay between iterations in milliseconds; mult is the number of iterations before it changes the state of the LEDs.  Why not just delay() for a longer time?  Because then you miss button presses.  If you keep msecs at 100 or less, it works pretty well.

void var_blink(int msecs, int mult) {
  // msecs is the number of milliseconds per iteration;
  // mult is the number of delays between state switches.
  i = (i + 1) % (2 * mult);
  if (i/mult == 1) {
    all_on();
  } else {
    all_off();
  }
  delay(msecs);
}

And (finally) here are the mode functions:

void all_on() {
  set_all(LOW);
}

void all_off() {
  set_all(HIGH);
}

void fast_blink() {
  var_blink(100, 1);
}

void slow_blink() {
  var_blink(100, 10);
}

marqee() lights one LED at a time in a rotating pattern; cyclon() bounces the lit LED back and forth.

void marquee() {
  i = (i + 1) % 5;
  set_one(FROM+i);
  delay(100);
}

void cylon() {
  i = i + incr;
  if (i == 0 || i == 4) {
    incr *= -1;
  }
  set_one(FROM+i);
  delay(100);
}

binary_counter() displays binary numbers from 0 to 31.  random_binary() displays a random 5-bit number, which is the same as setting each LED at random.

void binary_counter() {
  // count in binary
  i = (i + 1) % 32;
  binary_display(i);
}

void random_binary() {
  // display a random binary number
  i = random() % 32;
  binary_display(i);
}

You can download the code in the attachment below.

Room for improvement
This example is not perfect.  As an exercise, consider making some improvements:
  • There are still some arbitrary constants scattered throughout the code.  Which ones are worth replacing with variables?
  • How easy would it be to modify this code to work with a different number of LEDs?
  • Several of the modes share global state in the variable i.  It turns out that this causes a bug in marquee and cylon modes.  Can you find and fix it?
  • In binary_display(), I could have used the built-in function bitRead().  Check out the documentation and consider re-writing binary_display().
ċ
bike_light.pde
(2k)
Allen Downey,
Jan 24, 2012, 11:11 AM
Comments