EmbeddedRelated.com

General library for debouncing (filtering) of digital inputs

April 3, 2013 Coded in C

General library for debouncing (filtering) of digital inputs.
Some programmers use simple straightforward solution like this:

tmp_inp = get_input();
wait(xxx);
if (tmp_inp == get_input())
   ..........

This library solves the problem much more generally. The requirements are as follows:
- multiplatform
- non-blocking
- applicable to set of inputs
- filtering of both transition (0->1 and 1->0)
- user defined values for minimal Ton and Toff
- allowing separate times for each input
- possibility to use multiple instances (e.g. for more sets of inputs with different requirements)

FAQ:
Q: Why types like "uint_fast8_t" are used?
A: Same library can be used on 8-bit AVR as well as on 32 bit ARM. This type (defined in stdint.h) is as fast as possible on all platforms (e.g. on ARM it should be 32 bit, on AVR 8 bit).

Q: What is "sys_timer_ticks" variable?
A: It is considered that this variable is incremented in some timer interrupt service routine (e.g. each 1ms). More functions in the main code can use this variable for the timing.

Q: Why there is possibility of multiple instances (pointer to handle as the first parameter)?
A: For example in the system there can be several inputs which require fast response, so debounce_proc must be called very fast. Another set of inputs can have slower response, so debounce_proc will be in slower loop.

Q: Why macro __MAX_SIGNED is used, if there are macros like INT_MAX in standard limits.h header file?
A: Because nl_debouce_time_t can be defined differently on each platform, so general macro is necessary.

/**************************************************************************************/
/*                            sample usage of the library                             */
/**************************************************************************************/

//Remark: sys_timer_ticks should be provided for timing by the user.

/* Simplest example with undefined CONFIG_DEBOUNCE_WITH_HANDLE and CONFIG_DEBOUNCE_SEPARATE_TIMES */

#include <debounce.h>

#define INPUTS_NUM		12
nl_debouce_time_t inp_times[ INPUTS_NUM ];
nl_debouce_time_t filt_time = 10;   //same value will be used for t_on and t_off

int main(void)
{
    nl_inp_t inp_state, filtered_inp_state;

    debounce_init( inp_times, INPUTS_NUM, &filt_time, &filt_time, 
            (nl_ticks_t*)&sys_timer_ticks );

    while(1)
    {
        //user defined function, which return actual state of all inputs as bit array
        inp_state = get_input_state();

        debounce_proc(&inp_state, &filtered_inp_state);
        //do something with filtered_inp_state 

        //...............
        //main program functionality 
        //...............
    }
}

/* More complex example with defined CONFIG_DEBOUNCE_WITH_HANDLE and CONFIG_DEBOUNCE_SEPARATE_TIMES */
#include <debounce.h>

#define INPUTS_NUM		5
struct debounce_state_s inp_dbnc_s;
nl_debouce_time_t inp_times[ INPUTS_NUM ];

nl_debouce_time_t t_on[ INPUTS_NUM ] = {10,10,20,20,50};   
nl_debouce_time_t t_off[ INPUTS_NUM ] = {5,5,10,10,50};   

int main(void)
{
    nl_inp_t inp_state, filtered_inp_state;

    debounce_init( &inp_dbnc_s, inp_times, INPUTS_NUM, 
        t_on, t_off, (nl_ticks_t*)&sys_timer_ticks );

    while(1)
    {
        //user defined function, which return actual state of all inputs as bit array
        inp_state = get_input_state();

        debounce_proc(&inp_dbnc_s, &inp_state, &filtered_inp_state);
        //do something with filtered_inp_state 

        //...............
        //main program functionality 
        //...............
    }
}

/**************************************************************************************/
/*                     debounce library header file "debounce.h"                      */
/**************************************************************************************/
#ifndef _DEBOUNCE_H_
#define _DEBOUNCE_H_
    
#include <stdint.h>

/* because library is multiplatform, following types are defined in separate file */
#include "nlib_types.h"
/* examaple of types definition in "nlib_types.h" */
//typedef   uint32_t    nl_ticks_t;
//typedef   int16_t nl_debouce_time_t;  //so maximum filter time is 32767ms (considering period of ticks 1ms)
//typedef   uint32_t nl_inp_t;  //up to 32 inputs can be handled

/* in general case following macros should be provided in this header file, or can be defined directly */
#include <debounce_config.h>
//#define CONFIG_DEBOUNCE_WITH_HANDLE
//#define CONFIG_DEBOUNCE_SEPARATE_TIMES
    
#ifdef CONFIG_DEBOUNCE_WITH_HANDLE
    #define DEBOUNCE_STRUCT_PAR  struct debounce_state_s * debounce_state,
#else
    #define DEBOUNCE_STRUCT_PAR
#endif

typedef struct debounce_state_s
{
    const nl_ticks_t* ticks; //pointer to timing variable (is incremented e.g. each 1ms)
    nl_ticks_t old_ticks;
    uint_fast8_t inp_num; //number of inputs
    const nl_debouce_time_t *debounce_on_time,*debounce_off_time; //tables with desired filter times
    nl_debouce_time_t* inp_times;   //actual time ON/OFF - non-negative values = ON, negative = OFF
}
debounce_state_t;

void debounce_init(DEBOUNCE_STRUCT_PAR nl_debouce_time_t* inp_tim, uint_fast8_t num, const nl_debouce_time_t *dton,const nl_debouce_time_t *dtoff, const nl_ticks_t* ticks);
uint_fast8_t debounce_proc(DEBOUNCE_STRUCT_PAR const nl_inp_t* act_inp_state, nl_inp_t* debounced_inp_state);

#endif /*_DEBOUNCE_H_*/

/**************************************************************************************/
/*                     debounce library source file "debounce.c"                      */
/**************************************************************************************/

#include "debounce.h"
#include <string.h>

//allow using multiple instances
#ifndef CONFIG_DEBOUNCE_WITH_HANDLE
    struct debounce_state_s _debounce_state_;
    struct debounce_state_s * debounce_state = &_debounce_state_;
#endif

/* allow different times for each input */
#ifndef CONFIG_DEBOUNCE_SEPARATE_TIMES
    #define _debounce_times_idx_ 0
#else
    #define _debounce_times_idx_ i
#endif

//maco trick to find maximum value of given signed integer type "http://www.fefe.de/intof.html"
#define __HALF_MAX_SIGNED(type) ((type)1 << (sizeof(type)*8-2))
#define __MAX_SIGNED(type) (__HALF_MAX_SIGNED(type) - 1 + __HALF_MAX_SIGNED(type))

/*
Init function of the library
	
  DEBOUNCE_STRUCT_PAR - depending on "CONFIG_DEBOUNCE_WITH_HANDLE": nothing, or pointer to handle
  inp_tim - array of variables for storing of state for each input (number of elements must be the same as number of inputs!)
  num - number of inputs
  dton - depending on "CONFIG_DEBOUNCE_SEPARATE_TIMES": pointer to sigle value (minimal ON time), or array of times
  dtoff - depending on "CONFIG_DEBOUNCE_SEPARATE_TIMES": pointer to sigle value (minimal OFF time), or array of times
  ticks - pointer to variable, which is periodicaly incremented
*/
void debounce_init(DEBOUNCE_STRUCT_PAR nl_debouce_time_t* inp_tim, uint_fast8_t num, const nl_debouce_time_t *dton, const nl_debouce_time_t *dtoff ,const nl_ticks_t* ticks)
{
    debounce_state-> inp_times=inp_tim;
    debounce_state-> inp_num=num;
    debounce_state-> debounce_on_time=dton;
    debounce_state-> debounce_off_time=dtoff;
    debounce_state-> ticks=ticks;
    debounce_state-> old_ticks=*ticks;
    memset(inp_tim,0,sizeof(*inp_tim)*num);
    inp_tim[0]=__MAX_SIGNED(nl_debouce_time_t);  //this is used later to evaluate first iteration after start
}

/*
This is core function of the library
	
  DEBOUNCE_STRUCT_PAR - depending on "CONFIG_DEBOUNCE_WITH_HANDLE": nothing, or pointer to handle
  act_inp_state - pointer to variable with actual state of all inputs (1 bit for each input)
  debounced_inp_state - resulting state after filtering
  return - 0=no change, 1=some input(s) are changed 
*/
uint_fast8_t debounce_proc(DEBOUNCE_STRUCT_PAR const nl_inp_t* act_inp_state, nl_inp_t* debounced_inp_state)
{
    uint_fast8_t i,change=0;
    nl_inp_t mask=1;
    nl_ticks_t tic_diff;

    tic_diff=(nl_ticks_t) (*(debounce_state-> ticks) - debounce_state-> old_ticks);
    debounce_state-> old_ticks = *(debounce_state-> ticks);

    if ((debounce_state-> inp_times)[0] == __MAX_SIGNED(nl_debouce_time_t)) //evaluate, if it is a first iteration
    {
        *debounced_inp_state=*act_inp_state;
        for(i=0; i<debounce_state-> inp_num ;i++)
            (debounce_state-> inp_times)[i]=0;
        return 0;
    }

    for(i=0; i<debounce_state-> inp_num ;i++)
    {
        if ( *act_inp_state & mask) //actual state is ON
        {
            if ((debounce_state-> inp_times)[i] >= 0) //and last state was ON
            {
                if (((debounce_state-> inp_times)[i] + (nl_debouce_time_t) tic_diff) < debounce_state-> debounce_on_time[_debounce_times_idx_])
                {
                    (debounce_state-> inp_times)[i] += (nl_debouce_time_t) tic_diff; //filter time not elapsed
                }
                else
                {   //filter time elapsed
                    if (!( *debounced_inp_state & mask))
                    {
                        *debounced_inp_state |= mask;
                        change=1;
                    }
                }
            }
            else (debounce_state-> inp_times)[i] = 0;
        }
        else    //actual state is OFF
        {
            if (debounce_state-> inp_times[i] < 0) //and last state was OFF
            {
                if ( (nl_debouce_time_t)(((debounce_state-> inp_times)[i] - (nl_debouce_time_t) tic_diff)) >= (-1*debounce_state-> debounce_off_time[_debounce_times_idx_]))
                {
                    (debounce_state-> inp_times)[i] -= (nl_debouce_time_t) tic_diff; //filter time not elapsed
                }
                else
                {   //filter time elapsed
                    if ( *debounced_inp_state & mask)
                    {
                        *debounced_inp_state &= ~mask;
                        change=1;
                    }
                }
            }
            else (debounce_state-> inp_times)[i] = -1;
        }
        mask=(nl_inp_t) mask<<1;

    }
    return change;
}