{"id":3529,"date":"2013-04-15T08:00:44","date_gmt":"2013-04-15T07:00:44","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=3529"},"modified":"2013-04-12T14:26:10","modified_gmt":"2013-04-12T13:26:10","slug":"attentes-passives-sur-gpio","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2013\/04\/15\/attentes-passives-sur-gpio\/","title":{"rendered":"Attentes passives sur GPIO"},"content":{"rendered":"<p style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/2013\/04\/15\/attentes-passives-sur-gpio\/\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-full wp-image-3539\" alt=\"GPIO select() poll()\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/04\/GPIO-select-poll1.png\" width=\"200\" height=\"150\" \/><\/a>Nous avons d\u00e9j\u00e0 vu dans plusieurs articles qu&rsquo;il \u00e9tait facile de <a title=\"Les GPIO du Raspberry Pi\" href=\"http:\/\/www.blaess.fr\/christophe\/2012\/11\/26\/les-gpio-du-raspberry-pi\/\">manipuler les GPIO<\/a> sous Linux depuis l&rsquo;espace utilisateur avec l&rsquo;interface <code>\/sys<\/code>. Jusqu&rsquo;\u00e0 pr\u00e9sent je ne m&rsquo;\u00e9tais int\u00e9ress\u00e9 qu&rsquo;aux lectures et \u00e9critures, mais il est \u00e9galement possible de faire des attentes passives de changement d&rsquo;\u00e9tat avec <strong><code>select()<\/code><\/strong> ou <strong><code>poll()<\/code><\/strong>. Voyons-en une mise en \u0153uvre sur le Raspberry Pi.<\/p>\n<p>\n<!--more-->\n<\/p>\n<p style=\"text-align: justify;\">Il est facile de faire une attente de changement d&rsquo;\u00e9tat en \u00e9crivant un petit driver dans le kernel qui s&rsquo;accrochera \u00e0 l&rsquo;interruption d\u00e9clench\u00e9e par la broche GPIO. Nous l&rsquo;avons m\u00eame r\u00e9alis\u00e9 avec un <a title=\"Raspberry Pi \u2013 Interruptions GPIO avec RTDM\" href=\"http:\/\/www.blaess.fr\/christophe\/2013\/02\/15\/raspberry-pi-interruptions-gpio-avec-rtdm\/\">driver RTDM pour Xenomai<\/a>. Toutefois ceci pose un probl\u00e8me de portabilit\u00e9, car le code n&rsquo;est pas directement r\u00e9utilisable sur une autre architecture (bien qu&rsquo;il n&rsquo;y ait pas beaucoup de variations). En outre cela impose un d\u00e9veloppement en mode noyau, n\u00e9cessitant donc des privil\u00e8ges d&rsquo;administration et augmentant surtout le risque de crash syst\u00e8me en cas d&rsquo;erreur.<\/p>\n<p style=\"text-align: justify;\">C&rsquo;est pour simplifier ces acc\u00e8s que le noyau propose une interface passant par le syst\u00e8me de fichiers virtuels <code>\/sys<\/code>. On peut tr\u00e8s bien r\u00e9aliser des op\u00e9rations sur les GPIO de mani\u00e8re plus efficace en employant par exemple <code>mmap()<\/code> pour acc\u00e9der \u00e0 une projection en m\u00e9moire virtuelle des ports d&rsquo;entr\u00e9e-sortie. N\u00e9anmoins ceci pose de gros probl\u00e8mes de portabilit\u00e9 et d&rsquo;\u00e9volutivit\u00e9 des applications. Je pr\u00e9f\u00e8re donc, autant que possible, me contenter des acc\u00e8s via <code>\/sys<\/code>.<\/p>\n<p style=\"text-align: justify;\">Dans de nombreux cas d&rsquo;application, il est important d&rsquo;attendre passivement le changement d&rsquo;\u00e9tat d&rsquo;une broche (bouton, signal externe, capteur, etc.) en \u00e9vitant absolument toute m\u00e9thode de <em>polling<\/em> (scrutation en boucle p\u00e9riodique de l&rsquo;\u00e9tat de l&rsquo;entr\u00e9e). Si la broche GPIO concern\u00e9e est \u00e9ligible pour le d\u00e9clenchement d&rsquo;une interruption, Linux nous permet d&rsquo;utiliser l&rsquo;appel-syst\u00e8me <code>select()<\/code> ou l&rsquo;appel <code>poll()<\/code> pour attendre un \u00e9v\u00e9nement (un changement d&rsquo;\u00e9tat qui se mat\u00e9rialisera par une interruption) sans consommer de CPU pendant la dur\u00e9e de l&rsquo;attente.<\/p>\n<h1>Test avec select()<\/h1>\n<p style=\"text-align: justify;\">Voici un petit programme qui ouvre le fichier qu&rsquo;on lui indique en argument, puis il attend l&rsquo;arriv\u00e9e d&rsquo;un \u00ab\u00a0\u00e9v\u00e9nement exceptionnel\u00a0\u00bb sur le descripteur. Ces \u00ab\u00a0\u00e9v\u00e9nements exceptionnels\u00a0\u00bb trait\u00e9s par <code>select()<\/code> d\u00e9pendent du type de descripteur de fichier. Pour une socket TCP par exemple, il peut s&rsquo;agir de trames urgentes. Pour un descripteur d&rsquo;\u00e9tat de GPIO dans <em>sysfs<\/em>, il s&rsquo;agit d&rsquo;un changement de niveau ayant d\u00e9clench\u00e9 une interruption.<\/p>\n<p style=\"text-align: justify;\">Lorsqu&rsquo;il est r\u00e9veill\u00e9 par un changement d&rsquo;\u00e9tat, notre programme lit alors la valeur sur la broche et l&rsquo;affiche pr\u00e9c\u00e9d\u00e9e de l&rsquo;heure avant de se rendormir en attente du prochain r\u00e9veil.<\/p>\n<pre><strong><a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-15\/gpio-select.c\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-15\/gpio-select.c\" target=\"_blank\">gpio-select.c:<\/a><\/strong>\n#include &lt;fcntl.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/select.h&gt;\n\nint main(int argc, char * argv[])\n{\n    int fd;\n    fd_set fds;\n    struct timeval tv;\n    char buffer[2];\n\n    \/\/ On attend en argument un fichier \"value\" de GPIO.\n    \/\/ Par exemple : \/sys\/class\/gpio\/gpio23\/value\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s \\n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n    \/\/ Ouvrir le fichier\n    if ((fd = open(argv[1], O_RDONLY)) &lt; 0) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n\n    while (1) {\n        \/\/ Preparer la table des evenements exceptionnels attendus\n        FD_ZERO(&amp; fds);\n        \/\/ Avec uniquement le descripteur du fichier.\n        FD_SET(fd, &amp; fds);\n        \/\/ Attente passive (pas de timeout, donc infinie...\n        if (select(fd+1, NULL, NULL, &amp; fds, NULL) &lt; 0) {\n            perror(\"select\");\n            break;\n        }\n        \/\/ Lire l'heure du changement d'etat.\n        gettimeofday(&amp; tv, NULL);\n        \/\/ Revenir au debut du fichier (lectures successives).\n        lseek(fd, 0, SEEK_SET);\n        \/\/ Lire la valeur actuelle du GPIO.\n        if (read(fd, &amp; buffer, 2) != 2) {\n            perror(\"read\");\n            break;\n        }\n        \/\/ Effacer le retour-chariot.\n        buffer[1] = '\\0';\n        \/\/ Afficher l'heure et l'etat.\n        fprintf(stdout, \"[%ld.%06ld]: %s\\n\", tv.tv_sec, tv.tv_usec, buffer);\n    }\n    close(fd);\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Essayons notre programme sur un Raspberry Pi auquel j&rsquo;ai connect\u00e9 un petit oscillateur \u00e0 4Hz sur la broche 16 du port d&rsquo;extension. Ceci correspond au <a title=\"Connecteur P1 du Raspberry Pi\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2012-10-27\/Connecteur-P1.pdf\" target=\"_blank\">GPIO 23<\/a>. Commen\u00e7ons donc par demander au kernel de nous offrir l&rsquo;acc\u00e8s \u00e0 ce GPIO depuis <em>sysfs<\/em>.<\/p>\n<pre>R-Pi login: <strong>root<\/strong>\nPassword:\nroot@R-Pi [\/root]# <strong>cd \/sys\/class\/gpio\/<\/strong>\nroot@R-Pi [gpio]# <strong>ls<\/strong>\nexport     gpiochip0  unexport\nroot@R-Pi [gpio]# <strong>echo 23 &gt; export<\/strong>\nroot@R-Pi [gpio]# <strong>ls<\/strong>\nexport     gpio23     gpiochip0  unexport\nroot@R-Pi [gpio]# <strong>cd gpio23\/<\/strong>\nroot@R-Pi [gpio23]# <strong>ls<\/strong>\nactive_low  direction   edge        subsystem   uevent      value\nroot@R-Pi [gpio23]#<\/pre>\n<p style=\"text-align: justify;\">Essayons notre programme&#8230;<\/p>\n<pre>root@R-Pi [gpio23]# <strong>\/root\/gpio-select .\/value<\/strong>\n[47.742961]: 0\n   (rien ne se passe...)\n<strong>^C<\/strong>\nroot@R-Pi [gpio23]#<\/pre>\n<p style=\"text-align: justify;\">Avant de conclure que notre programme est erron\u00e9, v\u00e9rifions sur quels types de fronts (montants ou descendants) du signal l&rsquo;interruption est-elle d\u00e9clench\u00e9e.<\/p>\n<pre>root@R-Pi [gpio23]# <strong>cat edge<\/strong>\nnone\nroot@R-Pi [gpio23]#<\/pre>\n<p style=\"text-align: justify;\">Voici donc l&rsquo;explication. Par d\u00e9faut aucune interruption n&rsquo;est produite par les changements d&rsquo;\u00e9tat sur les broches GPIO (heureusement, car avec les variations permanentes de tension se produisant sur les broches laiss\u00e9es libres, il y aurait des interruptions sans cesse, ce qui consomme du CPU). Configurons donc notre GPIO pour qu&rsquo;il nous r\u00e9veille sur les fronts montants.<\/p>\n<pre>root@R-Pi [gpio23]# <strong>echo rising &gt; edge<\/strong>\nroot@R-Pi [gpio23]# <strong>\/root\/gpio-select .\/value<\/strong>\n[98.887860]: 1\n[99.137953]: 1\n[99.388067]: 1\n[99.638183]: 1\n[99.888280]: 1\n[100.138399]: 1\n[100.388500]: 1\n[100.638604]: 1\n[100.888726]: 1\n[101.138819]: 1\n[101.388940]: 1\n[101.639035]: 1\n[101.889145]: 1\n[102.139269]: 1\n[102.389365]: 1\n[102.639479]: 1\n[102.889577]: 1\n[103.139686]: 1\n[103.389810]: 1\n[103.639901]: 1\n<strong>^C<\/strong>\nroot@R-Pi [gpio23]#<\/pre>\n<p style=\"text-align: justify;\">Tr\u00e8s bien. Les \u00e9carts entre les d\u00e9clenchements sont bien de 250ms (soit 4Hz), et les valeurs lues sont toujours \u00e0 <code>1<\/code> car le signal chaque fois de monter pour atteindre +3.3V. Essayons de d\u00e9tecter les fronts descendants&#8230;<\/p>\n<pre>root@R-Pi [gpio23]# <strong>echo falling &gt; edge<\/strong>\nroot@R-Pi [gpio23]# <strong>\/root\/gpio-select .\/value<\/strong>\n[133.285615]: 0\n[133.527846]: 0\n[133.777937]: 0\n[134.028055]: 0\n[134.278160]: 0\n[134.528262]: 0\n[134.778380]: 0\n[135.028480]: 0\n[135.278598]: 0\n[135.528694]: 0\n[135.778802]: 0\n[136.028923]: 0\n[136.279026]: 0\n[136.529134]: 0\n[136.779234]: 0\n[137.029345]: 0\n[137.279467]: 0\n[137.529560]: 0\n<strong>^C<\/strong>\nroot@R-Pi [gpio23]#<\/pre>\n<p style=\"text-align: justify;\">Tout \u00e0 fait logiquement, ce sont maintenant des valeurs \u00e0 <code>0<\/code> qui sont toujours lues car le signal d&rsquo;entr\u00e9e vient donc de descendre pour d\u00e9clencher l&rsquo;interruption.<\/p>\n<p style=\"text-align: justify;\">Essayons de d\u00e9tecter simultan\u00e9ment les deux types de fronts.<\/p>\n<pre>root@R-Pi [gpio23]# <strong>echo both &gt; edge<\/strong>\nroot@R-Pi [gpio23]# <strong>\/root\/gpio-select .\/value<\/strong>\n[151.806837]: 0\n[151.910795]: 1\n[152.035833]: 0\n[152.160890]: 1\n[152.285962]: 0\n[152.411002]: 1\n[152.536047]: 0\n[152.661102]: 1\n[152.786164]: 0\n[152.911212]: 1\n[153.036265]: 0\n[153.161328]: 1\n[153.286373]: 0\n[153.411429]: 1\n[153.536494]: 0\n[153.661542]: 1\n[153.786587]: 0\n[153.911645]: 1\n[154.036705]: 0\n[154.161756]: 1\n[154.286890]: 0\n<strong>^C<\/strong>\nroot@R-Pi [gpio23]#<\/pre>\n<p style=\"text-align: justify;\">Cela fonctionne \u00e9galement, les valeurs lues sont alors alternativement <code>0<\/code> et <code>1<\/code> et les d\u00e9clenchements sont espac\u00e9s de 125ms.<\/p>\n<h1>Et poll()&nbsp;?<\/h1>\n<p style=\"text-align: justify;\">L&rsquo;appel-syst\u00e8me <code>poll()<\/code> est mal nomm\u00e9. Il n&rsquo;assure pas un <em>polling<\/em> tel qu&rsquo;on peut l&rsquo;entendre en programmation micro-contr\u00f4leur par exemple o\u00f9 l&rsquo;on vient examiner l&rsquo;\u00e9tat d&rsquo;une entr\u00e9e en boucle pour d\u00e9tecter ses variations. Sous Linux, <code>poll()<\/code> fonctionne de la m\u00eame mani\u00e8re que <code>select()<\/code> en endormant passivement la t\u00e2che appelante jusqu&rsquo;\u00e0 ce qu&rsquo;une interruption signale un \u00e9v\u00e9nement susceptible d&rsquo;int\u00e9resser le demandeur.<\/p>\n<p style=\"text-align: justify;\">Voici l&rsquo;impl\u00e9mentation du m\u00eame programme en employant <code>poll()<\/code> plut\u00f4t que <code>select()<\/code>.<\/p>\n<pre><strong><a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-15\/gpio-poll.c\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-15\/gpio-poll.c\" target=\"_blank\">gpio-poll.c:<\/a><\/strong>\n#include &lt;fcntl.h&gt;\n#include &lt;poll.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n\nint main(int argc, char * argv[])\n{\n    int fd;\n    struct pollfd  fds;\n    struct timeval tv;\n    char buffer[2];\n\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s \\n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n    if ((fd = open(argv[1], O_RDONLY)) &lt; 0) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n    while (1) {\n        fds.fd = fd;\n        fds.events = POLLPRI;\n        if (poll(&amp; fds, 1, -1) &lt; 0) {\n            perror(\"poll\");\n            break;\n        }\n        gettimeofday(&amp; tv, NULL);\n        lseek(fd, 0, SEEK_SET);\n        if (read(fd, &amp; buffer, 2) != 2) {\n            perror(\"read\");\n            break;\n        }\n        buffer[1] = '\\0';\n        fprintf(stdout, \"[%ld.%06ld]: %s\\n\", tv.tv_sec, tv.tv_usec, buffer);\n    }\n    close(fd);\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Les r\u00e9sultats d&rsquo;ex\u00e9cution sont exactement les m\u00eames que ceux du pr\u00e9c\u00e9dent programme.<\/p>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">L&rsquo;attente passive de changement d&rsquo;\u00e9tat sur un GPIO est une op\u00e9ration tr\u00e8s utile dans de nombreuses applications o\u00f9 une entr\u00e9e signale un \u00e9v\u00e9nement (alarme, acquisition d&rsquo;un capteur, pression sur un bouton, etc.). L&rsquo;interface <em>sysfs<\/em> de Linux nous permet d&rsquo;y acc\u00e9der de mani\u00e8re simple et portable, m\u00eame si le temps de prise en charge est un peu plus long que depuis un driver dans le kernel.<\/p>\n<p style=\"text-align: justify;\">Notez que mes exemples ci-dessus sont \u00e9crits en C, du fait de ma familiarit\u00e9 avec ce langage, mais qu&rsquo;ils pourraient parfaitement fonctionner dans d&rsquo;autres langages. Si vous avez envie de les r\u00e9\u00e9crire, par exemple en Python, n&rsquo;h\u00e9sitez pas \u00e0 m&rsquo;en faire part, j&rsquo;en mettrai volontiers une copie ici.<\/p>\n<p style=\"text-align: justify; padding-left: 30px;\">PS&nbsp;: je tiens \u00e0 remercier la personne d&rsquo;Hydequip dont je n&rsquo;ai pas not\u00e9 le nom et avec qui j&rsquo;ai parl\u00e9 de ce th\u00e8me il y a quelques semaines lors d&rsquo;un atelier Cap&rsquo;tronic \u00e0 Rouen. C&rsquo;est suite \u00e0 cette conversation que j&rsquo;ai eu envie d&rsquo;approfondir ce sujet.<\/p>\n<p style=\"text-align: justify;\">","protected":false},"excerpt":{"rendered":"<p>Nous avons d&eacute;j&agrave; vu dans plusieurs articles qu&rsquo;il &eacute;tait facile de manipuler les GPIO sous Linux depuis l&rsquo;espace utilisateur avec l&rsquo;interface \/sys. Jusqu&rsquo;&agrave; pr&eacute;sent je ne m&rsquo;&eacute;tais int&eacute;ress&eacute; qu&rsquo;aux lectures et &eacute;critures, mais il est &eacute;galement possible de faire des attentes passives de changement d&rsquo;&eacute;tat avec select() ou poll(). Voyons-en une mise en &oelig;uvre sur [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,10,11],"tags":[],"class_list":["post-3529","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-microprocesseur","category-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3529","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=3529"}],"version-history":[{"count":14,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3529\/revisions"}],"predecessor-version":[{"id":3531,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3529\/revisions\/3531"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=3529"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=3529"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=3529"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}