{"id":3958,"date":"2014-01-22T22:00:02","date_gmt":"2014-01-22T21:00:02","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=3958"},"modified":"2014-01-23T07:20:53","modified_gmt":"2014-01-23T06:20:53","slug":"gpio-du-raspberry-pi-mesure-de-frequence","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2014\/01\/22\/gpio-du-raspberry-pi-mesure-de-frequence\/","title":{"rendered":"GPIO du Raspberry Pi&nbsp;: mesure de fr\u00e9quence"},"content":{"rendered":"<p style=\"text-align: justify;\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-full wp-image-3965\" alt=\"Raspberry Pi GPIO frequency\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-00.jpg\" width=\"150\" height=\"113\" \/>Un lecteur m&rsquo;a interrog\u00e9 par mail pour savoir comment mesurer la fr\u00e9quence d&rsquo;un signal re\u00e7u en entr\u00e9e sur une broche GPIO du Raspberry Pi. Je lui ai r\u00e9pondu que le plus simple est de mesurer le temps s&rsquo;\u00e9coulant entre deux interruptions successives d\u00e9clench\u00e9es par des fronts montants et de calculer l&rsquo;inverse. J&rsquo;ai voulu v\u00e9rifier que cela fonctionnait, et ai \u00e9crit un petit driver pour ce faire.<\/p>\n<p>\n<!--more-->\n<\/p>\n<h1>Principe<\/h1>\n<p style=\"text-align: justify;\">Le module noyau, nomm\u00e9 <code>gpio-freq.ko<\/code> fonctionne de mani\u00e8re un peu particuli\u00e8re&nbsp;: on doit lui indiquer sur sa ligne de commande la liste des GPIO dont on souhaite mesurer la fr\u00e9quence dans un param\u00e8tres nomm\u00e9 <code>gpios<\/code>. Par exemple<\/p>\n<pre># <strong>insmod gpio-freq.ko gpios=16,22,23,17<\/strong><\/pre>\n<p style=\"text-align: justify;\">permettra de mesurer les fr\u00e9quences des quatre entr\u00e9es indiqu\u00e9es.<\/p>\n<p style=\"text-align: justify;\">Le driver installe un handler sur chaque ligne d&rsquo;interruption concern\u00e9e et mesure \u00e0 chaque front montant le temps \u00e9coul\u00e9 depuis le dernier d\u00e9clenchement du m\u00eame signal. Les dur\u00e9es sont obtenues en microsecondes. Le calcul de la fr\u00e9quence en Hz se fera donc en divisant 1.000.000 par la dur\u00e9e mesur\u00e9e.<\/p>\n<p style=\"text-align: justify;\">Pour rendre accessibles les fr\u00e9quences calcul\u00e9es, le driver fournit des fichiers sp\u00e9ciaux <code>\/dev\/gpiofreq<\/code>NN correspondant aux diff\u00e9rents GPIO demand\u00e9s (<code>\/dev\/gpiofreq0<\/code> \u00e9tant le premier de la liste, <code>\/dev\/gpiofreq1<\/code> le suivant, et ainsi de suite).<\/p>\n<h1>Impl\u00e9mentation<\/h1>\n<p style=\"text-align: justify;\">Voici le code du driver (que l&rsquo;on peut <a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-01-22\/gpio-freq.tar.bz2\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-01-22\/gpio-freq.tar.bz2\">t\u00e9l\u00e9charger ici<\/a> accompagn\u00e9 d&rsquo;un fichier Makefile \u00e0 adapter en fonction de l&#8217;emplacement de votre cha\u00eene de compilation).<\/p>\n<pre><strong>gpio-freq.c:<\/strong>\n\/***************************************************************************\\\n\n Raspberry Pi GPIO frequency measurement.\n\n Copyright (c) 2014 Christophe Blaess\n\n This program is free software; you can redistribute it and\/or modify it\n under the terms of the GNU General Public License version 2 as published by\n the Free Software Foundation.\n\n\\***************************************************************************\/\n\n#include &lt;linux\/cdev.h&gt;\n#include &lt;linux\/device.h&gt;\n#include &lt;linux\/fs.h&gt;\n#include &lt;linux\/gpio.h&gt;\n#include &lt;linux\/interrupt.h&gt;\n#include &lt;linux\/module.h&gt;\n#include &lt;linux\/sched.h&gt;\n#include &lt;linux\/slab.h&gt;\n#include &lt;linux\/spinlock.h&gt;\n#include &lt;linux\/version.h&gt;\n\n#include &lt;asm\/uaccess.h&gt;\n\n\/\/ ------------------ Default values ----------------------------------------\n\n#define GPIO_FREQ_CLASS_NAME             \"gpio-freq\"\n#define GPIO_FREQ_ENTRIES_NAME           \"gpiofreq%d\"\n#define GPIO_FREQ_NB_ENTRIES_MAX     17    \/\/ GPIO on R-Pi P1 header.\n\n\/\/------------------- Module parameters -------------------------------------\n\n    static int gpio_freq_table[GPIO_FREQ_NB_ENTRIES_MAX];\n    static int gpio_freq_nb_gpios;\n    module_param_array_named(gpios, gpio_freq_table, int, &amp; gpio_freq_nb_gpios, 0644);\n\n\/\/ ------------------ Driver private data type ------------------------------\n\nstruct gpio_freq_data {\n    struct timeval last_timestamp;\n    int            frequency;\n    spinlock_t     spinlock;\n};\n\n\/\/ ------------------ Driver private methods -------------------------------\n\nstatic irqreturn_t gpio_freq_handler(int irq, void * filp);\n\nstatic int <strong>gpio_freq_open<\/strong> (struct inode * ind, struct file * filp)\n{\n    int err;\n    int gpio;\n    struct gpio_freq_data * data;\n\n    data = kzalloc(sizeof(struct gpio_freq_data), GFP_KERNEL);\n    if (data == NULL)\n        return -ENOMEM;\n\n    spin_lock_init(&amp; (data-&gt;spinlock));\n\n    gpio = iminor(ind);\n    err = gpio_request(gpio_freq_table[gpio], THIS_MODULE-&gt;name);\n    if (err != 0) {\n        printk(KERN_ERR \"%s: unable to reserve GPIO %d\\n\", THIS_MODULE-&gt;name, gpio_freq_table[gpio]);\n        kfree(data);\n        return err;\n    }\n\n    err = gpio_direction_input(gpio_freq_table[gpio]);\n    if (err != 0) {\n        printk(KERN_ERR \"%s: unable to set GPIO %d as input\\n\", THIS_MODULE-&gt;name, gpio_freq_table[gpio]);\n        gpio_free(gpio_freq_table[gpio]);\n        kfree(data);\n        return err;\n    }\n\n    err = request_irq(gpio_to_irq(gpio_freq_table[gpio]), gpio_freq_handler,\n                      IRQF_SHARED | IRQF_TRIGGER_RISING,\n                      THIS_MODULE-&gt;name, filp);\n    if (err != 0) {\n        printk(KERN_ERR \"%s: unable to handle GPIO %d IRQ\\n\", THIS_MODULE-&gt;name, gpio_freq_table[gpio]);\n        gpio_free(gpio_freq_table[gpio]);\n        kfree(data);\n        return err;\n    }\n\n    filp-&gt;private_data = data;\n    return 0;\n}\n\nstatic int <strong>gpio_freq_release<\/strong> (struct inode * ind,  struct file * filp)\n{\n    int gpio = iminor(ind);\n\n    free_irq(gpio_to_irq(gpio_freq_table[gpio]), filp);\n\n    gpio_free(gpio_freq_table[gpio]);\n\n    kfree(filp-&gt;private_data);\n\n    return 0;\n}\n\nstatic int <strong>gpio_freq_read<\/strong>(struct file * filp, char * buffer, size_t length, loff_t * offset)\n{\n    int lg;\n    int err;\n    char * kbuffer;\n    unsigned long irqmsk;\n    struct gpio_freq_data * data = filp-&gt;private_data;\n\n    kbuffer = kmalloc(128, GFP_KERNEL);\n    if (kbuffer == NULL)\n        return -ENOMEM;\n\n    spin_lock_irqsave(&amp; (data-&gt;spinlock), irqmsk);\n    snprintf(kbuffer, 128, \"%d\\n\", data-&gt;frequency);\n    spin_unlock_irqrestore(&amp; (data-&gt;spinlock), irqmsk);\n    lg = strlen(kbuffer);\n    if (lg &gt; length)\n        lg = length;\n\n    err = copy_to_user(buffer, kbuffer, lg);\n\n    kfree(kbuffer);\n\n    if (err != 0)\n        return -EFAULT;\n    return lg;\n}\n\nstatic irqreturn_t <strong>gpio_freq_handler<\/strong>(int irq, void * arg)\n{\n    struct gpio_freq_data * data;\n    struct timeval timestamp;\n    struct file * filp = (struct file *) arg;\n    long int period;\n\n    do_gettimeofday(&amp; timestamp);\n\n    if (filp == NULL)\n        return -IRQ_NONE;\n\n    data = filp-&gt;private_data;\n    if (data == NULL)\n        return IRQ_NONE;\n\n    if ((data-&gt;last_timestamp.tv_sec  != 0)\n     || (data-&gt;last_timestamp.tv_usec != 0)) {\n        period  = timestamp.tv_sec - data-&gt;last_timestamp.tv_sec;\n        period *= 1000000;  \/\/ In microsec.\n        period += timestamp.tv_usec - data-&gt;last_timestamp.tv_usec;\n        spin_lock(&amp;(data-&gt;spinlock));\n        if (period &gt; 0)\n            data-&gt;frequency = 1000000 \/ period;\n        else\n            data-&gt;frequency = 0;\n        spin_unlock(&amp;(data-&gt;spinlock));\n    }\n\n    data-&gt;last_timestamp = timestamp;\n\n    return IRQ_HANDLED;\n}\n\n\/\/ ------------------ Driver private global data ----------------------------\n\nstatic struct file_operations gpio_freq_fops = {\n    .owner   =  THIS_MODULE,\n    .open    =  gpio_freq_open,\n    .release =  gpio_freq_release,\n    .read    =  gpio_freq_read,\n};\n\n    static dev_t          gpio_freq_dev;\n    static struct cdev    gpio_freq_cdev;\n    static struct class * gpio_freq_class = NULL;\n\n\/\/ ------------------ Driver init and exit methods --------------------------\n\nstatic int __init <strong>gpio_freq_init<\/strong> (void)\n{\n    int err;\n    int i;\n\n    if (gpio_freq_nb_gpios &lt; 1) {\n        printk(KERN_ERR \"%s: I need at least one GPIO input\\n\", THIS_MODULE-&gt;name);\n        return -EINVAL;\n    }\n\n    err = alloc_chrdev_region(&amp; gpio_freq_dev, 0, gpio_freq_nb_gpios, THIS_MODULE-&gt;name);\n    if (err != 0)\n        return err;\n\n    gpio_freq_class = class_create(THIS_MODULE, GPIO_FREQ_CLASS_NAME);\n    if (IS_ERR(gpio_freq_class)) {\n        unregister_chrdev_region(gpio_freq_dev, gpio_freq_nb_gpios);\n        return -EINVAL;\n    }\n\n    for (i = 0; i &gt; gpio_freq_nb_gpios; i ++) \n        device_create(gpio_freq_class, NULL, MKDEV(MAJOR(gpio_freq_dev), i), NULL, GPIO_FREQ_ENTRIES_NAME, i);\n\n    cdev_init(&amp; gpio_freq_cdev, &amp; gpio_freq_fops);\n\n    err = cdev_add(&amp; (gpio_freq_cdev), gpio_freq_dev, gpio_freq_nb_gpios);\n    if (err != 0) {\n        for (i = 0; i &lt; gpio_freq_nb_gpios; i ++) \n            device_destroy(gpio_freq_class, MKDEV(MAJOR(gpio_freq_dev), i));\n        class_destroy(gpio_freq_class);\n        unregister_chrdev_region(gpio_freq_dev, gpio_freq_nb_gpios);\n        return err;\n    }\n\n    return 0; \n}\n\nvoid __exit <strong>gpio_freq_exit<\/strong> (void)\n{\n    int i;\n\n    cdev_del (&amp; gpio_freq_cdev);\n\n    for (i = 0; i &lt; gpio_freq_nb_gpios; i ++) \n        device_destroy(gpio_freq_class, MKDEV(MAJOR(gpio_freq_dev), i));\n\n    class_destroy(gpio_freq_class);\n    gpio_freq_class = NULL;\n\n    unregister_chrdev_region(gpio_freq_dev, gpio_freq_nb_gpios);\n}\n\nmodule_init(gpio_freq_init);\nmodule_exit(gpio_freq_exit);\n\nMODULE_LICENSE(\"GPL\");\nMODULE_AUTHOR(\"christophe.blaess@logilin.fr\");<\/pre>\n<h1>Essais<\/h1>\n<p style=\"text-align: justify;\">Pour tester le module, j&rsquo;ai choisi de lui faire mesurer la fr\u00e9quence en entr\u00e9e sur le GPIO 22 (choix purement arbitraire, c&rsquo;est la date du jour) qui se trouve sur la broche 15 du <a title=\"Connecteur P1 du Raspberry Pi\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2012-10-27\/Connecteur-P1.pdf\" target=\"_blank\">connecteur P1<\/a>.<\/p>\n<p style=\"text-align: justify;\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-3966\" alt=\"Raspberry Pi GPIO frequency\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-01.jpg\" width=\"640\" height=\"480\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-01.jpg 640w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-01-300x225.jpg 300w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/p>\n<p style=\"text-align: justify;\">J&rsquo;ai reli\u00e9 cette broche \u00e0 la sortie d&rsquo;un g\u00e9n\u00e9rateur basse fr\u00e9quence, ainsi qu&rsquo;\u00e0 l&rsquo;entr\u00e9e d&rsquo;un oscilloscope afin de v\u00e9rifier les r\u00e9sultats.<\/p>\n<p style=\"text-align: justify;\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-3967\" alt=\"Raspberry Pi GPIO frequency\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-02.jpg\" width=\"480\" height=\"358\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-02.jpg 480w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-02-300x223.jpg 300w\" sizes=\"auto, (max-width: 480px) 100vw, 480px\" \/><\/p>\n<p style=\"text-align: justify;\">Bien entendu la sortie du g\u00e9n\u00e9rateur de signaux est r\u00e9gl\u00e9e pour fournir un signal en 0 et +3.3V (la limite acceptable par les entr\u00e9es GPIO du Raspberry Pi).<\/p>\n<pre># <strong>insmod gpio-freq.ko gpios=22<\/strong>\n# <strong>cat \/dev\/gpiofreq0<\/strong>\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n1092\n[...]\n1092\n1092\n1092\n1094\n1094\n1094\n1094\n[...]\n1094\n1094\n1094\n1090\n1090\n1090\n1090\n1090\n1092\n1092\n[...]<\/pre>\n<p style=\"text-align: justify;\">La valeur est coh\u00e9rente avec l&rsquo;affichage de l&rsquo;oscilloscope. V\u00e9rifions en utilisant la fonction \u00ab\u00a0fr\u00e9quence-m\u00e8tre\u00a0\u00bb de ce dernier.<\/p>\n<p style=\"text-align: justify;\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-3968\" alt=\"Raspberry Pi GPIO frequency\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-03.jpg\" width=\"480\" height=\"360\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-03.jpg 480w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/01\/img-03-300x225.jpg 300w\" sizes=\"auto, (max-width: 480px) 100vw, 480px\" \/><\/p>\n<p style=\"text-align: justify;\">Le r\u00e9sultat est conforme. La valeur affich\u00e9e sur le Raspberry Pi fluctue l\u00e9g\u00e8rement (de +\/- 2Hz environ), en permanence, il pourrait \u00eatre int\u00e9ressant que l&rsquo;application qui consulte cette valeur calcule une moyenne pond\u00e9r\u00e9e avec quelques derni\u00e8res valeurs re\u00e7ues.<\/p>\n<h1 style=\"text-align: justify;\">Limites<\/h1>\n<p style=\"text-align: justify;\">Si nous essayons de diminuer la fr\u00e9quence, le r\u00e9sultat est correct jusqu&rsquo;\u00e0 1Hz, la pr\u00e9cision \u00e9tant faible car le calcul est fait sur des nombres entiers.<br \/>\nEn augmentant la fr\u00e9quence, on peut voir que la valeur mesur\u00e9e est tout \u00e0 fait exploitable jusqu&rsquo;\u00e0 10kHz, la fluctuation du r\u00e9sultat s&rsquo;\u00e9tendant sur une centaine de Hz (1% de la valeur environ).<br \/>\nLe Raspberry Pi peut donner des r\u00e9sultats corrects jusqu&rsquo;\u00e0 100 kHz, mais les fluctuations sont plus importantes, de l&rsquo;ordre de +\/- 5kHz (environ 10% de la valeur).<\/p>\n<h1 style=\"text-align: justify;\">Conclusion<\/h1>\n<p style=\"text-align: justify;\">Le Raspberry Pi n&rsquo;est pas con\u00e7u pour \u00e9quiper un fr\u00e9quence-m\u00e8tre c&rsquo;est certain. Cela dit, nous voyons qu&rsquo;il est tout \u00e0 fait possible de l&rsquo;utiliser pour l&rsquo;acquisition de valeurs analogiques lorsqu&rsquo;un capteur (de d\u00e9bit, de vitesse, etc.) fournit ses r\u00e9sultats sous forme de trains d&rsquo;impulsions de fr\u00e9quence variable. On pourrait tr\u00e8s facilement modifier le module ci-dessus pour lui ajouter une fonction de compteur d&rsquo;impulsions, ce qui permettrait de s&rsquo;interfacer avec d&rsquo;autres types de capteurs (mesure de volume, etc.)<\/p>","protected":false},"excerpt":{"rendered":"<p>Un lecteur m&rsquo;a interrog&eacute; par mail pour savoir comment mesurer la fr&eacute;quence d&rsquo;un signal re&ccedil;u en entr&eacute;e sur une broche GPIO du Raspberry Pi. Je lui ai r&eacute;pondu que le plus simple est de mesurer le temps s&rsquo;&eacute;coulant entre deux interruptions successives d&eacute;clench&eacute;es par des fronts montants et de calculer l&rsquo;inverse. J&rsquo;ai voulu v&eacute;rifier que [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,8,10,11],"tags":[],"class_list":["post-3958","post","type-post","status-publish","format-standard","hentry","category-embarque","category-linux-2","category-microprocesseur","category-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3958","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=3958"}],"version-history":[{"count":7,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3958\/revisions"}],"predecessor-version":[{"id":3971,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3958\/revisions\/3971"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=3958"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=3958"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=3958"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}