detect which button is pressed and use that value in switch statement?

Hi,

I am trying to achieve the following:
4 buttons, momentary type, each representing a scenario.
Each scenario sets 4 relays with different combinations of on/off per relay.

So click button 1, store the value of this button (let's say 1) and then execute scenario 1, click button 2 and execute scenario 2 (with obviously different values for the 4 relays).

Is this possible?

I have been trying to achieve this using the switch statement, but failed miserably so far.
I want to start simple just to get this part to work, will add debouncing etc later. Once I have this part, I can use switch cases to start including triggering midi program changes too, but want to start simple.

Even a simple example with only 2 buttons and 2 switch cases will do, I can build on this.

Anyone have a working example or a direction where to start? The sample codes haven't been very helpful so far, but I might have been looking in the wrong sections?

Help is much appreciated!

4 switches, 16 possible combinations. Do you want to be able to handle them all?

Thanks for you quick response!

No, the intention here is that I will only use each button individually, therefor I used the momentary switches.

If required, i will update it later to exclude any errors when two buttons are pressed simultaneously by accident. So, button 1, press detected? then execute scenario 1. Button 2 press detected, execute scenario 2.

I could use some advice which operator to use. Switch seemed appropriate, but I need to take the value (representing which button is pressed) to trigger the case.

let's hope this works, first time posting code (using leds for now, easier to wire up in testing). Also a lot of serial prints, to verify if variables are set properly:

//setting the pins
int ledPin1 = 13;
int ledPin2 = 2;
int buttonPin1 = 7; //replace with relays later
int buttonPin2 = 8; //replace with relays later


//setting the variables
int button1State; //detect button state pressed.
int button2State;
int channelSelect = 0;

void setup() { 

//Led for channel select
 pinMode(ledPin1, OUTPUT);
 pinMode(ledPin2, OUTPUT);
// pinMode(ledPin3, OUTPUT);
// pinMode(ledPin4, OUTPUT);

//channel select switches
 pinMode(buttonPin1, INPUT_PULLUP);
 pinMode(buttonPin2, INPUT_PULLUP);
//  pinMode(buttonPin3, INPUT_PULLUP);
//  pinMode(buttonPin4, INPUT_PULLUP);

//Serial for displaying the values during debugging
Serial.begin(9600);

}


void loop() {

// output to serial monitor to validate if variables are set properly
Serial.print("Channelselect value:");
Serial.print(channelSelect);
Serial.print(" - ");
Serial.print("Button1state: ");
Serial.print(button1State);
Serial.print(" - ");
Serial.print("Button2state: ");
Serial.print(button2State);
Serial.print("\n");

int button1State = digitalRead(buttonPin1); //validate button1 pressed
int button2State = digitalRead(buttonPin2); //validate button2 pressed

//verify buttonstate and set the channelselect value to trigger case
   if(button1State == 1)
       {
       channelSelect = 1;
     }
     if(button2State == 1)
       {
       channelSelect = 2;
     }

switch(channelSelect)
   {
     case 0:
       digitalWrite(ledPin1, HIGH);
       digitalWrite(ledPin2, LOW);
       break;
     case 1:
       digitalWrite(ledPin1, LOW);
       digitalWrite(ledPin2, HIGH);
       break;
   }
}

I would connect to 4 pins, say 4:7 and then set the pin change interrupt which will trigger as soon as a button gets pressed (assumptions 1. you've debounced the switches and 2. they're on the same port, although not entirely necessary).

Your ISR can determine which switch has been pressed and pass this information on to a post-interrupt function where you can use your switch statement if you choose or have a separate function for each button. From a guideline perspective, I prefer the switch function when dealing with state changes rather than singular events in which case dedicated functions are preferable. Makes it a bit easier to understand for the guy that has to clean up afterwards.

Thanks, let me investigate on some of the things you mentioned, don't want to waste your time with rookie questions. When you say "they are on the same port", what do you mean with port?

And how do a "pass this information on to a post-interrupt function?

I will read up on the other stuff you mentioned and come back with probably more questions.

There's nothing fundamentally wrong with what you've done there, except for assigning channelSelect as 1 or 2 and then switching based on 0 and 1.

will make those changes and redo some testing!

I would use a for loop to read the 4 input pins with the pin numbers in an array.
Before you start the for loop set a flag variable to -1. Save the for loop variable value to the flag variable if you detect that an input is LOW (button pressed)

When the for loop is complete test the flag variable. If its value is not -1 then a keypress has been detected and the value will be between 0 and 3. Use this value as the row index to a 2 dimensional array with the columns holding the values (HIGH or LOW) to be output to the relays.

Iterate through the columns of the selected row of the array and set the outputs appropriately

I get the first part, but the second part, using the value, is something new for me:

"Use this value as the row index to a 2 dimensional array with the columns holding the values (HIGH or LOW) to be output to the relays.

Iterate through the columns of the selected row of the array and set the outputs appropriately"

Is there a sample you can refer to so I can learn this part?

The pin change interrupt is attached to a port which means that any pin on that port, so long as it has been enabled, will trigger the interrupt, PCINT0 is attached to PORTB which includes pins 8:13. PCINT1 is attached to PORTC (14:19 or A0 thru A5 if you prefer) and PCINT2 with PORTD (0:7).

You have your buttons spec'd on 7 and 8 which is one from PORTD and one from PORTB respectively. That means having to deal with different ISRs, which is fine and may be the way you wish to handle it.

DKWatson:
You have your buttons spec'd on 7 and 8 which is one from PORTD and one from PORTB respectively. That means having to deal with different ISRs, which is fine and may be the way you wish to handle it.

Ah, thanks, I was totally not aware that the pins were actually grouped like that. Now this makes complete sense. Thanks for taking the time, I will read up on this a bit more. Most of my projects I build using the starter guide and I was even able to create a laser-game set just expanding on the samples given. But this is completely new to me.

As Bob mentions, your buttonID could be any value not within the index range of the array [0:3]. As you set your loop. for(i = 0; i < 4; i++) and check button*, if you detect the button having been pressed set the buttonID to i.*
After the loop switch(buttonID) case 0 to case 3 with functions that deal with each button and default to deal with the no_button_pressed scenario.

This is a bit of a blurb I wrote a while back that tries to walk someone through the use of pin change interrupts.

Before we start, understand that we are talking about the atmega328p microcontroller which is used in the Uno, Nano, Micro and a host of other boards. As long as you're using AVR-GCC as the compiler and include avr/io.h with the defined processor being AVR_ATmega328P the translations will work. If you're usimg the Arduino IDE or ATStudio you do not really need to know this as it is done 'under the hood' for you. As this is kind of a tutorial though, I'll mention it for completeness.

Next, to make sure we're on the same page, see that you have the datasheet handy. I'll use what I believe to be the latest version dated 11/2016. If you need it, the following link will deliver it to you.

328P datasheet

INT0 and INT1 are the external interrupts attached to Arduino pins 2 and 3 respectively. We will not be discussing them here.

The sections of the datasheet that mean the most for this discussion begin on page 92, section 17.2.4, the Pin Change Interrupt Control Register.

Before that however, I think it useful to have a table that shows the mapping of the pins. Referring to the diagram on page 14,

PCINT Port Pin Arduino Uno

PCINT0 PB0 8
PCINT1 PB1 9
PCINT2 PB2 10
PCINT3 PB3 11
PCINT4 PB4 12
PCINT5 PB5 13
(PB6 and 7 are used for the crystal)

PCINT8 PC0 14/A0
PCINT9 PC1 15/A1
PCINT10 PC2 16/A2
PCINT11 PC3 17/A3
PCINT12 PC4 18/A4
PCINT13 PC5 19/A5
(PC6 and 7 are not avaiable on the Uno)
PCINT14 reset
PCINT15 doesn't exist

PCINT16 PD0 0
PCINT17 PD1 1
PCINT18 PD2 2
PCINT19 PD3 3
PCINT20 PD4 4
PCINT21 PD5 5
PCINT22 PD6 6
PCINT23 PD7 7

Ok so far? Go back to page 92 and read from the bottom up.

PCIE0: Pin Change Interrupt Enable 0
Handles PCINT[7:0], that's Atmel nomenclature for PCINT7, PCINT6, ... , PCINT0 which correspond the the pins on PORTB.

Similarly, PCIE1 handles PCINT[14:8] aka PORTC leaving PCIE2 for PORTD and PCINT[23:16].

Now look at the top of the page where you see the Pin Change Interrupt Control Register and the lower three bits reflect the PCIE we just talked about. Writing a 1 to any of these bit positions enables the interrupts for that port. In our case we want A0 - PCINT8 which is part of the group under PCIE1.

So you can:

PCICR |= 1 << PCIE1; or
bitSet(PCICR,PCIE1); or
bitSet(PCICR,1); or
PCICR |= 0B00000010; or
bitWrite(PCICR,1,1); or
bitWrite(PCICR,PCIE,HIGH); or
on and on ...

What you have just done is enable interrupts for the entire group (port) but as yet no interrupts are active. To activate interrupts on a specific pin in that group, you need to address the mask for that particular grouping. The masks are detailed on pages 94-96 with the mask port PCIE1 on page 95. Again skip to the bottom and read first confirming that PCMSK1 indeed enables PCINT8 thru PCINT14 and we see now from the register diagram that PCINT8 maps to bit position 0. As before we can:

PCMSK |= 1 << PCINT8; or any one of the other methods that meets your fancy.

Having done that now any level change on pin C0/A0/14/PCINT8 will fire the interrupt that deals with that group. Not here that there is one interrupt for each of the groups, not the individual pins. Any pin activated by writing a 1 to the corresponding bit position in its respective PCIE will trigger the interrupt. It is up to your interrupt service routine to determine which is the culprit. This is often done by reading the entire port and then comparing that reading with a previous value and noting the change(s?). As in this particular case we are only dealing with a single pin we know that any interrupt is a result of it changing. Turning to page 82 we see that the interrupt is at address 0x0008 which we know will jmp us to the routine named ISR(PCINT1_vect) so in your code,

ISR(PCINT1_vect)
{
// your interrupt service routine goes here
}

That just about does it. All of this presupposes that global interrupts are turned on. sei() will take care of that as will SREG |= 0B10000000;.

A couple of little extras, perhaps for convenience, switching the interrupt off/on, which you may choose to do while servicing it so as not to be sidelined;

#define enableA0 PCMSK1 |= 0B00000001
#define disableA0 PCMSK1 &= 0B11111110

Of course you can rename enableA0 and disableA0 to anything you like.

Good luck. Let me know if you're still confused. I've keyed this in as slowly as I could.

dkw

Will go through this and do some testing.
Much appreciated!!!!!

Is there a sample you can refer to so I can learn this part?

A small example using a 2 dimensional array to output values for a selected row

const byte outputs[4][4] =
{
  {HIGH, LOW, LOW, LOW},
  {LOW, HIGH, HIGH, LOW},
  {HIGH, LOW, HIGH, HIGH},
  {LOW, LOW, LOW, HIGH}
};

void setup()
{
  Serial.begin(115200);
  //suppose that you have detected an input on switch 2
  byte detectedSwitch = 2;

  //iterate through the columns of row 2 of the array and output the values
  byte row = detectedSwitch;

  for (int col = 0; col < 4; col++) //each column
  {
    Serial.print(outputs[row][col]);
    Serial.print("\t");
  }

}

void loop()
{
}

Change the value of detectedSwitch to output a different row of values.

Unless there is good reason to use them I would stay away from using interrupts.

Had some difficulty with getting it to work with the methods described in the various answers. So i tried it using various if statements and got it to work.

It is probably not the best way of programming and I will make it more efficient, because there is a lot of repetition in the code, but it works!!

I still have to include sending the midi signals per function, but that was working in my original sketch so I can copy that in, without any issues.

Thanks for all the help! Although I wasn't able to get it all incorporated, it did make me push through.

Code for anyone who is interested:

//buttons, leds en kanalen vastleggen
const int switch1 = 8;
const int switch2 = 9;
const int switch3 = 10;
const int switch4 = 11;
const int switch5 = 12;

const int led1 = A0;
const int led2 = A1;
const int led3 = A2;
const int led4 = A3;
const int led5 = A4;

const int peaveyChannel = 2;
const int peaveyBoost = 3;
const int peaveyReverb = 4;
const int peaveyLoop = 5;

//switches en status definieren
int switch1State = 0;
int switch2State = 0;
int switch3State = 0;
int switch4State = 0;
int switch5State = 0;

int prevSwitch1State = 0;
int prevSwitch2State = 0;
int prevSwitch3State = 0;
int prevSwitch4State = 0;
int prevSwitch5State = 0;

void setup() {     
  // type kanaal in- of uit vastleggen
  Serial.begin(9600);  //not needed now, but left in to monitor the variables during debugging.
  pinMode(switch1, INPUT_PULLUP);
  pinMode(switch2, INPUT_PULLUP);
  pinMode(switch3, INPUT_PULLUP);
  pinMode(switch4, INPUT_PULLUP);
  pinMode(switch5, INPUT_PULLUP);

  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
  pinMode(led4, OUTPUT);
  pinMode(led5, OUTPUT);

  pinMode(peaveyChannel, OUTPUT);
  pinMode(peaveyBoost, OUTPUT);
  pinMode(peaveyReverb, OUTPUT);
  pinMode(peaveyLoop, OUTPUT);
  
 }

void loop()       
//per switch een wijziging detecteren en daarna de functie voor kanaalwijziging aanroepen
{
switch1State = digitalRead(switch1);
//    if (switch1State =!prevSwitch1State){
      if (switch1State == LOW) {
          channel1();
//          prevSwitch1State = switch1State;
//      }
  }
  switch2State = digitalRead(switch2);
//  if (switch2State =!prevSwitch2State){
      if (switch2State == LOW) {
          channel2();
//          prevSwitch2State = switch2State;
//      }
  }

  switch3State = digitalRead(switch3);
//  if (switch3State =!prevSwitch3State){
      if (switch3State == LOW) {
          channel3();
//         prevSwitch3State = switch3State;
//      }
  }
    switch4State = digitalRead(switch4);
//  if (switch4State =!prevSwitch4State){
      if (switch4State == LOW) {
          channel4();
//         prevSwitch4State = switch4State;
//      }
  }
    switch5State = digitalRead(switch5);
//  if (switch5State =!prevSwitch5State){
      if (switch5State == LOW) {
          channel5();
//         prevSwitch5State = switch5State;
//      }
  }
}

//functie alle leds uit bij inschakelen nieuw kanaal en kanalen naar default
void AllOff(){
   digitalWrite(led1, LOW);
   digitalWrite(led2, LOW);
   digitalWrite(led3, LOW);
   digitalWrite(led4, LOW);
   digitalWrite(led5, LOW); 
   digitalWrite(peaveyChannel, LOW);
   digitalWrite(peaveyBoost, LOW);
   digitalWrite(peaveyReverb, LOW);
   digitalWrite(peaveyLoop, LOW);  
}

//functies per kanaalkeuze
void channel1(){
  AllOff();
  digitalWrite(led1, HIGH);
   digitalWrite(peaveyChannel, LOW);
   digitalWrite(peaveyBoost, LOW);
   digitalWrite(peaveyReverb, LOW);
   digitalWrite(peaveyLoop, LOW);  
}
void channel2(){
  AllOff();
  digitalWrite(led2, HIGH);
   digitalWrite(peaveyChannel, HIGH);
   digitalWrite(peaveyBoost, LOW);
   digitalWrite(peaveyReverb, LOW);
   digitalWrite(peaveyLoop, LOW);  
}
void channel3(){
  AllOff();
  digitalWrite(led3, HIGH);
   digitalWrite(peaveyChannel, HIGH);
   digitalWrite(peaveyBoost, HIGH);
   digitalWrite(peaveyReverb, LOW);
   digitalWrite(peaveyLoop, LOW);  
}
void channel4(){
  AllOff();
  digitalWrite(led4, HIGH);
   digitalWrite(peaveyChannel, LOW);
   digitalWrite(peaveyBoost, HIGH);
   digitalWrite(peaveyReverb, LOW);
   digitalWrite(peaveyLoop, LOW);  
}
void channel5(){
  AllOff();
  digitalWrite(led5, HIGH);
   digitalWrite(peaveyChannel, LOW);
   digitalWrite(peaveyBoost, LOW);
   digitalWrite(peaveyReverb, LOW);
   digitalWrite(peaveyLoop, HIGH);  
}

When you start numbering variables, that's a sure sign that you need arrays.

When you copy and paste code to change only the number in the name, that's another sign that arrays would lead to a for loop, instead of copy/paste/paste/paste/oh-shit-forgot-to-change-that-number/paste/paste crap.

You almost certainly DO want the state change detection code that you commented out.

You could almost certain have ONE channel() function, taking an argument (that happens to match the for loop/array index), with just a little thought, and 80% less code (and bug hiding places) than you have now.

thanks for the suggestions! Now that I have a working piece of code, I can build on this and make the improvements as per your post.

I will start with the arrays and then continue with the simplified functions.

It helps that the code now works so troubleshooting is easier since I can focus on the arrays only and continue from there.