Go Down

Topic: Debouncing multiple buttons using schmitt trigger (Read 684 times) previous topic - next topic

Qdeathstar

To be honest it sounds like you have coding issues...


I controlled 1600 leds with 30 different programs while also monitoring button presses and controlling a character lcd screen and had no need for interrupts...
A creaking creeping shadow
stiff against the freezing fog
glares at a tickless watch.

Time has failed him -- all things shall pass.

Kuusou

#31
Sep 10, 2019, 12:10 am Last Edit: Sep 10, 2019, 12:40 am by Kuusou
To be honest it sounds like you have coding issues...


I controlled 1600 leds with 30 different programs while also monitoring button presses and controlling a character lcd screen and had no need for interrupts...
As I said before, I'm new to coding and self taught. There are probably better ways to do it but I have no idea how and haven't been able to figure out a better way...

I'm using switch statements for the effects, color, and brightness. The issue is that some of the led effects run for a long time and I can't wait for the loop to finish before exiting and going to the next function/color/brightness

The only thing I can think of is using a interrupt to run every 5-10ms with a timer. It would constantly check for the state of the buttons and if a button has been pressed longer than 50ms then it could increment a counter for the button and switch the effect.

Is this something that could work, would the constant interrupt be noticeable in terms of a particular led effect running? If it is I'm not sure how to exit the function at the time its a valid press without waiting for it to finish. Does anyone have any thoughts or suggestions on this? Is this a better approach?

runaway_pancake

@septillion, @MarkT
I understand your point, makes sense, I'm nodding in agreement.
But I aver that I've never had an instance (in several years now) where I had a failure to dec/inc or stop nor any inadvertent dec/inc or stop.
I pulled the box off the bike, to see if I'd placed a pulldown without noting it, but it has none. Maybe I'm lucking out on the ragged edge.

@Kuusou - Sorry for the... controversy.

FWIW
Code: [Select]
byte newLevel = 0;       // comparison
byte Accel_Btn = 4;      // alias - Accelerate btn
byte Decel_Btn = 5;      // alias - Decelerate btn
byte Zeropwr = 6;        // alias - "kill" button
byte motorpin = 11;      // pin alias, PWM to gate/MOT
byte Level [6] = {0,20,40,100,175,250};
volatile boolean Detect = false;  // Interrupt Note

void setup()
{
  pinMode(motorpin, OUTPUT);
  pinMode(Zeropwr, INPUT);
  pinMode(Accel_Btn, INPUT);
  pinMode(Decel_Btn, INPUT);
  //Serial.begin(9600);
  attachInterrupt(0,Activity,RISING);  // '0' means Ext_Int on D2
}

void loop()
{
  if (Detect == true)
  {
    Decode();
  }
  if (currentLevel != newLevel)
  {
    analogWrite(motorpin,Level[speed_idx]);
    currentLevel = newLevel;
  }
}

void Activity()   // this is the Interrupt !
{
  Detect = true;
}

void Decode()
{
  Detect = false;          // clear out "Detect"
  if (digitalRead(Zeropwr) == HIGH)
  {
    speed_idx = 0;
    newLevel = 0;
    //Display_it();
  }
  else if (digitalRead(Accel_Btn) == HIGH)
  {
    Accelerate();
  }
  else if (digitalRead(Decel_Btn) == HIGH) // Activity detected wasn't Zeropwr or Accel_Btn
  {
    Decelerate();   // default = (A' & Z')
  }
}

void Accelerate()

  if (speed_idx < 5)
  {
    speed_idx ++;
  }
  newLevel = Level[speed_idx];
}

void Decelerate()
{
  if (speed_idx >= 1)
  {
    speed_idx --;
  }
  newLevel = Level[speed_idx];
}


"Who is like unto the beast? who is able to make war with him?"
When all else fails, check your wiring!

Kuusou

#33
Sep 10, 2019, 03:39 am Last Edit: Sep 10, 2019, 03:40 am by Kuusou
Ok guys, i'm trying to change my code to use the watchdog timer interrupt to check the state of my buttons every 15ms. What i'm trying to do is that every 3 interrupts (45ms) if the button in question is still in a high state then the effect will change and execute just like my old code. The issues is i'm getting crazy flashing off my leds, it appears to be rapidly changing the effects but i'm not touching the button.

Here is the code in the ISR i'm using. I'm sure my logic is flawed somewhere but just not sure where.

Code: [Select]
ISR (WDT_vect) {
  if (digitalRead (EFFECT_BUTTON) == HIGH ) {
    effect_count++;
  } else if (digitalRead (EFFECT_BUTTON) == LOW ) {
    effect_count == 0;
  }
  if (digitalRead (EFFECT_BUTTON) == HIGH && effect_count >= 3) {
    selectedEffect++;
    EEPROM.put(0, selectedEffect);
    asm volatile ("jmp 0"); // force reset of micro controller at line 0
  }
}

wvmarle

You are doing some really crazy, scary, and outright wrong things there.

For starters, the wrong things happen when you hold that button for longer than three WDT intervals - you continue to count after your reset.

That reset is simply the wrong way of doing things.

What you need is state change detection to count (so count when your button GOES down, not when it IS down as you're doing).

I have no idea what you're doing in the various routines or how you even call them so can't comment on that, really.

Your loop() may look a bit like this:

Code: [Select]

uint32_t lastPressed;
bool previousButtonState;
const uint8_t NPROGRAMS = 3;
uint8_t selectedEffect;

void loop() {
  if (digitalRead(EFFECT_BUTTON) == LOW) { // button is pressed
    if (previousButtonState == HIGH) { // but it was not
      previousButtonState = LOW;
      selectedEffect++; // increase the count.
      if (selectedEffect== NPROGRAMS) {
        selectedEffect= 0;
      }
      EEPROM.put(0, selectedEffect);
    }
      lastPressed = millis(); // record when it happens.
  }
  else if (millis() - lastPressed > 50) { //  Button is unpressed, and it's at least 50 ms since it last got pressed
    prevousButtonState = HIGH;
  }
  switch (selectedEffect) { // Select program.
    case 0:
      effects0();
      break;

    case 1:
      effects1();
      break;

    case 2:
      effects2();
      break;
  }
}


Now as long as you make sure those effectsn() functions return fast you don't need an interrupt, and you can count properly without crazy things like a reset - where did you even find that routine?
Quality of answers is related to the quality of questions. Good questions will get good answers. Useless answers are a sign of a poor question.

wvmarle

And another thing: you're not allowed to use delay() until you know why that is.
Quality of answers is related to the quality of questions. Good questions will get good answers. Useless answers are a sign of a poor question.

Kuusou

And another thing: you're not allowed to use delay() until you know why that is.
You can't use delay in an ISR because it will screw up your time keeping and everything will be thrown off correct?


I see why my LEDS were acting that way. I'm idiot and had my states wrong. Low is when its pressed, not high.

I'm using the reset as a way to exit the function. Many of these functions aren't fast and this is a way to go back to 0, reset all my stuff, use EEPROM to figure out the effect for the switches, and go into the next effect. I see what you're saying though, holding down the button causes rapid switching through the effects.

wvmarle

You can't use delay in an ISR because it will screw up your time keeping and everything will be thrown off correct?
Never even thought of using delay() in an ISR, that's double bad. An ISR should return as fast as possible.

Delay is blocking. Nothing happens during delay. No button reads, nothing. That's why. Write your code without delay() or other blocking loops and you can return to loop() hundreds or many thousands of times a second to read that button.
Quality of answers is related to the quality of questions. Good questions will get good answers. Useless answers are a sign of a poor question.

jremington

#38
Sep 10, 2019, 04:33 am Last Edit: Sep 10, 2019, 04:34 am by jremington
Quote
You can't use delay in an ISR because it will screw up your time keeping
No, the Arduino will instantly freeze until you push the reset button.

delay() requires the interrupts to be running so that millis() runs, and those are turned off in an interrupt.

Really, learn to program without using interrupts. For beginners, interrupts create many more problems than they solve.

Kuusou

Never even thought of using delay() in an ISR, that's double bad. An ISR should return as fast as possible.

Delay is blocking. Nothing happens during delay. No button reads, nothing. That's why. Write your code without delay() or other blocking loops and you can return to loop() hundreds or many thousands of times a second to read that button.
That's the issue then, almost all of these led functions have a delay in them to get the desired effect, hence why i'm going this route.

 
I think I see the solution thanks to you. Using the WDT to increment a counter I can use a conditional statement like you've done to check and make sure the button has remained unpressed those 3 cycles and if it has then make the desired change.


Code: [Select]
if (wdt_counter == 0 || wdt_counter < 3) { // 45ms debounce delay
    wdt_counter++;
  } else if (wdt_counter > 3) {
    wdt_counter = 0;
  }






wvmarle

Still the wrong approach. #35 was not about ISRs.

Get rid of ALL those delay() calls, use millis() based timing and finite state machines instead. delay() is another major cause of problems - like you're experiencing.
Quality of answers is related to the quality of questions. Good questions will get good answers. Useless answers are a sign of a poor question.

Kuusou

This code works in that it reacts to button presses, only issue is holding down the button causes it to cycle through. I'm trying to apply the concept you were talking about, counting how long the since the button has been pressed, not counting while its pressed.

Code: [Select]
ISR (WDT_vect) {
  if (digitalRead (EFFECT_BUTTON) == LOW ) {
    effect_count++;
  } else if (digitalRead (EFFECT_BUTTON) == HIGH ) {
    effect_count == 0;
  }
  if (digitalRead (EFFECT_BUTTON) == LOW && effect_count >= 3) {
    selectedEffect++;
    EEPROM.put(0, selectedEffect);
    asm volatile ("jmp 0"); // force reset of micro controller at line 0
  }
}






This is my attempt at that, but for some reason I can't get the logic to work out....what am I missing? When I push the button nothing happens. I know from the previous code though that the interrupt every 15ms is enough time to be responsive to the presses I just can't figure out what in this code is preventing this from working. I'm pulling my hair out at this point. If I can just get this section of code to work i'll be finished with the program  :smiley-confuse:
Code: [Select]
ISR (WDT_vect) {
  if (digitalRead (EFFECT_BUTTON) == LOW ) {
    previousButtonState == LOW;
    effect_count = 0;
  }

  if (digitalRead (EFFECT_BUTTON) == HIGH && previousButtonState == LOW ) {
    effect_count++;
  }

  if (digitalRead (EFFECT_BUTTON) == HIGH && effect_count >= 3) {
    selectedEffect++;
    EEPROM.put(0, selectedEffect);
    asm volatile ("jmp 0"); // force reset of micro controller at line 0
  }
}

Kuusou

#42
Sep 10, 2019, 06:42 am Last Edit: Sep 10, 2019, 07:25 am by Kuusou
It appears just changing

Code: [Select]
if (digitalRead (EFFECT_BUTTON) == LOW ) {
    previousButtonState == LOW;
    effect_count = 0;
  }


to this

Code: [Select]
if (digitalRead (EFFECT_BUTTON) == LOW ) {
    previousButtonState == LOW;
    effect_count++;
  }


in the code above solved the issue....but I don't understand why...

Kuusou

#43
Sep 10, 2019, 06:54 am Last Edit: Sep 10, 2019, 06:55 am by Kuusou
Code: [Select]
ISR (WDT_vect) {
  if (digitalRead (EFFECT_BUTTON) == LOW ) {
    effect_count++;
  }

  if (digitalRead (EFFECT_BUTTON) == HIGH && effect_count >= 5) {
    selectedEffect++;
    EEPROM.put(0, selectedEffect);
    asm volatile ("jmp 0"); // force reset of micro controller at line 0
  }
}


This code appears to be working perfectly but i'm not sure as to why...my guess is that 15ms as a rate of the interrupt and 3-5 cycles of that is a "sweet spot" in terms of how long a button is typically pressed down by a human. So because the controller is resetting each time those conditions are met and returning the counter to zero its in effect debounced/deglitched....

Smajdalf

No, the Arduino will instantly freeze until you push the reset button.
Are you sure? IIRC on Arduino Uno delay() with disabled interrupts generates shorter delay than expected. It polls for Timer0 Overflow Flag for some reason I don't remember and when the flag is not cleared by the ISR it keeps subtracting from the requested delay length. Or maybe delay() calls millis() which polls the flag?

@OP: In your last schematic you removed the WRONG set of resistors. Pressing a button will short Vcc to GND. Not a good design.
How to insert images: https://forum.arduino.cc/index.php?topic=519037.0

Go Up