Gérer les GPIO du Raspberry Pi avec RTDM

Publié par cpb
Jan 26 2013

Xenomai Raspberry-Pi GPIOPlusieurs personnes m’ont demandé récemment si l’utilisation des GPIO du Raspberry Pi étaient possible en employant un driver RTDM pour Xenomai. Si le traitement des entrées et sorties est effectivement simple, l’utilisation des interruptions pose encore quelques soucis.

Gérer les broches GPIO du Raspberry Pi avec le kernel Linux standard est simple, nous l’avons vu dans cet article.

L’adaptation sur un noyau plus orienté “temps réel” avec le patch PREEMPT_RT est possible. Je vous conseille cet article de Pierre Ficheux sur son blog.

On peut également employer l’API RTDM (Real Time Driver Model) de Xenomai pour piloter simplement les broches en entrées ou sortie.

Entrées-sorties simples avec RTDM

Voici à titre d’exemple un petit programme qui sort sur la broche 15 du port d’extension P1 un signal carré dont la période est configurable sur la ligne de commande de chargement du module.

oscillateur-gpio-rtdm.c :
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/version.h>

#include <asm/uaccess.h>

#include <rtdm/rtdm_driver.h>

static int periode_us = 1000;
module_param(periode_us, int, 0644);

static void timer_oscillateur(rtdm_timer_t *);
static rtdm_timer_t rtimer;

// Broche 15 du port P1 du Raspberry Pi : GPIO 22
#define GPIO_OSCILLATEUR 22

static int __init init_oscillateur (void)
{
    int err;

    if ((err = gpio_request(GPIO_OSCILLATEUR, THIS_MODULE->name)) != 0) {
        return err;
    }
    if ((err = gpio_direction_output(GPIO_OSCILLATEUR, 1)) != 0) {
        gpio_free(GPIO_OSCILLATEUR);
        return err;
    }

    if ((err = rtdm_timer_init(& rtimer, timer_oscillateur, "Oscillateur")) != 0) {
        gpio_free(GPIO_OSCILLATEUR);
        return err;
    }

    if ((err = rtdm_timer_start(& rtimer, periode_us*1000, periode_us*1000, RTDM_TIMERMODE_RELATIVE)) != 0) {
        rtdm_timer_destroy(& rtimer);
        gpio_free(GPIO_OSCILLATEUR);
        return err;
    }
    return 0; 
}

static void __exit exit_oscillateur (void)
{
    rtdm_timer_stop(& rtimer);
    rtdm_timer_destroy(& rtimer);
    gpio_free(GPIO_OSCILLATEUR);
}

static void timer_oscillateur(rtdm_timer_t * unused)
{
    static int value = 0;
    gpio_set_value(GPIO_OSCILLATEUR, value);
    value = 1 - value;
}

module_init(init_oscillateur);
module_exit(exit_oscillateur);
MODULE_LICENSE("GPL");

Ce module doit bien sûr être compilé et chargé dans un noyau modifié pour incorporer Xenomai comme nous l’avons vu dans cet article. Pour être exact, je l’ai installé en employant Xenomai-2.6.2 plutôt que la version 2.6.1 décrite dans l’article précédent.

Voici par exemple le chargement en précisant une période de 50 micro-secondes.

root@R-Pi [/root]# insmod /root/oscillateur-gpio-rtdm.ko periode_us=50
root@R-Pi [/root]#

Aussitôt nous voyons le signal suivant sur la sortie 15 du Raspberry Pi.
Sortie GPIO du Raspberry-Ri

Le timer a bien une période de 50 micro-secondes, le signal de sortie nécessitant deux déclenchements du timer pour effectuer un cycle complet, sa période est 100 microsecondes. Les fluctuations visibles sont très faibles, bien inférieures à 10 microsecondes (mais mon système n’est pas très chargé).

Interruptions GPIO avec RTDM

Tout d’abord, précisons tout de suite que cette partie est encore en cours de travail, j’espère pouvoir progresser dessus très prochainement.

Pour pouvoir gérer les interruptions du Raspberry Pi avec RTDM, il est nécessaire de modifier un fichier du noyau Linux avant compilation. Ceci sera prochainement intégré dans le patch Adeos (qui intègre le support Xenomai dans le kernel Linux), mais pendant quelques temps encore, il faut faire la modification manuellement.

Appliquez le patch suivant sur le noyau (précédement préparé pour Xenomai) :

--- a/arch/arm/mach-bcm2708/bcm2708_gpio.c	2013-01-20 08:57:35.114605274 +0100
+++ b/arch/arm/mach-bcm2708/bcm2708_gpio.c	2013-01-20 09:14:51.362655426 +0100
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/list.h>
 #include <linux/io.h>
+#include <linux/ipipe.h>
 #include <linux/irq.h>
 #include <linux/interrupt.h>
 #include <linux/slab.h>
@@ -218,7 +219,7 @@
 		edsr=readl(__io_address(GPIO_BASE)+GPIOEDS(bank));
 		for_each_set_bit(i, &edsr, 32) {
 			gpio=i+bank*32;
-			generic_handle_irq(gpio_to_irq(gpio));
+			ipipe_handle_demuxed_irq(gpio_to_irq(gpio));
 		}
 		writel(0xffffffff,  __io_address(GPIO_BASE)+GPIOEDS(bank));
 	}

Comme vous le voyez, outre l’ajout du fichier d’en-tête ipipe.h, ce patch remplace l’appel à la fonction generic_handle_irq() par ipipe_handle_demuxed_irq().

Exemple de driver utilisant les GPIO en interruption

Voici un petit module qui commute l’état de la broche 15 à chaque fois qu’un front montant se présente sur la broche 16.

irq-gpio-rtdm.c :
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>

#include <rtdm/rtdm_driver.h>

// GPIO IN 23 -> Broche 16
#define GPIO_IN  23
// GPIO OUT 22 -> Broche 15
#define GPIO_OUT 22

static rtdm_irq_t irq_rtdm;

static int handler_interruption(rtdm_irq_t * irq)
{
    static int value = 0;
    gpio_set_value(GPIO_OUT, value);
    value = 1 - value;
    return RTDM_IRQ_HANDLED;
}

    static int numero_interruption; 

static int __init exemple_init (void)
{
    int err;

    numero_interruption = gpio_to_irq(GPIO_IN);

    if ((err = gpio_request(GPIO_IN, THIS_MODULE->name)) != 0) {
        return err;
    }
    if ((err = gpio_direction_input(GPIO_IN)) != 0) {
        gpio_free(GPIO_IN);
        return err;
    }
    if ((err = gpio_request(GPIO_OUT, THIS_MODULE->name)) != 0) {
        gpio_free(GPIO_IN);
        return err;
    }
    if ((err = gpio_direction_output(GPIO_OUT, 1)) != 0) {
        gpio_free(GPIO_OUT);
        gpio_free(GPIO_IN);
        return err;
    }

    irq_set_irq_type(numero_interruption,  IRQF_TRIGGER_RISING);

    if ((err = rtdm_irq_request(& irq_rtdm, 
                     numero_interruption, handler_interruption, 
                     RTDM_IRQTYPE_EDGE,
                     THIS_MODULE->name, NULL)) != 0) {
        gpio_free(GPIO_OUT);
        gpio_free(GPIO_IN);
        return err;
    }

    rtdm_irq_enable(& irq_rtdm);

    return 0; 
}

static void __exit exemple_exit (void)
{
    rtdm_irq_disable(& irq_rtdm);
    rtdm_irq_free(& irq_rtdm);
    gpio_free(GPIO_OUT);
    gpio_free(GPIO_IN);
}

module_init(exemple_init);
module_exit(exemple_exit);
MODULE_LICENSE("GPL");

Résultats

Ce module fonctionne, mais les performances ne sont pas satisfaisantes. Le temps de réponse est de plusieurs dizaines de micro-secondes, avec des fluctuations allant jusqu’à plus de 100 micro-secondes. Pour mémoire, le même traitement réalisé par le noyau Linux Vanilla prend environ 7 micro-secondes.

Je vais procéder à de nouveaux essais ce week-end. Plusieurs pistes ont été suggérées sur la mailing-list de Xenomai et je ne manquerai pas de poster les évolutions prochaines.

3 Réponses

  1. Antoine dit :

    Super intéressant!

  2. Pierrot dit :

    Super ! Merci Christophe.
    Je suis avec vif intérêt les suggestions sur la mailing-list de Xenomai.

  3. Antoine dit :

    Je me demandais si vous aviez l’intention de proposer l’extension Xenomai à l’équipe du Raspberry Pi pour un support officiel. La board manque cruellement d’un RTOS officiel.
    Bon après, c’est sûr ca doit être beaucoup de travail…

URL de trackback pour cette page