Timer Function for Staggered Switching

Hi all,

I am attempting to create an Arduino that turns relays on and off in response to either momentary switch input or MIDI control change messages. I have got that bit working (although, please do tell me if this can be improved!). To prevent sharing hundreds of lines of codes, I will only share the code for one switch and one output as it is just duplicated and assigned different pins and control change values.

As the Arduino will be for switching audio signal paths I need a mute system to prevent the pops from the relays being heard. I have created a mute function (void mute ()) that needs to run when any control change occurs. This function enables an energized relay that grounds the signal to be grounded by setting the digital pin 9 on the other end of its coil to LOW (ground, which energies the relay and thus mutes the signal).

I started by trying the delay function inside the control change mechanism, but as this is a "blocking" function it simply enables the mute, turns it off, and then carries out the control change... which completely defeats the purpose. Some google searches lead me to believe the millis function is the solution I need. A friend mentioned a while loop but didn't elaborate further. I have tried the former, but to no avail. I am using 1000ms currently as I can see visually on my prototype whether it is working as intended as I am using LEDs in place of relays. In practice, the mute relay needs to turn on for ca. 40ms, then 5ms later the control change happens. After 40ms has elapsed, the mute self disables. Therefore these two events need to happen independently. When I toggle the control change I can see the mute LED flash for an instant, but definitely not the intended 1000ms. I cannot tell whether it is occurring before or after the control change. As a beginner I have probably made a stupid mistake - but I think what I am trying to do is quite simple and I'm hoping there is an obvious fix! Please also let me know if there are other ways in which my code can be improved and made more efficient.

#include <MIDI.h>
#include <midi_Defs.h>
#include <midi_Message.h>
#include <midi_Namespace.h>
#include <midi_Settings.h>
#include <EEPROM.h>

//timer bits
unsigned long ms_from_start = 0;
unsigned long ms_previous_read_mute   = 0;
unsigned long mute_interval = 1000;
int MUTE_state = 0;

//define MIDI CC constants
const int CC_RESET = 102;
const int CC_STORE = 104;
const int CC_MUTE = 105;
const int CC_MIDI_CHANNEL = 103;  //for changing MIDI channel
const int CC_CH1 = 85;            //channels
const int CC_CH2 = 86;
const int CC_CH3 = 87;
const int CC_CH4 = 88;
const int CC_FN1 = 89;  //functions (e.g. loop)
const int CC_FN2 = 90;
const int CC_FN3 = 91;
const int CC_FN4 = 92;

//define switch and relay pins
const int SW_CH1 = 5;  //channels
const int SW_CH2 = 6;
const int SW_CH3 = 7;
const int SW_CH4 = 8;
const int SW_FN1 = 4;  //functions
const int SW_FN2 = 3;
const int SW_FN3 = 2;
const int SW_FN4 = 1;
const int RLY_CH1 = 19;  //channels
const int RLY_CH2 = 18;
const int RLY_CH3 = 17;
const int RLY_CH4 = 16;
const int RLY_FN1 = 12;  //functions
const int RLY_FN2 = 13;
const int RLY_FN3 = 14;
const int RLY_FN4 = 15;

//define misc. pins
const int MUTE = 9;
const int STORE = 10;
const int storeControlPin = 11;

int swPush = 0;
boolean swState = LOW;
boolean swStateLast = LOW;
uint8_t midi_channel = 0;
MIDI_CREATE_DEFAULT_INSTANCE();
void handleError(int8_t err) {
  digitalWrite(LED_BUILTIN, (err == 0) ? LOW : HIGH);
}

//Poorly Implemented Mute function
void mute () {
  ms_from_start   = millis();
  digitalWrite(MUTE, LOW);
  if (ms_from_start - ms_previous_read_mute > mute_interval) {
    ms_previous_read_mute = ms_from_start;
    digitalWrite(MUTE, HIGH);
  }
}

// CC functions
void midiCtrlChange(byte c, byte v) {
  MIDI.sendControlChange(c, v, 0xB0 | midi_channel);

  if (c == CC_CH1 && v > 64) {  //cc, switch and relay set for channel 1
    mute ();  //I have only put the mute function in once place here as an example, rather than copying it to each CC given it is probably wrong!
    delay(5);
    digitalWrite(RLY_CH1, HIGH);
  }
  if (c == CC_CH1 && v <= 64) {
    mute ();
    delay(5);
    digitalWrite(RLY_CH1, LOW);
  }
  if (c == CC_CH2 && v > 64) {  //cc, switch and relay set for channel 2
    digitalWrite(RLY_CH2, HIGH);
  }
  if (c == CC_CH2 && v <= 64) {
    digitalWrite(RLY_CH2, LOW);
  }
  if (c == CC_CH1 && v > 64) {  //cc, switch and relay set for channel 3
    digitalWrite(RLY_CH1, HIGH);
  }
  if (c == CC_CH3 && v <= 64) {
    digitalWrite(RLY_CH3, LOW);
  }
  if (c == CC_CH4 && v > 64) {  //cc, switch and relay set for channel 4
    digitalWrite(RLY_CH4, HIGH);
  }
  if (c == CC_CH4 && v <= 64) {
    digitalWrite(RLY_CH4, LOW);
  }
  if (c == CC_FN1 && v > 64) {  //cc, switch and relay set for function 1
    digitalWrite(RLY_FN1, HIGH);
  }
  if (c == CC_FN1 && v <= 64) {
    digitalWrite(RLY_FN1, LOW);
  }
  if (c == CC_FN2 && v > 64) {  //cc, switch and relay set for function 2
    digitalWrite(RLY_FN2, HIGH);
  }
  if (c == CC_FN2 && v <= 64) {
    digitalWrite(RLY_FN2, LOW);
  }
  if (c == CC_FN3 && v > 64) {  //cc, switch and relay set for function 3
    digitalWrite(RLY_FN3, HIGH);
  }
  if (c == CC_FN3 && v <= 64) {
    digitalWrite(RLY_FN3, LOW);
  }
  if (c == CC_FN4 && v > 64) {  //cc, switch and relay set for function 4
    digitalWrite(RLY_FN4, HIGH);
  }
  if (c == CC_FN4 && v <= 64) {
    digitalWrite(RLY_FN4, LOW);
  }
}

void setup() {
  //Configure SW and RLY pins
  pinMode(SW_CH1, INPUT_PULLUP);
  pinMode(SW_CH2, INPUT_PULLUP);
  pinMode(SW_CH3, INPUT_PULLUP);
  pinMode(SW_CH4, INPUT_PULLUP);
  pinMode(SW_FN1, INPUT_PULLUP);
  pinMode(SW_FN2, INPUT_PULLUP);
  pinMode(SW_FN3, INPUT_PULLUP);
  pinMode(SW_FN4, INPUT_PULLUP);
  pinMode(RLY_CH1, OUTPUT);
  pinMode(RLY_CH2, OUTPUT);
  pinMode(RLY_CH3, OUTPUT);
  pinMode(RLY_CH4, OUTPUT);
  pinMode(RLY_FN1, OUTPUT);
  pinMode(RLY_FN2, OUTPUT);
  pinMode(RLY_FN3, OUTPUT);
  pinMode(RLY_FN4, OUTPUT);
  pinMode(MUTE, OUTPUT);

  // Initiate MIDI
  Serial.begin(9600);
  MIDI.setHandleError(handleError);
  MIDI.begin(MIDI_CHANNEL_OMNI);

  // Initiate CH1 on startup (will be removed once feature to remember last selected channel can be recalled after power off)
  digitalWrite(RLY_CH1, HIGH);
    //Disable mute on startup
  digitalWrite(MUTE, HIGH);
}

void loop() { //this needs tidying up, different behaviour required for channel switching
  MIDI.read();
  //SWITCH 1 FUNCTIONS
  swState = digitalRead(SW_CH1);
  if (swState != swStateLast)
  {
    if (swState == LOW)
    {
      swPush++;
      if (swPush > 1)
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(CC_CH1, 127);
          break;
        case 0:
          midiCtrlChange(CC_CH1, 0);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(SW_CH1) == LOW)
    {
      delay(5);
    }
  }
}

It is exactly what you need. Study these examples to learn how to do "several things at once".

These delay() statements will cause problems (e.g. incorrect interval timing, skipped intervals)

    delay(5);

Hello greeny2357

Do you have experience with programming in C++.

The task can easily be realised with an object.
A structured array contains all the information, such as the pin addresses for the I/O devices, as well as the information for the timing.
A single service takes care of this information and initiates the intended action.
The structured array makes the sketch scalable until all I/O pins are used up without having to adapt the code for the service.
It is cool stuff, isn´t it?

Have a nice day and enjoy coding in C++.

Hi!

To answer your question paulpaulson I don't really have any experience coding at all, let alone C++!

RE the lined examples above, I looked at something very similar which is what I adapted in my own attempts... which don't work unfortunately. Have I missed something?

Thanks!

The reason why it is hundreds of lines of code is because you duplicated and assigned different pins. That's a dumb way to code. It would be hundreds of lines shorter if you were a little smarter in the way you coded it. You need to learn to use arrays!

Any time you find yourself making variables with names that are the same but have a number at the end, that's when you should be using an array.

const int SW_CH[4] = {5, 6, 7, 8};  //channels

Then use them by changing this:

  pinMode(SW_CH1, INPUT_PULLUP);
  pinMode(SW_CH2, INPUT_PULLUP);
  pinMode(SW_CH3, INPUT_PULLUP);
  pinMode(SW_CH4, INPUT_PULLUP);

to this:

  for(byte c=0; c<4; c++) {
    pinMode(SW_CH[c], INPUT_PULLUP);
    ....
  }

As @PaulRB says, this code will collapse to a crisp fraction of its size when it is redone with arrays.

No need to panic - a code block you produced by pasting it and editing it will form the basis for all the code sections that resembles it.

So you'll only have a few things to get right, and then only a few places to make fixes or modifications.

Meanwhile, this

// https://wokwi.com/projects/353587272457307137
// https://forum.arduino.cc/t/timer-function-for-staggered-switching/1076239

# define mutePin    6
# define muteLED    7   // mute status lamp

# define MUTE4  777 // MUTE4 for 777 milliseconds

void setup() {
  Serial.begin(115200);
  Serial.println("mute timer demo\n");

  pinMode(mutePin, INPUT_PULLUP);
  pinMode(muteLED, OUTPUT);
}

unsigned long now;      // time for all non-blocked functions

void loop()
{
  now = millis();

// maybe initiaite/refresh a mute period
  if (!digitalRead(mutePin))
    mute();

// always check if it is time to unmute
  unMute();
}

bool muteIsOn;  // just so the mute is only switched on or off if necessary
unsigned long muteTimer;

void mute()
{
  muteTimer = now;

  if (muteIsOn == false) {
    muteIsOn = true;    
    Serial.println("mute turned ON!");
    digitalWrite(muteLED, HIGH);    // and do relay or whatever needs doing
  }
}

void unMute()
{
  if (now - muteTimer < MUTE4)      // time to unmute?
    return;

  if (muteIsOn == true) {
    muteIsOn = false;
    Serial.println("mute going OFF.");
    digitalWrite(muteLED, LOW);     // and do relay or whatever needs doing
  }
}

is a demonstration of some code that you might try to read and get your mind around. mute() turns on muting when you press the button: explicit and direct control that you can insert into the code anywhere you need mutation to begin, in as many places as required. If it gets called, a muting period is started. If it gets called whilst muting is in effect, the muting period is extended.

Also in the loop() is a fixed every-time-through call to unMite(), which check see if it is OK to turn off muting. Just that one call to unMute(), called at the loop rate. When it gets called and sees that enough time has gone by, it releases the mute.


Play with it here <- wokwi simulation of auto-unmute


Every time the button is pressed, and in this crude demo as long as the button is down, the mute will be turned (or left) on.

After you get your fat finger off the button, the mute will continue for 777 seconds.

This demo uses the idea from Examples / 02. Digital / BlinkWithoutDelay you can find in the IDE. Mastery of this technique is essential. Time spent figuring "blink without delay" or BWOD as it is called sometimes will not be wasted. Time spent seeing how it is applied in the above demo will not be waste either.

If the rest of your code is designed and implemented to never (ever) block this technique can make some real magical things happen.

HTH

a7

@greeny2357 I know you are focused on the mute thing, but I am curious about the physical device you are aimed toward. Perhaps you could describe your project at a high level.

We can reverse engineer it a bit, but comments like

//this needs tidying up, different behaviour required for channel switching

make me wonder if some buttons are to operate differently or...

So we could guess and you could confirm or deny, or you could just write a bit.

Same with the schematic. You could draw a fairly simple block diagram showing switch banks and relay modules and so forth, worry about every wire later.

What relay driver circuit or module are you using?

Also what Arduino board are you using?

After a bit more than a glance at your code, I can imagine you will be pleasantly surprised at how it can be made much easier to work with. Just now it isn't 'xactly clear what all it will do for you in the end. You are going to love array variables.

a7

So increase the times to a value that you can see clearly.

I don't understand your problem. What's wrong with:

muteOn()
controlChange()
muteOff()

@cedarlakeinstruments As stated, the LEDs are set to 1000ms - the flash I was seeing most certainly wasn't a second long.

@alto777 Sure - I wasn't trying to be mysterious, just sparing the details I didn't feel where needed as the post was lengthy already! The comment you referenced RE different switching behaviors was a note to myself I forgot about! The end goal here is to have a SPDT (ON)-OFF-(ON) switch which can cycle up and down through the channels. Pressing to one side increases the channel, and the other decreases it. As can probably inferred from my attempts thus far, this is outwith my current coding abilities...

The Arduino is used to control the switching in a multi-channel amp. The amp has four channels (CH-#), and four “voicing” (FN-#)options that affect the way each channel sounds.

The switching inside the amp is controlled via relays, supplied with 12VDC. These are toggled on and off by the MCU (ATMEGA328) via a Darlington array (ULN2803). The changes can be triggered by two things:

  • Momentary switches on the amplifier control panel, which when pressed are received by the MCU and converted into a midi control change (CC) or program change (PC) message.
  • Alternatively, CC and PC messages can be sent by an external device, such as a MIDI controller. The way this behaves is identical to the momentary switches. These are received through an optocoupler (6N138).

As many of the relays inside the amplifier switch the audio signal path directly, the sound of the relays transitioning can be amplified which is undesirable. To mitigate this, a relay at the end of the amplifiers signal path (downstream from all the other relays) is situated to shunt the signal path to ground (thus muting the output of the amplifier). This should be toggled on before any other relays are activated, and toggled off after the other relays have completed their transitions (ca. 40ms). This relay is not routed through the Darlington array like the others. It is supplied with +12V (as that is already readily available in the amp), and is energized by grounding the other side of the coil (by setting pin 9 to LOW = 0V).

I'd certainly love to implement arrays into this as it is extremely bloated and difficult to fault find in currently. The was the switching needs to work is that the four function toggle states can be turned on and off in any combination, but the channel toggle states must be mutually exclusive - IE, the user cannot be on both channels 1 and 3 at the same time (but they can be on channel 2 with as many of the function toggles turned on or off as they wish).

When I have got what I've written so far tided up and actually working the next thing I want to implement is some memory behavior: if the user is on channel 2, with some function toggles enabled and switches to channel 3 the functions should all be turned off. If the user reverts back to channel 2 the function toggles should remain as they were when that channel was last activated (and similarly if the user enabled toggles when on channel 3, those should remain on when returned to). I then also wish to have the most recent state for each of these stored so that they are saved when the unit is turned on or off, as well as the channel which was active at that time so it resumes where it left off.

THX for the excellent description of the project.

While it is very exciting I am sure to be seeing progress, I recommend that you slow your role just a wee bit - this is a huge project in some ways, and proceeding on a build / learn trajectory can work, has worked well.

But.

In this case I recommend that you focus on getting one channel with two pushbuttons and a control relay and a mute relay functioning perfectly.

Version 1C2B/Cr+Mr.

Believe it or don't, that is a huge stroke at the work.

I believe you are also implementing "radio buttons". That step can come when the code you wrote for 1C2B/Cr+Mr is magically turned into

Version 4C4SDPDT/4CR+Mr.

It will not be four times as many lines of code!

You want to remember as you switch around. That will virtually fall out of the box, a simple matter.

Lastly, did you say a power cycle or reset should not mean loss of any kind of setttings? If not, perhaps hold that as an aim. Again, it will be straightforward even if there are a few things to learn.

In quiet time away from thinking about the actual project, start learning about array variables, and as I believe I may have said, gain a complete undersdtanding of "blink without delay", a central technique presented solving the simplest task.

I see you have denouncing code for a pushbutton. You will need something better, but for Version 1C2B/Cr+Mr that kind of approach is adequate.

You may want to look into the IPO model for building control systems. This goes along with the general rule of dividing to conquer.

In IPO, basically, you would solicit all Inputs, use those inputs as well as all current conditions (called "the state") and then use the results of that Process to inform the necessary changes to the Outputs.

Soemone else can tell you more about everything I just scrtached out, as well as how many years of engineering school I casually rushed you through here.

Totally doable, nice project, did you say what Arduino board you are using?

L8R

a7

Thanks for the reply. I am currently prototyping on a Nano while I wait for some PCBs to arrive that have gotten stuck with customs. The amplifier itself will use the ATMEGA328 through-hole DIP package. The PCBs I've ordered are to test that section, and have pushbuttons, relays, LEDs and MIDI and power connectors to allow me to focus on getting the code sorted without having to deal with a ratsnest on breadboards.

The project will (hopefully) also store settings after poweroff.

I feel like my lack of coding background is stopping me from understanding what some of your response is regarding - I have no clue what "1C2B/Cr+Mr" and "4C4SDPDT/4CR+Mr" and a google search didn't return anything useful...

I have already begun implementing the arrays into my code and it's looking much clearer. I will update the post later with this but didn't want to waste everyone's time with a half-finished job.

LOL. I spend lotsa time doing puzzles, not all of which consists in figuring out what other ppls' code is even trying to do.

So those names are a wholly owned product of… mine.

4C4SDPDT/4CR+Mr.

Four channel version with four single pole double throw switches driving four control relays, with ability to mute when doing.

PCBs. Here I hope they will not constrain the software side.

I'm timid cautious and cheap - I would have gotten the software nearly perfected before I was needing anything more than LEDs and switches or pushbuttons.

And lately without touching an Arduino board:

a7

It isn't one PCB, they are all modular for that very reason. (although, for this project the functions have long since been decided to constraining wouldn't be an issue).
I'm still learning my way around the forum so apologies for my ignorance regarding your projects. Do you have a link to the projects you've mentioned? Thanks again!

OK, coffee taking over from the effects of some other things.

Those were just names I made up. Products of my giant brain.

But you do give me an opportunity to say, it looks like you are doing very well.

It wouldn't hurt to post a schematic of that part. You could post any drawings or plans for your entire mechanical concept.

While not necessary, necessarily (!), it would be nice to see and possibly energize the crowd gathering to watch and help as we may.

More is more, too much is never enough.

a7

As requested... here are the relevant modules for my prototyping rig. The boards are modular, and the pins along the edges line up and can be jumpered. Slots on the sides allow for them to be conveniently mounted on a support structure (picture of the side piece shown, the front and back aren't very exciting...) I also get made my the PCB fabhouse (because I too am cheap!). I have designed other boards of this format that are interchangeable on the support structure for testing things before wasting a lot of money on expensive tube amp failures.

This rig will have labelled Cherry MX keyboard switches on the 45 degree face for simulating the panel switches. I know its a bit of a daft choice, but I've tried other things in the past and this just works better (these are cheap and easy to get, although of course wouldn't look at place on an amplifier control panel...). The top will accommodate the Arduino board and relay board. The Arduino board has dual regulated voltage supplies (the heatsinks are overkill for this, but allow me to use the DC supply for other things, like tube filaments which are a lot more taxing for current draw). The relay board just has a bunch of relays on it. Not really much to say there... It also has LEDs to represent the mute and store functions (the latter has not yet been discussed). PCBs for prototyping might seem a bit odd (most people prototype before committing to a PCB), but as my pinouts are known and I need only work on the code, this makes it much easier than having a monstrous breadboard operation effort going. It will serve continuous use as I build a lot of amps and will need to tailor the code to different models, and this will allow me to check it is performing as intended without making a more expensive mistake... besides at £5 per board type from China its hardly any more expensive than actually breadboarding it...

Feast yer eyes! (EDIT: unfortunately because I have a new account it looks like I'm going to have to post my pictures one by one...)

THX again for the additional information.

And I'd say "one more question", but there might be more than one.

If you could draw (hand drawn prolly easiest and fastest) the panel that a user of the device sees, that would be good.

It isn't clear about the (one?) paddle switch (SPDT up channel increment, down channel decrement) nor how many of what kind of other buttons (real buttons) there are, and how they might also have some LEDs that work with them.

And is there a "save" button, like if I go to channel 3 and mess with it, step away and come back it reverts to what I last explicitly "saved" rather than where I left it?

And a block diagram showing the relationship between your device (with the Arduinio), the PC I think got mentioned, and anything that is MIDI controlled, I know only of keyboards and sound boxes. If it is N blocks with a line connecting them A - B - C an so forth

Maybe a few words that would be the basis of the "quick start" manual, too.

Sry to keep diverting your attention away from the serious fun I wish I was having. This is a nice project and I think the software will be fairly straightahead at least conceptually, and could turn out to be quite elegant.

TIA

a7