Interrupts and passing strings: avoiding code duplication

Hey everyone,
Happy new year.

I'm building a multi-way switching device. I've already designed the hardware and PCBs are made so now I'm turning my attention to the programming.

The unit has several limit switches ("emergency stops") which will use interrupt pins. Since there are 7 limit inputs, I'm trying to find out if there is a way to avoid code duplication by passing a character to a function.

For example, when input limit_1 is triggered, I would like to pass the number 1 to a function which will illuminate error LED 1. limit_2 will illuminate error LED 2, etc.

Since it is a multi-way switcher, there are other places in the code where avoiding code duplication would be helpful for ease of code management.

Can anyone make a suggestion on how this could be achieved in Arduino, please?

All the best for 2025,
Dax.

Which Arduino are you using?

Why are you thinking to sample the inputs by interrupts instead of polling?

Is there any other task (s) for the MCU to do other that sampling the inputs?

Using interrupts for e-stop functionality is widely frowned upon.

If the software fails in many ways, your systems can’t be stopped.

There’s much more to it,,but that’s the essence,

1 Like

What is e-stop functionality?

As mentioned - limit switches etc,

Thank you everyone for your replies, I really appreciate them.
I'm using Mega 2560 Pro MINI for its high pin count and ease of programming.

The device switches 5 guitar amplifiers and 2 speaker cabinets. There is one input socket for a guitar, 5 output (send) sockets to distribute that guitar input to one of the 5 guitar amps. Then there are 5 input (return) sockets from the guitar amplifiers' speaker outputs which route to a final relay that sends that amp's signal to either speaker cab A or B.
There are 7 buttons on the front panel: amps 1-5, cab A/B and random. Tube/valve guitar amps can be damaged if there is signal on their input and they are not connected to a load so a short pause is required between each switching (hence using Arduino instead of simple mechanical switch). That is also why I am checking if the sockets are occupied (using the switch contacts of the jack sockets) before switching to them.
It's not life-or-death stuff, but I really don't want to blow up guitar amps, hence these precautions.

This is the basic pseudocode I've started. Please forgive me for the inconsistent naming conventions used in it (but see at the bottom if you are curious to actual I/O pin names).

[MAIN FUNCTION LOOP:]
If amp button #X is pressed:
  If amp socket #X contact == high (socket occupied)
    Set channel state variable to #X,
    extinguish all 5 amp button LEDs,
    mute all GTR send relays,
    wait 50ms,
    mute all amp return relays,
    wait 50ms,
    engage amp return relay #X,
    wait 50ms,
    engage GTR send relay #X,
    illuminate button LED #X.
  else
   flash button LED #X three times (to indicate channel can't be selected),
   illuminate original button LED. (original needs to be saved as a variable)

When cab button is pressed:
  If cab state variable == A (red LED)
    If cab socket B contact == high (socket occupied)
      mute all GTR send relays,
      wait 50ms,
      mute all amp return relays,
      wait 50ms,
      set cab relay to B (high)
      wait 50ms,
      engage amp return relay #X,
      wait 50ms,
      engage GTR send relay #X,
      illuminate GREEN cab button LED,
      set cab state variable to B,
      return to MAIN FUNCTION.
    else
     flash cab B LED (GREEN) three times,
     illuminate cab A LED (RED).
  
  If cab state variable == B (green LED)
    If cab socket A contact == high (socket occupied)
      mute all GTR send relays,
      wait 50ms,
      mute all amp return relays,
      wait 50ms,
      set cab relay to A (low)
      wait 50ms,
      engage amp return relay #X,
      wait 50ms,
      engage GTR send relay #X,
      illuminate RED cab button LED,
      set cab state variable to A,
      return to MAIN FUNCTION.
    else
      flash cab A LED (RED) three times,
      illuminate cab B LED (GREEN).

If Random button is pressed:
 Check which amp return sockets are occupied (high), then randomly choose one of them (with the standard Mute, Wait, Mute, Wait, Engage amp return, Wait, Engage GTR send, but don't illuminate the corresponding amp button LED)
If random button is pressed again,
 Illuminate the corresponding amp button LED.

[END OF MAIN LOOP]

[INTERRUPTS]
If, at any time, any socket switch contacts go low:
  mute all GTR send relays,
  wait 50ms,
  mute all amp return relays,
  then flash the corresponding button LED for the amp channel (or cab button LED red or green for SKT_A or SKT_B)

And the setup section:

const IP_SPK_SKT_1 = A14; //interrupt
const IP_SPK_SKT_2 = A12; //interrupt
const IP_SPK_SKT_3 = A10; //interrupt
const IP_SPK_SKT_4 = A13; //interrupt
const IP_SPK_SKT_5 = A8;  //interrupt
const IP_SPK_SKT_A = A9;  //interrupt
const IP_SPK_SKT_B = A11; //interrupt
const int IP_AMP_SW_1 = 35;
const int IP_AMP_SW_2 = 37;
const int IP_AMP_SW_3 = 39;
const int IP_AMP_SW_4 = 41;
const int IP_AMP_SW_5 = 43;
const int IP_RAND_SW = 45;
const int IP_CAB_SW = 47;
const int OP_AMP_SW_LED_1 = 2;  //pwm
const int OP_AMP_SW_LED_2 = 4;  //pwm
const int OP_AMP_SW_LED_3 = 6;  //pwm
const int OP_AMP_SW_LED_4 = 8;  //pwm
const int OP_AMP_SW_LED_5 = 10; //pwm
const int OP_RAND_SW_LED = 12;  //pwm
const int OP_CAB_SW_LED_R = 14; //cab button has RGB LED
const int OP_CAB_SW_LED_G = 16;
const int OP_CAB_SW_LED_B = 18;
const int OP_GTR_RLY_1 = 22;
const int OP_GTR_RLY_2 = 24;
const int OP_GTR_RLY_3 = 26;
const int OP_GTR_RLY_4 = 28;
const int OP_GTR_RLY_5 = 30;
const int OP_SPK_RLY_1 = 33;
const OP_SPK_RLY_2 = A15;
const OP_SPK_RLY_3 = A5;
const OP_SPK_RLY_4 = A3;
const OP_SPK_RLY_5 = A1;
const int OP_CAB_RLY = 20;

void setup() {

  pinMode(IP_SPK_SKT_1, INPUT):
  pinMode(IP_SPK_SKT_2, INPUT):
  pinMode(IP_SPK_SKT_3, INPUT):
  pinMode(IP_SPK_SKT_4, INPUT):
  pinMode(IP_SPK_SKT_5, INPUT):
  pinMode(IP_SPK_SKT_A, INPUT):
  pinMode(IP_SPK_SKT_B, INPUT):
  pinMode(IP_AMP_SW_1, INPUT):
  pinMode(IP_AMP_SW_2, INPUT):
  pinMode(IP_AMP_SW_3, INPUT):
  pinMode(IP_AMP_SW_4, INPUT):
  pinMode(IP_AMP_SW_5, INPUT):
  pinMode(IP_RAND_SW, INPUT):
  pinMode(IP_CAB_SW, INPUT):
  pinMode(OP_GTR_RLY_1, OUTPUT):
  pinMode(OP_GTR_RLY_2, OUTPUT):
  pinMode(OP_GTR_RLY_3, OUTPUT):
  pinMode(OP_GTR_RLY_4, OUTPUT):
  pinMode(OP_GTR_RLY_5, OUTPUT):
  pinMode(OP_SPK_RLY_1, OUTPUT):
  pinMode(OP_SPK_RLY_2, OUTPUT):
  pinMode(OP_SPK_RLY_3, OUTPUT):
  pinMode(OP_SPK_RLY_4, OUTPUT):
  pinMode(OP_SPK_RLY_5, OUTPUT):
  pinMode(OP_CAB_RLY, OUTPUT):
  pinMode(OP_AMP_SW_LED_1, OUTPUT):
  pinMode(OP_AMP_SW_LED_2, OUTPUT):
  pinMode(OP_AMP_SW_LED_3, OUTPUT):
  pinMode(OP_AMP_SW_LED_4, OUTPUT):
  pinMode(OP_AMP_SW_LED_5, OUTPUT):
  pinMode(OP_RAND_SW_LED, OUTPUT):
  pinMode(OP_CAB_SW_LED_R, OUTPUT):
  pinMode(OP_CAB_SW_LED_G, OUTPUT):
  pinMode(OP_CAB_SW_LED_B, OUTPUT):

//set all outputs to off
  digitalWrite(OP_GTR_RLY_1, LOW);
  digitalWrite(OP_GTR_RLY_2, LOW);
  digitalWrite(OP_GTR_RLY_3, LOW);
  digitalWrite(OP_GTR_RLY_4, LOW);
  digitalWrite(OP_GTR_RLY_5, LOW);
  digitalWrite(OP_SPK_RLY_1, LOW);
  digitalWrite(OP_SPK_RLY_2, LOW);
  digitalWrite(OP_SPK_RLY_3, LOW);
  digitalWrite(OP_SPK_RLY_4, LOW);
  digitalWrite(OP_SPK_RLY_5, LOW);
  digitalWrite(OP_CAB_RLY, LOW);
  analogWrite(OP_AMP_SW_LED_1, 0); //  analogWrite(pin, dutyCycle)
  analogWrite(OP_AMP_SW_LED_2, 0);
  analogWrite(OP_AMP_SW_LED_3, 0);
  analogWrite(OP_AMP_SW_LED_4, 0);
  analogWrite(OP_AMP_SW_LED_5, 0);
  analogWrite(OP_RAND_SW_LED, 0);
  digitalWrite(OP_CAB_SW_LED_R, HIGH);
  digitalWrite(OP_CAB_SW_LED_G, LOW);
  digitalWrite(OP_CAB_SW_LED_B, LOW);

  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_1), unplugged_1, FALLING);
  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_2), function_name, FALLING);
  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_3), function_name, FALLING);
  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_4), function_name, FALLING);
  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_5), function_name, FALLING);
  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_A), function_name, FALLING);
  attachInterrupt(digitalPinToInterrupt(IP_SPK_SKT_B), function_name, FALLING);

Check if IP_SPK_SKT_A is high, if not, check if IP_SPK_SKT_B is high, if not STOP - flash cab button LEDs red,green,red,green.. until one of them is high.
//If no guitar speaker cabinet is connected, DO NOT PROCEED.
Once a cab is connected, set the cab button LED accordingly (red for SKT A, green for SKT B), then set cab state variable to A or B accordingly.

}

Back at ya!

Two things come to mind...

In which case it is not an "e-stop".

A relay is generally going to take milliseconds to switch. What is an interrupt gaining you that fast polling does not?

3 Likes

Honestly, I don't know. :slight_smile: I only have intermediate experience with coding, though the logic concepts do come fairly naturally to me.

EDIT: Actually, I can answer that: my plan was that, should a cable be accidentally unplugged, the routine would be interrupted no matter what it was doing.

Regarding relay switching times, the signal relays I've chosen have 3ms set and reset times, the power relays are 15ms set, 5ms reset. They are all being run at their preferred coil voltages.
I figured a 50ms delay would be more than enough to accommodate for any problems, while not being perceptibly long in use.

Actually, I can answer that: my plan was that, should a cable be accidentally unplugged, the routine would be interrupted no matter what it was doing.

Regarding relay and switch bounce; I opted to use external pull-up resistors (to +5v) and small capacitors (to GND) on all inputs and the relay coils all have reverse-biased diodes across them to absorb transients.

The question of whether or not interrupts are needed (or even a good idea) in this application aside, the answer to your question is no. There is no way to pass a parameter to an ISR on the Mega (there is on ESP32 / ESP8266). But, you can call short, individual ISRs that then pass the parameter to a common, larger function:

void setup() {
  attachInterrupt(digitalPinToInterrupt(4), isr4, FALLING);
  attachInterrupt(digitalPinToInterrupt(5), isr5, FALLING);
  attachInterrupt(digitalPinToInterrupt(6), isr6, FALLING);
}

void isr4() {
  commonFunction(4);
}

void isr5() {
  commonFunction(5);
}

void isr6() {
  commonFunction(6);
}

void commonFunction(uint8_t interruptPinTriggered) {
  //
  // Do something with interruptPinTriggered here
  //
}

BTW, passing a byte made more sense to me than passing a char as you proposed. But, that's up to you.

2 Likes

Thanks @gfvalvo, that's a great idea for a way to 'split up' the incoming interrupts while still using a common function to avoid code duplication.
I also appreciate everyone answering my (dumb) question about (misusing) interrupts, despite knowing it's probably not the best way to do it. :slight_smile: I have learned some things along the way.

This is one of the things I love about this forum community; people don't put others down for knowing less than them. :slight_smile:

By the way, the reason I suggested using a char was only because I have 1-5, A, B & R and that would keep it more readable, but if there is some problem with chars, I guess I could use 1-8 instead. Any thoughts?

The equivalent for interrupts is... I got one problem. So I used interrupts. Now I have 100 problems.

Given the timings and the problem description I believe you are best served by a run-to-completion state machine (no interrupts). Some things that help...

  • Save the current state of the system to use as inputs into the state machine. In other words, work from a snapshot.
    • In your case that's a timestamp (micros), the inputs of interest, and the outputs of interest.
  • Delays are implemented as Blink-Without-Delay
  • The stuff under INTERRUPTS is checked first and triggers a transition to a shutdown state.
  • To avoid code duplication, were I in your shoes, I'd make judicious use of class / struct. That allows capturing the stuff under MAIN FUNCTION LOOP as small state machines for managing each channel.
2 Likes

Hello daxliniere

In short words:

enums, arrays and structs are your friends.

2 Likes

Thank you everyone. I will start by writing a single routine (for single button) and then once that is confirmed working, I will look at how to 'struct-ify' it. :slight_smile:

Stay tuned!

Also, I just had the PCBs arrive. Exciting times!

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.