5
\$\begingroup\$

I made a panel of buttons which run commands when pushed. In order to not have the command run on every loop if the button is held down, I am comparing the button to a previous state and only run if it is not, like so:

buttonStateR1 = digitalRead(redButton1);
if (buttonStateR1 != lastButtonStateR1) {
 if (buttonStateR1 == LOW) {
 startFlash();
 Serial.println('R1');
 }
}
lastButtonStateR1 = buttonStateR1;

This works quite well for only a couple of buttons. Is there a more effective method to use when there are 20 or so buttons? Having 20 or so of these statements does work but it get unwieldy and hard to maintain. Luckily each button acts the same (only needs to know that it has been pressed, not that it is held down or held down for a certain period of time, etc).

This is the full code and setup with 4 buttons and a toggle to turn it all on or off:

// LED output mappings
int greenLED = 0;
int redLED = 1;
// Pin input mappings
const int redButton1 = 5;
const int redButton2 = 3;
const int blackButton1 = 4;
const int blackButton2 = 6;
const int safetyToggle = 2;
// Button states
int buttonStateR1 = HIGH;
int lastButtonStateR1 = HIGH;
int buttonStateR2 = HIGH;
int lastButtonStateR2 = HIGH;
int buttonStateB1 = HIGH;
int lastButtonStateB1 = HIGH;
int buttonStateB2 = HIGH;
int lastButtonStateB2 = HIGH;
// Serial reading
String inputString = "";
boolean stringComplete = false;
// Helper functions
void turnOn(int pin) {
 digitalWrite(pin, HIGH);
}
void turnOff(int pin) {
 digitalWrite(pin, LOW);
}
void cycleLED(int led, int time){
 turnOn(led);
 delay(time);
 turnOff(led);
 delay(time);
}
void startFlash() {
 cycleLED(greenLED, 50);
 cycleLED(redLED, 50);
 cycleLED(greenLED, 50);
 cycleLED(redLED, 50);
}
void successFlash() {
 cycleLED(greenLED, 50);
 cycleLED(greenLED, 50);
}
void failureFlash() {
 cycleLED(redLED, 50);
 cycleLED(redLED, 50);
}
void setup() {
 pinMode(greenLED, OUTPUT);
 pinMode(redLED, OUTPUT);
 digitalWrite(greenLED, LOW);
 digitalWrite(redLED, LOW);
 // INPUT_PULLUP sets input buttons when unconnected to high
 // this means that the 'pressed' state outputs low
 pinMode(redButton1, INPUT_PULLUP);
 pinMode(redButton2, INPUT_PULLUP);
 pinMode(blackButton1, INPUT_PULLUP);
 pinMode(blackButton2, INPUT_PULLUP);
 pinMode(safetyToggle, INPUT_PULLUP);
 Serial.begin(9600);
 startFlash();
}
void loop() {
 if (digitalRead(safetyToggle) == LOW) { // 'safety' toggle
 // Button Red 1
 buttonStateR1 = digitalRead(redButton1);
 if (buttonStateR1 != lastButtonStateR1) {
 if (buttonStateR1 == LOW) {
 startFlash();
 Serial.println('R1');
 }
 }
 lastButtonStateR1 = buttonStateR1;
 // Button Red 2
 buttonStateR2 = digitalRead(redButton2);
 if (buttonStateR2 != lastButtonStateR2) {
 if (buttonStateR2 == LOW) {
 startFlash();
 Serial.println('R2'); 
 }
 }
 lastButtonStateR2 = buttonStateR2;
 // Button Black 1
 buttonStateB1 = digitalRead(blackButton1);
 if (buttonStateB1 != lastButtonStateB1) {
 if (buttonStateB1 == LOW) {
 startFlash();
 Serial.println('B1');
 }
 }
 lastButtonStateB1 = buttonStateB1;
 //Button Black 2
 buttonStateB2 = digitalRead(blackButton2);
 if (buttonStateB2 != lastButtonStateB2) {
 if (buttonStateB2 == LOW) {
 startFlash();
 Serial.println('B2');
 }
 }
 lastButtonStateB2 = buttonStateB2;
 }
 if (stringComplete) {
 if (inputString.equals("OK")) {
 successFlash();
 } 
 else {
 failureFlash();
 };
 inputString = "";
 stringComplete = false;
 }
}
void serialEvent() {
 while (Serial.available()) {
 char inChar = (char)Serial.read();
 inputString += inChar;
 if (inChar == '\n') {
 stringComplete = true;
 }
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Feb 22, 2015 at 13:07
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

There's a few things about this that could potentially be improved here.

C++ related

You can improve the C++ code here in a few ways. If you have access to the C++11 language features I'd suggest replacing:

// Pin input mappings
const int redButton1 = 5;
const int redButton2 = 3;
const int blackButton1 = 4;
const int blackButton2 = 6;
const int safetyToggle = 2;

With a typesafe enum:

enum class InputPinMappings: uint8_t{
 redButton1 = 5,
 redButton2 = 3,
 blackButton1 = 4,
 blackButton2 = 6,
 safetyToggle = 2
}

I'd do the same for the LED pin mappings too.

Platform related

Most of the biggest imrpovements you can get here come from knowing the hardware platform better.

Get to know the hardware platform

In the arduino the GPIO pins are mapped to a hardware address where each pin is a bit in a block of pins. The Arduino UNO for example is a platform that uses the Atmega328p to power it. If you look at the datasheet for the Atmega 328p www.atmel.com/Images/doc8161.pdf you'll see how the pins are mapped to the physical addresses.

Specifically you want to look at the register summary on the datasheet. You'll see that Pins are grouped into blocks PINB, PINC, etc. These blocks are one 8bit byte in the memory with each pin represented by a bit in those bytes.

You can create a helper struct to map to the individal pins if you would like to encapsulate the bit manipulations, I'd highly recommend putting as much of that as possible at compile time though as you don't want to incur the runtime performance hit from doing that if you can at all avoid it. (If you improve your code and would like to look into some compile time hackery for this platform perhaps that would make for a good followup question, I think it's a bit out of the scope of this question though)

Use datatypes that match the underlying system

Currently you are using an int type to store each pin, now that you know how these pins are represented you can see that this might be wasteful in a few ways.

You can represent your buttons state more effeciently by packing them into a single 8bit integers as represented by the actual bit masks of the pins. You probably want to group the buttons into variables based on which Pin grouping they are on.

For example:

uint8_t ButtonsOnPINDmask = PIND7 | PIND3; //button on PIND7 and PIND3

This variable can represent which buttons you have connected to PIND.

We can then use this to our advantage when checking which buttons have changed state. We only need to do 1 integer comparison per block to see if anything has changed as opposed to having to check for each pin individually. Once you have seen that something has change then you can look through to find which buttons have changed state one bit at a time.

uint8_t previousPINDstate;
if(previousPINDstate == *PIND){
 //nothing changed
}else{
 //check for changes
 previousPINDstate = *PIND;
}

(Note that this should be in an atomic/synchronized block or you risk run into some nasty problems with the state of PIND changing in between the initial check for the state change and then using the PIND value)

Now this brings me to an important point, if you are checking the status of the buttons every time you go through the main loop you are likely wasting a lot of cycles. You are going to be doing a lot of redundant checks and wasting a bunch of cycles on this.

Use interrupts

Luckily the chip designers have taken pin change considerations into account and they provide pin change interrupts for you for this exact reason. So what I would suggest is using the pin change interrupt capabilities of the underlying hardware to inform you of when a pin change occurs.

You can hook up an interrupt that gets fired when the state of a pin gets changed which will then call your hander function. Specifically you want to look into the interrupts such as PCINT0_vect. The interrupts available will depend on what Arduino hardware you are running this on, so go check out the documentation.

By using the ISR approach you will save a lot of main loop cycles, so it's worth looking into this.

answered Jun 6, 2015 at 16:49
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.