Momentary push button software toggle

GroupDIY Audio Forum

Help Support GroupDIY Audio Forum:

ruffrecords

Well-known member
GDIY Supporter
Joined
Nov 10, 2006
Messages
14,947
Location
Norfolk - UK
I have a couple of momentary action push buttons I wan to turn into toggle switches in software. There are loads of debouncing examples on line, some better than others, but none about toggling. Here's what I have so far in psudocode. This all takes place in a once every 60mS timer interrupt.

oldswitch = newswitch // move last switch value to old
newswitch = read_switch
if newswitch != oldswitch exit / / not debounced
// here if switch debounced - now need to kook for high to low transition
oldtoggle = new toggle
newtoggle = newswitch
if newtoggle == oldtoggle exit // no transition
if newtoggle = high exit // 0 to 1 transition - not interested
switchvalue = !switchvalue // toggle switchvalue which is what we want
return

Can someone check my logic is not flawed.

Cheers

Ian
 

Bo Deadly

Well-known member
Joined
Dec 22, 2015
Messages
3,139
Location
New Jersey, USA
You definitely don't need timer interrupts. Interrupts are for when something needs to happen instantaneously. In this case, there is nothing a human being can do that would require instantaneous action. Even a midi implementation where it detects keys being hit would be better off using polling.

Incidentally, don't sleep in code. There are lots of examples that use delay() and such. That is for kids making robots. Just loop indefinitely. The microcontrollers run at a certain fixed frequency all the time. It's not like a PC CPU with sophisticated power management. You're not going to reduce power consumption by calling delay(). You will just make it impossible for your code to do anything while it's sleeping.

Here is my debounce toggle button pseudocode example. Bear in mind this is an unfiltered brain dump so it might be a little off (like my brain). But the idea should be clear.

Code:
#define DEBOUNCE_PERIOD 30

int tog_state = 0;
uint32_t debounce_time = 0;

int
debounce_toggle(uint32_t ctime)
{
    int rval = read(button);

    if (rval > 0) { // may be pressed

        if (debounce_time == 0) {
            debounce_time = ctime; // start debounce
        } else if (ctime > debounce_time + DEBOUNCE_PERIOD) {
            debounce_time = 0; // reset for next time

            // pressed for a while, do toggle
            tog_state = tog_state == 0 ? 1 : 0; // toggle!
        } 
    } else { // nope, reset
        debounce_time = 0;
    } 
}

// THE ENDLESS LOOP ...
for ( ;; ) {
    uint32_t ctime = now()

    do_stuff(ctime);
    do_other_stuff(ctime);
    debounce_toggle(ctime);
    do_more_stuff(ctime);
}

In practice my actual code looks nothing like this of course. I use structs to abstract ports, represent pins and to hold the debounce state and process all pins of all ports in one routine to update the state of things. Then the other code in the loop just looks at the current state.

At the very least you should use a struct to hold the state of a particular button and reuse the same debounce_toggle routine by just passing it the struct.
 
Last edited:

ruffrecords

Well-known member
GDIY Supporter
Joined
Nov 10, 2006
Messages
14,947
Location
Norfolk - UK
Many thanks for the detailed reply. It looks like the main simplification can be in the toggle logic. If I get a debounced zero this must be the 1 to 0 transition I am looking for to trigger a toggle.

I am not sure if your code actually toggles, it appears just to debounce the switch which is the first half of the problem.

The 8 pin micro I am using has only 1K of flash (500 words) so I am coding in assembler.

Cheers

Ian
 

Bo Deadly

Well-known member
Joined
Dec 22, 2015
Messages
3,139
Location
New Jersey, USA
I am not sure if your code actually toggles, it appears just to debounce the switch which is the first half of the problem.
True. Code corrected for a simple toggle (I think).

Although a more sophisticated implementation might use the if (rval != tog_state) { to detect more general events like the being button released vs pressed so that you can do things like check to see if the button was held down for a while.
 

Matador

Well-known member
Joined
Feb 25, 2011
Messages
2,478
Location
Bay Area, California
If you don't need the complexity of having the software measure the debounce period exactly, you can just stuff in a delay and read the state after the delay and not worry about the debounce. This goes especially true if you have a pull down (or up) and a small cap sized to give an RC response of 1 time period, and size your delay to 5 times that (e.g. size R and C to give 20ms time constant, then set your delay to 100ms...like a 20k pull down and a 1nF cap).

Python:
# Assumes 20ms filter time constant and pull down
# (meaning rising edge is what you care about)

toggleState = False # default to off
def some_function_to_poll_button():
    if GPIO.buttonValue = True:      # Something might have pressed the button?
        sleep(0.1)
        if GPIO.buttonValue = True:  # Is it still pressed?
            toggleState = not toggleState

Even without the RC, you likely can just look on a scope and measure the average debounce period, and just wait for 10 times longer and you'll also be fine.
 
Last edited:

ccaudle

Well-known member
Joined
Mar 18, 2010
Messages
575
Location
Houston
you likely can just look on a scope and measure the average debounce period

If you ever find yourself in the mood for TMI about bouncing switches, these two articles are a good read:
Ganssle on switch bouncing
Ganssle on switch bouncing part 2

Regarding the toggle, I'm inclined to agree with Matador, declare the toggle variable as a boolean, and the toggle code can be pretty simple.
Something like:
Code:
arm_switch()
if !switch:
    disarm_switch()
    cur_val = !cur_val
    debounce_and_arm()

In other words, if the switch is pressed it is assumed valid right away, stop watching the switch pin at that point, flip your toggle variable, then do all the debouncing after that so you toggle on the first push, but then you don't toggle again mistakenly on bounces. The side effect of that is it will probably set a minimum time that you can wait between key presses and have it toggle reliably. I would assume that is usually not a big deal in normal use, much better to miss a quick change of mind double press than to mistakenly toggle it when the user really only pressed once.

If you have really strict response time requirements you might be able to do something tricky like using ISR to trigger on pin change, then in the ISR disable trigger on change, and leave the pin disarmed until a timer goes off, at which point an ISR would re-arm the switch. That probably starts getting into too clever for your own good territory if you don't really have some time sensitive task running on the same processor that requires task switching like that.
 

ruffrecords

Well-known member
GDIY Supporter
Joined
Nov 10, 2006
Messages
14,947
Location
Norfolk - UK
True. Code corrected for a simple toggle (I think).

Although a more sophisticated implementation might use the if (rval != tog_state) { to detect more general events like the being button released vs pressed so that you can do things like check to see if the button was held down for a while.
Indeed. Just wanted to make sure I was on the right track.

Cheers

Ian
 

ruffrecords

Well-known member
GDIY Supporter
Joined
Nov 10, 2006
Messages
14,947
Location
Norfolk - UK
I agree there are lots of ways to skin this particular cat.

I might well just use a delay to start with to get things working. This little gadget will do nothing more than operate a channel mute relay in response to presses of a mute button or a solo button.

I looked at using a level change interrupt but the problem with that is when the switch bounces you can get a bunch of interrupts in quick succession.

I don't like adding capacitance to inputs unless they are Schmitt triggered which these are not.

A super loop is fine for the simple solo/mute application. However, this device will sit wight next to a fader so its relay can do the mute so I am toying with the idea of a later version incorporating an LED VU meter function using the in built ADC at which point interrupts will probably be unavoidable.

Thanks for all the input. Much food for thought.

Cheers

Ian
 

Bo Deadly

Well-known member
Joined
Dec 22, 2015
Messages
3,139
Location
New Jersey, USA
You will never need interrupts or ISR or whatever for this application.

But if you did decide to use the ADC, any sleep code would need to be removed.

Proper microcontroller code is a discrete state machine. You enter a loop, note the time and run routines that read and change the state. Then you run routines that act on the new state by toggling a relay or writing data to an LED driver or whatever. Updating the state and acting on the state should be orthogonal operations.

For asm, the simplest possible correct implementation would be to have an array of pin data that you endlessly iterate over and test like:

Code:
loop {
    pin = pins[...]
    if (pin.read()) {
        if (pin.counter++ == 30)
            toggle()
    } else {
       pin.counter = 0;
    }
}

This is similar to how arcade games work where you have 20 objects represented by an array of data that is simultaneously and independently being changed. The loop considers inputs from the player, updates the vector of each spaceship, laser blast, the score, etc. Then, in a secondary step, it redraws everything to reflect the new state.

Also, it is not unheard of to read erroneous data from a pin. This is especially true of an ADC. This could be caused by switching on a nearby florescent light or by an unexpected fluctuation in the power supply or something else. So you must test the pin many times before deciding to act on it.

Note: I updated the code slightly. The previous version would repeatedly toggle every 30 read cycles if you held the button down (probably 5 times during a typical press). In this version, the counter will just go above 30 and not be reset until read() indicates the button has been released. Technically this is still sloppy. Ideally the very original code I posted which separately tested for both press and release based on an expiration time would be much superior. But for an 8 pin micro running asm, this could be made to work.
 
Last edited:

Tubetec

Well-known member
Joined
Nov 18, 2015
Messages
4,422
I was avoiding this topic for a few days now , curiosity got the better of me , in any case I cant add anything useful ,
I should have paid more attention in math class , not played truant, dope smoking and beer drinking with the renegade musician bad boys :p
Nah no major regrets personally , loosing a few good pals along the way to the ravages of the musicians lifestyle is a bitter pill to swallow though .
 

Matador

Well-known member
Joined
Feb 25, 2011
Messages
2,478
Location
Bay Area, California
In other words, if the switch is pressed it is assumed valid right away, stop watching the switch pin at that point, flip your toggle variable, then do all the debouncing after that so you toggle on the first push, but then you don't toggle again mistakenly on bounces.
This is how I would do it as well, since human inputs are on the ~100ms or longer timeframes, I don't really see the point in debouncing anything at all for a pushbutton.

In other words, if you can trust the resting state of the button (and with any moderately strong pull-up or pull-down resistance, you can), then *any* activity on the button can indicate that the user has pressed it. The fact that it toggles many times is irrelevant, you can trust that *any* activity on the first rising (or falling) edge is a button press, and silently "throw away" the rest. Obviously this gets more complicated if you have to contemplate differences between push and release, or other actions while the button is "held down", but for simple toggles this shouldn't be necessary.

Since the use of coroutines has become common on even $1 microcontrollers, it could be something as simple as this:

Python:
# Assumes that the switch pulls input pin down to ground
async def pollswitch(self):
    while True:
        switch_state = self.pin.value()
        if switch_state != self.switch_state:
            # Button has changed!
            self.switch_state = switch_state
            if switch_state == 0:
                # Launch task to process button press.
                create_task(close_function, self._close_args)
        # Ignore any further button activity for 50 ms
        await asyncio.sleep_ms(50)

In effect, this code samples every 50ms, and suspends processing to other tasks. You can have individual coroutines for each pin that you need to watch as well, and they all spawn their own tasks to process the button presses.

I'm sure Ian doesn't care about any of this, but hopefully it helps someone. :D
 

qmp audio

Well-known member
Joined
Mar 4, 2011
Messages
202
Location
Chicago
This is my most recent implementation of a debounced momentary button.

C-like:
void loop()
{
    if (TapButtonPressed())
    {
        // do stuff
    }
}

int TapButtonPressed()
{
    tapState = digitalRead(TAPPIN);
    if (tapState == HIGH && now - lastTapTime > 100000 && tapState != lastTapState)
    {
        // Debounce
        lastTapState = tapState;
        return 1;
    }
    lastTapState = tapState;
    return 0;
}
 

FIX

Paul Wolff
Joined
May 5, 2021
Messages
234
Location
Nashville
This is my most recent implementation of a debounced momentary button.

C-like:
void loop()
{
    if (TapButtonPressed())
    {
        // do stuff
    }
}

int TapButtonPressed()
{
    tapState = digitalRead(TAPPIN);
    if (tapState == HIGH && now - lastTapTime > 100000 && tapState != lastTapState)
    {
        // Debounce
        lastTapState = tapState;
        return 1;
    }
    lastTapState = tapState;
    return 0;
}
It's also useful to have function when released and when pressed.
 

ruffrecords

Well-known member
GDIY Supporter
Joined
Nov 10, 2006
Messages
14,947
Location
Norfolk - UK
I got it working om a breadboard at last. Lots of struggles doing basic things like programming the ATtiny13A from an Arduino. I big 220uF from rest to 0V fixed that. Here is the prototype toggling code:
Code:
/*
  Buttontiny

  Fot ATtiny13A

  Toggles on and off a light emitting diode(LED) connected to digital pin 4 (PB4),
  when pressing a pushbutton attached to pin 3 (PB3).

  The circuit:
  - LED attached from pin 4 to ground through 330 ohm resistor
  - pushbutton attached to pin 3 from ground
  - 10K pullup resistor attached to pin 3 to +5V



  created 2022
  by Ian Thompson-Bell (www.customtubesconsoles.com)

 
*/

// constants
const int buttonPin = 3;        // PB3 pushbutton pin
const int ledPin =  4;          // PB4the LED pin

// variables
int ledState = 0;               // 0 or 1
int oldmute, newmute = 1;       // debounce switch values
int oldtoggle, newtoggle = 0;   // toggle transistion values

void setup() {
  // initialize the LED pin output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin input:
  pinMode(buttonPin, INPUT);
}

void loop() {
  // read pushbutton
  oldmute = newmute;                    // save last value
  newmute = digitalRead(buttonPin);
  if (newmute == oldmute)               // we have a debounced value
  {
    //digitalWrite(ledPin, newmute);    // write it directly to LED pin
    oldtoggle = newtoggle;              // save last toggle value
    newtoggle = newmute;
    if (newtoggle == 0 && oldtoggle == 1)     // one to zero transistion
    {
        ledState ^= 0x01;               // toggle led state
        digitalWrite(ledPin, ledState);
    }
  }
  delay(50);                            // debounce time
}
#

Compiles to just over 200bytes of code. About twice what it would be in assembler. Might have been a little shorter/faster if I had used chars instead of ints.

Cheers

Ian
 

Latest posts

Top