libmodbuspp

Un wrapper C++ pour la librairie libmodbus

libmodbus est une librairie opensource pour envoyer/recevoir données selon le protocole MODBUS. Cette librairie est écrite en C et supporte Communications RTU(série) et TCP(Ethernet).

La librairie libmodbuspp fournit une surcouche C++ pour libmodbus, un wrapper n’ayant pas d’autre dépendance que libmodbus et libstdc++.

Cet article fait suite à celui présentant l’utilisation de MODBUS sur une carte Nano Pi.

Un bon exemple étant toujours meilleur qu’une longue explication, voilà un très exemple simple:


uint16_t values[2]; // array to store the values of the input registers
Master mb (Rtu, "COM1", "19200E1"); // new master on RTU
mb.open(); // open a connection
Slave & slv = mb.addSlave (33); // to the slave at address 33
slv.readInputRegisters (1, values, 2);
cout << values[0] << endl;
cout << values[1] << endl;

Cet exemple lit les registres d’entrée numéro 1 et 2 de l’esclave MODBUS (serveur) à l’adresse 33.

Guide de démarrage rapide

Installation

Le moyen le plus rapide et le plus sûr d’installer libmodbuspp sur Debian, Armbian, Raspbian… est d’utiliser le dépôt APT de piduino.org, voilà comment s’y prendre :

wget -O- http://www.piduino.org/piduino-key.asc | sudo apt-key add -
sudo add-apt-repository 'deb http://apt.piduino.org stretch piduino'
sudo apt update
sudo apt install libmodbuspp-dev libmodbuspp-doc 

Ce dépôt fournit les packages libmodbus pour les architectures i386,amd64, armhf et arm64. Dans les commandes ci-dessus, le dépôt est une distribution Debian Stretch, mais vous pouvez également choisir Ubuntu Trusty, Xenial ou Bionic en remplaçant stretch par trusty, xenial ou bionic.
Il peut être nécessaire d’installer le paquet software-properties-common si la commande add-apt-repository n’est pas disponible.

Pour Raspbian, vous devez faire un peu différent:

wget -O- http://www.piduino.org/piduino-key.asc | sudo apt-key add -
echo 'deb http://raspbian.piduino.org stretch piduino' | sudo tee /etc/apt/sources.list.d/piduino.list
sudo apt update
sudo apt install libmodbuspp-dev libmodbuspp-doc

Le dépôt Raspbian fournit des packages Piduino pour l’architecture armhf uniquement pour Stretch.

Si vous voulez construire à partir de sources, vous pouvez suivre le Wiki.

Exemple

Voici un exemple complet qui peut être compilé sans erreur :

#include <iostream>
#include <string>
#include <modbuspp.h>

using namespace std;
using namespace Modbus;

int main (int argc, char **argv) {
  string port ("/dev/ttyUSB0");

  if (argc > 1) {

    port = argv[1]; // the serial port can be provided as a parameter on the command line.
  }

  Master mb (Rtu, port , "38400E1"); // new master on RTU
  // if you have to handle the DE signal of the line driver with RTS,
  // you should uncomment the lines below...
  // mb.rtu().setRts(RtsDown);
  // mb.rtu().setSerialMode(Rs485);

  Slave slv (mb.addSlave (33)); // SolarPi Pressure meter

  cout << "Reads input registers of slave[" << slv.slave() << "] on " <<
       mb.backend().connection() << " (" << mb.backend().settings() << ")" << endl;

  if (mb.open ()) { // open a connection
    // success, do what you want here
    uint16_t values[2];

    if (slv.readInputRegisters (1, values, 2) == 2) {

      cout << "R0=" << values[0] << endl;
      cout << "R1=" << values[1] << endl;
    }
    else {
      cerr << "Unable to read input registers ! "  << mb.lastError() << endl;
      exit (EXIT_FAILURE);
    }
    mb.close();
  }
  else {
    cerr << "Unable to open MODBUS connection to " << port << " : " << mb.lastError() << endl;
    exit (EXIT_FAILURE);
  }

  return 0;
}

Entrez le texte de ce programme avec votre éditeur de texte préféré et enregistrez le fichier dans main.cpp

Pour construire, vous devez taper la commande:

g++ -o read-input-registers main.cpp $(pkg-config --cflags --libs libmodbuspp)

Vous pouvez ensuite l’exécuter:

./read-input-registers 
R0=9964
R1=10029

Vous trouverez plusieurs exemples dans le dossier /usr/share/doc/modbuspp/examples

Avec Codelite ce sera beaucoup plus facile et amusant car l’installation du paquet libmodbuspp-dev ajoute un modèle de projet modbuspp à Codelite.

Débogage avec Codelite

Pour créer un nouveau programme modbuspp dans Codelite, il faut, une fois votre workspace créé, utiliser le menu Workspace/New Project et sélectionner le modèle Simple Executable (C++ MODBUSPP) :

Documentation

Le paquet libmodbuspp-doc fournit de la documentation.

Les classes fournies par la librairie sont documentées par les pages de man:

  • La page Modbus_Master pour la classe Modbus::Master
  • La page Modbus_Data pour la classe Modbus::Data
  • La page Modbus_RtuLayer pour la classe Modbus::RtuLayer
  • La page Modbus_TcpLayer pour la classe Modbus::TcpLayer
  • La page Modbus_Timeout pour la classe Modbus::Timeout

L’API complète est documentée dans le dossier /usr/share/doc/modbuspp/api-manual/index.html

À propos de Modbus

MODBUS est un protocole de messagerie de la couche application, fournissant Communication maître/esclave entre appareils connectés entre eux par des bus ou des réseaux. Sur le modèle OSI, MODBUS est positionné au niveau 7. MODBUS est destiné à être un protocole de requête/réponse et fournit les services spécifiés par les codes de fonction. Les codes de fonction de MODBUS sont des éléments requête/réponse de MODBUS ou PDU (Unité de données de protocole).

Utilisation de MODBUS sur NanoPi

MODBUS est un protocole de communication non-propriétaire, créé en 1979 par Modicon, utilisé pour des réseaux d’automates programmables, relevant du niveau applicatif, c’est-à-dire du niveau 7 du Modèle OSI. Ce protocole basé sur une structure hiérarchisée entre un client unique et plusieurs serveurs est dans le domaine public et sa spécification est publique.

Ce protocole a rencontré beaucoup de succès depuis sa création du fait de sa simplicité et de sa bonne fiabilité. Il repose sur un protocole maître-esclave.

Le protocole MODBUS peut être implémenté :

  • sur une liaison série asynchrone de type RS-232, RS-422 ou RS-485 ou TTY (boucle de courant), avec des débits et sur des distances variables ; on parle alors de MODBUS over Serial Line;
  • via TCP/IP sur Ethernet ; on parle alors de MODBUS over TCP/IP ; le port logiciel 502 est dédié à ce protocole
  • via ModBus Plus. ModBus Plus est un réseau à passage de jetons à 1 Mb/s, pouvant transporter les trames Modbus et d’autres services propre à ce réseau.

Une liaison multipoints de type RS-485 relie client et serveurs via une paire différentielle qui permet un débit élevé (jusqu’à 10 méga-bits par seconde) sur une distance importante (jusqu’à 1 200 m). Elle ne dispose que de 2 bornes qui alternativement passent les données dans un sens puis dans l’autre.

Sur une liaison série asynchrone, le codage des caractères peut être en binaire (RTU) ou en ASCII (plus lent !).

Le NanoPi est probablement le plus récent des concurrents du Raspberry Pi. FriendlyARM mise sur les prix et la compacité.

Dans cet article nous allons apprendre à contrôler des esclaves MODBUS à partir d’un NanoPi Neo utilisant une liaison RS485 (en RTU).

Le NanoPi utilise un système Armbian en version 5.41 (kernel 4.14.18), c’est important de la savoir car la configuration des liaisons série (UART) sur les noyaux à partir de la version 4 est totalement différente des précédents noyaux en version 3.4.

Le matériel

Nous avons donc besoin d’un module supplémentaire afin d’adapter les signaux de l’UART du NanoPi au RS485. Il faut qu’il soit alimenté en 3.3V, tension utilisée par tous les signaux du GPIO.

Nous pouvons choisir celui de Sparkfun qui utilise un circuit SP3485. On trouve des clones de ce module sur le site AliExpress pour quelques centimes d’euros.

Nous choisissons d’utiliser l’UART1 du NanoPi. Nous utiliserons donc les signaux:

  • UART1_TX (PG6, broche 8 du GPIO) qui sera relié au signal DI (Driver Input) du SP3485 (RX-I sur le module Sparkfun)
  • UART1_RX (PG7, broche 10 du GPIO) qui sera relié au signal RO (Receiver Output) du SP3485 (TX-O sur le module Sparkfun)
  • UART1_RTS (PG8, broche 16 du GPIO) qui sera relié aux signaux DE (Driver Enable) et /RE (Receiver Enable) du SP3485 (rélié ensemble à RTS sur le module Sparkfun). Ce signal est très important car il assure le multiplexage half-duplex sur la paire différentielle.
  • GND (broche 6 du GPIO) et 3.3V (broche 1 du GPIO) qui seront reliés respectivement aux broches GND et 3-5V sur le module Sparkfun.

La connexion du ou des esclaves MODBUS se fera conformément au standard RS485. Il convient de bien repéré la polarité de la paire différentielle (brancher à l’envers: ça ne marche pas ! hein Mickaël). Comme le décrit cet article, ce problème est loin d’être simple. En effet, certains fabricant repère leurs broches par A et B, mais ne respectent par la standard RS485.

C’est la raison pour laquelle, il est préférable de raisonner en borne + et borne -. Sur la ligne, toutes les bornes + de la paire différentielles seront reliées ensemble (idem pour le -). L’inversion de polarité est la cause de « panne » la plus courante sur un bus RS485, on vérifiera donc systématiquement ce point (en vérifiant le schéma interne de l’esclave si nécessaire) en cas de dysfonctionnement.

Une autre cause de dysfonctionnement sur RS485 est l’absence de résistances de terminaison et/ou de polarisation. Pour que la laison fonctionne dans de bonnes conditions, il faut que la paire différentielle:

  • soit terminée à chaque extrémité par une résistance de terminaison de 120 ohms (dépend de l’impédance caracttistique du câble)
  • soit polarisée en l’absence de signal par une résistante reliant le VCC à la borne + et une résistance reliant le GND à la borne – (voir cet article )

Cablage RS485

Configuration du NanoPi

Il est nécessaire de valider l’UART1 et d’activer le signal RTS sur la NanoPi.

Pour valider l’UART1, il suffit de lancer Armbian-config:

$ sudo armbian-config

Il faut ensuite chosir le menu System puis Hardware et valider uart1. Il faudra accepter le redémarrage proposé…

Armbian-config

Pour activer le signal RTS, il faut modifier le fichier /boot/armbianEnv.txt :

$ sudo nano /boot/armbianEnv.txt

On y ajoutera la ligne :

param_uart1_rtscts=1

Puis il faut redémarrer le NanoPi.

La liaison série de l’UART1 est accessible par le fichier /dev/ttyS1

Le logiciel

Nous utiliserons le logiciel mbpoll en version 1.4.

mbpoll est un utilitaire en ligne de commande permettant de dialoguer avec des esclaves ModBus RTU ou TCP.
Il utilise libmodbus.

mbpoll permet de:

  • lire des entrées discrètes
  • lire et écrire des sorties binaires (coil)
  • lire des registres d’entrée (input register)
  • lire et écrire des registres de sortie (holding register)

La lecture et l’écriture de registres peut se faire au format décimal, hexadécimal ou flottant simple précision.

Installation de mbpoll

Le moyen le plus rapide et le plus sûr d’installer mbpoll consiste à utiliser le référentiel APT de piduino.org. Vous devez donc procéder comme suit:

wget -O- http://www.piduino.org/piduino-key.asc | sudo apt-key add -
sudo add-apt-repository 'deb http://apt.piduino.org stretch piduino'
sudo apt update
sudo apt install mbpoll

Ce référentiel fournit les packages mbpoll et libmodbus (version 3.1.4) pour les architectures i386, amd64, armhf et arm64. Dans les commandes ci-dessus, le référentiel est une distribution Debian Stretch, mais vous pouvez également choisir Ubuntu Xenial en remplaçant stretch par xenial.

Des explications plus complètes sur l’installation et la compilation sont disponibles sur le site de mbpoll.

Utilisation de mbpoll

mbpoll dispose d’une aide en ligne avec l’option -h :

$ mbpoll -h
usage : mbpoll [ options ] device|host [ writevalues... ] [ options ]

ModBus Master Simulator. It allows to read and write in ModBus slave registers
                         connected by serial (RTU only) or TCP.

Arguments :
  device        Serial port when using ModBus RTU protocol
                  COM1, COM2 ...              on Windows
                  /dev/ttyS0, /dev/ttyS1 ...  on Linux
                  /dev/ser1, /dev/ser2 ...    on QNX
  host          Host name or dotted IP address when using ModBus/TCP protocol
  writevalues   List of values to be written.
                If none specified (default) mbpoll reads data.
                If negative numbers are provided, it will precede the list of
                data to be written by two dashes ('--'). for example :
                mbpoll -t4:int /dev/ttyUSB0 -- 123 -1568 8974 -12
General options : 
  -m #          mode (rtu or tcp, TCP is default)
  -a #          Slave address (1-255 for rtu, 0-255 for tcp, 1 is default)
                for reading, it is possible to give an address list
                separated by commas or colons, for example :
                -a 32,33,34,36:40 read [32,33,34,36,37,38,39,40]
  -r #          Start reference (1 is default)
  -c #          Number of values to read (1-125, 1 is default)
  -u            Read the description of the type, the current status, and other
                information specific to a remote device (RTU only)
  -t 0          Discrete output (coil) data type (binary 0 or 1)
  -t 1          Discrete input data type (binary 0 or 1)
  -t 3          16-bit input register data type
  -t 3:hex      16-bit input register data type with hex display
  -t 3:int      32-bit integer data type in input register table
  -t 3:float    32-bit float data type in input register table
  -t 4          16-bit output (holding) register data type (default)
  -t 4:hex      16-bit output (holding) register data type with hex display
  -t 4:int      32-bit integer data type in output (holding) register table
  -t 4:float    32-bit float data type in output (holding) register table
  -0            First reference is 0 (PDU addressing) instead 1
  -B            Big endian word order for 32-bit integer and float
  -1            Poll only once only, otherwise every poll rate interval
  -l #          Poll rate in ms, ( > 100, 1000 is default)
  -o #          Time-out in seconds (0.01 - 10.00, 1.00 s is default)
Options for ModBus / TCP : 
  -p #          TCP port number (502 is default)
Options for ModBus RTU : 
  -b #          Baudrate (1200-921600, 19200 is default)
  -d #          Databits (7 or 8, 8 for RTU)
  -s #          Stopbits (1 or 2, 1 is default)
  -P #          Parity (none, even, odd, even is default)
  -R [#]        RS-485 mode (/RTS on (0) after sending)
                 Optional parameter for the GPIO RTS pin number
  -F [#]        RS-485 mode (/RTS on (0) when sending)
                 Optional parameter for the GPIO RTS pin number

  -h            Print this help summary page
  -V            Print version and exit
  -v            Verbose mode.  Causes mbpoll to print debugging messages about
                its progress.  This is helpful in debugging connection...

Par exemple si l’on souhaite lire les 2 premiers regsitres d’entrée (1 et 2 donc) d’un esclave à l’adresse décimale 33 connecté en RS485 avec un baudrate de 38400 et une période d’interrogation de 500 millisecondes :

$ mbpoll -a33 -b38400 -t3 -c2 -R  /dev/ttyS1 -l 500
mbpoll 1.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2018 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Protocol configuration: Modbus RTU
Slave configuration...: address = [33]
                        start reference = 1, count = 2
Communication.........: /dev/ttyS1,      38400-8E1 
                        t/o 1.00 s, poll rate 500 ms
Data type.............: 16-bit register, input register table

-- Polling slave 33... Ctrl-C to stop)
[1]:  9975
[2]:  10021
-- Polling slave 33... Ctrl-C to stop)
[1]:  9997
[2]:  10021
^C--- /dev/ttyS1 poll statistics ---
2 frames transmitted, 2 received, 0 errors, 0.0% frame loss

everything was closed.
Have a nice day !

On peut voir à l’oscilloscope que la transmission s’effectue correctement :

Signaux RS485

En voie 1 on a le signal RTS (DE/RE) et en voie 2 le signal TX (DI).

Si on souhaite lire l’identifiant de l’esclave (Code fonction 17: Report Slave ID) :

$ mbpoll -a33 -b38400 -u -R  /dev/ttyS1 
mbpoll 1.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2018 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Protocol configuration: Modbus RTU
Slave configuration...: address = 33, report slave id
Communication.........: /dev/ttyS1,      38400-8E1 
                        t/o 1.00 s, poll rate 1000 ms
Length: 14
Id    : 0x02
Status: On
Data  : press-1.1.58

mbpoll dispose d’un mode « mise au point » grâce à l’option -v, très pratique pour le dépannage :

$ mbpoll -a33 -b38400 -u -R  /dev/ttyS1 -v
debug enabled
Set mode to RTU for serial port
Set device=/dev/ttyS1
mbpoll 1.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2018 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Opening /dev/ttyS1 at 38400 bauds (E, 8, 1)
Set response timeout to 1 sec, 0 us
Protocol configuration: Modbus RTU
Slave configuration...: address = 33, report slave id
Communication.........: /dev/ttyS1,      38400-8E1 
                        t/o 1.00 s, poll rate 1000 ms
[21][11][D9][EC]
Sending request using RTS signal
Waiting for a confirmation...
<21><11><0E><02><FF><70><72><65><73><73><2D><31><2E><31><2E><35><38><81><B7>
Length: 14
Id    : 0x02
Status: On
Data  : press-1.1.58

Dans la commande ci-dessus, on peut voir que le NanoPi a envoyer la trame [21][11][D9][EC] et que l’esclave MODBUS lui a répondu <21><11><0E><02><FF><70><72><65><73><73><2D><31><2E><31><2E><35><38><81><B7>.