Crosswalk Project

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

Stage 6 - IR Remote

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, and a piezoelectric speaker (piezo) wired to the Arduino that turn on when the Arduino is powered.

IR Receivers and Modulation

IR receivers are photodiodes (basically light sensors) that are specifically sensitive to the infrared sperctrum. They require direct (or very close to direct) line of sight to the emitting infrared LED. Since infrared is invisible to the human eye, you can use most smartphone cameras to check the batteries on the infrared remote by looking at the transmitting LED through the camera. If the batteries are good, pressing a button will make the LED flash a violet color a few times in quick succession.

Now, there's one problem with infrared transmitters and receivers. Everything emits infrared light in the form of heat, so there is a lot of noise. To get around this, the transmitters and receivers modulate the light transferred in the same way that FM (frequency modulation) radio modulates radio waves (thus the rapid blinking of the LED in the phone camera). They still don't work well in sunlight due to just how much infrared light the sun is giving off drowning out any LED. If you're curious about what the actual modulation protocols are, this website gives a pretty good explanation.

IR Receiver Pinout

With black LED facing you, the pins read from left to right as follows: output, ground, 5V

The IR receiver is actually a pretty easy component to wire. With the black face facing your face, the left-most pin is the output pin that you should wire to a digital pin on the Arduino (2-13), the middle pin is the ground, and the right-most pin is the reference voltage of 5V. If you haven't used a breadboard before, review the first stage's description on how to use one to wire circuit components together.

IR Receiver Library

Working with an IR receiver is extremely simple if you important a library to handle the asynchonous demodulating and decoding of received values. The most common IR library is the IRremote Arduino Library. Follow the installation instructions on the page to get it ready to use. In short, they just say to unzip it to a folder, and put that folder inside your libraries folder. For me, that was located at C:\Users\bserg_000\Documents\Arduino\libraries, but it may be somewhere in your Program Files folder. To use the library in your sketch, you'll need to include the header file IRremote.h. An example of how to do this may be found in the code below.

Once you have the headers for the library included, you can start using it. The general procedure for using the library to detect button presses is to call decode() on your IRrecv object within your main loop(). If it returns true, there was a button press from the remote that needs to be handled. Check the value property of your decode_results object and compare it against a list of known button code values. Generally, this is done with a switch statement, but since we're only checking for one button, an if statement will suffice. Once you've handled the button press, you must call resume() on your IRrecv object to start listening for the next button press.

Modulated Button Codes

To check the decoded button value against a known value, you'll need to know some button values. I could just give them to you, but then you wouldn't learn as much. So, I've included a full sketch below that you can upload directly to the Arduino to start receiving arbitrary codes. All you have to modify is the pin number in the constants at the top of the code.

To view the received codes, you'll need to open the Serial Monitor under Tools > Serial Monitor from the menu bar. For serial data, the Arduino uses the USB cable to communicate directly with your computer in a pretty unsophisticated manner (there's a reason serial ports aren't included on every computer nowadays). It does the job though and you'll get some text output in the form of 0xAAAAAA where the A's are the hex code for the button press. When coding these values into the sketch later, make sure to include the '0x' prefix to signify to the compiler that the following number is written in hexadecimal (base 16).

#include <IRremote.h>
#include <IRremoteInt.h>

const int PIN_IR_RECEIVER = -1; // TODO Replace with your pin number
IRrecv irrecv(PIN_IR_RECEIVER);
decode_results results_ir;

void setup() {
  Serial.begin(9600);
  irrecv.enableIRIn();
}

void loop() {
 if (irrecv.decode(&results_ir)) {
    Serial.println(results_ir.value, HEX);
    irrecv.resume();
  }
}

What are the codes printed to the Serial Monitor for Play/Pause, CH, and 5?

Play/Pause =
CH =
5 =
Check

Changing State on Request

This section is going to be extremely similar to the previous stage's button press. The only two differences are that we're using irrecv.decode(&results_ir) instead of digitalRead() and that we need to clean up some state after processing the button press (i.e. call irrecv.resume()).

Use the play/pause button to request to walk (set the state to STATE_WALK). Also remember that irrecv.resume() needs to be called every time irrecv.decode(&results_ir) returns true, not just when the correct button was pressed and the state is changed. The skeleton code below should handle everything except the two calls to decode(&results_ir) and resume() and the associated logic. I recommend defining a new local variable inside loop() to store whether a state change to walk is requested, and then setting it via the previous digitalRead() and your new IR code checking.

Skeleton Code

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

// Pins
const int PIN_LED_RED = -1; // TODO Replace -1s with correct pin numbers
const int PIN_LED_GREEN = -1;
const int PIN_PIEZO = -1;
const int PIN_BUTTON = -1;
const int PIN_IR_RECEIVER = -1;

// 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;

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();
}

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;

  // TODO Check if button press is waiting to be processed, irrecv.decode(&results_ir)
  // TODO Change to walk state if button pressed was play/pause
  // TODO TODO Call resume to detect next button press if button was pressed
  
  // State Machine
  if ((state_current == STATE_WAIT && millis() >= transition_last + DELAY_WAIT)
      || digitalRead(PIN_BUTTON)) {
    // Switch to walk state
    state_current = STATE_WALK;
    transition_last = millis();
    digitalWrite(PIN_LED_RED, LOW);
    digitalWrite(PIN_LED_GREEN, HIGH);
  } 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);
}

Video

Once you're finished, you should have something like what's in the below video.