9

I was following this guide, Raspberry Pi GPIO interrupts in kernel space, to create a kernel module handling GPIO interrupt. The problem is that this code doesn't have software debounce implemented.

Can you please give me advice about how software debounce can be implemented, or alternatively, how to easily implement a hardware debouncing system?

Greenonline
  • 2,740
  • 4
  • 23
  • 36
Flavio Barisi
  • 357
  • 1
  • 4
  • 10
  • You could move your code to an answer section (you can answer your own questions). This way it would be much cleaner. Remember, this is not a forum. – Krzysztof Adamski Jul 22 '13 at 21:07

5 Answers5

8

I have never tried to do this no the RPi, but I have on the Arduino platform several times, and I have used something along the lines of this Arduino code. Note you will have to rewrite a function similar to this for whatever language you are using:

unsigned long last_interrupt_time = 0;

void my_interrupt_handler()
{
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 200) 
  {
    ... Interrupt okay, do something;
  }
  last_interrupt_time = interrupt_time;
}
Butters
  • 1,587
  • 8
  • 23
  • ok, i've done it...millis() in not a Raspberry function, so i had to implement it myself. Here is the source code modified – Flavio Barisi Jul 22 '13 at 19:02
  • @FlavioBarisi Sorry about that. This is code for the Arduino, included only as an example. I didn't have time yesterday to rewrite it in Python of C. I will make note of that in the answer. – Butters Jul 23 '13 at 13:34
1

I've done it by checking the jiffies instead of the time. I think there's less overhead this way. However I'm still looking to improve the debouncing algorithm because I'm not fully satisfied with it: there are still bounces although they are less frequent than before.

#include <linux/jiffies.h>

extern unsigned long volatile jiffies;

static irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs) {

   static unsigned long old_jiffie = 0;
   unsigned long diff = jiffies - old_jiffie;
   if (diff < 20)
   {
     return IRQ_HANDLED;
   }

   printk(KERN_NOTICE "Interrupt [%d] for device %s was triggered, jiffies=%lu, diff=%lu, direction: %d \n",
          irq, (char *) dev_id, jiffies, diff, gpio_get_value(GPIO_BUTTON1));

   old_jiffie = jiffies;
   return IRQ_HANDLED;
}

I've managed to further refine the debouncing algorithm. I now trigger the interrupt when the line reaches LOW instead of falling edge. In the interrupt handler I then change the interrupt to trigger on HIGH next time, then in LOW and so on. This combined with the jiffies method from above significantly reduces the bounces.

I think it's the best one can achieve in software. For further improvements I think this has to be done in hardware, indeed. When I have time I'll try with a capacitor and things like this.

The full code bellow:

#include <linux/module.h>   
#include <linux/kernel.h>   
#include <linux/init.h>

#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/jiffies.h>

#define DRIVER_AUTHOR ""
#define DRIVER_DESC   "Button pad"


extern unsigned long volatile jiffies;
struct _Button
{
    unsigned gpio;
    int irq;
    const char* description;
    char last_value;
};
typedef struct _Button Button;

// we want GPIO's 17,27,22 (pin 11,13,15 on P5 pinout raspberry pi rev. 2 board) to generate interrupt
Button button1 = {17, 0, "Gpio for button 1", 1 };
Button button2 = {27, 0, "Gpio for button 2", 1 };
Button button3 = {22, 0, "Gpio for button 3", 1 };

/****************************************************************************/
/* IRQ handler - fired on interrupt                                         */
/****************************************************************************/
static irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs) 
{
   Button* button = (Button*) dev_id; 
   char value = gpio_get_value(button->gpio);

   if (value == button->last_value)
       return IRQ_HANDLED;

   if (0 == value)
   {
        static unsigned long old_jiffie = 0;
        unsigned long diff = jiffies - old_jiffie;
        if (diff < 23)
        {
          button->last_value = value;
          irq_set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH);
          // old_jiffie = jiffies;
          return IRQ_HANDLED;
        }

        printk(KERN_NOTICE "Interrupt [%d] for device %s was triggered, jiffies=%lu, diff=%lu, direction: %d \n",
               irq, button->description, jiffies, diff, value);
        irq_set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH);
        old_jiffie = jiffies;
   }

   else
   {
     irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW);
   }

   button->last_value = value;
   return IRQ_HANDLED;
}
/****************************************************************************/
/* This function configures interrupts.                                     */
/****************************************************************************/
void int_config(Button* button)
{
   if (!button || gpio_request(button->gpio, button->description)) {
      printk("GPIO request faiure: %s\n", button->description);
      return;
   }
   gpio_direction_input(button->gpio);

   if ( (button->irq = gpio_to_irq(button->gpio)) < 0 ) {
      printk("GPIO to IRQ mapping faiure %s\n", button->description);
      return;
   }

   printk(KERN_NOTICE "Mapped int %d\n", button->irq);

   if (request_irq(button->irq,
                   (irq_handler_t ) irq_handler,
                   IRQF_TRIGGER_LOW,
                   button->description,
                   button)) {
      printk("Irq Request failure\n");
      return;
   }
   return;
}

/****************************************************************************/
/* This function releases interrupts.                                       */
/****************************************************************************/
void int_release(Button* button) {

   free_irq(button->irq, button);
   gpio_free(button->gpio);

   return;
}

int init_module(void)
{
    printk(KERN_INFO "init_module() called\n");
    int_config(&button1);
        int_config(&button2);
        int_config(&button3);

    return 0;
}

void cleanup_module(void)
{
    printk(KERN_INFO "cleanup_module() called\n");
    int_release(&button1);
    int_release(&button2);
    int_release(&button3);
}

/****************************************************************************/
/* Module licensing/description block.                                      */
/****************************************************************************/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
MODULE_DESCRIPTION("Driver for RaspeberryPi's GPIO");
Greenonline
  • 2,740
  • 4
  • 23
  • 36
John D
  • 11
  • 1
1

the simplest is the hardware debouncer, that consists of RC filter followed by schmitt trigger, or just RC filter, if you are lazy. parts are cheap, available everywhere and could be easily accomodated in whatever circuitry you're going to connect to your Raspberry Pi.

lenik
  • 11,541
  • 1
  • 30
  • 37
1

Done it!

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/time.h>

#define DRIVER_AUTHOR "Igor <hardware.coder@gmail.com>"
#define DRIVER_DESC   "Tnterrupt Test"

// we want GPIO_17 (pin 11 on P5 pinout raspberry pi rev. 2 board)
// to generate interrupt
#define GPIO_ANY_GPIO                17

// text below will be seen in 'cat /proc/interrupt' command
#define GPIO_ANY_GPIO_DESC           "Some gpio pin description"

// below is optional, used in more complex code, in our case, this could be
// NULL
#define GPIO_ANY_GPIO_DEVICE_DESC    "some_device"


/****************************************************************************/
/* Interrupts variables block                                               */
/****************************************************************************/
short int irq_any_gpio    = 0;

unsigned int last_interrupt_time = 0;
static uint64_t epochMilli;

unsigned int millis (void)
{
  struct timeval tv ;
  uint64_t now ;

  do_gettimeofday(&tv) ;
  now  = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ;

  return (uint32_t)(now - epochMilli) ;
}

/****************************************************************************/
/* IRQ handler - fired on interrupt                                         */
/***************************************************************************/
static irqreturn_t r_irq_handler(int irq, void *dev_id, struct pt_regs *regs) {
   unsigned long flags;
   unsigned int interrupt_time = millis();

   if (interrupt_time - last_interrupt_time < 1000) 
   {
     printk(KERN_NOTICE "Ignored Interrupt!!!!! [%d]%s \n",
          irq, (char *) dev_id);
     return IRQ_HANDLED;
   }
   last_interrupt_time = interrupt_time;

   // disable hard interrupts (remember them in flag 'flags')
   local_irq_save(flags);

   printk(KERN_NOTICE "Interrupt [%d] for device %s was triggered !.\n",
          irq, (char *) dev_id);

   // restore hard interrupts
   local_irq_restore(flags);

   return IRQ_HANDLED;
}




/****************************************************************************/
/* This function configures interrupts.                                     */
/****************************************************************************/
void r_int_config(void) {

  struct timeval tv ;

  do_gettimeofday(&tv) ;
  epochMilli = (uint64_t)tv.tv_sec * (uint64_t)1000    + (uint64_t)(tv.tv_usec / 1000) ;

   if (gpio_request(GPIO_ANY_GPIO, GPIO_ANY_GPIO_DESC)) {
      printk("GPIO request faiure: %s\n", GPIO_ANY_GPIO_DESC);
      return;
   }

   if ( (irq_any_gpio = gpio_to_irq(GPIO_ANY_GPIO)) < 0 ) {
      printk("GPIO to IRQ mapping faiure %s\n", GPIO_ANY_GPIO_DESC);
      return;
   }

   printk(KERN_NOTICE "Mapped int %d\n", irq_any_gpio);

   if (request_irq(irq_any_gpio,
                   (irq_handler_t ) r_irq_handler,
                   IRQF_TRIGGER_FALLING,
                   GPIO_ANY_GPIO_DESC,
                   GPIO_ANY_GPIO_DEVICE_DESC)) {
      printk("Irq Request failure\n");
      return;
   }

   return;
}


/****************************************************************************/
/* This function releases interrupts.                                       */
/****************************************************************************/
void r_int_release(void) {

   free_irq(irq_any_gpio, GPIO_ANY_GPIO_DEVICE_DESC);
   gpio_free(GPIO_ANY_GPIO);

   return;
}


/****************************************************************************/
/* Module init / cleanup block.                                             */
/****************************************************************************/
int r_init(void) {

   printk(KERN_NOTICE "Hello !\n");
   r_int_config();

   return 0;
}

void r_cleanup(void) {
   printk(KERN_NOTICE "Goodbye\n");
   r_int_release();

   return;
}


module_init(r_init);
module_exit(r_cleanup);


/****************************************************************************/
/* Module licensing/description block.                                      */
/****************************************************************************/
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
Flavio Barisi
  • 357
  • 1
  • 4
  • 10
1

What if you monitor the previous value/condition of the pin/GPIO?

I had to use usleep() in the while loop to avoid CPU thrashing/racing, See this answer to C, error of compilation during using “usleep” function.

This works for my purpose:

#include <unistd.h>
uint8_t prevStatus;

void sendStatus (void) {
        uint8_t status = digitalRead(ISR_Pin);
        if (prevStatus != status) {
                if (status == 0)
                         falling();
                else
                         rising();
        }
        prevStatus = status;
}


int main (void) {
    /// Make sure WiringPi is installed and cofigured
    if ( wiringPiSetup() == -1 )
        exit( 1 );

    /// Set-up GPIO as INPUT, with Pull-down
    pinMode (ISR_Pin, INPUT);
    pullUpDnControl(TISR_Pin, PUD_DOWN); // If you need it or PUD_UP for Pull-up

    /// Set-up Hardware Interrupt for BOTH Falling & Rising Edge
    wiringPiISR(ISR_Pin, INT_EDGE_BOTH, &sendStatus);

    /// Wait for ISR_Pin pin change
    while (1) {
         usleep(50000);
    }

    return(0);
}
Greenonline
  • 2,740
  • 4
  • 23
  • 36
electron1979
  • 121
  • 11