Now I'll go through the code in detail.

/*-------------------------------------------------------------

* Software Options * - DEBUG (serial output) * - Battery stay alive function * /#define DEBUG // debug mode; outputs debug messages to Serial port #define BATSA // Battery stay alive; enables hardware to keep 'smart' // USB batterys from turning off due to low current. // NOTE: Settings may have to be adjusted for specific // batteries. /*------------------------------------------------------------- * Macros */ #ifdef DEBUG #define DEBUG_PRINT(msg) Serial.println(msg) #else #define DEBUG_PRINT(msg) #endif

This code defines options. The #define DEBUG activates debug code, and will output to the Serial port. This is useful if you are having problems. The #define BATSA activates the USB battery stay alive functionality. To disable any of these, put double slashes "//" in front.

/*-------------------------------------------------------------

* Pins * / LED pins const uint8_t redLight = 5; const uint8_t yellowLight = 6; const uint8_t greenLight = 9; // switch pins const uint8_t redBttn = A4; const uint8_t yellowBttn = A3; const uint8_t greenBttn = A2; const uint8_t modeBttn = A5; const uint8_t brightKnob = A0; // USB battery stay alive pins const uint8_t saDriver = 11;

This code defines the pins for each function. The LED pins are the pins that drive the output MOSFETs. The switch pins are the pins of each button, and the potentiomiter.

/*-------------------------------------------------------------

* Variables * / brightness of LEDs uint8_t brightness = 255; // relative brightness of LEDs; used to make each color the same brightness // highly depends on LEDs used! float redComp = 0.85; float yellowComp = 1.0; float greenComp = 0.9; // poteniometer value int potVal; // mode; 0 - traffic light; 1 - manual; 2 - disco uint8_t mode = 0; // default mode is 0 // state - used for modes 0, 2 uint8_t state = 0; // button checking variables uint8_t modeBttnState = 0; // timing variables unsigned long timeStart = 0; unsigned long timeCurr = 0; unsigned long timeElapsed = 0; // normal mode timing (in ms) #define TGREEN 8000 #define TYELLOW 4000 #define TRED 8000 // manual mode switch variables uint8_t redBttnState = 0; uint8_t yellowBttnState = 0; uint8_t greenBttnState = 0; // disco mode timing variables short beats[4] = {600, 420, 210, 125}; unsigned short discoDelayIndex = 2; // change to new colour every 500ms unsigned long discoPrev = 0; // time of last disco color change // disco mode variables short discoLightMode = 0; // 0 - constant; 1 - pulse uint8_t currCol = 0; // current colour; 0 - green; 1 - yellow; 2 - red uint8_t newCol = 1; #define pulseDelay 75 // duration of pulse in pulse mode disco mode // USB battery stay alive variables

uint8_t saState = 0; // on or off unsigned long saCurr = 0; unsigned long saNext = 0; // USB battery stay alive OPTIONS #define saTH 2 // time (ms) to draw current #define saTL 497 // time (ms) to wait (no current draw)

This code defines variables needed for the various modes, along with their default values. There are a few #define statements that define things open to change, based on your preferences and hardware.

#define TGREEN 8000

#define TYELLOW 4000 #define TRED 8000

These define the duration, in milliseconds, of the different colors. For example, TGREEN 8000 says that the green light will stay on for 8000 milliseconds, or 8 seconds.

/*-------------------------------------------------------------

* Arduino standard setup function */ void setup() { #ifdef DEBUG Serial.begin(115200); #endif #ifdef BATSA DEBUG_PRINT("Battery stay alive active"); // initialize digital pins for optional USB battery stay alive pinMode(saDriver, OUTPUT); digitalWrite(saDriver, LOW); // initialize values for USB stay alive saCurr = millis(); saNext = saCurr + saTL; #endif // initialize digital pins for LEDs pinMode(redLight, OUTPUT); pinMode(yellowLight, OUTPUT); pinMode(greenLight, OUTPUT); // initialize digital pins for switches pinMode(redBttn, INPUT); pinMode(yellowBttn, INPUT); pinMode(greenBttn, INPUT); pinMode(modeBttn, INPUT); // 'randomize' seed for random numbers randomSeed(analogRead(A0)); }

The setup function initializes pins, the Serial port (if enabled by DEBUG) and the USB battery stay alive (if enabled by BATSA). The pins are initialized as INPUT (switch inputs) or OUTPUT (LEDs).

void loop() {

#ifdef BATSA stayAlive(); #endif getBrightness(); checkModeBttn(); // select mode if (mode == 1) { mode1(); // disco mode } else if (mode == 2) { mode2(); // manual mode } else { mode0(); // normal mode; default } }

The loop is pretty simple. It does four things: activates the stay alive function, gets the value from the potentiometer, and converts it to a brightness value, checks if any buttons are pressed, and activates LEDs based on which mode is active.

/*-------------------------------------------------------------

* getBrightness function * calculates brightness from pot */ void getBrightness() { potVal = analogRead(brightKnob); // get analog reading brightness = (uint8_t)( ( (potVal/32)*(potVal/32) )/4 ); // convert pot value to brightness // using an exponential scale if (brightness < 4) { brightness = 0; } DEBUG_PRINT("Brightness:"); DEBUG_PRINT(brightness); }

The getBrightness function reads the value of the potentiometer. The potentiometer uses a potential divider to put a variable voltage on an analog pin of the ATMega328P. This value is then converted to a brightness value. Since potVal is a float (meaning it had a fractional value), it needs to be converted to a non-fractional value. This is done using the (unit8_t).

/*-------------------------------------------------------------

* checkModeBttn function * checks for input from mode button * sets defaults for each mode */ void checkModeBttn() { // read mode button modeBttnState = digitalRead(modeBttn); if (modeBttnState) { // if mode button was pressed ++mode; // increment mode variable DEBUG_PRINT("Mode:"); DEBUG_PRINT(mode); // turn off all LEDs at mode change analogWrite(redLight, 0); analogWrite(greenLight, 0); analogWrite(yellowLight, 0); // if mode variable is greater then 2, reset to 0 if (mode > 2) { mode = 0; } // if mode is 2, initialize disco mode delay and reset to color 0 if (mode == 2) { discoDelayIndex = 2; currCol = 0; } // if mode is 0 or 1, reset timing variables & reset to color 0 if (mode < 2) { timeCurr = 0; timeStart = 0; currCol = 0; // if mode is 1, set color to red if (mode == 1) { analogWrite(redLight, brightness*redComp); } } // delay for a rough software debounce delay(200); } }

The checkModeBttn function checks to see if the mode button has been pressed. If so, then it will increase the mode variable by one. When it gets to greater than 2, it will reset to 0. Remember, when coding, variables start at 0, not 1! The delay(200) is a rough debounce feature.

this code also sets default values for each mode.

/*-------------------------------------------------------------

* mode0 function * normal traffic light mode */ void mode0() { timeCurr = millis(); // get curent millisecond timeElapsed = timeCurr - timeStart; // get elapsed time from previous color change if (state == 1) { // yellow analogWrite(yellowLight, brightness*yellowComp); // turn on yellow LED analogWrite(greenLight, 0); // turn off green LED DEBUG_PRINT("Yellow ON"); if (timeElapsed > TYELLOW) { // go to next color after so many milliseconds state = 2; // go to red next timeStart = millis(); // 'save' time of color change } } else if (state == 2) { // red analogWrite(redLight, brightness*redComp); // turn on red LED analogWrite(yellowLight, 0); // turn off yellow LED DEBUG_PRINT("Red ON"); if (timeElapsed > TRED) { // go to next color after so many milliseconds state = 0; // go to green next timeStart = millis(); // 'save' time of color change } } else { // green (state 0) analogWrite(greenLight, brightness*greenComp); // turn on green LED analogWrite(redLight, 0); // turn off red LED DEBUG_PRINT("Green ON"); if (timeElapsed > TGREEN) { // go to next color after so many milliseconds state = 1; // go to yellow next timeStart = millis(); // 'save' time of color change } } }

mode0 is the normal traffic light function. It cycles through the colors based on the time elapsed. Each time the function is called, it updates the current millisecond value. The elapsed time triggers the next color change.

/*-------------------------------------------------------------

* mode2 function * manual traffic light mode */ void mode1() { redBttnState = digitalRead(redBttn); // read red button if (redBttnState) { // turn on red analogWrite(greenLight, 0); analogWrite(yellowLight, 0); analogWrite(redLight, brightness*redComp); DEBUG_PRINT("Red ON"); } else { yellowBttnState = digitalRead(yellowBttn); // read yellow button if (yellowBttnState) { // turn on yellow analogWrite(greenLight, 0); analogWrite(redLight, 0); analogWrite(yellowLight, brightness*yellowComp); DEBUG_PRINT("Yellow ON"); } else { greenBttnState = digitalRead(greenBttn); // read green button if (greenBttnState) { // turn on green analogWrite(yellowLight, 0); analogWrite(redLight, 0); analogWrite(greenLight, brightness*greenComp); DEBUG_PRINT("Green ON"); } } } }

This code is for the 'manual' mode. All it does it wait for a button is pressed. When a button is pressed, it activates a color LED, and deactivates the others.

/*-------------------------------------------------------------

* mode3 function * disco traffic light mode */ void mode2() { timeCurr = millis(); // get curent millisecond timeElapsed = timeCurr - discoPrev; // get elapsed time from previous color change if (timeElapsed >= beats[discoDelayIndex]) { // get random color, but NOT the same as current do { newCol = (int)(random(1199)/400); // get random number between 0 and 2 (inclusive) } while (newCol == currCol); currCol = newCol; // change to new color, turn off all other colors if (currCol == 0) { analogWrite(yellowLight, 0); analogWrite(redLight, 0); analogWrite(greenLight, brightness*greenComp); } else if (currCol == 1) { analogWrite(greenLight, 0); analogWrite(redLight, 0); analogWrite(yellowLight, brightness*yellowComp); } else { analogWrite(greenLight, 0); analogWrite(yellowLight, 0); analogWrite(redLight, brightness*redComp); } // if in 'pulse' mode, pulse LED, then turn off if (discoLightMode == 1) { delay(pulseDelay); analogWrite(greenLight, 0); analogWrite(yellowLight, 0); analogWrite(redLight, 0); } // get current millisecond discoPrev = millis(); } // read all button states redBttnState = digitalRead(redBttn); yellowBttnState = digitalRead(yellowBttn); greenBttnState = digitalRead(greenBttn); // if red button pressed if (redBttnState) { // switch between pulse and constant disco mode if (discoLightMode == 0) { discoLightMode = 1; DEBUG_PRINT("Pulse Mode ON"); } else { discoLightMode = 0; DEBUG_PRINT("Pulse Mode OFF"); } delay(150); // debounce } // if yellow button pressed if (yellowBttnState) { // descrease disco speed --discoDelayIndex; if (discoDelayIndex < 1) { // limit to min 1 discoDelayIndex = 1; DEBUG_PRINT("More speed!"); } delay(250); // debounce } // if green button pressed if (greenBttnState) { // increase disco speed ++discoDelayIndex; if (discoDelayIndex > 3) { // limit to max 3 discoDelayIndex = 3; DEBUG_PRINT("Less speed"); } delay(250); // debounce } }

This code is part of the 'disco' mode. There are a few options for the disco mode. One is the speed of the color change. When the red or yellow button is pressed, the speed of the color change is increased of decreased, respectively. When the green button is pressed, the pulse mode is changed. The default mode is 'constant mode'. In this mode, the LED will stay on until the next color change. In 'pulse mode' the light will stay on for a number of milliseconds, defined by pulseDelay.