Reading switch with resistors to analog input, break before make issue

Another of my SimRail train simulator controllers.

This time a simple one, a 4 position industrial style rotary switch and a button, to operate the wipers and wash the windscreen in the virtual trains.

I have 4 x 10K resistors linking each rotary switch output as a resistor ladder, 5 volts and ground to each side of the resistor ladder,
Then i'm reading the analog value through a single pin, A0 on an arduino beetle (leonardo style board)

When the rotary switch is moved, the sketch looks for which way the switch moves, and sends the keyboard codes to move the in game switch one notch in the correct.

There's just 3 positions i am using on the 4 position switch, i deliberately got a 4 position switch so i could read the switches pins without having OFF as no connection as is normal with switches.

:

The issue i'm having is when rotating the switch, sometimes the arduino is picking up the 'gap' between switch states, where the switch goes open circuit before making the next circuit.

This results in the switch in the game moving to positions it shouldn't (i imagine there's a little bit of contact bounce in here too)

Any suggestions on how to handle the brief open circuit situations?

:

I can re-wire things and read each individual switch state using 4 digital inputs on the arduino, but i guess that will also suffer the open circuit 'break before make' issue?

My sketch is:

#include <Keyboard.h>  // Keyboard library
#define Washers_PIN 11

unsigned long startTime, endTime = 10;               //10 millisecond timer
byte wiperSwitchPos, oldwiperSwitchPos, currentPos;  // store wiper switch positions
int8_t diff;
int val;
bool posSent = true;  // Position sent, set to yes


void setup() {

  pinMode(Washers_PIN, INPUT_PULLUP);
  val = getAnalog();  // Variable for storing wiper switch's position (resistor values)

  Serial.begin(115200);  // Start serial comms
  while (!Serial)
    ;  // Wait for pro-micro's usb comms to start
  Serial.print("\nCurrent Wiper Switch position = ");
  Serial.print(wiperSwitchPos);

  currentPos = oldwiperSwitchPos = wiperSwitchPos;
}

void loop() {

  val = getAnalog();
  if (wiperSwitchPos != currentPos) {  // if switch position has changed
    startTime = millis();              // start the timer
    currentPos = wiperSwitchPos;       // Set current position in the switch position variable
    posSent = false;
    diff = 0;
  }

  if (!posSent && millis() - startTime > endTime) {
    diff = wiperSwitchPos - oldwiperSwitchPos;

    Serial.print("Wiper Switch Position =  ");
    Serial.print(wiperSwitchPos);
    Serial.print("\t");
    Serial.print("Difference = ");
    Serial.println(diff);

    // if diff number decreases
    if (diff < 0) {                            // Wiper switch has been moved ClockWise
      for (int i = 0; i < (diff * -1); i++) {  // not sure
        Keyboard.write(111);                   // send keyboard key O
      }
    }  

    // if diff number increases
    if (diff > 0) {  // Wiper switch moved Anti ClockWise
      for (int i = 0; i < diff; i++) {
        Keyboard.write(112);  // send keyboard key P
        delay(65);            // 65 Ms delay.... must change to non blocking delays
      }
    }

    oldwiperSwitchPos = wiperSwitchPos;
    posSent = true;
  }


  int Washer_Button = digitalRead(Washers_PIN);

  if (Washer_Button == HIGH) {
    Keyboard.press(118);
    Serial.println("Washing Windscreen");
  } else {
    Keyboard.release(118);
  }
}

int getAnalog() {
  val = analogRead(A0);                    // Read Wiper Switch resistors connected to A0
  if (val > 750) wiperSwitchPos = 0;       // Off
  else if (val > 500) wiperSwitchPos = 1;  // On Constant
  else if (val > 245) wiperSwitchPos = 2;  // Intermittant Short Delay
  else if (val > -5) wiperSwitchPos = 3;   // Intermittant Long Delay
}

What is the value of val when the circuit is open?

Can you use something like

const int tolerance=25;
if (abs(val-750) < tolerance)

etc.

Treat it exactly like contact bounce. Debouncing can be just waiting to see if a reading is stable.

If the current reading is not the previous reading, keep resetting a timer.

When the time expires at 50 milliseconds, consider the last reading to be stable.

  x = new reading
  if x != previous x,
    timer = millis();
    previous x = x

  if (millis - timer > 50) 
    x is a stable switch reading, so act on it.

I can't test this now, but it should be the idea. 50 may be not the right value, so once it is working you could play with it. 100 should enough, lowering the value while it still works makes the switch more responsive.

a7

3 Likes

What is the ADC value of each switch position?

I think @alto777 is right here. Just treat it like any other debounce situation. How long can you tolerate? 100 ms? 150 ms? 200 ms? That should be plenty of time.

Me too. I set up a slide fader to generate the slots. Valid positions are debounced and changes between them detected. I used 500 milliseconds denouncing so I could see it working.

Try it here


Wokwi_badge UA Analog Bounce and Edge Detection


// https://forum.arduino.cc/t/reading-switch-with-resistors-to-analog-input-break-before-make-issue/1265837
// https://wokwi.com/projects/399305157246238721

void setup()
{
  Serial.begin(115200);
  Serial.println("Jello Whirled!\n"); 
}

unsigned long now;

void loop() {
  now = millis();

  static unsigned long timer;
  static int oldReading = -1;
  static int stableValue;
  static int oldStableValue = -1;

// debounce

  int newReading = getVal();
  if (newReading && newReading != oldReading) {
    timer = now;
    oldReading = newReading;
  }

  if (now - timer > 500)
    stableValue = newReading;

// transition detect

  if (stableValue && stableValue != oldStableValue) {
    Serial.print("switch has become ");
    Serial.println(stableValue);
    oldStableValue = stableValue;
  }
}

int getVal()
{
  int val = analogRead(A0);

  switch (val) {
  case 0 ... 150 :
    val = 1;
    break;

  case 200 ... 350 :
    val = 2;
    break;

   case 400 ... 550 :
    val = 3;
    break;
 
  case 600 ... 750 :
    val = 4;
    break;
 
  case 800 ... 950 :
    val = 5;
    break;
 
  default :
    val = 0;
    break;
  }

  return val;
}

HTH

a7

1 Like

Many ways to skin that cat. I use a lookup table of thresholds half way between ideal values(24 of them) and a tiny while loop instead of switch structure, returning index value at threshold crossed. Then, same debounce logic. Same difference, but mine's probably shorter. I'd probably throw that whole switch structure in a function, so I never see it.

unfortunately when the contacts are open between the switch states, it bounces around between 0 and about 300!

Likewise, if i rotate the switch to the 4th position which is all contacts open, the signal bounces around the same (i guess as you'd expect from an analog pin having no input)

position 0 = 768
position 1 = 512
position 2 = 256
position 3 = 0

And between positions it reads all over the place, i'm looking at the raw values now, and seeing 100 to 400 and all inbetween.

I thought that line 4 should be a debounce timer?

unsigned long startTime, endTime = 75;  //75 millisecond timer

I changed that from 10 to 75ms, and it does seem to help, resulting in the switch in the game responding ever so slightly after i change the real switch.

this seems to work for all positions except going from 2 to 1, where it will sometimes move to 3 then back to 1.
so i guess i have a slightly longer opening switch on the 2 to 1 position.

Place a 1 megohm resistor between the analog pin and ground.

Place a 0.1 uF ceramic capacitor between the analog pin and ground.

Repeat your experiments.

a7

2 Likes

while blocks are ...blocking. But this may work. You can add a timeout just in case.

void getAnalog() { // void: does not return a value
  val = analogRead(A0);       // Read Wiper Switch resistors connected to A0
  wiperSwitchPos = -1;
  int tolerance=20;
  while wiperSwitchPos < 0 {
  if (       abs(val - 768) < tolerance) wiperSwitchPos = 0;       // Off
    else if (abs(val - 512) < tolerance) wiperSwitchPos = 1;  // On Constant
    else if (abs(val - 256) < tolerance) wiperSwitchPos = 2;  // Intermittant Short Delay
    else if (    val        < tolerance) wiperSwitchPos = 3;   // Intermittant Long Delay
  }
}

Can you show the circuit that provides the input for the analogue pin?

If the common pin is tied to ground via e.g. a 5 kOhm resistor, you should not get floating values in the gaps (unless you have very long wires).

You can adjust the other resistors (that go between the switch positions and the Vcc), e.g.

  • 1k and 5k gives 5/6*5V = 4V (ADC 800)
  • 3k and 5k gives 5/8*5V = 3V (ADC 600)
  • 7k and 5k gives 5/12*5V = 2V (ADC 400)

Values are rough approximations.

Neglected to mention, I use a 0.022 uF capacitor on my circuit, at the AI. Stabilizes nicely.

Thats fixed it,

Dang i always forget about things like this, assuming code on the arduino's can take care of everything.

A simple extra resistor and a capacitor made it all stable, and if i put the switch into the 4th position where all contacts are open, it's no longer a floating pin, so it reads 0 and nothing happens, which is exactly what i wanted.

Thankyou everyone for your help.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.