Rotary Switch IRQ Kernel Module


The code is working as expected and it reads the switches reliably with no debounce problems.
The code needs quite a bit of tidying up, creating the open and release functions and maybe an ioctl or two to interact with it.
It might also be a good idea to allow the user to set a starting point (ioctl?) and maybe even and end point and then ensuring that it won’t wrap outside these values – useful for menu selection and such like.


This needs to be built as a module (i.e with access to the kernel headers).

When the module is running it will print out in the dmesg buffer a line saying what the module saw on each interrupt and the new vale of a counter. The counter will also be available as a single character in the file /dev/rotarySwicthDev.for user space programs to access it. Note that this counter wraps around from 0 to 255 (backwards turn) and 255 back to 0 (forwards turn) so user space will need to keep track of where it is relative to it’s starting point.

Also note that the pinouts are for a old RPI 1. May need adjusting for a RPI2 or 3.

The code is good for fairly fast turns (10mS debounce = 100Hz steps) and shouldn’t lose steps at this rate even if it gets extra interrupts. It works reasonably reliably without the RC filter but it is included in the HW for extra stability.

WordPress seems to have joined the rest of the HTML community in it’s inability to create a code displayer without destroying the formatting, so if you want a decently formatted version of this code then contact me

Circuit Diagram



Once built, you need to move the module file (roatary-sw-irq.ko) to the RPI (if it is not already there) and insert it with:

sudo insmod rotary-sw-irq.ko

You can remove it with

sudo rmmod rotary-sw-irq

And see what the message output is via


Source code of the Module

#include 	<linux/module.h>
#include 	<linux/delay.h>
#include 	<linux/kernel.h>
#include 	<linux/init.h>
#include 	<linux/irq.h>
#include 	<linux/interrupt.h>
#include 	<linux/gpio.h>
#include 	<linux/fs.h>
#include 	<linux/time.h>
#include 	<linux/device.h>
#include <asm/uaccess.h>
#include "rotary_sw_irq.h"

#define DEVICE_NAME "rotarySwitchDev" /* this is the device name i.e /dev/rotartSwicthDev */
#define CLASS_NAME "rotarySwitch" /* the name of the device class */

int irq_a;
int irq_b;
int posn=71;
static int major;
static char msg[3];
static struct class* rotarySwitchClass = NULL;
static struct device* rotarySwitchDevice = NULL;

static ssize_t device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
return simple_read_from_buffer(buffer, length, offset, msg, 3);

static ssize_t device_write(struct file *filp, const char __user *buff, size_t len, loff_t *off)
if (len > 2)
return -EINVAL;
copy_from_user(msg, buff, len);
msg[len] = '\0';
return len;

static struct file_operations fops = {
.read = device_read,
.write = device_write,

void process(void)
int enc_states[] = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};
static int old_AB = 0;
int a,b;

old_AB <<= 2;
old_AB += (a<<1)+b;
posn+= enc_states[( old_AB & 0x0f )];
if (posn<0) posn++;
if (posn==256) posn--;
printk(KERN_ERR "A and B are: %i %i, posn=%i\n",a,b,posn);

static irqreturn_t gpio_a_interrupt(int irq, void* dev_id)

static irqreturn_t gpio_b_interrupt(int irq, void* dev_id)

static int __init mymodule_init(void)
printk (KERN_ERR "Rotary switch module loading\n");

major = register_chrdev(0, "Rotary_Switch", &fops);
if (major < 0)
printk (KERN_ERR "Roraty Switch: Registering the character device failed with %d\n", major);
return major;
printk(KERN_ERR "Rotary_switch: assigned major: %d\n", major);

/* Register the device class */
rotarySwitchClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(rotarySwitchClass))
{ // Check for error and clean up if there is
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ERR "Rotary Switch: Failed to register device class\n");
return PTR_ERR(rotarySwitchClass);
printk(KERN_INFO "Roatry Switch: device class registered correctly\n");

/* Register the device driver */
rotarySwitchDevice = device_create(rotarySwitchClass, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR(rotarySwitchDevice))
{ // Clean up if there is an error
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ERR "Rotary Switch: Failed to create the device\n");
return PTR_ERR(rotarySwitchDevice);
printk(KERN_ERR "Rotary Switch: device created correctly\n");

if (gpio_request(ROTARY_SWITCH_A_GPIO,"rotary switch A"))
printk(KERN_ERR "Rotary Switch: can't request GPIO A");
return (-EIO);
if (gpio_request(ROTARY_SWITCH_B_GPIO,"rotary switch B"))
printk(KERN_ERR "Rotary Switch: can't request GPIO B");
return (-EIO);


irq_a = gpio_to_irq(ROTARY_SWITCH_A_GPIO);
irq_b = gpio_to_irq(ROTARY_SWITCH_B_GPIO);

if ( request_irq(irq_a, gpio_a_interrupt,
"gpio_a", NULL) )
printk(KERN_ERR "Rotary Switch: trouble requesting IRQ A %d",irq_a);
} else
printk(KERN_ERR "Rotary_switch: requested IRQ A%d\n",irq_a);

if ( request_irq(irq_b, gpio_b_interrupt,
"gpio_b", NULL) )
printk(KERN_ERR "Rotary Switch: trouble requesting IRQ B%d",irq_b);
} else
printk(KERN_ERR "Rotary_switch: requested IRQ B%d\n",irq_b);

return 0;

static void __exit mymodule_exit(void) {

free_irq(irq_a, NULL);
free_irq(irq_b, NULL);
device_destroy(rotarySwitchClass,MKDEV(major, 0));
unregister_chrdev(major, "Rotary_switch");

printk ("Rotary switch module unloaded\n");



Header File: rotary-sw-irq.h




# obj-m is a list of what kernel modules to build. The .o and other
# objects will be automatically built from the corresponding .c file -
# no need to list the source files explicitly.

obj-m := rotary_sw_irq.o 

# KDIR is the location of the kernel source. The current standard is
# to link to the associated source tree from the directory containing
# the compiled modules.
KDIR := ~/pi/linux/

# PWD is the current working directory and the location of our module
# source files.
PWD := $(shell pwd)

# default is the default make target. The rule here says to run make
# with a working directory of the directory containing the kernel
# source and compile only the modules in the PWD (local) directory.
 $(MAKE) -C $(KDIR) M=$(PWD) -j6 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

%d bloggers like this: