{"id":2863,"date":"2012-09-03T10:00:55","date_gmt":"2012-09-03T09:00:55","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=2863"},"modified":"2016-06-30T07:32:45","modified_gmt":"2016-06-30T06:32:45","slug":"rs-485-half-duplex-sous-linux","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2012\/09\/03\/rs-485-half-duplex-sous-linux\/","title":{"rendered":"RS-485 Half-duplex sous Linux"},"content":{"rendered":"<p style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/2012\/09\/03\/rs-485-half-duplex-sous-linux\/\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-full wp-image-2895\" title=\"RS-485 half-duplex sous Linux\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/09\/2012-09-03.png\" alt=\"RS-485 half-duplex sous Linux\" width=\"150\" height=\"150\" \/><\/a>La norme RS-485 (EIA-485 de son nom officiel) d\u00e9finit des communications s\u00e9ries rappelant celles de la norme RS-232 bien connue mais disposant de beaucoup moins de signaux de contr\u00f4le. Son utilisation sous Linux est un peu plus compliqu\u00e9e que ce que je pensais initialement.<\/p>\n<p>\n<!--more-->\n<\/p>\n<h1 style=\"text-align: justify;\">RS-485<\/h1>\n<p style=\"text-align: justify;\">Dans la panoplie des liaisons s\u00e9ries couramment utilis\u00e9es dans le monde industriel, il existe essentiellement 3 normes. L&rsquo;EIA-232 (la liaison s\u00e9rie RS-232 classique, que l&rsquo;on rencontre encore sur la plupart des PC industriels) utilisant trois fils pour transporter les donn\u00e9es et jusqu&rsquo;\u00e0 six fils pour les signaux de contr\u00f4le de la communication, l&rsquo;EIA-422 (ou RS-422) qui permet de dialoguer sur quatre fils en utilisant des signaux diff\u00e9rentiels, peu sensibles aux parasites, et l&rsquo;EIA-485 (RS-485) qui est une version proche de la RS-422 capable de fonctionner en mode <em>full-duplex<\/em> (\u00e9mission et r\u00e9ception simultan\u00e9es) sur quatre fils ou en mode <em>half-duplex<\/em> (\u00e9mission et r\u00e9ception successives mais pas simultan\u00e9es) sur deux fils. C&rsquo;est cette derni\u00e8re version qui m&rsquo;int\u00e9resse ici.<\/p>\n<p style=\"text-align: justify;\">J&rsquo;ai eu r\u00e9cemment \u00e0 faire \u00e0 un PC industriel comportant six ports s\u00e9rie dont deux pr\u00e9vus pour communiquer en RS-485 et les quatre autres en RS-232. Il \u00e9tait tous accessibles via des connecteurs DE-9 habituels num\u00e9rot\u00e9s de 1 \u00e0 6. Je devais installer une application qui recevait des donn\u00e9es sur l&rsquo;un des ports RS-485. Ceci ne fonctionnait pas, alors que les tests sur des ports RS-232 r\u00e9ussissaient parfaitement.<\/p>\n<p style=\"text-align: justify;\">Il faut savoir que contrairement \u00e0 la norme RS-232, il n&rsquo;existe aucun standard de c\u00e2blage du connecteur DE-9 pour la RS-485. Chaque constructeur propose sa propre disposition des signaux. Non sans mal, j&rsquo;ai pu obtenir le sch\u00e9ma chez le fournisseur du mat\u00e9riel (<em>Unicorn Computer Corp.<\/em>). \u00c0 titre indicatif, le voici:<\/p>\n<p><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/08\/Sch\u00e9ma-DB-9-RS-485.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2869\" title=\"Sch\u00e9ma DB-9 RS-485\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/08\/Sch\u00e9ma-DB-9-RS-485.png\" alt=\"Sch\u00e9ma DB-9 RS-485\" width=\"231\" height=\"80\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Simple, n&rsquo;est-ce pas&nbsp;? Et pourtant rien ne marchait. Lorsque mon application d\u00e9marrait, je voyais \u00e0 l&rsquo;oscilloscope varier la tension de r\u00e9f\u00e9rence des broches 1 et 3, mais je ne recevais aucune donn\u00e9e, alors qu&rsquo;un flux d&rsquo;informations transitait entre les lignes Data, bien visible sur l&rsquo;\u00e9cran de l&rsquo;oscilloscope. Ce qui \u00e9tait d&rsquo;autant plus \u00e9nervant, c&rsquo;est qu&rsquo;une autre application utilisait en \u00e9mission le port RS-485 d&rsquo;un autre PC du m\u00eame type sans aucun souci.<\/p>\n<h1 style=\"text-align: justify;\">Analyse du probl\u00e8me<\/h1>\n<p style=\"text-align: justify;\">Apr\u00e8s avoir retourn\u00e9 les \u00e9l\u00e9ments du probl\u00e8me pendant longtemps, accusant successivement mon application,\u00a0 le c\u00e2blage, la configuration des ports dans le Bios, et m\u00eame les drivers kernel, j&rsquo;ai essay\u00e9 de reprendre l&rsquo;historique des programmes que j&rsquo;avais d\u00e9j\u00e0 d\u00e9velopp\u00e9s utilisant des ports RS-485. Quelques-uns me sont revenus.<\/p>\n<ul>\n<li style=\"text-align: justify;\">un outil d&rsquo;enregistrement et d&rsquo;analyse de trames de g\u00e9olocalisation sous Ms-Dos recevant des donn\u00e9es depuis une radio sur un port s\u00e9rie RS-485,<\/li>\n<li style=\"text-align: justify;\">une application Linux recevant des alarmes en UDP\/IP et les renvoyant vers un central en RS-485,<\/li>\n<li style=\"text-align: justify;\">un outil sous Ms-Dos de centralisation d&rsquo;alarmes avec enregistrement et impression,<\/li>\n<li style=\"text-align: justify;\">un simulateur sous Linux qui envoyait des donn\u00e9es de positionnement de v\u00e9hicules depuis des sc\u00e9narios construits par l&rsquo;utilisateur,<\/li>\n<li style=\"text-align: justify;\">cette application sous Linux, enfin, qui supervise un chantier en recevant des trames radio par port RS-485 (et qui ne marchait pas).<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Pour les applications qui fonctionnaient sous Linux, rien de sp\u00e9cifique n&rsquo;avait \u00e9t\u00e9 n\u00e9cessaire par rapport \u00e0 un port s\u00e9rie RS-232. Il suffisait d&rsquo;ouvrir le port <code>\/dev\/ttyS<em>xxx<\/em><\/code>, fixer une vitesse de fonctionnement, une parit\u00e9 etc. \u00e0 l&rsquo;aide de la fonction <code>tcsetattr()<\/code> et d&rsquo;effectuer des \u00e9critures sur le port en question. Je me suis aper\u00e7u alors que les programmes qui fonctionnaient sous Linux faisaient tous de l&rsquo;<em>\u00e9mission<\/em> de donn\u00e9es. Ceux qui recevaient des informations depuis un port RS-485 avaient toujours tourn\u00e9 sous Ms-Dos, en utilisant une API un peu diff\u00e9rente, bas\u00e9e sur une biblioth\u00e8que sp\u00e9cifique pour cette interface. Il devait s\u00fbrement y avoir une op\u00e9ration particuli\u00e8re \u00e0 r\u00e9aliser pour mettre le port en \u00e9coute ou en diffusion. Et pourtant, m\u00eame avec cet indice, il m&rsquo;a fallu un long moment avant de trouver les informations n\u00e9cessaires.<\/p>\n<h1>Explication<\/h1>\n<p style=\"text-align: justify;\">Le port RS-485 fonctionnant en mode <em>half-duplex<\/em>, il faut disposer d&rsquo;un moyen de choisir entre l&rsquo;\u00e9mission ou la r\u00e9ception de donn\u00e9es. \u00c9lectriquement ceci est g\u00e9r\u00e9 par un \u00e9tat dit <em>idle<\/em>, inactif, o\u00f9 les deux signaux D+ et D- sont tous deux \u00e0 z\u00e9ro. Le protocole inclut une gestion des collisions (si les deux extr\u00e9mit\u00e9s essayent de parler en m\u00eame temps). Toutefois le basculement de la lecture \u00e0 l&rsquo;\u00e9criture doit \u00eatre pris en charge par les couches hautes du protocole.<\/p>\n<p style=\"text-align: justify;\">Sur la plupart des PC incluant des ports RS-485, se trouvent en r\u00e9alit\u00e9 install\u00e9s des convertisseurs qui emploient les signaux RS-232 (ramen\u00e9s dans l&rsquo;intervalle [0-5V] ou [0-3,3V]) issus de la carte m\u00e8re. Et pour savoir dans quel sens les donn\u00e9es doivent transiter, ces convertisseurs emploient le signal RTS (<em>Request To Send<\/em>) qui est activ\u00e9 lorsque le port doit \u00e9mettre des donn\u00e9es et inhib\u00e9 sinon.<\/p>\n<p style=\"text-align: justify;\">Le probl\u00e8me est que d\u00e8s l&rsquo;ouverture d&rsquo;un port s\u00e9rie, Linux active le signal RTS. M\u00eame si l&rsquo;ouverture a lieu en lecture seule&nbsp;! V\u00e9rifions-le \u00e0 l&rsquo;aide du petit programme suivant qui ouvre un port sp\u00e9cifi\u00e9 en argument en lecture seule, puis attend ind\u00e9finiment.<\/p>\n<p style=\"text-align: justify;\">Ce test a lieu sur un port RS-232, afin que le signal RTS soit visible sur le connecteur DB-9 de sortie.<\/p>\n<pre><strong>test-open.c:<\/strong>\n\n#include &lt;fcntl.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;termios.h&gt;\n#include &lt;unistd.h&gt;\n\nint main (int argc, char * argv[])\n{\n    int    fd_port;\n    struct termios parametres;\n\n    if (argc &lt; 2) {\n        fprintf(stderr, \"usage: %s &lt;serial-port&gt;n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    fd_port = open(argv[1], O_RDONLY | O_NONBLOCK);\n    if (fd_port &lt; 0) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n    if (tcgetattr(fd_port, &amp; parametres) != 0) {\n        perror(\"tcgetattr\");\n        exit(EXIT_FAILURE);\n    }\n    cfmakeraw(&amp; parametres);\n    parametres.c_iflag  = 0;\n    parametres.c_oflag  = 0;\n    parametres.c_cflag |= CLOCAL;\n\/\/  parametres.c_cflag &amp;= ~CLOCAL;\n    parametres.c_cflag |= CRTSCTS;\n\/\/  parametres.c_cflag &amp;= ~CRTSCTS;\n    if (tcsetattr(fd_port, TCSANOW, &amp; parametres) != 0) {\n        perror(\"tcsetattr\");\n        exit(EXIT_FAILURE);\n    }\n    pause();\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">On peut jouer \u00e0 activer et d\u00e9sactiver les options <code>CLOCAL<\/code> (ignorer les signaux de contr\u00f4le du modem) et <code>CRTSCTS<\/code> (g\u00e9rer les signaux RTS\/CTS), rien n&rsquo;y fait&nbsp;: dans tous les cas le signal RTS est activ\u00e9 d\u00e8s l&rsquo;ouverture du port, m\u00eame en lecture seule. Pour le v\u00e9rifier, nous mesurons la tension entre les broches 5 (Gnd) et 7 (Rts) du connecteur DE-9.<\/p>\n<p><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/08\/capture-01.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-2880\" title=\"Signal RTS actif sur port RS-232\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/08\/capture-01-300x292.jpg\" alt=\"Signal RTS actif sur port RS-232\" width=\"300\" height=\"292\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Nous mesurons une tension de 6.88 volts, ce qui correspond \u00e0 l&rsquo;activation de RTS suivant le standard RS-232 (qui tol\u00e8re entre +3 et +15 V pour l&rsquo;activation d&rsquo;une ligne de contr\u00f4le).<\/p>\n<h1>Solution<\/h1>\n<p style=\"text-align: justify;\">Revenons \u00e0 notre liaison RS-485 half-duplex. Nous devons contr\u00f4ler manuellement le signal RTS, afin de pouvoir l&rsquo;inhiber lorsque nous d\u00e9sirons \u00e9crire et l&rsquo;activer lorsqu&rsquo;on veut lire des donn\u00e9es. Pour cela, nous allons faire appel \u00e0 un <code>ioctl()<\/code>.<\/p>\n<p style=\"text-align: justify;\">Il existe deux num\u00e9ros d&rsquo;<em>ioctl<\/em> susceptibles de nous int\u00e9resser&nbsp;: <code>TIOCMGET<\/code> qui permet de lire l&rsquo;\u00e9tat des signaux de contr\u00f4le (le <code>M<\/code> indiquant qu&rsquo;il s&rsquo;agit de signaux destin\u00e9s aux modems) et <code>TIOCMSET<\/code> qui permet d&rsquo;\u00e9crire leur nouvel \u00e9tat. Naturellement certains signaux seront uniquement accessibles en lecture. Les noms symboliques associ\u00e9s aux signaux sont les suivants.<\/p>\n<ul>\n<li style=\"text-align: justify;\"><code>TIOCM_CTS<\/code> (<em>Clear to send<\/em>) nous avons l&rsquo;autorisation d&rsquo;\u00e9crire,<\/li>\n<li style=\"text-align: justify;\"><code>TIOCM_DCD<\/code> (<em>Data Carier Detect<\/em>) la porteuse est pr\u00e9sente sur le modem,<\/li>\n<li style=\"text-align: justify;\"><code>TIOCM_DSR<\/code> (<em>Data Set Ready<\/em>) notre correspondant est disponible,<\/li>\n<li style=\"text-align: justify;\"><code>TIOCM_DTR<\/code> (<em>Data Terminal Ready<\/em>) nous sommes pr\u00eats \u00e0 recevoir,<\/li>\n<li style=\"text-align: justify;\"><code>TIOCM_RI<\/code> (<em>Ring Indicator<\/em>) appel entrant sur le modem,<\/li>\n<li style=\"text-align: justify;\"><code>TIOCM_RTS<\/code> (<em>Request to Send<\/em> nous voulons \u00e9crire.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Les signaux que nous pouvons configurer sont donc RTS et DTR. Le programme suivant agit sur RTS en le basculant au niveau bas apr\u00e8s ouverture du port.<\/p>\n<pre><strong>test-open-rts-bas.c<\/strong>\n\n#include &lt;fcntl.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;termios.h&gt;\n#include &lt;unistd.h&gt;\n\n#include &lt;sys\/ioctl.h&gt;\n\nvoid set_rts (int fd, int actif);\n\nint main (int argc, char * argv[])\n{\n    int    fd_port;\n    struct termios parametres;\n\n    if (argc &lt; 2) {\n        fprintf(stderr, \"usage: %s &lt;serial-port&gt;n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    fd_port = open(argv[1], O_RDONLY | O_NONBLOCK);\n    if (fd_port &lt; 0) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n    if (tcgetattr(fd_port, &amp; parametres) != 0) {\n        perror(\"tcgetattr\");\n        exit(EXIT_FAILURE);\n    }\n    cfmakeraw(&amp; parametres);\n    parametres.c_iflag  = 0;\n    parametres.c_oflag  = 0;\n    parametres.c_cflag |= CLOCAL;\n    parametres.c_cflag &amp;= ~CRTSCTS;\n    if (tcsetattr(fd_port, TCSANOW, &amp; parametres) != 0) {\n        perror(\"tcsetattr\");\n        exit(EXIT_FAILURE);\n    }\n    <strong>set_rts(fd_port, 0);<\/strong>\n    pause();\n    return EXIT_SUCCESS;\n}\n\nvoid set_rts (int fd, int actif)\n{\n    int bits;\n    <strong>ioctl(fd, TIOCMGET, &amp; bits);<\/strong>\n\n    if (actif)\n        bits |= TIOCM_RTS;\n    else\n        bits &amp;= ~TIOCM_RTS;\n    <strong>ioctl(fd, TIOCMSET, &amp; bits);<\/strong>\n}<\/pre>\n<p style=\"text-align: justify;\">Ex\u00e9cutons-le \u00e0 nouveau sur un port RS-232 afin de surveiller le signal RTS.<br \/>\n<a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/08\/capture-02.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-2882\" title=\"Signal RTS inactif sur port RS-232\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/08\/capture-02-300x255.jpg\" alt=\"Signal RTS inactif sur port RS-232\" width=\"300\" height=\"255\" \/><\/a><br \/>\nCette fois la tension est de -6.51V, ce qui est interpr\u00e9t\u00e9 suivant le standard RS-232 comme une d\u00e9sactivation de RTS (les valeurs dans l&rsquo;intervalle [-15V, -3V] sont consid\u00e9r\u00e9es comme des d\u00e9sactivation pour les lignes de contr\u00f4le).<\/p>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">En agissant directement sur le signal RTS d&rsquo;un port s\u00e9rie gr\u00e2ce \u00e0 un <em>ioctl<\/em>, il est possible d&rsquo;indiquer si ce port veut \u00e9mettre, et ainsi de s\u00e9lectionner le sens de fonctionnement dans le cas de communication <em>half-duplex<\/em> comme c&rsquo;est le cas sur un port RS-485.<\/p>","protected":false},"excerpt":{"rendered":"<p>La norme RS-485 (EIA-485 de son nom officiel) d&eacute;finit des communications s&eacute;ries rappelant celles de la norme RS-232 bien connue mais disposant de beaucoup moins de signaux de contr&ocirc;le. Son utilisation sous Linux est un peu plus compliqu&eacute;e que ce que je pensais initialement.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,10],"tags":[],"class_list":["post-2863","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-microprocesseur"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/2863","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=2863"}],"version-history":[{"count":3,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/2863\/revisions"}],"predecessor-version":[{"id":4532,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/2863\/revisions\/4532"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=2863"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=2863"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=2863"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}