Crosswalk Project

ECE301 - Intro. to Circuits, Summer 2019, EiL Written by Ben Sergent V
ECE301 Crosswalk | Stage 7
Back

Stage 7 - Countdown

Basic Setup

Review the first stage's sections on using the Arduino IDE before continuing on if you're fuzzy on the topic.

From the last group's stage, you should have a breadboard with three resistors, two LEDs, a piezoelectric speaker (piezo), and an infrared receiver wired to the Arduino that turn on when the Arduino is powered.

Seven Segment Displays

Pinout for seven segment display. Google should give plenty of alternate images.

Seven segment displays (7sd) are just an array of LEDs with a common lead. They generally also include resistors within the display so that you can just apply the digital 5V directly to the pins instead of calculating your own resistor values like in stage one. Thankfully, you will not need to worry about multiplexing/scanning either since you're only working with a single digit display.

The most complicated part of single 7sd's is making sense of the pinout. To the right, you'll find a diagram where the letters on the pins correspond with the letters on the LED segments. The '-'s represent ground.

There are generally two types of 7sd's: common cathode and common anode. These give the same result but require different wiring schemes that are opposite of each other.

LEDs share a common ground.

The type that makes more sense to me is common cathode. These 7sd's share a common cathode (negative lead) that is grounded. To turn on a segment, you just set the pin connected to the positive lead of the LED you want to digital high (5V) to generate a potential difference (5V anode to 0V cathode). For the other segments, you set the anodes to digital low (0V) so that there is no potential difference across them (0V anode to 0V cathode).

LEDs share a common ground.

The second type is common anode where the LEDs share a common positive lead with a voltage of 5V. To turn on a segment, you set the pin connected to the cathode of the LED you want to digital low (0V) which creates a potential diffence across the LED (5V anode to 0V cathode) so that current flows. For the other segments, you'll want to set their cathodes to digital high (5V) so that there is no potential difference (5V anode to 5V cathode).

An additional note is that you may be running out of digital pins (2-13) at this point. If that's the case, you can still use the analogue pins (A0-A5) as if they were digital.

Mapping Digits to LED Pins

There are three common methods for mapping the integers 0-9 to a combination of LEDs in the 7sd. The first is to just write a huge switch statement with each case corresponding to a digit, and then setting every pin one at a time within each. This is probably the most straight-forward method but also the ugliest.

switch (digit) {
  case 0:
    digitalWrite(PIN[0], HIGH);
    digitalWrite(PIN[1], HIGH);
    digitalWrite(PIN[2], HIGH);
    digitalWrite(PIN[3], HIGH);
    digitalWrite(PIN[4], HIGH);
    digitalWrite(PIN[5], HIGH);
    digitalWrite(PIN[6], LOW);
    digitalWrite(PIN[7], LOW);
    break;
  case 1:
    // Continues on for every digit
}

The second is to use an array of arrays. The outer arrays represent each digit, and the inner arrays represent each LED for each digit (a 10x8 matrix in this case). With this approach, your index into the outer array is the digit you want to represent. Then you write a for loop to call digitalWrite(pin) for each LED segment. Pretty simple and straight-forward. An example of this is below.

const int MAP_DIGIT[11] = {
  { 1, 1, 1, 1, 1, 1, 0, 0 }, // 0
  { 0, 1, 1, 0, 0, 0, 0, 0 }, // 1
  { 1, 1, 0, 1, 1, 0, 1, 0 }, // 2
  // Arrays for 3-9 here
  { 0, 0, 0, 0, 0, 0, 0, 0 }  // off
};

for (int d = 0; d < 8; d++)
  digitalWrite(PIN[d], MAP_DIGIT[digit][d]);

The third approach is the tidiest as it loses the least memory while still being very readable to use. This is to use bitwise operations and store the segment true/false array as a single integer. C++ treats booleans as integers, so you reduce you memory footprint by a factor of eight moving from an array of eight integers to a single integer. You can then check a single bit of the integer via bitRead(integer, index) after defining it with a preceding B (e.g. B11111100). An example of this may be found below as well.

const int MAP_DIGIT[11] = {
  B11111100, // 0
  B01100000, // 1
  B11011010, // 2
  // Integers for 3-9 here
  B00000000  // off
};

for (int d = 0; d < 8; d++)
  digitalWrite(PIN_SEGMENT[d], bitRead(MAP_DIGIT[digit], 7 - d));

I recommend the second approach because it's quick and clean, but feel free to use either of the other approaches or an entirely different one. Keep in mind that depending on the type of 7sd, you may need to invert the HIGH and LOW in the code above (it is assuming common cathode).

What are the sequences of segment values for the digits 1, 5, and 8? Please enter your answers as a sequence of 0s and 1s in the order of A, B, C, D, E, F, G, DP.

1 =
5 =
8 =
Commas and spaces will be ignored.
Check

Time Remaining

Now that you can decode digits to LEDs on the 7sd, you need to figure out what numbers to actually show. We want to have two countdowns: one during the wait state, and one across the warning and walk state. The wait state countdown is quite simple. Each loop() in the wait state, update the 7sd to show the amount of time left before the delay will elapse, rounding up (or ceiled). As a starting point, the number of milliseconds remaining would be DELAY_WAIT - (millis() - transition_last) or the total time for the state minus the current time into the state. Once you have that number (depending on the other two states as well), follow a procedure similar to the code examples above to update the pins. Remember that for the walk state, you don't want to just use DELAY_WALK by itself or you'll be counting down to the warning and then again from the warning to the wait state. There is skeleton code below that encompasses the previous stage's solution as well as some changes to help you with the current stage. Mainly focus on the TODO comments when making changes as the rest of the code should not require any modification in this stage.

The final series of numbers the 7sd should show (assuming no crossings are requested) is as follows: off, 9, 8, 7, 6, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, repeat.

Skeleton Code

// Crosswalk Project - Stage 7
// ECE301 - Intro. to Circuits
#include <IRremote.h>
#include <IRremoteInt.h>

// Pins
const int PIN_LED_RED = -1; // TODO Update these pins to the used values
const int PIN_LED_GREEN = -1;
const int PIN_PIEZO = -1;
const int PIN_BUTTON = -1;
const int PIN_IR_RECEIVER = -1;
const int PIN_SEGMENT[8] = {
  -1, // A   _Mapping_
  -1, // B     AAA
  -1, // C    F   B
  -1, // D    F   B
  -1, // E     GGG
  -1, // F    E   C
  -1, // G    E   C
  -1  // DP    DDD  DP
};

// State
const int STATE_WAIT = 0;
const int STATE_WALK = 1;
const int STATE_WARN = 2;
const unsigned long DELAY_WAIT = 10000;
const unsigned long DELAY_WALK = 3000;
const unsigned long DELAY_WARN = 2000;
int state_current = STATE_WAIT;
unsigned long transition_last = 0;

// Tone
const float FREQ_A4 = 440; // Hz
bool tone_current = false;
unsigned long tone_last = 0;

// Beep
const float FREQ_BEEP = 2; // Hz
bool beep_current = false;
unsigned long beep_last = 0;

// IR Decoding
IRrecv irrecv(PIN_IR_RECEIVER);
decode_results results_ir;

// Seven Segment Display Mappings
const int MAP_DIGIT[11][8] = {
  { 1, 1, 1, 1, 1, 1, 0, 0 }, // 0
  { 0, 1, 1, 0, 0, 0, 0, 0 }, // 1
  { 1, 1, 0, 1, 1, 0, 1, 0 }, // 2
  // TODO Add the arrays for 3-9 here
  { 0, 0, 0, 0, 0, 0, 0, 0 }  // off
};

void setup() {
  pinMode(PIN_LED_RED, OUTPUT);
  pinMode(PIN_LED_GREEN, OUTPUT);
  pinMode(PIN_PIEZO, OUTPUT);
  pinMode(PIN_BUTTON, INPUT);
  pinMode(PIN_IR_RECEIVER, OUTPUT);
  irrecv.enableIRIn();
  for (int s = 0; s < 8; s++)
    pinMode(PIN_SEGMENT[s], OUTPUT);
}

void loop() {
  // Handle millis() and micros() rolling back over to 0
  if (millis() < transition_last)
    transition_last = 0;
  if (micros() < tone_last)
    tone_last = 0;

  // Request to Walk
  bool walk_requested = false;
  walk_requested |= digitalRead(PIN_BUTTON);

  // IR Decoding
  if (irrecv.decode(&results_ir)) {
    walk_requested |= results_ir.value == 0xFFC23D;
    irrecv.resume();
  }
  
  // State Machine
  if ((state_current == STATE_WAIT && millis() >= transition_last + DELAY_WAIT)
      || walk_requested) {
    // Switch to walk state
    state_current = STATE_WALK;
    transition_last = millis();
    digitalWrite(PIN_LED_RED, LOW);
    digitalWrite(PIN_LED_GREEN, HIGH);
    walk_requested = false;
  } else if (state_current == STATE_WALK && millis() >= transition_last + DELAY_WALK) {
    // Switch to warn state
    state_current = STATE_WARN;
    transition_last = millis();
    digitalWrite(PIN_LED_GREEN, LOW);
  } else if (state_current == STATE_WARN && millis() >= transition_last + DELAY_WARN) {
    // Switch to wait state
    state_current = STATE_WAIT;
    transition_last = millis();
    digitalWrite(PIN_LED_RED, HIGH);
    digitalWrite(PIN_LED_GREEN, LOW);
  }

  // Beep
  unsigned long period_beep_walk = 1 / FREQ_BEEP * 1000;
  unsigned long period_beep_warn = period_beep_walk / 2;
  unsigned long period_beep = state_current == STATE_WARN ? period_beep_warn : period_beep_walk;
  if (millis() > beep_last + (period_beep / 2)) {
    beep_current = !beep_current;
    beep_last = millis();
  }
  // Flash LED during warning
  if (state_current == STATE_WARN)
    digitalWrite(PIN_LED_RED, beep_current ? HIGH : LOW);

  // Tone
  unsigned long period_a4 = 1 / FREQ_A4 * 1000 * 1000;
  if ((state_current == STATE_WALK || state_current == STATE_WARN) && beep_current) {
    if (micros() > tone_last + (period_a4 / 2)) {
      tone_current = !tone_current;
      tone_last = micros();
    }
    digitalWrite(PIN_PIEZO, tone_current ? HIGH : LOW);
  } else digitalWrite(PIN_PIEZO, LOW);

  // Seven Segment Display
  int seconds_remaining = 0;
  // TODO Calculate seconds remaining based off the state
  // TODO Update the segments based on the decoded digit
}

Video

Once you're finished, you should have something similar to the video below, but with only a single 7sd.