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

  1. ben says:

    Seems a pretty solid walk-through.
    But I have to ask why? Whats the end goal?

  2. Frank says:

    Great.Fantastic.
    The kernel and the driver can be compiled in a standard pc using cross-compiling. The process take me only 20 mins.

  3. CHARLES says:

    Hi,

    I use raspberry 2 with debian-jessie.
    But I didn’ t found /proc/config.gz

    The file is still supplied ?

    Regards.

  4. Vincent says:

    Hello there,

    Thanx for this great tutorial !
    I can’t compile the krfrpi module, here is the error i get when i “make” :
    /home/pi/krfrpi/krfrpi.c: In function ‘rfrpi_init’:
    /home/pi/krfrpi/krfrpi.c:170:85: error: ‘IRQF_DISABLED’ undeclared (first use in this function)

    Any idea ? I’m on RPI2. Thank you.

    • Juan Souto Prieto says:

      Change the line 170 of krfrpi.c for:
      ret = request_irq(rx_irqs[0], rx_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, “rfrpi#rx”, NULL);

      because IRQF_DISABLED is deprecated and it’s not used any more.

  5. Nagius says:

    First of all, thanks for sharing your amazing work, that was a great help !

    I did some tests with both versions, the first version using soft irq and the second using the kernel module. Both are working but have different sensitivity to noise. For example, when I moved slightly the antenna it stopped receiving data with the kernel module version and when I put it back in place both version worked again but with a crappy reception range (10m with no missed data) but as soon as I moved the arduino beside the raspberri pi, the range got bad again (<2m).
    Note that I've got better range result while powering the receiver with 3.3v instead of 5v (better meaning ~5m). I tried different antennas (1/4 wave, Helical, straight coil…) with no significant differences.

    I believe the raspberri py (or it's power supply and all the wiring around it) is creating lots of noise messing with the data decoding.
    Did you encounter these kind of issues ?

    • Paul says:

      Hello, I did not so much once using the right receiver and mounting it on a proper PCB. For sure the RPI is generating noise and having an external antenna and a box around the RPI helps. The RPI frequency is also important if it run at 890MHz or around you may have more noise at 433 MHZ by the way any harmonical frequency may generate a larger noise.

  6. Srishti Saha says:

    It compiled but I am wondering to know that, which GPIO pin should I use!! Thank in advance

    • Paul says:

      Search for the other post about RF433, there are some with schematics. I’ve written this post a long time ago so I don’t have the answer in mind but you may find it that way.

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.