Bit banging a contact closure input -

Bit banging a contact closure input

Some projects require a microcontroller to monitor a set of mechanical contacts. These contacts can range from a simple switch, push button, or a set of mechanical relay contacts. The issue with monitoring these mechanical interfaces is they tend to be very noisy. Figure 1 shows a typical mechanical contact interface along with the contact closure signal.

Figure 1. Mechanical contact interface with resultant electrical signal

Figure 1 also shows the initial electrical contact followed by a larger “bounce.” This bounce is then followed by a “spray” of impulses also known as the contact chatter . To reliably detect the mechanical contact state, we must find a way to filter the bounce and chatter into a clean and reliable signal.

Hardware solutions
Before we go on to firmware implementations, it would be beneficial to at least mention some of the hardware approaches to solving the contact closure interface problem. All of the hardware implementations usually involve some level of filtration or state machine / counters. There are a few off-the-shelf integrated circuits that work very well for debouncing a mechanical input.

Maxim MAX6818: Originally, it appears as if Maxim created the debounce circuit to interface a manual reset push button for a microcontroller application. The MAX6818 is a bit pricey however. The single quantity cost of this device is around $7.00 and change for eight (8) contact channels. The URL for the MAX6818 debouncer IC is found at the following website:

ON Semiconductor MC14490: ON Semiconductor makes a six (6) channel switch debouncer. This part was originally made by the IC division of Motorola many years ago. I can say from personal experience, this IC works extremely well. It can also be repurposed into a nifty single-bit delay line too. The single unit cost for the MC14490 is around $3.20 or so. This is not bad for a six channel part. The URL for the MC14490 IC is as follows:

Discrete solution
Of course, there is always the discrete hardware solution. The hardware implementation for this interface is shown in Figure 2 .

Figure 2. Debouncing a switch the discrete way

The debouncing filter in this case is an RC network coupled with a Schmitt trigger. The RC time constant for the high-to-low transition is 10 milliseconds. The RC time constant for the low-to-high transition is approximately 13 milliseconds. The single pole filtration along with the Schmitt trigger usually provides an adequate filtration for the contact closure inputs. Of course, the capacitor, or the resistor values can be changed to increase or decrease the time delay and filtration.

The cost of the circuit depicted in Figure 2 is primarily with the discrete components. This is because the cost of a 74CH14 Schmitt trigger is around $0.14. The component cost makes this a very attractive solution for debouncing a contact closure input.

The downside to the discrete solution is the quantity of parts that is required to debounce each channel. For example, if you needed to debounce six channels, you only need one 74HC14 IC. However, you will need six (6) times the discrete parts (resistors, capacitors, and diodes).

Cost summary
Table 1 show the cost summary for each hardware solution. The rough cost per channel for the integrated solution is roughly in the $0.50 to $0.88 per channel range. The rough cost per channel for the discrete solution is approximately in the $0.12 per channel range.

Table 1. Cost and description summary of each of the hardware contact closure interface solutions

The discrete solution is clearly the cost winner here. However, it may not be appropriate when a large number of channels are needed. This is due to the large number of parts that are required to affect the discrete solution.

Bit banging
Hardware interface: Of course, this being the Embedded Round Table, we need to bring up a method for debouncing the interface using some type of firmware technique. The hardware implementation for the software debounce interface is shown in Figure 3 .

Figure 3. Interfacing a contact closure input to a microcontroller

Figure 3 shows a nearly direct interface between the contact closure and the microcontroller port pin. Of course, we have added a 1 kohm buffer resistor and some protection diodes to protect the microcontroller port. One can see that this direct interface requires very few parts and is also very easy on the cost of goods (roughly $0.03 to $0.08 per channel).

It is also important to note that one should not do a direct interface, as shown in Figure 3 to the internal interrupt. The reason for not wiring the direct interface to an interrupt is because we do not know in advance how fast the chatter will be. It can be anywhere from microseconds to milliseconds and is completely unpredictable. It is much better to simply route the contact closure input to a port pin and to sample it using a timer interrupt.

Software schema : The approach for direct filtering the switch input is based on a shift register (single bit delay line) along with a comparator. This schema is shown in Figure 4 .

Figure 4. Software control schema for debouncing a contact closure input

Figure 4 shows the sequence of ones and zeros being monitored via a timer service. This sampled data is then shifted into a shift register and the resulting shift value is compared to a comparator value. If the comparator value is either all ones or all zeros, the output state will change to a 1 or 0 accordingly. The output of the comparator will hold the last state if the compared value does NOT match an all ones or an all-zero sequence.

Software debouncer
Attributes and methods: As always, the first task at hand will beto define the switch debouncer interface file. As usual, this is in theform of a C header file. Here we define the main interface objectalong with the setup and control device control block (DCB). Listing 1 shows the header file for our debounce control interface.

Listing 1. The contact closure controller driver and interface

  struct  dbSwS;                            /* a forward ref of main object */  typedef int   (*DBSW_hwGetF)  (struct dbSwS*);  /* hardware get           */  /**************************************************************************/  /*  The switch debouncer device control block                             */  /**************************************************************************/  typedef struct  dbSw_dcbS  {    int m_id;                               /* some unique handle           */    int m_defaultState;                     /* switch NO / NC state         */    unsigned int m_filtMask;                /* filter / debounce mask       */    DBSW_hwGetF sw_readF;                   /* read the hardware function   */  } dbSw_dcbS;  /**************************************************************************/  /*  The switch debouncer control object                             */  /**************************************************************************/  typedef struct dbSwS  {    dbSw_dcbS     dcb;            /* copy of the DCB that made this         */    unsigned int  m_filter;       /* switch filter word                     */    int           m_state;        /* current switch state                   */  } dbSwS;  #ifdef __cplusplus    extern "C"  {  #endif    void dbSwS_init     (dbSwS *_this, const dbSw_dcbS *dcb);    void dbSwS_update   (dbSwS *_this);    int  dbSwS_hwRead   (dbSwS *_this);    int  dbSwS_stateGet (const dbSwS *_this);  #ifdef __cplusplus    }  #endif

Device control block (DCB)
The interface device control block consists of four (4) fields. Thefollowing is a brief description of each of the fields and how they areused.

int m_id
This is some unique integer identifier. There may be cases where we wishto identify the object by some unique handle. This field represents aplaceholder for the object ID or handle.

int m_defaultState
This is the “normal” state of the contact when it is not activated. Ingeneral, since most contacts are active low, the value for this statewill be logic high or a one (1). There are, however, cases where this isnot so (positive logic) and the inactive state will be a logic low ora zero (0).

unsigned int m_filtMask
This field represents the number of bits within the delay line, or shiftregister. For each bit defined, we need to set a one (1). For example, afour (4) bit shift register will be represented by a filter mask value0xF. An eight bit shift register will be represented by a filter maskvalue of 0xFF.

DBSW_hwGetF sw_readF
This field represents the low-level PORT read function pointer. Weimplement the PORT read interface as a function pointer. This allows usto define the hardware dependent implementation as part of theinitialization process. Doing this allows us to make the driverindependent of hardware implementation.

Debouncer control object
This is the object where the process is affected. There are a total ofthree fields within this object. The following is a briefdescription of the fields within the control object.

dbSw_dcbS dcb
This is the device control block (DCB) where the setup andconfiguration information for the debounce controller is placed. The DCBis described in the previous section.

unsigned int m_filter
This is the main delay line. For a 32-bit processor, this field will be32-bits wide. Therefore, the maximum shifter length will be 32 bits. Theactual size of the shifter is set by the m_filtMask value as describedin the DCB section.

int m_state
This is the current post-filtered state. In general, this state can haveeither a one (1) or zero (0) value. This value represents thepost-filtered, debounced contact state.

Driver methods
There are a total of four methods for the switch debouncer. The driver methods are briefly described in Table 2 .

Table 2. Description of the switch debouncer methods

The debouncer switch update method will, in general, be called by someperiodic timer service. This method is where all of the filtration andthe implementation of the schema take place. Listing 2 shows the implementation of the update filtration schema.

Listing 2. Implementation of the switch debouncer schema

void dbSwS_update (dbSwS *_this){  int my_swResult = dbSwS_hwRead (_this);     /* read the hardware          */  _this->m_filter = _this->m_filter << 1;     /* shift the filter word      */  _this->m_filter |= my_swResult;             /* mask in the hw result      */  my_swResult     = _this->m_filter & _this->dcb.m_filtMask;  if (my_swResult == _this->dcb.m_filtMask)   {    _this->m_state  = 1;  }  else if (0==my_swResult)   {    _this->m_state  = 0;  }}

I have included an application which demonstrates how to use thisdriver. The application is for a four (4) position joystick along withan ENTER pushbutton. The primary interfaces for the application arelisted in the dbSw_mgr.h header file. Listing 3 shows the header file for the primitive joystick application.

#include  "dbSw_driver.h"  /**************************************************************************/  /*  There are five(5) distinct switch states for the joystick.            */  /**************************************************************************/  #define   STICK_F_UP     0x00000001  #define   STICK_F_RIGHT  0x00000002  #define   STICK_F_DOWN   0x00000004  #define   STICK_F_LEFT   0x00000008  #define   STICK_F_ENTER  0x00000010  typedef enum swidE  {    SWID_UP     = 0,    SWID_RIGHT  = 1,    SWID_DOWN   = 2,    SWID_LEFT   = 3,    SWID_ENTER  = 4,    SWID_MAX  } swidE;  /**************************************************************************/  /*  The following is the function type for the application callback. This */  /*  callback is invoked during the following:                             */  /*                                                                        */  /*    1.  Any state change is detected                                    */  /*    2.  Callback function pointer is NOT NULL                           */  /*                                                                        */  /*  The prototype for this function is as follows:                        */  /*                                                                        */  /*    void my_callback_fn (const unsigned curr, const unsigned prev);     */  /*                                                                        */  /*  Where:                                                                */  /*    unsigned curr;    ' current switch state                        '   */  /*    unsigned prev;    ' previous switch state                       '   */  /*                                                                        */  /*  NOTES:                                                                */  /*                                                                        */  /*    The callback function is invoked inside an interrupt context!!!     */  /*                                                                        */  /**************************************************************************/  typedef void (*DBSWMGR_callback)(const unsigned, const unsigned);  /**************************************************************************/  /*  The following is the app-specific db switch controller object         */  /**************************************************************************/  typedef struct dbSwMgr_ctlS  {    unsigned  d_mask;               /* some switch bit mask                 */    dbSwS     m_sw;                 /* a single switch element              */  } dbSwMgr_ctlS;  /**************************************************************************/  /*  The following is the main switch manager / debouncer container        */  /**************************************************************************/  typedef struct dbSwMgrS  {    int m_id;                       /* some unique identifier               */    DBSWMGR_callback fn_callback;   /* app-specific callback function       */    dbSwMgr_ctlS ctl [SWID_MAX];    /* the five(5) switch control elements  */    unsigned int m_state;           /* current bit states                   */  } dbSwMgrS;  #ifdef __cplusplus    extern "C"  {  #endif    dbSwMgrS *dbSwMgr_getInstance     (void);    void      dbSwMgr_init            (DBSWMGR_callback callback_fn);    void      dbSwMgr_updateISR       (void);    unsigned  dbSwMgr_stateGet        (void);    /************************************************************************/    /*  The following must be provided by the developer                     */    /************************************************************************/    void  dbSwMgr_myCallbackFn    (const unsigned curr, const unsigned prev);  #ifdef __cplusplus    }  #endif

In this case, the USER wanted a callback which gets executed upondetection of a successful state change. The person did not mind that thecallback will get called within an interrupt context. This callbackpasses in the previous and current states for the joystick interface.The callback is represented by the fn_callback FIELD in the dbSwMgrS record.

The initialization is affected via the code in the dbSwMgr_init.c file. Opening this file, you will see a macro:

#define DBSW_HIT_MASK 0x0000000F

This macro specifies the length of the shift register to four (4) bits.Of course, you can increase the size of the shift register to any lengthup to and including 32 bits. For example, if you wish to have a six (6)bit delay line, then you should redefine this macro as follows:

#define DBSW_HIT_MASK 0x0000003F

Also, the dbSwMgr_init.c file has the low level PORT readinterfaces stubbed out. You will need to add your code into theseinterfaces to get it to work in your application. Since I have no ideawhat the actual hardware will be, I have stubbed these interfaces out.

So, how do you filter a contact closure input? Do you use a completehardware solution? Do you use a firmware only solution? Or perhaps, yoursolution is a mix of both? I show here the shift register / softwaresingle bit delay line approach. I have seen other approaches that usecounters. The counter approach is equally effective in my opinion.

Ken Wada is president and owner of AuriumTechnologies, an independent product design and consulting firm inCalifornia's Silicon Valley. Ken has over 25 years of experiencearchitecting and designing high-tech products and systems, including theFASTRAK vehicle-sensing system for toll roads and bridges. Hisexpertise includes industrial automation, biotechnology, and high-speedoptical networks. Ken holds four patents. You may reach him at .

2 thoughts on “Bit banging a contact closure input

  1. My team worked on an embedded device in 1986 with a severe bounce problem. We used both a discrete Schmitt trigger like hardware solution and a software solution quite like the one described. I am a programmer and not an electrical engineer but I still pre

    Log in to Reply
  2. I have many clients who do both as you suggest. The combined solution works extremely well.

    I also have clients who do not want to add an extra penny if they can help it … for this, you need some kind of software filter.

    Log in to Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.