Project tutorial
10 Buttons Using 1 Interrupt

10 Buttons Using 1 Interrupt © CC BY

Connect up to 10 buttons using a single interrupt.

  • 29,884 views
  • 10 comments
  • 73 respects

Components and supplies

Apps and online services

About this project

Introduction

Interrupts are handy. They, in addition to sometimes making code simpler, can be used for precise timing or for waking the Arduino up from sleep mode.

Let's say you've got a user interface, a remote controller, for example, that runs on batteries. You might want to put the Arduino (or stand-alone ATmega) to power-down mode to save power. When an Arduino enters power-down mode it can only be woken up by an external interrupt. The ATmega328P chip used in an Arduino Uno has only two external pin interrupts. (INT0 and INT1 on pins 2 and 3) Since a user interface is likely to have more than two buttons, that's a problem.

The standard way to solve

this would be to connect all buttons normally, but to also connect them to an interrupt pin with a diode. This does, however, complicate the circuit significantly.

In addition to standard external interrupts, the ATmega328P also has pin change interrupts. There are libraries to deal with them and they are a good solution to this problem.

However, during a programming competition, I figured out how to do this using standard external interrupts with no extra electrical components.

The circuit

We have a few buttons. I used ten, which fit nicely on my breadboard. (I also don't have more.) You can have one button per pin, which means up to 20 on a Uno and up to 70 on a Mega! (If you actually need 70 buttons, I recommend using multiplexing, you don't need an entire Mega for that.)

Each button has one side connected to an arbitrary pin. (4-13 in my case) The other sides of all buttons are connected together to a single interrupt-capable pin. (2 in my case)

The code

The code is attached below. To get this example to work, upload it to your board. Open your serial monitor. When you press a button, its number will appear. As you can see, the loop function is not used at all.

How does it work?

There is, obviously, an interrupt. In my case, it's attached to pin 2. It's configured as FALLING.

To avoid using diodes, the Arduino rewires the circuit on the fly. There are two possible configurations: Common mode and Distinct mode.

Common mode

Most of the time, the circuit will be in common mode. The interrupt pin will be configured as INPUT_PULLUP and the rest will be OUTPUT and LOW.

void configureCommon() {
 pinMode(commonPin, INPUT_PULLUP);
 for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
   pinMode(buttonPins[i], OUTPUT);
   digitalWrite(buttonPins[i], LOW);
 }
}

In common mode, pressing any button will pull our interrupt pin down and fire our interrupt. Once that happens, our interrupt service routine will reconfigure the pins for distinct mode.

Distinct mode

Once our interrupt triggers, we quickly switch to distinct mode.

Distinct mode is the opposite of common mode. The interrupt pin will be OUTPUT and LOW and the rest will be INPUT_PULLUP.

void configureDistinct() {
 pinMode(commonPin, OUTPUT);
 digitalWrite(commonPin, LOW);
 for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
   pinMode(buttonPins[i], INPUT_PULLUP);
 }
}

In distinct mode, only pins corresponding to buttons actually pressed will be pulled down. We can easily go over all pins to find out which one triggered the interrupt.

After that's done, the Arduino can switch back to common mode and wait for another interrupt. Or, depending on your application, it can stay in distinct mode and process user input as it would normally, switching back to common mode before the Arduino goes to sleep.

A more complex example

Let's try something a bit more complex. We'll attach a servo and map each button to a different angle. (1=0°, 2=20°... 10=120°) We'll also power our Arduino with a few batteries.

In this example, we put the Arduino to power-down mode after five seconds of inactivity to conserve power. You can find some tutorials on sleep mode online. In addition to that, we power the servo through a transistor to shut it down when it's not in use.

The code for this example is also attached below.

Demonstration

Code

Serial loggerArduino
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};

unsigned long lastFire = 0;

void setup() {
  configureCommon(); // Setup pins for interrupt

  attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);

  Serial.begin(9600);
}

void loop() {
  // Empty!
}

void pressInterrupt() { // ISR
  if (millis() - lastFire < 200) { // Debounce
    return;
  }
  lastFire = millis();

  configureDistinct(); // Setup pins for testing individual buttons

  for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) { // Test each button for press
    if (!digitalRead(buttonPins[i])) {
      press(i);
    }
  }

  configureCommon(); // Return to original state
}

void configureCommon() {
  pinMode(commonPin, INPUT_PULLUP);

  for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
    pinMode(buttonPins[i], OUTPUT);
    digitalWrite(buttonPins[i], LOW);
  }
}

void configureDistinct() {
  pinMode(commonPin, OUTPUT);
  digitalWrite(commonPin, LOW);

  for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
    pinMode(buttonPins[i], INPUT_PULLUP);
  }
}

void press(int button) { // Our handler
  Serial.println(button + 1);
}
Servo with sleep modeArduino
#include <Servo.h>
#include <avr/sleep.h>
#include <avr/power.h>

const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};

const int servoEnablePin = A1;
const int servoPin = A0;
Servo servo;

unsigned long lastFire = 0;
int status = 0;

void setup() {
  pinMode(commonPin, INPUT_PULLUP);
  
  configureCommon();
  
  attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);

  servo.attach(servoPin);
  pinMode(servoEnablePin, OUTPUT);
}

void loop() {
  if (millis()-lastFire > 5000) {
    digitalWrite(servoEnablePin, LOW);
    
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();  
    sleep_mode();
  }

  delay(10);
}

void pressInterrupt() {
  sleep_disable();
  power_all_enable();
  
  if (millis()-lastFire < 200) {
    return;
  }
  lastFire = millis();
  
  configureDistinct();

  for (int i=0;i<sizeof(buttonPins)/sizeof(int);i++) {
    if (!digitalRead(buttonPins[i])) {
      press(i);
    }
  }

  configureCommon();
}

void configureCommon() {
  pinMode(commonPin, INPUT_PULLUP);
  
  for (int i=0;i<sizeof(buttonPins)/sizeof(int);i++) {
    pinMode(buttonPins[i], OUTPUT);
    digitalWrite(buttonPins[i], LOW);
  }
}

void configureDistinct() {
  pinMode(commonPin, OUTPUT);
  digitalWrite(commonPin, LOW);
  
  for (int i=0;i<sizeof(buttonPins)/sizeof(int);i++) {
    pinMode(buttonPins[i], INPUT_PULLUP);
  }
}

void press(int button) {
  digitalWrite(servoEnablePin, HIGH);
  servo.write(map(button,0,9,0,180));
}

Schematics

Serial logger
Diagram 1tkyyremc0
Servo with sleep
Servo with sleep 0dfsqwvaru
Serial logger - Fritzing file
multiinterrupt_hoF76Oc4T5.fzz
Servo with sleep - Fritzing file
servo_with_sleep_u9ZqxF0jhY.fzz

Comments

Similar projects you might like

A PM2.5 and PM 10 Detector design for Windows 10 UWP App

Project tutorial by Jiong Shi

  • 8,777 views
  • 5 comments
  • 24 respects

Home Automation Using Raspberry Pi 2 And Windows 10 IoT

Project tutorial by Anurag S. Vasanwala

  • 306,775 views
  • 99 comments
  • 735 respects

Home Automation with Arduino MKR1000 and Windows 10

Project tutorial by Ioannis Kydonis

  • 9,608 views
  • 1 comment
  • 32 respects

Windows 10 IoT Core: UltraSonic Distance Mapper

Project showcase by Anurag S. Vasanwala

  • 54,371 views
  • 23 comments
  • 145 respects

Arduino Bluetooth Robot for Android Device

Project showcase by aip06

  • 3,097 views
  • 2 comments
  • 15 respects

Windows 10 IoT Plant Monitoring System

Project tutorial by Team BME-AUT

  • 13,920 views
  • 5 comments
  • 60 respects
Add projectSign up / Login