Interrupt Programming with Arduino

Setting up interrupt functions on Arduino is quite simple. Search the internet, you’ll find several nice tutorials on the subject. I’m just sharing my own example for style, and to clarify, why do interrupts at all?

Interrupts are immediate, and they allow clean separation of concerns between main (loop) code and special-purpose functions. Like object-oriented programming, they clarify code intent.

The main loop() function typically has delays built in, so while you could test for a button push in the loop, it may take a while before the loop gets to that point in the code, so you’ll see a delay between the push the resulting effect. An interrupt does what it says – it interrupts the main loop and executes all the code in the interrupt function first, before returning back to the main loop.

Setting up an interrupt is super easy: just define a function, then “attach” it as an interrupt with the attachInterrupt() function, with a digitalPinToInterrupt() identifying the appropriate pin (on Arduino Duo and Nano, digital pins 2 and 3 are available for interrupts), and the type of interrupt, RISING, FALLING, or CHANGE. For simple button interrupts, RISING is your best choice. If your interrupt function references any global variables, make sure those variables are defined as volatile.

For this demo, I have set up a simple circuit with two LEDs, red and green, and a button. Typical set up: grounded LEDs with 220 ohm resistors in serial on the anode pin; button is grounded with a 10 kiloohm resistor and its common pin to digital pin 2, with power on the other switch pin.

The program is simple: a green light that blink endlessly. The button push interrupts that loop to turn on the red light. Push it again (it’s a toggle) to turn off the red light. If the red light is on, green remains off.

To those new to C programming, let me explain a few more lines. First, the ternary operator: (toggle == HIGH) ? LOW : HIGH . This says if toggle value is HIGH, then return LOW, else return high, all in a single line of code. Since toggle itself is being assigned (toggle = ) to this operator’s return result, toggle is just becoming the opposite value of whatever it was last. The ternary operator is concise, and saves you a long if then else statement with lots of curly braces.

This line digitalRead(red) == HIGH , also saves you a tedious if then else statement. If digitalRead() is HIGH, it returns true, else it returns false.

byte red = 11;
byte green = 9 ;
byte interrupt_pin = 2 ;
volatile int toggle = LOW ;
void setup() {
  pinMode(red,OUTPUT) ;
  pinMode(green,OUTPUT) ;
  pinMode(interrupt_pin,INPUT) ;
  digitalWrite(green,LOW) ;
  attachInterrupt(digitalPinToInterrupt(interrupt_pin),toggleRed,RISING); 

}

void loop() {
  blinkGreen() ;
}

void blinkGreen() {
  if (!isRed()) {
    digitalWrite(green,HIGH) ;  
  }
  delay(2000) ;
  digitalWrite(green,LOW) ;
  delay(2000) ;
}

void toggleRed() {
   toggle = (toggle == HIGH) ? LOW : HIGH ;
   digitalWrite(red,toggle) ;
   if (isRed()) {
    digitalWrite(green,LOW) ;
   }
}

boolean isRed() {
  return digitalRead(red) == HIGH ; 
}

Object-oriented Programming with the Arduino Microcontroller

A Traffic Light Simulation with pedestrian crosswalks

These days, when I’m not managing teams and projects, I am primary a Python developer. But, recently I took it upon myself to learn Arduino, and it’s been fun going back to old hobbies – building circuits and C!

The Arduino programming language include some C++ elements as well, especially classes, so this has allowed me to introduce object-oriented design concepts as well. Here’s a simple circuit with two interconnected traffic lights, programmed in such a way that neither can be green at the same time. The connected pedestrian crosswalk switches initiate a countdown, then switching one light to red before allowing the other light to turn green. I’ve added an additional safety consideration for broken red lights.

By using a TrafficLight class, I’m able to reuse code as well, instead of repeating code for each traffic light circuit. The method names clearly indicate their functional purpose, so the code is easy to read.

There’s several ways to approach this circuit control, the code here is just for demo purposes, something I hacked out rather quickly. Overall, it was a fun project to develop, and my first Arduino circuit beyond the starter package projects.

The TrafficLight class header

The interest element of this class is interconnectedLight. Notice that it is defined as a TrafficLight class as well. One traffic light refers to another traffic light. This being C++, the syntax is actually a pointer to a class, thus TrafficLight*.

class TrafficLight {
    byte red, yellow, green, switchPin ;
    TrafficLight* interconnectedLight ;
    boolean switchWasPressed = false ;
  public:
    void attach(int switchPin, int red, int yellow, int green) ;
    void interconnect(TrafficLight anotherLight) ;
    void countdownToGreen() ;
    boolean canSwitch() ;
    boolean isSwitchPressed() ;
    void turnLightGreenInitial() ;
  private:
    boolean isGreen() ;
    boolean switchAlreadyPressed() ;
    void turnLightRedInitial() ;
    void flashLights() ;
    boolean isRedFunctioning() ;
    void turnLightRed() ;
    void turnLightGreen() ;
    void greenOn() ;
      
} ;

The TrafficLight method implementations

Modeled after the Servo class in Arduino, the class is initialized with an attach() method, passing in all the pins wired on your circuit. It also sets up all the pinModes and initial settings for the LED lights.

void TrafficLight::attach(int aPin, int aRedPin, int aYellowPin, int aGreenPin) {
  switchPin = aPin ;
  red = aRedPin ;
  yellow = aYellowPin ;
  green = aGreenPin ;
  pinMode(yellow,OUTPUT) ;
  pinMode(green,OUTPUT) ;
  pinMode(red,OUTPUT) ;
  pinMode(switchPin,INPUT) ;
  digitalWrite(red,LOW) ;
  digitalWrite(yellow,LOW) ;
  digitalWrite(green,LOW) ;
}

void TrafficLight::interconnect(TrafficLight anotherLight) {
  interconnectedLight = &anotherLight ;
}

Also make note of the interconnect() method. Passed in is another TrafficLight, and since interconnectedLight is a pointer definition, we use &anotherLight, the “reference” to this object, to get the pointer assignment. For those new to C++, this pointer and address references are perhaps the most tricky syntaxes you’ll have to learn.

turnLightGreen() is perhaps the the most significant method in this class. The main microcontroller calls countdownToGreen() when the appropriate button is pushed; that method then calls turnLightGreen(). Notice what it does: it’s very first step is to turn the other light red. In then double checks that light, is it really red – isRedFunctioning() ? If it does, it then proceeds to turn this light green, after a suitable delay.

Oh, here’s another syntax quirk of C++: see the -> ? That’s the arrow reference, is used when calling a method of another class pointer. It is also used with this. This is not necessary, but makes most code easier to read: this->greenOn() meets means to call the greenOn() method on the class object itself. That is, if light1.turnLightGreen() is called, this is light1.

void TrafficLight::turnLightGreen() {
  interconnectedLight->turnLightRed() ;
  if (interconnectedLight->isRedFunctioning()) {
    delay(1000) ;
    this->greenOn() ;
    crosswalkSounding() ;
  }
  else {
    this->flashLights() ;
  }
  switchWasPressed = false ;
}

Here are the remaining methods. They should all be fairly easy to understand, but if you have any questions, feel free to email me.



void TrafficLight::countdownToGreen() {
  switchWasPressed = true ;
  countdown() ;
  this->turnLightGreen() ;
}

void TrafficLight::turnLightGreenInitial() {
  interconnectedLight->turnLightRedInitial() ;
  if (interconnectedLight->isRedFunctioning()) {
    this->greenOn() ;
  }
  else {
    this->flashLights() ;
  }
}

void TrafficLight::turnLightRedInitial() {
  digitalWrite(green,LOW) ;
  digitalWrite(yellow,LOW) ;
  digitalWrite(red,HIGH) ;
}

void TrafficLight::turnLightRed() {//this should probably be a private method
    digitalWrite(green,LOW) ;
    //if (!isRed()) {  //when recovering from malfunction, the light may already be red
      digitalWrite(yellow,HIGH) ;
      delay(5000) ;
      digitalWrite(yellow,LOW) ;
    //}
    digitalWrite(red,HIGH) ;
}

boolean TrafficLight::isGreen() {
  return digitalRead(green) == HIGH ;
}

boolean TrafficLight::isRedFunctioning() {
  pinMode(red,INPUT_PULLUP) ;
  boolean pullup_high ;
  pullup_high = digitalRead(red) ;//if input_pullup is high, this means the LED is off or missing
  pinMode(red,OUTPUT) ;
  return !pullup_high ;
}

boolean TrafficLight::isSwitchPressed() {
  //int switchState = 0 ;
  //switchState = digitalRead(switchPin) ;
  return digitalRead(switchPin) == HIGH ;
}

boolean TrafficLight::canSwitch() {
  return !this->switchAlreadyPressed() && this->isGreen() ;
}

boolean TrafficLight::switchAlreadyPressed() {
  return switchWasPressed ;
}

void TrafficLight::flashLights() {
  while (!interconnectedLight->isRedFunctioning()) {
    digitalWrite(green,HIGH) ;
    digitalWrite(yellow,HIGH) ;
    digitalWrite(red,HIGH) ;
    delay(500) ;
    digitalWrite(green,LOW) ;
    digitalWrite(yellow,LOW) ;
    digitalWrite(red,LOW) ;
    delay(500) ;
  }
  this->turnLightGreen() ;
}

void TrafficLight::greenOn() {
  digitalWrite(green,HIGH) ;
  digitalWrite(yellow,LOW) ;
  digitalWrite(red,LOW) ;
}

The microcontroller setup and main loop:

The setup() is straightforward: define light1 and light2 as TrafficLights, then “attach” them to their appropriate pins. interconnect light1 to light2 and, vice versa. Then turn on one of the lights. Calling turnLightGreenInitial() will automatically turn light2 to red.

TrafficLight light1 ;
TrafficLight light2 ;
const int piezoPin = 7 ;
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(LED_BUILTIN, LOW) ;
  light1.attach(2,3,4,5) ;    //switch, red, yellow, & green pins
  light2.attach(9,10,11,12) ;
  light1.interconnect(light2) ;
  light2.interconnect(light1)  ;
  light1.turnLightGreenInitial() ;
}

The microcontroller loop() itself if deceptively simple: it’s just checking whether any of the switches are pressed, and if the the light can be switched (i.e., isn’t already green or in the middle of being switched), then go ahead and start the countdown.

void loop() {
      if (light1.isSwitchPressed() && light1.canSwitch()) {
        light2.countdownToGreen() ;
      }
      if (light2.isSwitchPressed() && light2.canSwitch()) {
        light1.countdownToGreen() ;
      }  
}

Here’s an additional few functions, to control the piezo sound. I could have made a Piezo object as well, but there’s not much gain to such for this simple example.

void countdown() {
  for (int i = 0 ; i < 10; i++) {
      tone(piezoPin,1000,50) ;
  delay(1000) ;
  }
}

void crosswalkSounding() {
  for (int i = 0 ; i < 50; i++) {
    tone(piezoPin,250,10) ;
    delay(100) ;
  }
}