Momentary Pushbuttons to Toggle Dual Coil Latching Relays On/Off

Good news and bad news. Using the Bounce2 library to handle debouncing the switch works really well, and the NOT function is easily implemented. (Debouncing stops false positives from happening. Without it, a single button press can be interpreted as multiple presses).
Bad news is that it doesn't work with interrupts, so you can't put the controller to sleep.
I seem to remember looking at Bounce2, but I nixed it for this very reason. I ended up hacking together a simple debounce code that works through interrupts. It has yet to glitch on me once:

C-like:
// delayMicroseconds() works even with interrupts enabled !

if (digitalRead(button[i]) == LOW) {            // initial button press
    delayMicroseconds(3000);                      // wait a bit to give it time to settle
    if (digitalRead(button[i]) == LOW) {        // read it again to confirm it's still down (no glitch)
        delayMicroseconds(2000);                   // wait some more
          if (digitalRead(button[i]) == LOW) {    // Still down? Third time's a charm
          // We accept the button press as stable, and do something about it
          }
}
 
@szukalski your code is only using one coil of the relay, correct? So this could either be used for a single coil dual-relay (e.g., TQ2-L-5V)?
Yes, single coil relay. I have only tested with the LED for the moment. To be honest, JTEX has better utility in his code with the sleep and interrupts, so I will refactor at some stage.

I am also thinking about playing around with non-latching relays. They should only need a single pin from the controller (with the other side of the coil going to ground). This would let you control another relay from an ATtiny85, at the expense of power. But pedalboard power is free, right?
 
Ok, playing a lot last night but just couldn't get it working satisfactorily with a sleep mode doing debouncing manually. It's not a big thing for me, so I kept with the Bounce2 library and have a proof-of-concept working with non-latching relays. Theoretically, lets you handle 2 sets of relays (switch + relay + led), or one switch + dual led + 2 relays (change of voicing, or channel, or boost).

Code:
#include <Arduino.h>
#include <avr/power.h>
#include <Bounce2.h>
// Made for ATtiny85
const int pinButton = 0;
const int pinRelay = 2;
const int pinLed = 1;
Bounce pedal1 = Bounce();
const int debounceInterval = 3;
int state = 0;
void pinOn(int pin) {
  digitalWrite(pin, HIGH);
}
void pinOff(int pin) {
  digitalWrite(pin, LOW);
}
void ledOn(int pin) {
  pinOff(pin);
}
void ledOff(int pin) {
  pinOn(pin);
}
void relayToggle() {
  if(state) { // turn the relay on
    digitalWrite(pinLed, HIGH);
    digitalWrite(pinRelay, HIGH);
  }
  else { // turn it off
    digitalWrite(pinLed, LOW);
    digitalWrite(pinRelay, LOW);
  }
}
void setup () {
  power_adc_disable(); // save some power
  for (byte i = 0; i < NUM_ANALOG_INPUTS; i++) {
    pinMode(i, INPUT_PULLUP);  // initially set all pins to INPUT_PULLUP, which reduces current draw
  }
  digitalWrite(pinLed, LOW); // Start with the LED off
  pinMode(pinLed, OUTPUT); // Set the LED pin as OUTPUT
  pinMode(pinRelay, OUTPUT); // Set relay set pin as output
  digitalWrite(pinRelay, LOW);  // turn it off
  pedal1.attach(pinButton);
  pedal1.interval(debounceInterval);
  state = 0;
  relayToggle(); // initialise in the required state
}
void loop() {
  pedal1.update();
  if(pedal1.changed()) {
    if(pedal1.read() == LOW) { // if button was pressed
      state = !state;
      relayToggle();
    }
    else { // if button was released
      if (pedal1.previousDuration() > 100) { // if you hold it down longer, revert back to the original state. NOT function.
        state = !state;
        relayToggle();
      }
    }
  }
}
 
Ok, playing a lot last night but just couldn't get it working satisfactorily with a sleep mode doing debouncing manually. It's not a big thing for me, so I kept with the Bounce2 library and have a proof-of-concept working with non-latching relays
If you're using non-latching relays, I guess there's no point bothering with sleep modes and interrupts.Keeping a relay powered draws more power than a non-sleeping ATtiny anyway...
 
If you're using non-latching relays, I guess there's no point bothering with sleep modes and interrupts.Keeping a relay powered draws more power than a non-sleeping ATtiny anyway...
Yeah, the engineer in me knows (and hurts) from the power inefficiency, but the developer says "infrastructure costs are not my problem"!

Ignoring the power, non-latching means I can potentially have 3 relays from a single ATtiny85 with a single footswitch! 1 footswitch, 2 leds (or a single dual colour led), and 3 relays! That's a lot of options to play with..
 
Ignoring the power, non-latching means I can potentially have 3 relays from a single ATtiny85 with a single footswitch! 1 footswitch, 2 leds (or a single dual colour led), and 3 relays! That's a lot of options to play with..
Or, if your LEDs are supposed to turn on together with the non-latching relays, you can just parallel them with the coils, and then you can do 3 switches, 3 relays and 3LEDs. That would have been enough for my pedal, save for the battery drain issue.
 
Or, if your LEDs are supposed to turn on together with the non-latching relays, you can just parallel them with the coils, and then you can do 3 switches, 3 relays and 3LEDs. That would have been enough for my pedal, save for the battery drain issue.
You are a genius! Man, I love that idea. It tested fine..
 
You are a genius! Man, I love that idea. It tested fine..
I'd suggest high brightness LEDs, so you can under drive them with a low current, like 1mA or even less, and they'd still be plenty bright. Otherwise the LED +relay combo might draw too much juice from the output pin. In my pedal, I used some LEDs so bright that I had to use a 15k or even 20k series resistor to tame them to a non-blinding output.
 
I'd suggest high brightness LEDs, so you can under drive them with a low current, like 1mA or even less, and they'd still be plenty bright. Otherwise the LED +relay combo might draw too much juice from the output pin. In my pedal, I used some LEDs so bright that I had to use a 15k or even 20k series resistor to tame them to a non-blinding output.
Yeah, that's a great point. I already noticed that when I skipped the CLR on the LED, the relay wouldn't latch.
 
Ok, here's my final code for today..

ATtiny85 relay with up to 3 channels, you just need to change "NUM_SWITCHES" and mind that pin mapping in the controller doesn't map with the physical pin mapping. The hold feature is enabled (set by "HOLD_TIME"), but I didn't implement saving of states to save on the EEPROM of the controller. This might be a nice this though, depending on implementation.

Code:
// ATtiny85 implementation for up to 3x non-latching relays
#include <Arduino.h>
#include <avr/power.h>
#include <Bounce2.h>
#define NUM_SWITCHES 1 // maximum 3 because there are only 6 pins
#define DEBOUNCE_INTERVAL 5
#define HOLD_TIME 100
// PB0 == pin 5 == Channel 1 switch
// PB1 == pin 6 == Channel 1 relay + LED
// PB2 == pin 7 == Channel 2 switch
// PB3 == pin 2 == Channel 2 relay + LED
// PB4 == pin 3 == Channel 3 switch
// PB5 == pin 1 == Channel 3 relay + LED
// Each channel has two pins, one for the switch, and one for the relay + LED
struct channel {   // Structure declaration
  int pinSwitch;
  int pinRelayLed;
  bool state;
};
channel * channels = new channel[NUM_SWITCHES];
Bounce * sw = new Bounce[NUM_SWITCHES];
void relayToggle(channel channel) {
  if(channel.state) { // turn the relay on
    digitalWrite(channel.pinRelayLed, HIGH);
  }
  else { // turn it off
    digitalWrite(channel.pinRelayLed, LOW);
  }
}
void setup () {
  power_adc_disable(); // save some power
  for (int i = 0; i < NUM_ANALOG_INPUTS; i++) { // pullup all pins and set them low
    pinMode(i, INPUT_PULLUP);
    digitalWrite(i, LOW);
  }
  for (int i = 0; i < NUM_SWITCHES; i++) {
    channels[i].pinSwitch = i*2;
    channels[i].pinRelayLed = (i*2)+1;
    channels[i].state = 0;
    pinMode(channels[i].pinRelayLed, OUTPUT); // set relay + LED pin as output
    sw[i].attach(channels[i].pinSwitch, INPUT_PULLUP); // attach the switch to the debouncer
    sw[i].interval(DEBOUNCE_INTERVAL);
    relayToggle(channels[i]); // initialise in the required state
  }
}
void loop() {
  for (int i = 0; i < NUM_SWITCHES; i++) {
    sw[i].update();
    if(sw[i].changed()) {
      if(sw[i].read() == LOW) { // if button was pressed
        channels[i].state = !channels[i].state;
        relayToggle(channels[i]);
      }
      else { // if button was released
        if (sw[i].previousDuration() > HOLD_TIME) { // if you hold it down longer, revert back to the original state. NOT function.
          channels[i].state = !channels[i].state;
          relayToggle(channels[i]);
        }
      }
    }
  }
}
 
I just noticed this thread, or I would have jumped in sooner.

I've been working on a project with the lofty goal of making microcontroller-driven relay bypass easy as possible for the DIY pedal builder.

See here: mcu-relay-controller on GitHub. I'm not using the Arduino suite, I'm doing more generic C, trying to create an abstraction to easily support multiple microcontrollers. (Currently working on ATtiny85, ATtiny13a, pic12f675, pic10f320.)

I just received my first PCBs last week, and this weekend was finally able to move from the breadboard to an actual PCB. On the breadboard, instead of using an actual momentary switch, I simulated the switch by manually touching a jumper wire to GND. This appears to not have bounce (or at least not like a momentary switch). So my initial too-simple debounce scheme didn't work reliably. I spent a lot of the time reading about debouncing. My current GitHub code has something that seems to work pretty well, though I have another version that's a bit simpler, but not pushed into the GitHub repo yet.

My impression is that most people who want their MCU to react button presses aren't putting the MCU to sleep. If you don't put the chip to sleep, then you can use a timer interrupt to basically periodically poll the switch pin. I look at it as a quasi-"threaded" approach to debouncing.

I ended up doing essentially the same as what you guys are doing here in this thread: read the switch pin, sleep a bit, read it again. The argument against this method is that having those sleeps in the main execution loop means CPU time is being wasted. But, in my case at least, I don't really care, as monitoring the switch and (re)setting the relay is literally all the MCU is doing.

One other thing I'll note: the schematic posted in the first post appears to drive the relays directly from the MCU pins. It seems that this is one of those things that "everyone does", but may not be best practice. In particular, the relay coil is essentially an inductor, and once current to it stops, the coil's field will collapse generating a huge voltage spike. In practice, it appears that the GPIO pins of AVR and PIC MCUs are robust enough to handle that, but, that has to be weighed against this discussion.

My current bypass PCB indeed drives the relay directly from the MCU pins. But I just ordered "v2.0" PCBs that use a double-coil latching relay (Kemet EC2-3TNU); the coils are transistor driven (the MCU just turns the transistors on/off), and the transistors are protected with flyback diodes. (It seems a lot more components are needed to do this with the single-coil latching relay.)
 
One other thing I'll note: the schematic posted in the first post appears to drive the relays directly from the MCU pins. It seems that this is one of those things that "everyone does", but may not be best practice.
Indeed, it's not best practice, but in my case, fully intentional. I wanted it to be as simple as possible - maybe even simpler. I'm taking a chance here, relying on the ATtiny's built-in, feeble protection diodes. But this is how I roll :) If they're good enough to provide protection against 2000V ESD, I figure they should handle the spikes from a tiny relay (famous last words...).

You can tell I'm not an engineer, right?
 
Indeed, it's not best practice, but in my case, fully intentional. I wanted it to be as simple as possible - maybe even simpler. I'm taking a chance here, relying on the ATtiny's built-in, feeble protection diodes. But this is how I roll :) If they're good enough to provide protection against 2000V ESD, I figure they should handle the spikes from a tiny relay (famous last words...).

I just discovered this excellent post from @Chuck D. Bones - he actually does the math, looks like the ATtiny should be able to handle the coil's flyback energy.
 
I just discovered this excellent post from @Chuck D. Bones - he actually does the math, looks like the ATtiny should be able to handle the coil's flyback energy.
Come to think of it, I'm pretty sure I ran into his post in the past and it was one of the resources that helped me build up the confidence to drive relays directly and stop worying. Time will tell. So far I havent fried any ATtinys. Other MCUs may or may not be as forgiving.
 
I just noticed this thread, or I would have jumped in sooner.

I've been working on a project with the lofty goal of making microcontroller-driven relay bypass easy as possible for the DIY pedal builder.

See here: mcu-relay-controller on GitHub. I'm not using the Arduino suite, I'm doing more generic C, trying to create an abstraction to easily support multiple microcontrollers. (Currently working on ATtiny85, ATtiny13a, pic12f675, pic10f320.)
Cheers Matt, I went through your code when I was experimenting with different approaches. I enjoyed it, but it does take some programming experience to read it (I mean, it's code after all..). In the end I went with an Arduino scope for the same goal, to make it as simple as possible for any developer (or DIYer without coding experience) to understand and modify the code. Same reason I went with Bounce2 and not put the MCU to sleep when not in use.

Ultimately, I think I will revisit the sleep thing at some stage. It just means implementing a debounce class which can work with interrupts (most are tracking pin state over time, which doesn't work if the MCU is sleeping.. funny that). It's a better solution for the wider community after all, but would require latching relays.

For now, an ATtiny85 with non-latching (and no sleep mode) is great for a break out board. You can run two channels for a whole bunch of flexibility. Hopefully, I get a prototype ordered this week. I don't think I can make it 1590B friendly though :LOL:
 
Cheers Matt, I went through your code when I was experimenting with different approaches. I enjoyed it, but it does take some programming experience to read it (I mean, it's code after all..). In the end I went with an Arduino scope for the same goal, to make it as simple as possible for any developer (or DIYer without coding experience) to understand and modify the code. Same reason I went with Bounce2 and not put the MCU to sleep when not in use.

That's useful feedback, thanks!

The ultimate goal is that you don't need to worry about the code at all: once things are a little better tested, I'll have image files that you can just directly program to your chip of choice.

The other goal is having an abstracted hardware interface, so you can add features/tweak functionality without worrying about the hardware details. I updated the README and changed the folder structure, hopefully making it a little more obvious/intuitive as to how to work with it.

I have extra hardware I'm willing to send to folks (free!) if they want to help with my testing.
 
I really like the implementation of the control bus in the pedal pcb intelligent relay bypass for combo builds or a channel switcher. In the later case, the implementation requires that the unit be power cycled to change modes. With @szukalski's code, I'm interested in adding 1) a control bus and 2) on-the-fly mode switching.

For 1, does that mean that an unused PB# is set up to broadcast/receive logic signals to/from connected mcus to NOT their state? For 2, how can a preset sequence (e.g., three button presses within a second) on any mcu/footswitch control the mode (i.e., standard non-synchronized bypass and control bus radio button bypass)?
 
Finally spent some time on my dual coil prototype yesterday. Two things threw me off:
Firstly, I had my relay footprint reversed so I had to desolder them and mount them under the PCB (which wasn't so bad).
Secondly, and more importantly, I used the reset pin on the ATtiny for the 2nd channel LED. This was a n00b error as the reset pin is always on (it resets the ATtiny if it goes below 2.2V) so it's not usable unless you disable it as reset (which means you can't program the MCU anymore).

I ended up rewriting my code to use the 2nd channel footswitch pin for the LED and all is working well. I need to redo my PCB layout a little (although it works with modification as-is) but it is restricted at present for a single footswitch and two channels. The channel changes when you release the footswitch, a short press toggles channel 1, a long press toggles channel 2.
The code is below for reference, it needs some cleanup from a read-ability perspective but works:

Code:
#include <Arduino.h>
#include <avr/power.h>
#include <Bounce2.h>
#define DEBOUNCE_INTERVAL 5
#define HOLD_TIME 100
// PB0 == pin 5 == Footswitch 1
// PB1 == pin 6 == Channel 1 relay
// PB2 == pin 7 == Channel 1 LED
// PB3 == pin 2 == Channel 2 LED
// PB4 == pin 3 == Channel 2 relay
// PB5 == pin 1 == Control bus
struct footSwitch {
  int pinSwitch;
};
// Each channel has two pins, one for the relay and one for the LED
struct channel {   // Structure declaration
  int pinRelay;
  int pinLed;
  bool state;
};
channel * channels = new channel[2];
Bounce sw = Bounce();
bool pressed = false;
void relayToggle(channel channel) {
  if(channel.state) { // turn the relay on
    digitalWrite(channel.pinRelay, HIGH);
    digitalWrite(channel.pinLed, HIGH);
  }
  else { // turn it off
    digitalWrite(channel.pinRelay, LOW);
    digitalWrite(channel.pinLed, LOW);
  }
}
void setup () {
  power_adc_disable(); // save some power
  for (int i = 0; i < NUM_ANALOG_INPUTS; i++) { // pullup all pins and set them low
    pinMode(i, INPUT_PULLUP);
    digitalWrite(i, LOW);
  }
  sw.attach(0, INPUT_PULLUP);
  sw.interval(DEBOUNCE_INTERVAL);
  channels[0].pinRelay = 1;
  channels[0].pinLed = 2;
  channels[1].pinRelay = 4;
  channels[1].pinLed = 3;
  for (int i = 0; i < 2; i++) {
    channels[i].state = 0;
    pinMode(channels[i].pinRelay, OUTPUT); // set relay pin as output
    pinMode(channels[i].pinLed, OUTPUT); // set LED pin as output
    relayToggle(channels[i]); // initialise in the required state
  }
}
void loop() {
  sw.update();
  if(sw.changed()) {
    if(sw.fell()) {
      pressed = true;
    }
    else if(sw.rose()) { // when switch is released, check how long it was held down for
      if (sw.previousDuration() > HOLD_TIME) { // if button was pressed longer than hold time, toggle channel 2
        channels[1].state = !channels[1].state;
        relayToggle(channels[1]);
      }
      else { // toggle channel 1
        channels[0].state = !channels[0].state;
        relayToggle(channels[0]);
      }
      pressed = false;
    }
  }
}

I have two further improvements that I would like to investigate.
After chatting with @benny_profane , I will look to implement a control bus with on-the-fly mode changing. You can use the relay pin on the MCU as an analog I/O as long as you don't go below 2.2V (if you do, you reset the MCU), so this can be used to send messages to other boards. I need to test this thoroughly.
The other improvement would be multiple footswitches from a single pin. At the moment, I use it as a digital I/O, the pin is either high or low, but you can also do this as an analog I/O. A number of footswitches in series with varying resistors will send different voltages to the pin when pressed. I am not sure how useful this would be in practice but it's an interesting approach:
1687068730320.png
 
Back
Top