{"id":5224,"date":"2018-10-22T06:00:33","date_gmt":"2018-10-22T05:00:33","guid":{"rendered":"https:\/\/www.blaess.fr\/christophe\/?p=5224"},"modified":"2018-10-29T07:13:55","modified_gmt":"2018-10-29T06:13:55","slug":"pilotage-de-gpio-avec-lapi-libgpiod-partie-2","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2018\/10\/22\/pilotage-de-gpio-avec-lapi-libgpiod-partie-2\/","title":{"rendered":"Pilotage de GPIO avec l\u2019API Libgpiod (partie 2)"},"content":{"rendered":"\n<p>Dans <a href=\"https:\/\/www.blaess.fr\/christophe\/2018\/10\/15\/pilotage-de-gpio-avec-lapi-libgpiod-partie-1\/\">l&rsquo;article pr\u00e9c\u00e9dent<\/a> de cette s\u00e9rie, nous avons examin\u00e9 les commandes disponibles au niveau du shell pour piloter des lignes GPIO avec la nouvelle API propos\u00e9e par le noyau Linux.<\/p>\n\n\n\n<p>Nous allons \u00e0 pr\u00e9sent nous int\u00e9resser \u00e0 l&rsquo;acc\u00e8s depuis un programme C\/C++ en utilisant cette API.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Avec la nouvelle API de Linux, l&rsquo;acc\u00e8s aux lignes GPIO depuis un programme peut se faire de deux fa\u00e7ons diff\u00e9rentes&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>En acc\u00e9dant directement au fichier de <code>\/dev<\/code> repr\u00e9sentant le contr\u00f4leur de GPIO par l&rsquo;interm\u00e9diaire d&rsquo;appels syt\u00e8me <code>ioctl()<\/code>. Ceci est possible sur tous syst\u00e8mes Linux depuis le noyau 4.8.<br\/><\/li><li>En utilisant la biblioth\u00e8que <em>libgpiod<\/em> (que nous avons install\u00e9e au d\u00e9but de l&rsquo;article pr\u00e9c\u00e9dent) et qui nous offre des fonctionnalit\u00e9s de plus haut niveau pour acc\u00e9der aux lignes GPIO.<br\/><\/li><\/ul>\n\n\n\n<p>Nous allons \u00e9tudier ces deux m\u00e9thodes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">ACC\u00c8S PAR <code>IOCTL()<\/code> VERS LE CONTR\u00d4LEUR DE GPIO<\/h2>\n\n\n\n<p>Les fichiers sources des exemples d\u00e9crits dans ce paragraphe se trouvent dans le d\u00e9p\u00f4t <em>Framagit<\/em> suivant&nbsp;: <a href=\"https:\/\/framagit.org\/cpb\/ioctl-access-to-gpio\" target=\"_blank\">https:\/\/framagit.org\/cpb\/ioctl-access-to-gpio<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lecture d&rsquo;informations concernant les lignes GPIO disponibles<\/h3>\n\n\n\n<p>Quelques structures et constantes symboliques sont d\u00e9crites dans le fichier <code>&lt;linux\/gpio.h><\/code>. Les premi\u00e8res nous donnent des informations sur les lignes GPIO.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>struct gpiochip_info<\/strong> {\n    char name[32];\n    char label[32];\n    __u32 lines;\n};<br\/>\ndefine GPIOLINE_FLAG_KERNEL            (1UL &lt;&lt; 0)<br\/>define GPIOLINE_FLAG_IS_OUT            (1UL &lt;&lt; 1)<br\/>define GPIOLINE_FLAG_ACTIVE_LOW        (1UL &lt;&lt; 2)<br\/>define GPIOLINE_FLAG_OPEN_DRAIN        (1UL &lt;&lt; 3)<br\/>define GPIOLINE_FLAG_OPEN_SOURCE       (1UL &lt;&lt; 4)<br\/>\n<strong>struct gpioline_info<\/strong> {<br\/>    __u32 line_offset;<br\/>    __u32 flags;<br\/>    char name[32];<br\/>    char consumer[32];<br\/>};<\/pre>\n\n\n\n<p>Pour obtenir les \u00e9l\u00e9ments concernant un contr\u00f4leur GPIO, on ex\u00e9cute les op\u00e9rations suivantes (pour des raisons de lisibilit\u00e9, j&rsquo;ai supprim\u00e9 les gestions d&rsquo;erreur, mais elles sont pr\u00e9sentes dans les programmes d&rsquo;exemple de cet article).<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int fd;<br\/>struct <strong>gpiochip_info<\/strong> info;<br\/>\n<code>fd = open(\"\/dev\/gpiochip0\", O_RDONLY);\n<strong>ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &amp;info);<\/strong>\nprintf(\"name = %s, label = %s, lines = %d\\n\",<br\/>        info.name, info.label, info.lines);<\/code><\/pre>\n\n\n\n<p>On peut ensuite parcourir les num\u00e9ros de lignes et afficher les informations. On appelle <em>offset<\/em> d&rsquo;une ligne GPIO son num\u00e9ro au sein de son contr\u00f4leur.<br\/><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int offset;<br\/>struct gpioline_info info;<br\/>\nfor (offset = 0; offset &lt; info.lines; offset ++) {\n  memset(&amp;info, 0, sizeof(struct gpioline_info));\n  info.line_offset = offset;\n  <strong>ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &amp;info);<\/strong>\n  printf(\" %d: name = %s, consumer = %s, flags = %s %s %s %s %s\\n\",\n        info.line_offset, info.name, info.consumer,\n        info.flags &amp; GPIOLINE_FLAG_IS_OUT ? \"OUT\" : \"IN \",\n        info.flags &amp; GPIOLINE_FLAG_ACTIVE_LOW ? \"ACTIVE_LOW \" : \"ACTIVE_HIGH\",\n        info.flags &amp; GPIOLINE_FLAG_OPEN_DRAIN ? \"OPEN_DRAIN\" : \"\",\n        info.flags &amp; GPIOLINE_FLAG_OPEN_SOURCE ? \"OPEN_SOURCE\" : \"\",\n        info.flags &amp; GPIOLINE_FLAG_KERNEL ? \"KERNEL\" : \"\");<\/pre>\n\n\n\n<p>Le programme <code><a href=\"https:\/\/framagit.org\/cpb\/ioctl-access-to-gpio\/blob\/master\/ioctl-gpio-list.c\" target=\"_blank\">ioct-gpio-list.c<\/a><\/code> r\u00e9alise ce travail. Voici un exemple d&rsquo;ex\u00e9cution sur Raspberry Pi 3.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ <strong>.\/ioctl-gpio-list <\/strong><br\/>file = \/dev\/gpiochip0, name = gpiochip0, label = pinctrl-bcm2835, lines = 54<br\/>    offset = 0, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 1, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 2, name = PIN-#03, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 3, name = PIN-#05, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 4, name = PIN-#07, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 5, name = PIN-#29, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 6, name = PIN-#31, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 7, name = PIN-#26, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 8, name = PIN-#24, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 9, name = PIN-#21, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 10, name = PIN-#19, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 11, name = PIN-#23, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 12, name = PIN-#32, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 13, name = PIN-#33, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 14, name = PIN-#08, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 15, name = PIN-#10, consumer = , flags = OUT ACTIVE_HIGH   <br\/>    offset = 16, name = PIN-#36, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 17, name = PIN-#11, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 18, name = PIN-#12, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 19, name = PIN-#35, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 20, name = PIN-#38, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 21, name = PIN-#40, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 22, name = PIN-#15, consumer = , flags = OUT ACTIVE_HIGH   <br\/>    offset = 23, name = PIN-#16, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 24, name = PIN-#18, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 25, name = PIN-#22, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 26, name = PIN-#37, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 27, name = PIN-#13, consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 28, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 29, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 30, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 31, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 32, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 33, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 34, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 35, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 36, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 37, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 38, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 39, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 40, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 41, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 42, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 43, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 44, name = , consumer = , flags = IN  ACTIVE_HIGH   <br\/>    offset = 45, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 46, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 47, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 48, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 49, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 50, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 51, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 52, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 53, name = , consumer = , flags = IN  ACTIVE_HIGH   \nfile = \/dev\/gpiochip1, name = gpiochip1, label = brcmexp-gpio, lines = 8\n    offset = 0, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 1, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 2, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 3, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 4, name = , consumer = , flags = IN  ACTIVE_HIGH   \n    offset = 5, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 6, name = , consumer = , flags = OUT ACTIVE_HIGH   \n    offset = 7, name = , consumer = led1, flags = IN  ACTIVE_HIGH   KERNEL\nfile = \/dev\/gpiochip2, name = gpiochip2, label = brcmvirt-gpio, lines = 2\n    offset = 0, name = , consumer = led8, flags = OUT ACTIVE_HIGH   KERNEL\n    offset = 1, name = , consumer = , flags = IN  ACTIVE_HIGH   \n$  <\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u00c9criture sur une ligne GPIO de sortie<\/h3>\n\n\n\n<p>Pour acc\u00e9der (en lecture ou en \u00e9criture) \u00e0 des lignes GPIO, on r\u00e9clame au kernel la mise \u00e0 disposition d&rsquo;un <em>handle<\/em> sur lequel on agira. Ce <em>handle<\/em> permet d&rsquo;op\u00e9rer simultan\u00e9ment sur plusieurs lignes GPIO si on le souhaite. Tout d&rsquo;abord il faut remplir une structure <code>gpiohandle_request<\/code> contenant un tableau d&rsquo;<em>offsets<\/em> &#8212; contenant au maximum <code>GPIOHANDLES_MAX<\/code> (64) lignes pour un m\u00eame contr\u00f4leur &#8212; avec les num\u00e9ros de lignes nous int\u00e9ressant. On indique \u00e9galement s&rsquo;il s&rsquo;agit de lectures ou d&rsquo;\u00e9critures. Les lignes sur lesquelles on agit simultan\u00e9ment doivent toutes \u00eatre acc\u00e9d\u00e9es dans la m\u00eame direction, entr\u00e9e ou sortie.<\/p>\n\n\n\n<p>Le programme <a href=\"https:\/\/framagit.org\/cpb\/ioctl-access-to-gpio\/blob\/master\/ioctl-toggle-gpio.c\" target=\"_blank\"><code>ioct-toggle-gpio.c<\/code><\/a> fait clignoter une ligne GPIO (indiqu\u00e9e sur sa ligne de commande) avec une fr\u00e9quence d&rsquo;une seconde. Le tableau <code>lineoffsets<\/code> ne contient donc qu&rsquo;une seule entr\u00e9e, ce qui est pr\u00e9cis\u00e9 dans le champs <code>lines<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int output_fd;\nint output_offset;\nstruct gpiohandle_request output_request;\n\n[...]\nmemset(&amp;output_request, 0, sizeof(struct gpiohandle_request));\noutput_request.lineoffsets[0] = output_offset;\noutput_request.flags = GPIOHANDLE_REQUEST_OUTPUT;\noutput_request.lines = 1;\n\n<strong>ioctl(output_fd, GPIO_GET_LINEHANDLE_IOCTL, &amp;output_request);<\/strong><\/pre>\n\n\n\n<p>Le r\u00e9sultat de l&rsquo;<em>ioctl<\/em> ci-dessus est un nouveau descripteur de fichiers, fourni dans le champs <code>fd<\/code> de cette structure. C&rsquo;est le <em>handle<\/em> que l&rsquo;on pourra utiliser pour lire ou \u00e9crire sur les lignes GPIO r\u00e9serv\u00e9es. Les valeurs lues ou \u00e9crites sont regroup\u00e9es dans un tableau contenu dans une structure <code>gpiohandle_data<\/code>.<\/p>\n\n\n\n<p>Voici un exemple d&rsquo;\u00e9criture, notez bien que l&rsquo;<code>ioctl()<\/code> est ex\u00e9cut\u00e9 sur le descripteur obtenu pr\u00e9c\u00e9demment dans la structure <code>gpiohandle_request<\/code>, pas sur le descripteur initial ouvert sur <code>gpiochip0<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">struct gpiohandle_data output_values;\n\n[...]\noutput_values.values[0] = 0;\nfor (;;) {\n    output_values.values[0] = 1 - output_values.values[0];\n    <strong>ioctl(output_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &amp;output_values);<\/strong>\n    usleep(500000);\n}<br\/><\/pre>\n\n\n\n<p>Pour que ce programme fasse clignoter la ligne GPIO 22 (broche 15) d&rsquo;un Raspberry Pi, on le lance ainsi&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ <strong>.\/ioctl-toggle-gpio \/dev\/gpiochip0 22<\/strong><\/pre>\n\n\n\n<p>Comme indiqu\u00e9 plus haut, la sortie change d&rsquo;\u00e9tat toutes les 0,5 secondes gr\u00e2ce \u00e0 l&rsquo;attente dans <code>usleep()<\/code>. Si on supprime cette instruction, le code fait basculer la sortie le plus vite possible, simplement limit\u00e9 par la vitesse du processeur et le temps d&rsquo;\u00e9change entre l&rsquo;espace utilisateur et le kernel. Sur un Raspberry Pi 3, la fr\u00e9quence la plus \u00e9lev\u00e9e obtenue ainsi est 580 kHZ environ, soit 1,72\u00a0microsecondes pour un cycle complet. On peut ainsi consid\u00e9rer que l&rsquo;\u00e9criture sur une ligne GPIO depuis l&rsquo;espace utilisateur prend 0,86\u00a0microseconde.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2018\/10\/oscillo-01.png\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"401\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2018\/10\/oscillo-01.png\" alt=\"\" class=\"wp-image-5239\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2018\/10\/oscillo-01.png 800w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2018\/10\/oscillo-01-300x150.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2018\/10\/oscillo-01-768x385.png 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/a><figcaption>Oscillation d&rsquo;une GPIO \u00e0 la fr\u00e9quence maximale depuis l&rsquo;espace utilisateur<\/figcaption><\/figure>\n\n\n\n<p>Anecdote amusante&nbsp;: lorsque j&rsquo;ai fait cette mesure une premi\u00e8re fois, j&rsquo;ai obtenu une p\u00e9riode de 3.5 microsecondes pour le cycle complet soit le double de la valeur mesur\u00e9e ici. En fait, mon Raspberry Pi \u00e9tait aliment\u00e9 par un adaptateur USB trop faible, et il passait en mode \u00ab\u00a0\u00e9conomie d&rsquo;\u00e9nergie\u00a0\u00bb en limitant sa fr\u00e9quence processeur \u00e0 600 MHz. Lorsque je l&rsquo;ai aliment\u00e9 avec une v\u00e9ritable alimentation de labo, il a pu utiliser sa fr\u00e9quence processeur maximale de 1,2 GHz.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lecture sur une ligne GPIO d&rsquo;entr\u00e9e<\/h3>\n\n\n\n<p>Voyons \u00e0 pr\u00e9sent un exemple de lecture. Le programme <a href=\"https:\/\/framagit.org\/cpb\/ioctl-access-to-gpio\/blob\/master\/ioctl-invert-gpio.c\" target=\"_blank\"><code>ioct-invert-gpio.c<\/code><\/a> affiche sur une ligne GPIO de sortie l&rsquo;\u00e9tat inverse de celui d&rsquo;une ligne d&rsquo;entr\u00e9e. Le code est en substance le suivant.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int input_fd;<br\/>int output_fd;<br\/>int input_offset;<br\/>int output_offset;<br\/>struct gpiohandle_request input_request;<br\/>struct gpiohandle_request output_request;<br\/>struct gpiohandle_data input_values;<br\/>struct gpiohandle_data output_values;<br\/><br\/>[...]<br\/>memset(&amp;input_request, 0, sizeof(struct gpiohandle_request));<br\/>input_request.lineoffsets[0] = input_offset;<br\/>input_request.flags = GPIOHANDLE_REQUEST_INPUT;<br\/>input_request.lines = 1;<br\/><strong>ioctl(input_fd, GPIO_GET_LINEHANDLE_IOCTL, &amp;input_request);<\/strong> <br\/>memset(&amp;output_request, 0, sizeof(struct gpiohandle_request));<br\/>output_request.lineoffsets[0] = output_offset;<br\/>output_request.flags = GPIOHANDLE_REQUEST_OUTPUT;<br\/>output_request.lines = 1;<br\/><strong>ioctl(output_fd, GPIO_GET_LINEHANDLE_IOCTL, &amp;output_request);<\/strong> <br\/>for (;;) {<br\/>    <strong>ioctl(input_request.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &amp;input_values);<\/strong> <br\/>    output_values.values[0] = (input_values.values[0] == 0) ? 1 : 0;<br\/>    <strong>ioctl(output_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &amp;output_values);<\/strong> <br\/>}<\/pre>\n\n\n\n<p>On peut lancer le programme pour afficher sur la sortie 22 (broche 15) l&rsquo;inverse de la valeur lue sur l&rsquo;entr\u00e9e 23 (broche 16).<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ <strong>.\/ioctl-invert-gpio \/dev\/gpiochip0 23 \/dev\/gpiochip0 22<\/strong><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Attente d&rsquo;\u00e9v\u00e9nements sur des lignes GPIO<\/h3>\n\n\n\n<p>Il est souvent n\u00e9cessaire d&rsquo;attendre l&rsquo;occurrence d&rsquo;un \u00e9v\u00e9nement sur une ligne GPIO (par exemple le passage d&rsquo;une entr\u00e9e d&rsquo;un niveau bas \u00e0 un niveau haut). Pour cela on utilisera l&rsquo;appel syst\u00e8me <code>poll()<\/code> qui permet d&rsquo;attendre passivement (en endormant la t\u00e2che appelante) que la transition demand\u00e9e se produise sur l&rsquo;une des lignes GPIO surveill\u00e9es. On peut \u00e9galement ajouter un d\u00e9lai maximal d&rsquo;attente avant \u00e9chec de l&rsquo;appel syst\u00e8me.<\/p>\n\n\n\n<p>On remplit une structure <code>gpioevent_request<\/code> avec l&rsquo;offset de la ligne GPIO qui nous int\u00e9resse et le type d&rsquo;\u00e9v\u00e9nement que l&rsquo;on souhaite attendre&nbsp;: <code>GPIOEVENT_REQUEST_RISING_EDGE<\/code> (front montant), <code>GPIOEVENT_REQUEST_FALLING_EDGE<\/code> (front descendant) ou <code>GPIOEVENT_REQUEST_BOTH_EDGES<\/code> (les deux types de fronts). Apr\u00e8s invocation de l&rsquo;<code>ioctl<\/code>, la structure contiendra un descripteur dans son champ <code>fd<\/code> que l&rsquo;on pourra surveiller avec <code>poll()<\/code>. Bien entendu il est possible de surveiller simultan\u00e9ment plusieurs descripteurs, \u00e9ventuellement de diff\u00e9rents types (GPIO, socket, <em>pipe<\/em>, fichiers sp\u00e9ciaux de p\u00e9riph\u00e9riques, etc.).<\/p>\n\n\n\n<p>Lorsque <code>poll()<\/code> se termine avec succ\u00e8s, nous pouvons interroger le noyau pour savoir quel type d&rsquo;\u00e9v\u00e9nement s&rsquo;est produit en lisant avec l&rsquo;appel systeme <code>read()<\/code> le descripteur pr\u00e9c\u00e9demment soumis \u00e0 <code>poll()<\/code>, afin d&rsquo;obtenir une structure <code>gpioevent_data<\/code>. Son champs <code>id<\/code> contiendra le type d&rsquo;\u00e9v\u00e9nement (<code>GPIOEVENT_EVENT_RISING_EDGE<\/code> ou <code>GPIOEVENT_EVENT_FALLING_EDGE)<\/code>) et son champs <code>timestamp<\/code> un horodatage sur 64 bits repr\u00e9sentant le nombre de nanosecondes \u00e9coul\u00e9es depuis le 1er janvier 1970.<\/p>\n\n\n\n<p>Le programme <a href=\"https:\/\/framagit.org\/cpb\/ioctl-access-to-gpio\/blob\/master\/ioctl-poll-gpio.c\" target=\"_blank\"><code>ioctl-poll-gpio.c<\/code><\/a> surveille la ligne GPIO d&rsquo;entr\u00e9e qui lui est soumise sur sa ligne de commande et affiche l&rsquo;horodatage et le type d&rsquo;\u00e9v\u00e9nement survenu. En outre il affiche toutes les secondes un retour-chariot faisant ainsi \u00ab\u00a0scroller\u00a0\u00bb l&rsquo;\u00e9cran et montrant que le programme est actif. On peut le lancer ainsi pour surveiller l&rsquo;entr\u00e9e 23 (broche 16) du Raspberry Pi.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ <strong>.\/ioctl-poll-gpio \/dev\/gpiochip0 23<\/strong> <br\/>[1538291737.756504167] RISING <br\/>[1538291737.756514479] RISING <br\/>[1538291737.756518646] RISING <br\/>[1538291740.444199629] RISING <br\/>[1538291740.444257805] RISING <br\/>[1538291740.444260774] RISING <br\/>[1538291740.444285513] FALLING <br\/>[1538291740.444306086] RISING <br\/>[1538291740.444329419] RISING <br\/>[1538291740.444343794] RISING <br\/>[1538291740.444358273] RISING <br\/>[1538291740.444360513] FALLING <br\/>[1538291740.444363638] FALLING <br\/>[1538291740.444382492] FALLING <br\/>[1538291740.444403377] FALLING <br\/>  (Contr\u00f4le C)<\/pre>\n\n\n\n<p>Pendant l&rsquo;ex\u00e9cution j&rsquo;ai simplement mis en contact la broche 16 (ligne GPIO 23) et la broche 1 (alimentation +3.3V) puis retir\u00e9 ce contact trois secondes plus tard. On voit bien que le contact m\u00e9canique n&rsquo;est jamais vraiment franc et que de multiples rebonds se produisent, espac\u00e9s de quelques dizaines de microsecondes. Il est g\u00e9n\u00e9ralement n\u00e9cessaire de prendre en consid\u00e9ration ce probl\u00e8me avec un m\u00e9canisme (\u00e9lectronique ou logiciel) d&rsquo;anti-rebond.<\/p>\n\n\n\n<p>Nous voyons qu&rsquo;il est possible de piloter les lignes GPIO directement avec les appels syst\u00e8mes <code>open()<\/code>, <code>ioctl()<\/code>, <code>poll()<\/code> et <code>read()<\/code>. Toutefois ces m\u00e9thodes sont un peu r\u00e9barbatives et la manipulation des structures un peu complexe.<br\/>\n        C&rsquo;est pourquoi Bartosz Golaszewski a \u00e9crit une biblioth\u00e9que nomm\u00e9e <code>libgpiod<\/code> qui permet d&rsquo;acc\u00e9der plus ais\u00e9ment \u00e0 ces lignes d&rsquo;entr\u00e9es-sorties.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">PROGRAMMATION AVEC LA BIBLIOTH\u00c8QUE LIBGPIOD<\/h2>\n\n\n\n<p>Le fichier d&rsquo;en-t\u00eate n\u00e9cessaire pour utiliser la biblioth\u00e8que Libgpiod est nomm\u00e9 <code>gpiod.h<\/code>. Il existe deux niveaux d&rsquo;acc\u00e8s aux fonctionnalit\u00e9s de la biblioth\u00e8ques&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>les fonctions <em>context-less<\/em> permettent un acc\u00e8s rapide et simple aux lignes GPIO,<br\/><\/li><li>les fonctions bas-niveaux permettent un acc\u00e8s plus fin aux contr\u00f4leurs GPIO, au prix d&rsquo;une manipulation un peu plus compliqu\u00e9e s&rsquo;appuyant sur plusieurs structures. <\/li><\/ul>\n\n\n\n<p>Les fichiers sources des exemples d\u00e9crits dans ce paragraphe se trouvent dans le d\u00e9p\u00f4t <em>Framagit<\/em> suivant&nbsp;: <a href=\"https:\/\/framagit.org\/cpb\/example-programs-using-libgpiod\">https:\/\/framagit.org\/cpb\/example-programs-using-libgpiod<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Les fonctions <em>context-less<\/em> de la biblioth\u00e8que libGPIOd<\/h3>\n\n\n\n<p>Les fonctions <em>context-less<\/em> permettent un acc\u00e8s simplifi\u00e9 en une seule op\u00e9ration, o\u00f9 l&rsquo;on indique le contr\u00f4leur concern\u00e9, l&rsquo;offset de la ligne GPIO, le sens d&rsquo;activit\u00e9 (0 = normal, 1 = invers\u00e9) et le nom \u00e0 inscrire temporairement comme <em>consumer<\/em> de la ligne.<\/p>\n\n\n\n<p>Pour lire l&rsquo;\u00e9tat d&rsquo;une ou plusieurs broches on utilisera<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int <strong>gpiod_ctxless_get_value<\/strong>(const char *<em>device<\/em>,<br\/>                            unsigned int <em>offset<\/em>,\n\t\t\t    bool <em>active_low<\/em>,<br\/>                            const char *<em>consumer<\/em>);<br\/><br\/>int <strong>gpiod_ctxless_get_value_multiple<\/strong>(const char *<em>device<\/em>,<br\/>                            unsigned int *offsets,<br\/>                            int *<em>values<\/em>,<br\/>                            unsigned int <em>num_lines<\/em>,\n\t\t\t    bool <em>active_low<\/em>,<br\/>                            const char *<em>consumer<\/em>);<br\/><\/pre>\n\n\n\n<p>Le programme <code><a href=\"https:\/\/framagit.org\/cpb\/example-programs-using-libgpiod\/blob\/master\/read-gpio.c\" target=\"_blank\">read-gpio.c<\/a><\/code> utilise ce m\u00e9canisme pour afficher le contenu d&rsquo;une broche demand\u00e9e en argument.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#include &lt;gpiod.h><br\/><br\/>[...]\n\nint main(int argc, char *argv[])\n{<br\/>  int value;<br\/>  int offset;<br\/><br\/>  sscanf(argv[2], \"%d\", &amp;offset);<br\/>  <strong>value = gpiod_ctxless_get_value(argv[1], offset, 0, argv[0])<\/strong>;<br\/>  [...]<br\/>}<br\/><\/pre>\n\n\n\n<p>On peut utiliser plusieurs notations pour le nom du contr\u00f4leur GPIO&nbsp;: <\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ <strong>.\/read-gpio \/dev\/gpiochip0 23<\/strong>\n0<br\/>\n$ <strong>.\/read-gpio gpiochip0 23<\/strong><br\/>0<br\/>\n$ <strong>.\/read-gpio 0 23<\/strong><br\/>0<br\/><br type=\"_moz\"\/><\/pre>\n\n\n\n<p>Pour fixer l&rsquo;\u00e9tat d&rsquo;une ou plusieurs lignes GPIO on utilisera les fonctions suivantes&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int <strong>gpiod_ctxless_set_value<\/strong>(const char *<em>device<\/em>,\n                            unsigned int <em>offset<\/em>,\n                            int <em>value<\/em>,\n                            bool <em>active_low<\/em>,\n                            const char *<em>consumer<\/em>,\n                            gpiod_ctxless_set_value_cb <em>cb<\/em>,\n                            void *<em>data<\/em>);\n\nint <strong>gpiod_ctxless_set_value_multiple<\/strong>(const char *<em>device<\/em>,\n                              const unsigned int *<em>offsets<\/em>,\n                              const int *<em>values<\/em>,\n                              unsigned int <em>num_lines<\/em>,\n                              bool <em>active_low<\/em>,\n                              const char *<em>consumer<\/em>,\n                              gpiod_ctxless_set_value_cb <em>cb<\/em>,<br\/>                              void *<em>data<\/em>);<\/pre>\n\n\n\n<p>Ces fonctions n\u00e9cessitent une petite explication. Les premiers champ &#8211; <em>device<\/em>, <em>offset<\/em>, <em>value<\/em>, <em>active_low<\/em> et <em>consumer<\/em> &#8211; sont assez \u00e9vidents et semblables \u00e0 ceux des fonctions de lecture. Vient ensuite un champ <em>cb<\/em> (pour <em>callback<\/em>) qui est un pointeur sur une fonction du type suivant.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void <em>set_value_callback<\/em> (void *arg);<\/pre>\n\n\n\n<p>Cette routine sera appel\u00e9e d\u00e8s que la ligne GPIO aura \u00e9t\u00e9 configur\u00e9e comme demand\u00e9e. Elle recevra en argument le param\u00e8tre <em>data<\/em> de <code>gpiod_ctxless_set_value()<\/code>.<\/p>\n\n\n\n<p>Il faut comprendre que l&rsquo;un des buts de la nouvelle API des GPIO est de ne jamais laisser une ligne GPIO de sortie active alors que le processus qui l&rsquo;a configur\u00e9e est termin\u00e9 depuis longtemps. Aussi d\u00e8s que la fonction <code>gpiod_ctxless_set_value()<\/code> se termine, le descripteur de fichier acc\u00e9dant \u00e0 la ligne GPIO est lib\u00e9r\u00e9 et la sortie est rel\u00e2ch\u00e9e, retournant \u00e0 un \u00e9tat de haute imp\u00e9dance.<\/p>\n\n\n\n<p>Ainsi, si l&rsquo;on essaye de faire clignoter une sortie en utilisant<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">for (;;) {<br\/>    gpiod_ctxless_set_value(argv[1], offset, value, 0,<br\/>                            argv[0], NULL, NULL);<br\/>    value = 1 - value;<br\/>    usleep(500000);<br\/>}\n<\/pre>\n\n\n\n<p>La sortie ne passera \u00e0 l&rsquo;\u00e9tat d\u00e9sir\u00e9 qu&rsquo;une br\u00e8ve fraction de seconde avant de revenir \u00e0 son \u00e9tat par d\u00e9faut.<\/p>\n\n\n\n<p>Si l&rsquo;on souhaite laisser la ligne en sortie pendant une dur\u00e9e d\u00e9termin\u00e9e, on peut employer une fonction <em>callback<\/em> d&rsquo;attente.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void <strong>output_delay<\/strong>(void *arg_delay)<br\/>{<br\/>    unsigned long delay = (unsigned long) arg_delay;<br\/><br\/>    <strong>usleep<\/strong>(delay);<br\/>}<br\/><\/pre>\n\n\n\n<p>Et basculer\u00a0 la sortie ainsi&nbsp;:<br\/><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">for (;;) {<br\/>  <strong>gpiod_ctxless_set_value<\/strong>(argv[1], offset, value, 0, argv[0],<br\/>                          <strong>output_delay<\/strong>, (void *)500000);<br\/>     value = 1 - value;<br\/>}<\/pre>\n\n\n\n<p>Dans le programme <a href=\"https:\/\/framagit.org\/cpb\/example-programs-using-libgpiod\/blob\/master\/toggle-gpio.c\" target=\"_blank\"><code>toggle-gpio.c<\/code><\/a> j&rsquo;ai m\u00eame pris un raccourci suppl\u00e9mentaire&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  <strong>gpiod_ctxless_set_value<\/strong>(argv[1], offset, value, 0, argv[0],<br\/>                    <strong>(void (*)(void *))usleep<\/strong>, (void *)500000);<\/pre>\n\n\n\n<p>Bien s\u00fbr, l&rsquo;approche avec une <em>callback<\/em> \u00e0 prolonger tant que la ligne doit rester active se complique si l&rsquo;on doit g\u00e9rer de nombreuses sorties, qui doivent fonctionner ind\u00e9pendamment les unes des autres pendant des dur\u00e9es non connues \u00e0 l&rsquo;avance. Pour cela on se tournera plut\u00f4t vers l&rsquo;API bas-niveau de Libgpiod.<\/p>\n\n\n\n<p>Il est \u00e9galement possible de superviser une ou plusieurs lignes GPIO pour d\u00e9tecter les changements d&rsquo;\u00e9tat en utilisant les fonctions suivantes&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int <strong>gpiod_ctxless_event_monitor<\/strong>(\n                        const char *<em>device<\/em>,\n                        int <em>event_type<\/em>,\n                        unsigned int <em>offset<\/em>,\n                        bool <em>active_low<\/em>,\n                        const char *<em>consumer<\/em>,\n                        const struct timespec *<em>timeout<\/em>,\n                        gpiod_ctxless_event_poll_cb <em>poll_cb<\/em>,\n                        gpiod_ctxless_event_handle_cb <em>event_cb<\/em>,\n                        void *<em>data<\/em>);\n\nint <strong>gpiod_ctxless_event_monitor_multiple<\/strong>(\n                       const char *<em>device<\/em>,\n                       int <em>event_type<\/em>,\n                       const unsigned int *<em>offsets<\/em>,\n                       unsigned int <em>num_lines<\/em>,\n                       bool <em>active_low<\/em>,\n                       const char *<em>consumer<\/em>,\n                       const struct timespec *<em>timeout<\/em>,\n                       gpiod_ctxless_event_poll_cb <em>poll_cb<\/em>,\n                       gpiod_ctxless_event_handle_cb <em>event_cb<\/em>,\n                       void *<em>data<\/em>);<\/pre>\n\n\n\n<p>Ces routines attendent sur une ou plusieurs lignes GPIO (d\u00e9crites par les champs <em>device<\/em>, <em>offset<\/em>, <em>active_low<\/em> et <em>consumer<\/em>) les \u00e9v\u00e9nements contenus dans le param\u00e8tre <em>event_type<\/em> :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>GPIOD_CTXLESS_EVENT_RISING_EDGE<\/code> : d\u00e9tection des mont\u00e9es de 0 \u00e0 1,<br\/><\/li><li><code>GPIOD_CTXLESS_EVENT_FALLING_EDGE<\/code> : d\u00e9tection des descentes de 1 \u00e0 0,<br\/><\/li><li><code>GPIOD_CTXLESS_EVENT_BOTH_EDGE<\/code> : d\u00e9tection de toutes les transitions.<br\/><\/li><\/ul>\n\n\n\n<p>Lorsque la transition attendue est d\u00e9tect\u00e9e, la routine invoque notre <em>callback<\/em> <code>event_cb()<\/code>, du type suivant&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int <strong>event_cb<\/strong>(int <em>event<\/em>, unsigned int <em>offset<\/em>, const struct timespec *<em>timestamp<\/em>, void *<em>arg<\/em>);<\/pre>\n\n\n\n<p>En lui passant en argument le type d&rsquo;\u00e9v\u00e9nement d\u00e9tect\u00e9, l&rsquo;offset de la ligne, l&rsquo;horodatage et le pointeur data que l&rsquo;on avait \u00e9ventuellement transmis en dernier argument de <code>gpiod_ctxless_event_monitor()<\/code>.<\/p>\n\n\n\n<p>Attention, l&rsquo;\u00e9v\u00e9nement d\u00e9tect\u00e9 est d&rsquo;un type diff\u00e9rent de celui utilis\u00e9 pour enregistrer le monitoring. Les constantes symboliques sont diff\u00e9rentes (<code>_CB_<\/code> en plus) et n&rsquo;ont pas les m\u00eames valeurs&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>GPIOD_CTXLESS_EVENT_CB_RISING_EDGE<\/code> : un front montant a \u00e9t\u00e9 observ\u00e9,<\/li><li><code>GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE<\/code> : un front descendant a \u00e9t\u00e9 observ\u00e9,<br\/><\/li><li><code>GPIOD_CTXLESS_EVENT_CB_TIMEOUT<\/code> : il ne s&rsquo;est rien pass\u00e9 pendant la dur\u00e9e de timeout indiqu\u00e9e \u00e0 <code>gpiod_ctxless_event_monitor()<\/code>.<\/li><\/ul>\n\n\n\n<p>La fonction <em>callback<\/em> doit ensuite renvoyer <code>GPIOD_CTXLESS_EVENT_CB_RET_OK<\/code> s&rsquo;il faut continuer le monitoring, <code>GPIOD_CTXLESS_EVENT_CB_RET_STOP<\/code> s&rsquo;il faut l&rsquo;arr\u00eater sur une condition normale et <code>GPIOD_CTXLESS_EVENT_CB_RET_ERR<\/code> s&rsquo;il faut l&rsquo;arr\u00eater en indiquant une erreur.<\/p>\n\n\n\n<p>Pour se mettre en attente passive des changements d&rsquo;\u00e9tat la fonction <code>gpiod_ctxless_event_monitor()<\/code> invoque la callback <code>poll_cb()<\/code> qu&rsquo;on lui passe en argument ou utilise la routine syst\u00e8me <code>ppoll()<\/code> si cet argument <code>poll_cb<\/code> est <code>NULL<\/code>.<\/p>\n\n\n\n<p>Le programme <a href=\"https:\/\/framagit.org\/cpb\/example-programs-using-libgpiod\/blob\/master\/monitor-gpio.c\" target=\"_blank\"><code>monitor-gpio.c<\/code><\/a> surveille une ligne d&rsquo;entr\u00e9e GPIO indiqu\u00e9e en argument.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ <strong>.\/monitor-gpio \"0\" 23<\/strong>\n[1540133528.306326156] rising\n[1540133528.496137727] falling\n[1540133528.728579646] rising\n[1540133528.914934773] falling\n[1540133529.442333930] rising\n[1540133529.820589058] falling\n[1540133530.189641150] rising\n[1540133530.442220331] falling<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">CONCLUSION<\/h2>\n\n\n\n<p>Nous voyons ici une API \u00e9l\u00e9gante et relativement simple pour piloter des lignes GPIO. Le seul b\u00e9mol \u00e0 mon avis est la n\u00e9cessit\u00e9 d&rsquo;utiliser des callbacks pour maintenir les sorties actives. Ceci peut compliquer sensiblement l&rsquo;\u00e9criture d&rsquo;une application g\u00e9rant de multiples lignes et l&rsquo;on pr\u00e9f\u00e9rera sans doute utiliser la version bas-niveau de Libgpiod, que nous aborderons dans <a href=\"https:\/\/www.blaess.fr\/christophe\/2018\/10\/29\/pilotage-de-gpio-avec-lapi-libgpiod-partie-3\/\">le prochain article<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">R\u00c9F\u00c9RENCES<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>[GOLASZEWSKI 2017] Bartosz Golaszewsi&nbsp;: Doxygen documentation of <a href=\"https:\/\/git.kernel.org\/pub\/scm\/libs\/libgpiod\/libgpiod.git\/tree\/include\/gpiod.h\" target=\"_blank\">gpiod.h<\/a>.<\/li><li>[GOLASZEWSKI 2018] Bartosz Golaszewski&nbsp;: \u00ab\u00a0<a href=\"https:\/\/www.youtube.com\/watch?v=XVFht4MkIl4\" target=\"_blank\" rel=\"noopener\">New GPIO Interface for User Space (video)<\/a>\u00a0\u00bb <em>Kernel Recipes<\/em> 2018.<\/li><li>[WALLEIJ 2016] Linus Walleij&nbsp;: \u00ab\u00a0<a href=\"https:\/\/elinux.org\/images\/9\/9b\/GPIO_for_Engineers_and_Makers.pdf\" target=\"_blank\" rel=\"noopener\">GPIO for Engineers and Makers<\/a>\u00ab\u00a0, Las Vegas 2016.<\/li><\/ul>","protected":false},"excerpt":{"rendered":"<p>Dans l&rsquo;article pr&eacute;c&eacute;dent de cette s&eacute;rie, nous avons examin&eacute; les commandes disponibles au niveau du shell pour piloter des lignes GPIO avec la nouvelle API propos&eacute;e par le noyau Linux. Nous allons &agrave; pr&eacute;sent nous int&eacute;resser &agrave; l&rsquo;acc&egrave;s depuis un programme C\/C++ en utilisant cette API.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,19,8,11],"tags":[],"class_list":["post-5224","post","type-post","status-publish","format-standard","hentry","category-embarque","category-kernel","category-linux-2","category-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/5224","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=5224"}],"version-history":[{"count":33,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/5224\/revisions"}],"predecessor-version":[{"id":5363,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/5224\/revisions\/5363"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=5224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=5224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=5224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}