JTEX
Well-known member
This code uses momentary, SPST, normally-open pushbuttons (or stompswitches) to toggle On/Off some dual coil latching relays. I'm not a seasoned Arduino programmer by any stretch, so it took me quite a while to come up with it. Especially since I had to figure out a way to cut down on power as much as possible, for battery operation, so I had to use sleep mode and hardware interrupts when buttons are pressed. First time using either...
Hope it helps someone. Do whatever you want with it.
Hope it helps someone. Do whatever you want with it.
C:
// ****** 3 momentary pushbuttons (tactile switches) toggle 3 dual coil DPDT latching relays (push on/push off) ******
// Coded by Jerry Catanescu (jtex.ca)
// VERSION 0.1, 2022-12-28
//
// Works with Digispark Pro (ATtiny167 Pro). Optimised for minimal power use
// (go to sleep mode whenever idle, use interrupts to wake up only when a button is pressed)
//
// I used Kemet EA2-5TNJ dual coil latching relays. A 5V-powered ATtiny167/87 can drive them directly. Not even discrete flyback diodes are needed.
// The momentary pushbuttons are NO (normally open).
// One pin of every pushbutton, as well as the negative pins of all relay coils, are connected to ground.
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
const byte rly_setpin[3] = { 6, 8, 10 }; // relay +SET pins
const byte rly_rstpin[3] = { 7, 9, 11 }; // relay +RESET pins
const byte btn_pin[3] = { 0, 1, 2 }; // pushbutton pins (note: there's an LED on pin 1 Digispark Pro. I removed it. You can leave it alone and use another pin if you prefer.)
bool rly_state[3] = { 0, 0, 0 }; // array containing the current relay states: 0 = RESET, 1 = SET
// setup() runs only once when program starts:
void setup() {
for (byte i = 0; i < 12; i++) {
pinMode(i, INPUT_PULLUP); // initially set all pins to INPUT_PULLUP, which reduces current draw
}
// iterate through all relays, set up their pins and initialize
for (byte i = 0; i < 3; i++) {
pinMode(rly_setpin[i], OUTPUT); // configure all SET coil pins as OUTPUT
pinMode(rly_rstpin[i], OUTPUT); // configure all RESET coil pins as OUTPUT
digitalWrite(rly_setpin[i], 0); // initialize SET pins to 0
digitalWrite(rly_rstpin[i], 1);
delay(10);
digitalWrite(rly_rstpin[i], 0); // send pulse to latch the relays in the RESET state
}
// Turn off unused ATtiny167 subsystems to save power
power_adc_disable(); // Analog to Digital Converter
power_lin_disable(); // LIN module
power_spi_disable(); // Serial Peripheral Interface
power_usi_disable(); // Universal Serial Interface
power_timer1_disable();
// Pin Change Mask Register 1. Applies to Port B pins (PB0, 1...). See ATtiny167 datasheet
PCMSK1 = 0b00000111; // Turn on interrupt triggering for the specified pin(s) (logic 1)
// Pin Change Interrupt Control Register. See ATtiny167 datasheet.
PCICR = 0b00000010; // Second bit from the right is the Port B bank.
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
}
// Interrupt handler fires whenever there is a logic change on the designated Port B interrupt pin(s) (where the pushbuttons are wired)
ISR(PCINT1_vect) {
delayMicroseconds(5000); // just wait a bit to avoid firing multiple times due to bouncy button
// then we simply exit and return control to the main loop()
}
// latch relays in the SET or RESET state
void latchRelay() {
noInterrupts(); // disable interrupts, to ignore any button activity (glitches, double-presses etc) that may happen until we're done latching the relay (debounce)
bool btnLastState[3] = { HIGH, HIGH, HIGH }; // for starters, initialize all button states as "up" (logic HIGH)
do {
for (byte i = 0; i < 3; i++) {
if (digitalRead(btn_pin[i]) == LOW) { // if this button # was pushed
// delayMicroseconds() works even while the interrupts are turned off, unlike delay()
delayMicroseconds(3000); // wait a bit to account for bouncy contacts.
if (digitalRead(btn_pin[i]) == LOW) { // read button again after a delay, to insure it's still down (no glitches)
delayMicroseconds(2000);
if (digitalRead(btn_pin[i]) == LOW) { // read button state the 3rd time. If it's still low, we accept it as stable
if (btnLastState[i] == HIGH) {
rly_state[i] = !rly_state[i]; // toggle the relay state
btnLastState[i] = LOW; // record the fact that the button was pressed
if (rly_state[i]) { // if the relay should be latched to the SET state
digitalWrite(rly_setpin[i], 1);
delayMicroseconds(5000); // Hold the logic "1" long enough to latch the relay. 10ms shoud do it.
digitalWrite(rly_setpin[i], 0);
} else { // if the relay should be RESET
digitalWrite(rly_rstpin[i], 1);
delayMicroseconds(5000);
digitalWrite(rly_rstpin[i], 0);
}
}
}
}
} else { // if this button # wasn't pushed
btnLastState[i] = HIGH;
}
// optional: do something if all 3 buttons are pressed simulaneously
if (!digitalRead(btn_pin[0]) && !digitalRead(btn_pin[1]) && !digitalRead(btn_pin[2])) { // all three buttons pressed
// do something awesome, maybe? Such as, true bypass? TBD.
} else {
// placeholder
}
}
} while (!digitalRead(btn_pin[0]) || !digitalRead(btn_pin[1]) || !digitalRead(btn_pin[2])); // stay in this loop as long as at least one button is pressed
interrupts(); // re-enable interrupts before exiting function
}
///////////////////////// MAIN PROGRAM LOOP ///////////////////////////////////
void loop() {
sleep_cpu(); // Sleep all the time except when woken by an interrupt (meaning that a button was pressed)
latchRelay(); // If we ever get here, we got kicked out of sleep by a button press, so we need to handle the relays
}
Last edited: