#include <avr/io.h>
#include <util/delay.h>
#include <avr/power.h>
#include <avr/sleep.h>
// The simulation diagram is for non-latching relay and use of the control feature.
// The button on the right in the diagram is the foot switch.
// The button on the left in the diagram represents an incoming control signal being asserted.
// The simulator doesn't have a latching relay. Only regular non-latching.
// Pin assignment was for ease of drawing the diagram. Choose what ever you need for your platform.
// There are two primary features included here:
// Temporary Change:
// If the foot switch is held for more than a half second, the relay toggles back to previous position when released.
// This allows for a short use of a some foot pedal instead of needing to tap-on and tap-off again.
//
// Control group:
// Multiple modules can be connected together via the control line.
// Only one module can be engaged at anytime.
// When a module is engaged, it asserts the control line high.
// Any other module hooked to the control line will disengage when it sees that.
// This allows for creating a multi-channel effect where only one channel is to be active at a time (or none).
//
// Both features also work together. If a channel is temporarily engaged,
// any module disengaged by the control feature will re-engage.
#define USE_CTRL true // Choose between control feature or a second LED.
#define USE_LATCHING_RELAY false // Using a latching relay? Simulator only has a regular relay.
#define NORMALLY_CLOSED false // Is the foot switch normally closed?
#define TEMPORARY_SWITCH true // Use Temporary change feature?
#define SLOW_CLOCK false // Slow the clock way down to save power? Simulator does not handle this correctly.
#define LEVEL_SENSITIVE false // Intended for monitoring a signal instead of a foot switch.
#define ENABLE_SLEEP false // Put the CPU to sleep while waiting for input. Doesn't quite work right in the simulator.
#define TEMPORARY_SWITCH_TIME_ms 500 // Foot switch held this long is a temporary change.
#define RELAY_SWITCH_TIME_ms 40 // Time needed to switch the relay.
#define DEBOUNCE_DELAY_ms 14 // Debounce time of the switch.
#define POWER_ON_DELAY_ms 0 // Hold in bypass for this long on power up.
#define PIN_LED PB0 // LED is on PB0 on sparkfun programmer.
#define PIN_ENGAGE PB1 // Drives the engage pin of a latching relay or the coil of a non-latching relay.
#define PIN_BYPASS PB2 // Drives the disengage pin of a latching relay.
#define PIN_LED2 PB3 // For a second LED if the control feature is not use.
#define PIN_CTRL PB3 // Not enough pins for control feature and a 2nd LED. Shared pin.
#define PIN_SW PB4 // Connect to a momentary foot switch to ground.
#if SLOW_CLOCK == false
#define DELAY(ms) delay(ms)
#else // SLOW_CLOCK == true
// Try something much slower... 125kHz.
#define CLOCK_PRESCALE clock_div_128 // Divides 1Mhz down to 125kHz..
#define DELAY_DIVIDER 8 // 1/8th the speed.
#define DELAY(ms) delay(ms/DELAY_DIVIDER)
#endif // SLOW_CLOCK
// Helper macros
#define ENABLE_PCIE GIMSK |= (1 << PCIE)
#define DISABLE_PCIE GIMSK &= ~(1 << PCIE)
#define LED_ON PORTB |= (1 << PIN_LED); // high
#define LED_OFF PORTB &= ~(1 << PIN_LED); // low
// CTRL and LED2 usually share the same pin. Can't use both features at the same time.
#if USE_CTRL == false
#define LED2_ON PORTB |= (1 << PIN_LED2); // high
#define LED2_OFF PORTB &= ~(1 << PIN_LED2); // low
#define CTRL_INPUT
#define CTRL_OUTPUT
#define CTRL_HIGH
#define CTRL_LOW
#else
#define LED2_ON
#define LED2_OFF
#define CTRL_INPUT DDRB &= ~(1 << PIN_CTRL); // Input most of the time.
#define CTRL_OUTPUT DDRB |= (1 << PIN_CTRL); // Output some of the time.
#define CTRL_HIGH PORTB |= (1 << PIN_CTRL); // high
#define CTRL_LOW PORTB &= ~(1 << PIN_CTRL); // low
#endif // USE_CTRL == false
#define SW_PRESSED false
#define SW_NOT_PRESSED true
// Global state variables.
bool is_bypassed = true; // Is relay in bypass state?
bool is_sw_pressed = false; // Log the state of the switch in the ISR and get out of the ISR.
bool is_ctrl_asserted = false; // Log the state of the control line in the ISR and get out of the ISR.
#if USE_CTRL == true
bool honor_ctrl_signal = true; // Should we watch the CTRL line or not?
#endif // USE_CTRL
#if LEVEL_SENSITIVE == true
bool sw_last_loop, sw_state = SW_NOT_PRESSED;
#endif // LEVEL_SENSITIVE
void setup()
{
#if SLOW_CLOCK == true
clock_prescale_set(CLOCK_PRESCALE);
#endif // SLOW_CLOCK
// Configure the ports.
DDRB &= ~(1 << PIN_SW); // Switch pin is an input.
PORTB |= (1 << PIN_SW); // activate pull-up resistor for the switch.
DDRB |= (1 << PIN_BYPASS); // output
DDRB |= (1 << PIN_ENGAGE); // output
DDRB |= (1 << PIN_LED); // output
DDRB |= (1 << PIN_LED2); // output for 2nd LED
CTRL_INPUT; // CTRL is an input in the idle state.
PORTB &= ~(1 << PIN_BYPASS); // low
PORTB &= ~(1 << PIN_ENGAGE); // low
// wait for the pull-up on the switch pin to do its job,
// otherwise PIN_SW can sometimes get false LOW readings.
DELAY(5);
// Set relay to be bypassed initially.
is_bypassed = true;
write_bypass();
DELAY(POWER_ON_DELAY_ms);
PCMSK = (1 << PIN_SW); // Add the foot switch pin to the pin-change interrupt mask.
#if USE_CTRL == true
// Add the CTRL pin to the pin change interrupt mask.
PCMSK |= (honor_ctrl_signal << PIN_CTRL);
#endif // USE_CTRL
// Low power mode...
ADCSRA = 0; // disable ADC
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
noInterrupts (); // timed sequence follows
sleep_enable();
// turn off brown-out enable in software
MCUCR = bit (BODS) | bit (BODSE);
MCUCR = bit (BODS);
interrupts (); // guarantees next instruction executed
ENABLE_PCIE; // Enable the pin change interrupt.
}
void sleep()
{
ENABLE_PCIE; // Enable the pin change interrupt.
#if ENABLE_SLEEP == true
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable();
sei();
sleep_cpu(); // Sleep until interrupt.
cli();
sleep_disable();
#endif // ENABLE_SLEEP
}
///////////////////////////////////////////////////////////////
// The loop will sleep between interrupts.
#if LEVEL_SENSITIVE == true
void loop()
{
sleep(); // Sleep until interrupt.
sw_state = read_switch();
// Did the switch change?
if (sw_state != sw_last_loop)
{
// If the switch is closed, set relay to bypass.
// To reverse this, set NORMALLY_CLOSED to true.
is_bypassed = (sw_state == SW_PRESSED);
write_bypass();
}
sw_last_loop = sw_state;
DELAY(DEBOUNCE_DELAY_ms);
}
#else // LEVEL_SENSITIVE == false
void loop()
{
sleep(); // Sleep until interrupt.
if(is_sw_pressed == true)
{
switchPressed();
}
#if USE_CTRL == true
// We will honor the CTRL signal if we booted into the proper state.
else if(honor_ctrl_signal == true)
{
// Has some other module issued a CTRL signal?
if (is_ctrl_asserted == true) // Is CTRL pin high?
{
// Blocking call until the control signal is no longer active.
ctrlState();
}
}
#endif // USE_CTRL == true
}
#endif // LEVEL_SENSITIVE
// Interrupt driven to save power.
// This is invoked when the switch is pressed or the control signal is asserted.
// Figure out what caused the interrupt and get out.
ISR (PCINT0_vect)
{
DISABLE_PCIE; // Disable further interrupt.
#if USE_CTRL == true
// We will honor the CTRL signal if we booted into the proper state.
if( (honor_ctrl_signal == true) &&
(bool(PINB & (1 << PIN_CTRL)) == true) ) // Has some other module issued a CTRL signal?
{
is_ctrl_asserted = true;
}
else
#endif // USE_CTRL == true
//if(read_switch() == SW_PRESSED)
{
// Assume the switch is pressed, else why did we get an interrupt?
is_sw_pressed = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// Handle the state of a pressed switch. It's pressed when this is called.
void switchPressed()
{
// Need to know how long the switch is pressed.
unsigned long hold_time = DEBOUNCE_DELAY_ms;
is_sw_pressed = false; // Reset the flag from the ISR.
// The switch must remain pressed (debounced) to be valid.
DELAY(DEBOUNCE_DELAY_ms);
if(read_switch() == SW_PRESSED)
{
#if USE_CTRL == true
// Issue a ctrl pulse to tell other modules we're engaging.
if (is_bypassed == true)
{
CTRL_OUTPUT; // Change the pin to output.
CTRL_HIGH; // High
}
#endif // USE_CTRL
// Change the relay state.
toggle_bypass_state();
// Relay switching in toggle_bypass_state takes this amount of time.
hold_time += RELAY_SWITCH_TIME_ms;
// Now wait for the switch to release and track it's hold time.
while (read_switch() == SW_PRESSED)
{
DELAY(DEBOUNCE_DELAY_ms);
hold_time += DEBOUNCE_DELAY_ms;
}
#if USE_CTRL == true
// End the ctrl pulse. Doesn't matter if we started one or not.
CTRL_LOW; // Low
CTRL_INPUT; // Change the pin to input.
#endif // USE_CTRL
#if TEMPORARY_SWITCH == true
// Was it a long press?
if (hold_time > TEMPORARY_SWITCH_TIME_ms)
{
// Change the relay state again.
toggle_bypass_state();
}
#endif // TEMPORARY_SWITCH == true
// To get here, the switch is no longer pressed but perhaps not stable.
// Wait for stabilzation to leave the Switch-Pressed state.
DELAY(RELAY_SWITCH_TIME_ms);
}
ENABLE_PCIE; // Enable the pin change interrupt.
}
/////////////////////////////////////////////////////////////////////////////////////
// Handle the state of an incoming CTRL signal. It's active when this is called.
#if USE_CTRL == true
void ctrlState()
{
is_ctrl_asserted = false; // Reset the flag from the ISR.
// If engaged, change to disengaged.
if (is_bypassed == false)
{
toggle_bypass_state();
unsigned long hold_time = RELAY_SWITCH_TIME_ms;
// Wait for the CTRL signal to go away.
while( bool(PINB & (1 << PIN_CTRL)) == true )
{
DELAY(DEBOUNCE_DELAY_ms);
hold_time += DEBOUNCE_DELAY_ms;
}
#if TEMPORARY_SWITCH == true
// Was it a long press?
if (hold_time > TEMPORARY_SWITCH_TIME_ms)
{
// Change the relay state back to engaged if this was temporary.
toggle_bypass_state();
}
#endif // TEMPORARY_SWITCH == true
}
ENABLE_PCIE; // Enable the pin change interrupt.
}
#endif // USE_CTRL == true
void toggle_bypass_state()
{
is_bypassed = !is_bypassed;
write_bypass();
}
void write_bypass()
{
set_led();
#if USE_LATCHING_RELAY == true
if (is_bypassed)
{
PORTB |= (1 << PIN_BYPASS); // high
DELAY(RELAY_SWITCH_TIME_ms);
PORTB &= ~(1 << PIN_BYPASS); // low
}
else
{
PORTB |= (1 << PIN_ENGAGE); // high
DELAY(RELAY_SWITCH_TIME_ms);
PORTB &= ~(1 << PIN_ENGAGE); // low
}
#else // USE_LATCHING_RELAY= false
// Use static levels to drive devices other than the latching relay.
if (is_bypassed)
{
PORTB |= (1 << PIN_BYPASS); // high
PORTB &= ~(1 << PIN_ENGAGE); // low
}
else
{
PORTB |= (1 << PIN_ENGAGE); // high
PORTB &= ~(1 << PIN_BYPASS); // low
}
DELAY(RELAY_SWITCH_TIME_ms);
#endif // USE_LATCHING_RELAY
}
void set_led()
{
if (is_bypassed)
{
LED_OFF;
LED2_ON;
}
else
{
LED_ON;
LED2_OFF;
}
}
// Finds switch state.
// "false" is a 0 on the pin, which is a pressed switch.
// "true" is a 1 on the pin, which is an open switch (not pressed).
bool read_switch()
{
bool rv = bool(PINB & (1 << PIN_SW));
#if NORMALLY_CLOSED == true
rv = !rv;
#endif
return rv;
}