RF433 – Raspberry PI GPIO kernel driver for interrupt management

You may have read some of my post about RF433 and Raspberry PI. Basically with RPI 1, I was using wiringPi interrupt handler to manage the RF433 decoding. The problem is that with RPI2 and RPI B+ the delay to take an interrupt that was becomes unpredictable. And the timing constraints are not respected. As a consequence part of the messages are loss because for these delay.

One of the solution (the software one) is to be more efficient to proceed the interrupts and the way to do this is to compile a kernel driver for directly handling the interrupts. This is what this post is about. This comes to complete the RFRPI code and associated hardware. A complete source code and software for using it is on the rfrpi bitbucket repository.

Challenge of the coming days : write a kernel driver to manage interruption quicker on a raspberry pi 2. I’m happy to find a lot of example on Internet and in particular this one, that is really looking like what I’m trying to do. This post is describing all the step needed to do this.

Be prepared to compile a kernel module

From the same source this post also describes well what you have to do to be able to compile a kernel module on a raspberry pi.

The first step for me was to install a fresh new raspbian image on a 8GB card as you need at least 2GB for doing a such operation. The process to reinstall an image is here.

Then you need to configure the Raspbian : expend filesystem, activate ssh, configure keyboard and so on. After the final reboot you can connect to your RPI over ssh and you are ready to start the installation.

Update your distribution and install needed packages

# apt-get update
# apt-get upgrade
# apt-get install bc

Clone the kernel repo :

# git clone http://github.com/raspberrypi/linux rpi-kernel

This is going to take a while, it is about 2Gb… have a coffee and browse my website 😉

Check if your kernel version match the one cloned

# cd /rpi-kernel
# head Makefile
VERSION = 3
PATCHLEVEL = 18
SUBLEVEL = 12
# uname -a
Linux (none) 3.18.7-v7+...

Here the Version and Patch match, we can clone the current configuration to make a new kernel.

# zcat /proc/config.gz > ./.config

Now we can compile a kernel benefiting of parallelism of RPI2

# make -j 4

If in the version of the kernel you clone some configuration have been added, you will be asked for a few question. To make it simple, just hit ENTER every time. This will take a while … a while … a while … but less than 6 hours on a RPI2

# make modules_install

Install kernel and reboot

# cp /boot/kernel7.img /boot/kernel7.old
# cp arch/arm/boot/zImage /boot/kernel7.img
# reboot

Check after reboot kernel version

# uname -a
Linux (none) 3.18.12-v7+ ...

Create a kernel module

Basically, what we want is to attach an interrupt on a specific GPIO and we want to measure the time between two interrupts. This time is reported to a character device : one line = one time.

The module contains an init part run with insmod

static int __init rfrpi_init(void)
{
    int ret = 0;
    printk(KERN_INFO "%s\n", __func__);

    // INITIALIZE IRQ TIME AND Queue Management
    getnstimeofday(&lastIrq_time);
    pRead = 0;
    pWrite = 0;
    wasOverflow = 0;

    // register GPIO PIN in use
    ret = gpio_request_array(signals, ARRAY_SIZE(signals));

    if (ret) {
        printk(KERN_ERR "RFRPI - Unable to request GPIOs for RX Signals: %d\n", ret);
        goto fail2;
    }
    
    // Register IRQ for this GPIO
    ret = gpio_to_irq(signals[0].gpio);
    if(ret < 0) {
        printk(KERN_ERR "RFRPI - Unable to request IRQ: %d\n", ret);
        goto fail2;
    }
    rx_irqs[0] = ret;
    printk(KERN_INFO "RFRPI - Successfully requested RX IRQ # %d\n", rx_irqs[0]);
    ret = request_irq(rx_irqs[0], rx_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_DISABLED, "rfrpi#rx", NULL);
    if(ret) {
        printk(KERN_ERR "RFRPI - Unable to request IRQ: %d\n", ret);
        goto fail3;
    }

    // Register a character device for communication with user space
    misc_register(&rx433_misc_device);

    return 0;

    // cleanup what has been setup so far
fail3:
    free_irq(rx_irqs[0], NULL);

fail2: 
    gpio_free_array(signals, ARRAY_SIZE(signals));
    return ret;    
}

The irq is managing a queue of timing in micro-second between two pin status change

/*
 * The interrupt service routine called on every pin status change
 */
static irqreturn_t rx_isr(int irq, void *data)
{
    struct timespec current_time;
    struct timespec delta;
    unsigned long ns;

    getnstimeofday(&current_time);
    delta = timespec_sub(current_time, lastIrq_time);
    ns = ((long long)delta.tv_sec * 1000000)+(delta.tv_nsec/1000); 
    lastDelta[pWrite] = ns;
    getnstimeofday(&lastIrq_time);

    pWrite = ( pWrite + 1 )  & (BUFFER_SZ-1);
    if (pWrite == pRead) {
        // overflow
        pRead = ( pRead + 1 ) & (BUFFER_SZ-1);
        if ( wasOverflow == 0 ) {
           printk(KERN_ERR "RFRPI - Buffer Overflow - IRQ will be missed");
           wasOverflow = 1;
        }
    } else {
        wasOverflow = 0;
    }
    return IRQ_HANDLED;
}

The character device is declared with an handler for reading that returns the content of the queue:

static ssize_t rx433_read(struct file *file, char __user *buf,
                size_t count, loff_t *pos)
{
    // returns one of the line with the time between two IRQs
    // return 0 : end of reading
    // return >0 : size
    // return -EFAULT : error
    char tmp[256];
    int _count;
    int _error_count;

    _count = 0;
    if ( pRead != pWrite ) {
        sprintf(tmp,"%ld\n",lastDelta[pRead]);
        _count = strlen(tmp);
        _error_count = copy_to_user(buf,tmp,_count+1);
        if ( _error_count != 0 ) {
            printk(KERN_ERR "RFRPI - Error writing to char device");
            return -EFAULT;
        }
        pRead = (pRead + 1) & (BUFFER_SZ-1);
    }
    return _count;
}

static int rx433_open(struct inode *inode, struct file *file)
{
    return nonseekable_open(inode, file);
}

static int rx433_release(struct inode *inode, struct file *file)
{
    return 0;
}

static ssize_t rx433_write(struct file *file, const char __user *buf,
                           size_t count, loff_t *pos)
{
    return -EINVAL;
}

Device is declared in a specific structure :

static struct file_operations rx433_fops = {
    .owner = THIS_MODULE,
    .open = rx433_open,
    .read = rx433_read,
    .write = rx433_write,
    .release = rx433_release,
};

static struct miscdevice rx433_misc_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEV_NAME,
    .fops = &rx433_fops,
};

The full code and Makefile can be downloaded here : https://bitbucket.org/disk_91-admin/rfrpi/src/625129fecef974059a4f8f4353aa31e1ca48b8c4/rfrpi_src/krfrpi/?at=master

Compile your kernel module

Copy the kernel module source code into any directory and create a Makefile :

ifneq (${KERNELRELEASE},)
        obj-m  = krfrpi.o
else
        KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build
        MODULE_DIR := $(shell pwd)

.PHONY: all

all: modules

.PHONY:modules

modules:
        ${MAKE} -C ${KERNEL_DIR} SUBDIRS=${MODULE_DIR}  modules

clean:
        rm -f *.o *.ko *.mod.c .*.o .*.ko .*.mod.c .*.cmd *~
        rm -f Module.symvers Module.markers modules.order
        rm -rf .tmp_versions
endif

Then run the compilation with typing

# make

You can insert the module by typing

# insmod krfrpi.ko

Then check it has been inserted correctly

# lsmod
Module                  Size  Used by
krfrpi                  2586  0

Also with dmesg

# dmesg | tail
[  496.798143] rfrpi_init
[  496.798186] Successfully requested RX IRQ # 260

The char device has been created on the filesystem:

# ls -ld /dev/rfrpi 
crw------- 1 root root 10, 58 déc.   6 10:50 /dev/rfrpi

Now when some Interrupt occurs, you can get timing in micro-second between each of them by running cat on the device :

# cat /dev/rfrpi
8853115
4539218
83
12
8
8
8
4446084

 

Sources of information

This entry was posted in Systems and tagged , , , . Bookmark the permalink.

13 Responses to RF433 – Raspberry PI GPIO kernel driver for interrupt management

Leave a Reply

Your email address will not be published. Required fields are marked *

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