read and debounces a mechanical switch

MaxHeadRoom

Joined Jul 18, 2013
30,680
Have you actually found that you need de-bouncing?
I have found in most instances depending on application, it is not always needed.
Max.
 

AlbertHall

Joined Jun 4, 2014
12,626
A simple software/hardware solution is to use a hardware timer that interrupts every 30ms at which time you read the switches.
Do not accept a switch state as valid unless you detect the same state twice in succession.
If you read the switch(es) less often than the bounce time then no other checks are necessary. You simply use the value you read as valid.
 

jpanhalt

Joined Jan 18, 2008
11,087
If you read the switch(es) less often than the bounce time then no other checks are necessary. You simply use the value you read as valid.
Or, use an edge sensitive interrupt. The problem is, of course, when the ISR takes less time than the bounce takes. Easily solved.

Edit: I did not mean the ISR should wait 40 ms or so. What I meant is that the code would not re-enable that edge detect until bounce is over. The ISR itself could return to the program very quickly. Re-enabling could be done by a timer and another interrupt as has been mentioned for various polling schemes.
 
Last edited:

AlbertHall

Joined Jun 4, 2014
12,626
What happens if you read the switches just at the instant that it is bouncing?
Let's suppose the switch is just closing. Now you might read either open or closed. If you read open then this is the correct reading for just a few ms before the reading. If you read closed then you have the correct reading for a few ms after the reading. As long as the bounce is complete before the next reading then all is well. No further action is required.

The only hardware required is the switch and a pull-up (which may be built in to the micro). The only software is the timer to set up when to read and a simple port pin read. Quick and easy.
 

atferrari

Joined Jan 6, 2004
5,012
For many years I used on purpose the clunkiest pushbuttons I could find to be sure if it worked with them it would work probably with any other.

I wrote my own keyboard /pushbuttons routines. Never needed more than 25 msec.
 

nsaspook

Joined Aug 27, 2009
16,330
So far this has been a robust solution for to up to 24 mechanical switch detection and debounce in a single inexpensive package. System and switch operational voltages should be in the range of 9-24 volts (the demo system uses a 9 volt battery).

https://www.ti.com/lit/ds/symlink/tic12400-q1.pdf?ts=1620182495225&ref_url=https%3A%2F%2Fwww.ti.com%2Fproduct%2FTIC12400-Q1
The TIC12400-Q1 is an advanced Multiple Switch Detection Interface (MSDI) designed to detect external switch status in a 12-V automotive system

https://www.ti.com/document-viewer/TIC12400-Q1/datasheet/features-scps2601240#SCPS2601240
1 Features
Qualified for Automotive Applications
AEC-Q100 Qualified With the Following Results:
Device Temperature Grade 1: –40°C to 125°C Ambient Operating Temperature
Device HBM ESD Classification Level H2
Device CDM ESD Classification Level C4B
Designed to Support 12-V Automotive Systems with Over-voltage and Under-voltage Warning
Monitors up to 24 Direct Switch Inputs with 10 Inputs Configurable to Monitor Switches Connected to Either Ground or Battery
Switch Input Withstands up to 40 V (Load Dump Condition) and down to –24 V (Reverse Polarity Condition)
6 Configurable Wetting Current Settings:
(0 mA, 1 mA, 2 mA, 5 mA, 10 mA, and 15 mA)
Integrated 10-bit ADC for Multi-Position Analog Switch Monitoring
Integrated Comparator with 4 Programmable Thresholds for Digital Switch Monitoring
Ultra-low Operating Current in Polling Mode:
68 μA Typical (tPOLL = 64 ms, tPOLL_ACT = 128 μs,
All 24 Inputs Active, Comparator Mode, All Switches Open)
Interfaces Directly to MCU Using 3.3 V / 5 V Serial Peripheral Interface (SPI) Protocol
Interrupt Generation to Support Wake-Up Operation on All Inputs
Integrated Battery and Temperature Sensing
±8 kV Contact Discharge ESD Protection on Input Pins per ISO-10605 With Appropriate External Components
38-Pin TSSOP Package
Simple PIC32 driver.
C:
#ifndef TIC12400_H    /* Guard against multiple inclusion */
#define TIC12400_H

#include "vcan.h"
#include "dio.h"
#include "timers.h"

#define TIC12400_DRIVER    "V0.3"

#define por_bit    0x01
/*
 * switch bit masks in the raw 32-bit register from the TIC12400
 */
#define raw_mask_0    0b010
#define    raw_mask_11    0b100000000000000

/*
 * TIC12400 command structure
 */
typedef struct __attribute__((packed))
{
    uint32_t par : 1;
    uint32_t data : 24;
    uint32_t addr : 6;
    uint32_t wr : 1;
}
ticbuf_type;

/*
 * TIC12400 response structure
 */
typedef struct __attribute__((packed))
{
    uint32_t par : 1;
    uint32_t data : 24;
    uint32_t oi : 1;
    uint32_t temp : 1;
    uint32_t vs_th : 1;
    uint32_t ssc : 1;
    uint32_t parity_fail : 1;
    uint32_t spi_fail : 1;
    uint32_t por : 1;
}
ticread_type;

void tic12400_reset(void);
bool tic12400_init(void);
uint32_t tic12400_wr(const ticbuf_type *, uint16_t);
uint32_t tic12400_get_sw(void);
void tic12400_interrupt(uint32_t, uintptr_t);

extern volatile uint32_t tic12400_status, tic12400_counts;
extern volatile uint32_t tic12400_value;
extern volatile bool tic12400_init_fail, tic12400_event;
extern ticread_type *ticstatus;
extern ticread_type *ticvalue;
extern volatile bool tic12400_parity_status;

/* Provide C++ Compatibility */
#ifdef __cplusplus
extern "C" {
#endif


    /* Provide C++ Compatibility */
#ifdef __cplusplus
}
#endif

#endif /* TIC12400_H */

/* *****************************************************************************
 End of File
 */
C:
/*
 * TC12400 driver for PIC32MK v0.1
 * uses SPI5 mode1 at 4MHz no interrupts
 * external interrupt 2 is used to detect chip switch events
 */

#include "tic12400.h"

/*
 * command structure data
 * the parity bit must be set correctly for the command to execute on the chip
 */
const ticbuf_type setup32 = {
    .wr = 1,
    .addr = 0x32,
    .data = 0,
    .par = 1,
};
const ticbuf_type setup21 = {
    .wr = 1,
    .addr = 0x21,
    .data = 0,
    .par = 0,
};
const ticbuf_type setup1c = {
    .wr = 1,
    .addr = 0x1c,
    .data = 0,
    .par = 1,
};
const ticbuf_type setup1b = {
    .wr = 1,
    .addr = 0x1b,
    .data = 0xffffff,
    .par = 0,
};
const ticbuf_type setup1a = {
    .wr = 1,
    .addr = 0x1a,
    .data = 0xc000,
    .par = 1,
};
const ticbuf_type setup1a_trigger = {
    .wr = 1,
    .addr = 0x1a,
    .data = 0x0a00, // trigger and do config register CRC 
    .par = 1,
};
const ticbuf_type setup22 = {
    .wr = 1,
    .addr = 0x22,
    .data = 0xffffff,
    .par = 0,
};
const ticbuf_type setup23 = {
    .wr = 1,
    .addr = 0x23,
    .data = 0xffffff,
    .par = 1,
};
const ticbuf_type setup24 = {
    .wr = 1,
    .addr = 0x24,
    .data = 0xfff,
    .par = 0,
};
const ticbuf_type setup1d = {
    .wr = 1,
    .addr = 0x1d,
    .data = 011111111, // octal constant, all inputs 1mA wetting current
    .par = 0,
};
const ticbuf_type ticread05 = {
    .wr = 0,
    .addr = 0x05,
    .data = 0,
    .par = 1,
};
const ticbuf_type ticdevid01 = {
    .wr = 0,
    .addr = 0x01,
    .data = 0,
    .par = 0,
};
const ticbuf_type ticstat02 = {
    .wr = 0,
    .addr = 0x02,
    .data = 0,
    .par = 0,
};
const ticbuf_type ticreset1a = {
    .wr = 1,
    .addr = 0x1a,
    .data = 0x1,
    .par = 0,
};

/*
 * global status and value registers
 */
volatile uint32_t tic12400_status = 0, tic12400_counts=0;
volatile uint32_t tic12400_value = 0;
ticread_type *ticstatus = (ticread_type*) & tic12400_status;
ticread_type *ticvalue = (ticread_type*) & tic12400_value;
volatile bool tic12400_init_fail = false, tic12400_event = false; // chip error detection flag
volatile bool tic12400_parity_status = false;

/*
 * software reset of the chip using SPI
 * all registers set to their default values
 */
void tic12400_reset(void)
{
    TIC12400_EN0_Set();
    tic12400_wr(&ticreset1a, 2);
}

/*
 * chip detection and configuration for all inputs with interrupts for
 * switch state changes with debounce
 * returns false on configuration failure
 */
bool tic12400_init(void)
{
    TIC12400_EN0_Set();
    tic12400_status = tic12400_wr(&ticstat02, 0); // get status to check for proper operation

    if ((ticstatus->data > por_bit) || !ticstatus->por) { // check for any high bits beyond POR bits set
        tic12400_init_fail = true;
        goto fail;
    }

    tic12400_wr(&setup32, 0); //all set to compare mode, 0x32
    tic12400_wr(&setup21, 0); //Compare threshold all bits 2V, 0x21
    tic12400_wr(&setup1c, 0); //all set to GND switch type, 0x1c
    tic12400_wr(&setup1b, 0); //all channels are enabled, 0x1b
    tic12400_wr(&setup22, 0); //set switch interrupts, 0x22
    tic12400_wr(&setup23, 0); //set switch interrupts, 0x23
    tic12400_wr(&setup24, 0); // enable interrupts, 0x24
    tic12400_wr(&setup1d, 0); // set wetting currents, 0x1d
    tic12400_wr(&setup1a, 0); // set switch debounce to max 4 counts, 0x1a
    tic12400_status = tic12400_wr(&setup1a_trigger, 2); // trigger switch detections & CRC, 0x1a

    if (ticstatus->spi_fail) {
        tic12400_init_fail = true;
        goto fail;
    }

    tic12400_status = tic12400_wr(&ticdevid01, 0); // get device id, 0x01
    /*
     * configure event handler for tic12400 interrupts
     */
    EVIC_ExternalInterruptCallbackRegister(EXTERNAL_INT_2, tic12400_interrupt, 0);
    EVIC_ExternalInterruptEnable(EXTERNAL_INT_2);

fail:
    return !tic12400_init_fail; // flip to return true if NO configuration failures
}

/*
 * send tic12400 commands to SPI port 5 with possible delay after transfer
 * returns 32-bit spi response from the tic12400
 */
uint32_t tic12400_wr(const ticbuf_type * buffer, uint16_t del)
{
    static uint32_t rbuffer = 0;

    TIC12400_EN0_Clear();
    SPI5_WriteRead((void*) buffer, 4, &rbuffer, 4);
    TIC12400_EN0_Set();

    if (ticvalue->parity_fail) { // check for command parity errors
        tic12400_parity_status = true;
    };

    if (del) {
        delay_ms(del);
    }

    return rbuffer;
}

/*
 * switch data reading testing routine
 * tic12400 value is updated in external interrupt #2 ISR
 */
uint32_t tic12400_get_sw(void)
{
    if (tic12400_init_fail) { // Trouble in River City
        return 0;
    }

    if (tic12400_value & (raw_mask_0)) {
        BSP_LED1_Clear();
    } else {
        BSP_LED1_Set();
    }

    if (tic12400_value & (raw_mask_11)) {
        BSP_LED2_Clear();
    } else {
        BSP_LED2_Set();
    }

    tic12400_event = false;
    return tic12400_value;
}

/*
 * external interrupt 2 ISR
 * switch SPI status and switch data updates
 * toggles debug led and clears interrupt by reading status
 * sets event flag for user application notification
 */
void tic12400_interrupt(uint32_t a, uintptr_t b)
{
    tic12400_value = tic12400_wr(&ticread05, 0); // read switch
    tic12400_status = tic12400_wr(&ticstat02, 0); // read status
    RESET_LED_Toggle();

    if (ticvalue->ssc) { // only trigger on switch state change
        BSP_LED3_Toggle();
        tic12400_event = true;
    }
    tic12400_counts++;
}
PXL_20210507_153203469.jpgPXL_20210507_152410630.jpgPXL_20210507_152442283.jpgPXL_20210507_152522120.jpg
30us to read all 24 debounced and conditioned inputs with one interrupt as a background process. No waiting for or processing to the raw physical switch events.
 
Last edited:

djsfantasi

Joined Apr 11, 2010
9,237
When you’re processing other tasks, pausing 20,30,50,100ms may not be acceptable. Hence, my debounce routines are state based. I check the hardware status, change states and return for more processing. So while others wait 50ms, I can do other things during that 50ms. My function also detects long or short presses...

I’ll post code when I get back to my laptop.
 

paoloberno

Joined May 3, 2021
13
Waiting for 10s of milliseconds can be a problem when at the same time the micro has something else to do (i.e. communication avoiding timeouts).
When I have to write a pure software debounce (with no interrupts or timers) I use a counter inside the main program loop
- Read input value
- If the button is pressed increase counter, if counter value > threshold then ButtonPressed=TRUE
- if button is not pressed reset counter
- Execute the rest of main program loop
In this way a bounce will reset the counter so the ButtonPressed variable is set only after a continuous stable state, another advantage is that you can debounce multiple buttons at the same time.
The same idea becomes much more elegant with interrupts and timers.
 

ErnieM

Joined Apr 24, 2011
8,415
I prefer to wait to test my buttons, and I test them each and every 25 or 50 mS.

The trick is to do the test inside a timer driven interrupt routine and put the button results into a global value. As a bonus I get an accurate timing heartbeat.
 
Top