Author Topic: Simple debounce function with some extras.  (Read 265 times)

0 Members and 1 Guest are viewing this topic.

Offline vtx_boyTopic starter

  • Newbie
  • Posts: 1
  • Country: us
Simple debounce function with some extras.
« on: October 21, 2024, 07:00:36 pm »
Few times I got to debounce keys so I wrote a piece of software that I just include in my projects. This may help a programmer save some time.

Code: [Select]
/*
 * Copyright (c) 2024, vtx_boy <regis@u-io.com>
 * All rights reserved.
 *
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
This debounce function should be called at a period long enough to allow
the switch contacts to stabilize. Here all the parameters are optimized
for 60Hz (16mS) and it takes 3 cycles to debouce a key (Two cycles to detect
a state disturbance and 1 cycle to confirm a steady state).

Use of global variables simplify some type of software but might not be the best practice.

A function call returning a pointer could be considered to check key presses.

Preparing the port for input and pull up is not shown and should be the programmers task.

This software is in a modular format. Features not needed can simply be removed for a smaller footprint.
*/
   
#define BUTTON0 (1 << 25) //PA25 Xplained mini button
#define BUTTON1 (1 << 1) //Change to your design definition
#define BUTTON2 (1 << 2) //Change to your design definition
#define BUTTON3 (1 << 3) //Change to your design definition
#define BUTTON4 (1 << 4) //Change to your design definition
#define BUTTON5 (1 << 5) //Change to your design definition

//#define USED_BUTTONS (BUTTON0 | BUTTON1 | BUTTON2 | BUTTON3 | BUTTON4 | BUTTON5)
//#define REPEATING_BUTTONS (BUTTON0 | BUTTON1 | BUTTON2 | BUTTON3)

#define USED_BUTTONS (BUTTON0) //Xplained mini button
#define REPEATING_BUTTONS (BUTTON0) //Xplained mini button

#if (REPEATING_BUTTONS <= 16) /* Short form of popcount() for BIT0 to BIT3 */
const char bitCount_16[16] = {
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4
};

#elif (REPEATING_BUTTONS <= 256)/* Short form of popcount() for BIT0 to BIT7 */

const char bitCount_256[256] = {
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8
};

#endif



/*
Global variables.

The event consumer function should reset the event bit flag:
if(single_H & BUTTON0)
{
single_H &= ~BUTTON0;
...
...
}

if(*get_pulse_L() & BUTTON0)
{
*get_pulse_L() &= ~BUTTON0;
...
...
}

Pressing a button will set pulse_L flag the moment it is pressed and
will set pulse_H flag the moment it is released.

If programmed to accept long presses, pulse_L flag will be set when pressed. If kept pressed
for the programmed time, it eventually may set long_L flag and may skip the following pulse_H if programmed
to do so and may call some function.

Combinations of pressed keys could be detected and optionally have a timer delay to set a
flag or call some function to respond to that

pulse_H is set when uC port bit goes from 0 -> 1 and could repeat if kept pressed.
pulse_L is set when uC port bit goes from  1 -> 0 and could repeat if kept pressed.
single_L is set when uC port bit goes from 1 -> 0 only one time for each key press.

long_L is set when select uC port bit stay pressed for a preprogrammed time and
when it sets, it skips the following pulse_H.

Select keys repeats after a preprogrammed time delay if kept pressed.

Please note that all globals are of positive logic. All events will show in positive logic (set when occurred).

Variable "keys" is the true state of the keys after debouncing. Pressed key will read '1' even
though, when pressed, it is read as '0' at the pin since it has a pull up and it grounds the uC pin when pressed.
*/
volatile size_t pulse_H, pulse_L, single_L, long_L, keys;

/*
IN_PORT is the uC port the has the switches connected.
This software was tested on a Microchip SAMD10 xplained mini uC hence the
#define IN_PORT PORT->Group[0].IN.reg
*/
#define IN_PORT (PORT->Group[0].IN.reg)

void debounce (void)
{
size_t inverted_keys;
static size_t debounce_state = 0;
static size_t skip_pulse_H = 0;
static size_t inverted_image = USED_BUTTONS;
static size_t aux = USED_BUTTONS;
static size_t button5_timer = 0;
static size_t button4_button5_timer = 0;
static size_t button0_button1_button5_timer = 0;
static size_t loop1 = 0;
static size_t loop2 = 0;

/*
A button is a single switch connected to a pin port with internal pull up enabled or
an external pull up resistor. A pressed key will read as '0'.
*/
inverted_keys = IN_PORT & USED_BUTTONS;
switch (debounce_state)
{
default:
case 0:
if(inverted_keys != inverted_image)
{
debounce_state = 1; //Different -> advance.
}
break;

case 1:
if(inverted_keys != inverted_image)
{
debounce_state = 2; //Still different? Check for new key press.
inverted_image = inverted_keys;
}
else debounce_state = 0;
break;

case 2:
if(inverted_keys != inverted_image)
{
debounce_state = 0;
inverted_image = inverted_keys;
}
else
{

                                /*If keys or events will be checked inside interrupts a  disable interrupts should be here. */
                                /*  __disable_irq(); */                               
                                debounce_state = 0;
pulse_L  |= (aux & ~inverted_image); //May repeat itself when pressed if programmed to do so.
single_L |= (aux & ~inverted_image); //Does not repeat. One press one pulse.
pulse_H  |= (~aux & inverted_image);
aux = inverted_image;
keys =  (~aux) & USED_BUTTONS;
if(skip_pulse_H & pulse_H)
{
size_t skip_pulse_H_image = skip_pulse_H & pulse_H;
skip_pulse_H &= ~skip_pulse_H_image;
pulse_H &= ~skip_pulse_H_image;
}
                                /* __enable_irq(); */

}
break;
}
/*
Here we detect that one or more keys together are pressed for a programmed time
Please note that if a long press of a single key is needed (ex open a menu) make sure
that that key is not in the list of the autorepeat keys (check "Repeating pulses at a programmed rate")
*/
switch(keys & USED_BUTTONS)
{
case (BUTTON4 | BUTTON5):
if(button4_button5_timer)
{
if(button4_button5_timer == 1)
{
//Skip following pulse_H if buttons pressed for a programmed time
skip_pulse_H |= (BUTTON4 | BUTTON5);
//Set flag or execute a function when those selected keys are pressed for
//a programmed time.
long_L |= (BUTTON4 | BUTTON5);
// or call some useful function - function_button4_button5_long_pressed();

}
button4_button5_timer--;
}
//Reset all other combinations timers
button0_button1_button5_timer = (60 * 5);  //5s @ 60Hz
button5_timer = (60 * 1); //1sec @ 60Hz
break;

case (BUTTON0 | BUTTON1 | BUTTON5):
if(button0_button1_button5_timer)
{
if(button0_button1_button5_timer == 1)
{
//Skip following pulse_H if buttons pressed for a programmed time
skip_pulse_H |= (BUTTON0 | BUTTON1 | BUTTON5);
//Set flag or execute a function when those selected keys are pressed for
//a programmed time.
long_L |= (BUTTON0 | BUTTON1 | BUTTON5);
// or call some useful function - function_button0_button1_button5_long_pressed();
}
button0_button1_button5_timer--;
}
//Reset all other combinations timers
button4_button5_timer = (60 * 5);  //5s @ 60Hz
button5_timer = (60 * 1); //1sec @ 60Hz
break;

case BUTTON5:
if(button5_timer)
{
if(button5_timer == 1)
{
//Skip following pulse_H if buttons pressed for a programmed time
skip_pulse_H |= (BUTTON5);
//Set flag or execute a function when those selected keys are pressed for
//a programmed time.
long_L |= (BUTTON5);
//or call some useful function - function_button5_long_pressed();
}
button5_timer--;
}
//Reset all other combinations timers
button4_button5_timer = (60 * 5);  //5s @ 60Hz
button0_button1_button5_timer = (60 * 5);  //5s @ 60Hz
break;


default:
//No combinations detected
//Reset all combinations timers
button5_timer = (60 * 1); //1sec @ 60Hz
button4_button5_timer = (60 * 5);  //5s @ 60Hz
button0_button1_button5_timer = (60 * 5);  //5s @ 60Hz
break;
}

/*
Repeating pulses at a programmed rate if any SINGLE KEY kept pressed for a programmed time
bitCount is a table of quantity of '1' bits in the binary value. 
bitCount_16 table constant have 16 combinations if buttons to repeat are bit0 to bit3.
bitCount_256 table constant have 256 combinations if buttons to repeat are bit0 to bit7.
*/
#if (REPEATING_BUTTONS <= 16)
if(bitCount_16[(keys & (REPEATING_BUTTONS))] == 1)
#elif (REPEATING_BUTTONS <= 256)
if(bitCount_256[(keys & (REPEATING_BUTTONS))] == 1)
#else
if(__builtin_popcount(keys & REPEATING_BUTTONS) == 1)
#endif
{
//During the first 2 sec of a single key pressed
//pulse_L flag will be pulsed at 4 times per second
if (loop2 < 120)
{
if (++loop2 > 30) //Wait 0.5sec to start pulses...
{
if (++loop1 > 15)  //  60/15  4 pulses/sec
{
loop1 = 0;
//If repeating low pulses are needed
pulse_L |= (keys & REPEATING_BUTTONS);

//If repeating high pulses are needed
pulse_H |= (keys & REPEATING_BUTTONS);
}
}
}
else //After 2 seconds, pulse_L flag will be pulsed 10 times per second
{
if (++loop1 > 6) //60/6  10 pulses/sec
{
loop1 = 0;
//If repeating low pulses are needed
pulse_L |= (keys & REPEATING_BUTTONS);

//If repeating high pulses are needed
pulse_H |= (keys & REPEATING_BUTTONS);
}
}
}
else loop1 = loop2 = 0;
}

volatile __inline size_t* get_pulse_H (void) { return(&pulse_H);}
volatile __inline size_t* get_pulse_L (void) { return(&pulse_L);}
volatile __inline size_t* get_single_L (void) { return(&single_L);}
volatile __inline size_t* get_long_L (void) { return(&long_L);}
volatile __inline size_t* get_keys (void) { return(&keys);}

/* Elegant and safer */
size_t get_pulse_H_2 (void) {return(pulse_H);}
void reset_pulse_H_2(size_t bit_to_reset) {pulse_H =& ~bit_to_reset;}

/* Elegant but not safer
if(*get_pulse_L() & BUTTON0)
{
*get_pulse_L() &= ~BUTTON0;
...
...
}
*/

Happy coding.
« Last Edit: October 23, 2024, 05:58:17 pm by vtx_boy »
 
The following users thanked this post: SteveThackery


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf