SPDT toggle switch register only once

Hi there
So, I have searched through the forums and found a few other with the same question as me (I think), but I might have an additional issue I hope someone may have an input to.

Basically, I have built a surprisingly well functioning flight stick. One of the buttons however is a SPDT on-on toggle switch with one leg to pin 14, one to pin 15, and the last to ground. I then do pullup and the switch works as it is meant to, i.e., in one of its two possible positions 14 is always low and 15 high, and in the other position it is reversed.
Now, as this is a flight stick it would need to register the position it is pushed into as a single push and release, in contrast to the constant push it is currently doing. I regret that I have as of yet not figured it out, despite tips form the existing forum posts.

I have seen suggestions hinting at state change and using millis, but I am having difficulties making it work. It might be due to the joystick library used, which can be found here Arduino Joystick Library.

My code is as follows, please bear with the toggle switch parts, as this a little nonsense written in my frustration.
Note that the Joystick.releaseButton(10); is for releasing pin14 which does appear to work, but digitalWrite does not? odd.

#include "Joystick.h"

#define X_PIN A2
#define Y_PIN A3

#define X_MIN 0
#define X_MAX 1023
#define X_TRIM 89
#define X_INVERT 1

#define Y_MIN 0
#define Y_MAX 1023
#define Y_TRIM 12
#define Y_INVERT -1

// joystick characteristics (numer of buttons, hats, throttle etc.)
Joystick_ Joystick(0x04,JOYSTICK_TYPE_JOYSTICK,
  14, 0,
  true, true, false,
  false, false, false,
  false, false,
  false, false, false);

// toggle switch part
int buttonPushCounter = 0;
int buttonState = 0;
int lastButtonState = 0;
unsigned long previousMillis = 0; 
const long interval = 500; 

void setup() {
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(16, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);
  pinMode(18, INPUT_PULLUP);
  pinMode(19, INPUT_PULLUP);

  Joystick.begin(false);
  Joystick.setXAxisRange(-512, 512);
  Joystick.setYAxisRange(-512, 512);
  Joystick.setRudderRange(-512, 512);
  Joystick.setThrottleRange(-512, 512);
}

void loop() {
  // set joystick buttons
  Joystick.setButton(0, !digitalRead(10));
  Joystick.setButton(1, !digitalRead(4));
  Joystick.setButton(2, !digitalRead(8));
  Joystick.setButton(3, !digitalRead(2));
  Joystick.setButton(4, !digitalRead(7));
  Joystick.setButton(5, !digitalRead(5));
  Joystick.setButton(6, !digitalRead(3));
  Joystick.setButton(7, !digitalRead(6));
  Joystick.setButton(8, !digitalRead(9));
  Joystick.setButton(9, !digitalRead(16));
  Joystick.setButton(10, !digitalRead(14));
  Joystick.setButton(11, !digitalRead(15));
  Joystick.setButton(12, !digitalRead(18));
  Joystick.setButton(13, !digitalRead(19));

//----------- toggle switch part ------------------------------------------//

unsigned long currentMillis = millis();
buttonState = digitalRead(14);
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH && currentMillis - previousMillis >= interval) {
      buttonPushCounter++;
      Joystick.pressButton(10);
    } else {
      Joystick.releaseButton(10);
    }
    delay(50);
  }
  lastButtonState = buttonState;

//----------- toggle switch part end ---------------------------------------//

// read axis, adjust for trim and send state
  int value = map(analogRead(X_PIN) + X_TRIM , X_MIN, X_MAX, -512, 512) * X_INVERT;
  Joystick.setXAxis(value);
  value = map(analogRead(Y_PIN) + Y_TRIM , Y_MIN, Y_MAX, -512, 512) *Y_INVERT;
  Joystick.setYAxis(value);
  Joystick.sendState();
  delay(10);
}

I hope there is something to go on for a bright mind, otherwise I am more than happy to provide more info for a little hint and/or help.

-Spaziba

Excuse me! A SPST toggle switch has ONLY TWO connections. What are you really using here?

Paul

If the sw has only 2 positions, it is either ON or OFF, why 3 wires, 2 inputs, when you only need 2wires, 1 input?

Paul_KD7HB:
Excuse me! A SPST toggle switch has ONLY TWO connections. What are you really using here?

Paul

No no no, ’tis I that should excuse mine typo sir!
It is of course a SPDT switch, more specifically an On-On. So in the style of this:

Can't make hide nor hair of the code, but for a single two-position switch, there is clearly no need to use two inputs. :roll_eyes:

JCA34F:
If the sw has only 2 positions, it is either ON or OFF, why 3 wires, 2 inputs, when you only need 2wires, 1 input?

This one is actually on-on, but yes with just two wires the on/off effect could be made.

As I mentioned this is a flight stick controller, so for instance button 1 is a trigger. Pull it and windows registers the stroke for as long as it is held, and then stops when released. There are several other buttons as well.
The toggle switch is two of the buttons, in windows registered as 11 and 12. When it is in the top position, 11 is registered as being pushed, whereas in the low position 12 is. What I would like is to make it so when it is pushed in e.g. top position, it registers a push for 0.5 second, and then releases. (same for bottom position.

I must admit I am not certain if this is even feasible?


Sorry with the continuous spamming: Essentially what I am trying to recreate is more or less this: momentary toggle switch. Now I could have used such a switch, or even just two regular momentary switches – no problem – but I really wanted the physical properties of having the switch stay in position (rather than pushing two buttons, or having a switch flip back to centre)

All your questions have to be answered with -yes- they are ALL possible. It is just software you must write to accomplish what you ask.

Paul

Hi Paul

That's great, so one problem down.
I will give it another go tomorrow, but ideas are starting to run out

Hmm still no luck.
Might anyone have some suggestions to the achive the desired effect? Otherwise I'm thinking some heavy compromising and using a different type of switch that just don’t stay in place.

Spaziba:
What I would like is to make it so when it is pushed in e.g. top position, it registers a push for 0.5 second, and then releases. (same for bottom position.

Examine this short sketch:

// A basic timer that runs when enable is false (INPUT_PULLUP button configuration)
// else is reset.  timer1TimedOut true only when timer1AccValue >= timer1Preset,
// false otherwise.

uint32_t timer1AccValue;
uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;
bool timer1TimedOut;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
//
//-----------------------------
//
void setup() {
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
  if (digitalRead(enablePin) == LOW) { // If input is grounded, enable timer to run.
    timer1AccValue = timer1AccValue + (timer1CurrentMillis - timer1PreviousMillis);
    // Signal the timer's completion status
    if (timer1AccValue >= timer1Preset) { // If time elapsed, limit timer1AccValue and set timer1TimedOut
      timer1AccValue = timer1Preset;
      timer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1AccValue = 0;
    timer1TimedOut = false;
  }
  timer1PreviousMillis = timer1CurrentMillis; // Update timer1PreviousMillis.

  digitalWrite(LED_BUILTIN, timer1TimedOut);  // Send the timer completion status to the onboard LED

} // end of loop

If you combine the fact that the switch is pressed and the timer IS NOT timed out you will have your output pulse.

dougp:
Examine this short sketch:

Hi dougp

That's great, thank you! Definitely took it a step much further.
Now I am just trying to work out the last issue. Typically for my luck I am fully able to make it work in reverse, that is when the switch is flipped up, a delay of 1.5 second will occur and then button 1 (let’s call it that) is continuously pressed and button 2 is not - likewise the other way around when the switch is flipped down.

I will give it a go and see if I can make it work correctly (button 1 continuously pressed for 1.5 second, and then released, button 2 not pressed at the same time – and vice versa) but it might be better to get a good night’s sleep first.

Success, it now works as intended with the following adjustments to dougp’s code in the loop. :slight_smile:

timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
 
  if (digitalRead(14) == LOW) { // If input is grounded, enable timer to run.
    timer1AccValue = timer1AccValue + (timer1CurrentMillis - timer1PreviousMillis);    // Signal the timer's completion status
    if (timer1AccValue >= timer1Preset) { // If time elapsed, limit timer1AccValue and set button to release
      timer1AccValue = timer1Preset;
      Joystick.setButton(10, 0); // button set as 10, with 0 = released, 1 = pressed
    }
  }

  else if (digitalRead(15) == LOW) {
    timer1AccValue = timer1AccValue + (timer1CurrentMillis - timer1PreviousMillis);    // Signal the timer's completion status
    if (timer1AccValue >= timer1Preset) { // If time elapsed, limit timer1AccValue and set button to release
      timer1AccValue = timer1Preset;
      Joystick.setButton(11, 0); // button set as 10, with 0 = released, 1 = pressed
    }
  }

  else {
  timer1AccValue = 0;
  }

  timer1PreviousMillis = timer1CurrentMillis; // Update timer1PreviousMillis.

However, it can sometimes “misfire” by not registering. I thought this might be due to using the same timer for both pin 14 and 15 but making a separate timer for each of them do not seem to change this issue.

Using Serial.println for reading button state and timer1AccValue I think I have located the problem, as timer1AccValue starts from zero and counts up, but sometimes when switched, start at the end value (1500). I have tried adding a little delay and making separate timers for pin 14 and pin 15, but with no luck. Even though it starts at end value, conditions should be fulfilled by

...
timer1AccValue2 >= timer1Preset2)

as it is equal or less than.

Very odd…

I'm confused (not that hard to do!) on several points.

  • the latest example posted shows timer1 used for both cases but affecting separate buttons
  • if there's a Joystick.setButton(10, 0); shouldn't there also be a counterpart Joystick.setButton(10, 1);[/li]
  1. button pressed (input LOW) sends button released?
    [/list]

And I understand the confusion, not easy to follow a silly man.

  • Following code is with two timers
uint32_t timer1AccValue;
uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;

uint32_t timer2AccValue;
uint32_t const timer2Preset = 1500; // 1.5 seconds
uint32_t timer2PreviousMillis;
uint32_t timer2CurrentMillis;
timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
timer2CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
 
  if (digitalRead(14) == LOW) { // If input is grounded, enable timer to run.
    timer1AccValue = timer1AccValue + (timer1CurrentMillis - timer1PreviousMillis);    // Signal the timer's completion status
    Joystick.setButton(10, 1); // button set as 10, with 0 = released, 1 = pressed
    if (timer1AccValue >= timer1Preset) { // If time elapsed, limit timer1AccValue and set button to release
      timer1AccValue = timer1Preset;
      Joystick.setButton(10, 0); // button set as 10, with 0 = released, 1 = pressed
    }
  }

  else if (digitalRead(15) == LOW) {
    timer2AccValue = timer2AccValue + (timer2CurrentMillis - timer2PreviousMillis);    // Signal the timer's completion status
    Joystick.setButton(11, 1); // button set as 10, with 0 = released, 1 = pressed
    if (timer2AccValue >= timer2Preset) { // If time elapsed, limit timer1AccValue and set button to release
      timer2AccValue = timer2Preset;
      Joystick.setButton(11, 0); // button set as 10, with 0 = released, 1 = pressed
    }
  }

  else {
  timer1AccValue = 0;
  timer2AccValue = 0;
  }

  timer1PreviousMillis = timer1CurrentMillis; // Update timer1PreviousMillis.
  timer2PreviousMillis = timer2CurrentMillis; // Update timer1PreviousMillis.
  • There should probably yes, have added it
  • Yes, this is the tricky part, input LOW sends button released AFTER designated time, i.e., 1.5 seconds (otherwise it would just stay pressed, see (bad)image for wiring)

Please consider that with such a mechanical switch, there will be a short time when NEITHER side will be ON! During the transition time from one position to the other.

Paul

That could perhaps explain the earlier point.

I should probably just have used a self-centering switch and omitted the feature of it staying in top/bottom.
Anyways, I learned quite a bit, and it can work fine for now - have ordered other switches but delivery times eh... fortunately i have made it quite easy to swap out.

I should probably just have used a self-centering switch and omitted the feature of it staying in top/bottom.

Lots of options to get what you want. Many types of SPDT or even SPST switches could do the job. Lots of options in code too ... logic, state-change, debouncing, or using 1 or 2 inputs.

Since you already have many unused inputs, the 2-input option is a good choice. Take a look at this for a simple RS latch solution (no debouncing required). Just need to add state-change detection in your code.

Here's a crude example to show the 5 possible states of your switch (tested) ...

// toggle switch
bool up;
bool dn;
bool previousUp;
bool previousDn;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(15, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
}

void loop() {

  // update previous status
  previousUp = up;
  previousDn = dn;

  // get current status
  up = digitalRead(15);
  dn = digitalRead(14);

  // now test for the logic conditions

  // switch is down
  if (up && !dn) {
    // code here runs continuously while switch is up
    // LED is bright
    digitalWrite(LED_BUILTIN, HIGH);
  }

  // switch is up
  if (dn && !up) {
    // code here runs continuously while switch is down
    // LED is dim
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1);
    digitalWrite(LED_BUILTIN, LOW);
    delay(20);
  }

  // switch is transferring either way
  if (up && dn) {
    // code here runs while switch is transferring from state to state
    // LED blinks quickly 3 times
    digitalWrite(LED_BUILTIN, HIGH);
    delay(5);
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
    digitalWrite(LED_BUILTIN, HIGH);
    delay(5);
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
    digitalWrite(LED_BUILTIN, HIGH);
    delay(5);
    digitalWrite(LED_BUILTIN, LOW);
  }

  // switch just went up
  if (dn && !up && previousUp) {
    // code here runs only once
    // LED blinks slowly once
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
  }

  // switch just went down
  if (up && !dn && previousDn) {
    // code here runs only once
    // LED blinks slowly twice
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(500);
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(500);
  }
}

Oooh very clever with the transfer state as one of the 5 possibilities!
I will have to give it a go by combining it with the previous timing code.

Since you have an ON-ON type switch, the transfer state might not activate if the code outside of the conditions takes longer than the transfer time (probably 50-200ms). Never a problem with:

 ON -OFF- ON
(ON)-OFF- ON
(ON)-OFF-(ON)