Modbus est un protocole de communication industriel très répandu. Ce protocole permet de lire et d’écrire des données sur un équipement (un automate programmable industriel par exemple). Sa première version, nommée Modbus RTU, était basée sur une liaison série (RS232, RS485…). Devant l’adoption massive d’Ethernet dans l’industrie, une seconde version nommée Modbus TCP a été développée. Le format des trames Modbus RTU a été conservé, elles sont simplement encapsulées dans un paquet TCP et transmises sur Ethernet. Modbus TCP suit le principe du client-serveur, c’est à dire qu’un serveur répond aux demandes d’un ou plusieurs clients.
Dans ce tuto nous allons configurer un serveur Modbus TCP sur un Arduino. Vous pourrez utiliser un client Modbus TCP sur votre PC pour lire et écrire les données du serveur Modbus TCP de l’Arduino.
Le protocole Modbus
Chaque message Modbus échangé entre un client et un serveur est composé principalement d’un “Code fonction” et d’un “Numéro de registre” suivi d’une valeur de ce registre.
Les registres
Les données Modbus sont le plus souvent lues et écrites sous forme de “registres”. Les registres ont généralement une taille de 16 bits (1 mot), mais certains registres peuvent avoir une taille différente. Par exemple les “coil” qui sont des sorties binaires (0 ou 1) d’un serveur ont une taille de 1 bit.
Il existe 4 types de registres :
- Les ‘coils’, qui correspondent à des sorties binaires (0 ou 1)
- Les ‘input bits’, qui correspondent à des entrées binaires en lecture seule
- Les ‘input registers’, qui correspondent à des entrées analogiques en lecture seule
- Les ‘holding registers’, qui correspondent à des paramètres analogiques modifiables
Les code fonction
Le type de registre adressé par une requête Modbus est déterminé par le code de fonction.
Les codes les plus courants sont :
- 01 : lire un “coil”
- 02 : lire un “input register”
- 03 : lire un ou plusieurs “holding registers”
- 04 : lire un ou plusieurs “input registers”
- 05 : écrire un “coil”
- 06 : écrire un “holding register”
- 15 : écrire plusieurs “coils”
- 16 : écrire un ou plusieurs “holding registers”
Shield Ethernet Arduino
J’ai utilisé un Arduino Uno pour ce tuto, et comme il n’ont pas de port Ethernet je l’ai équipé d’un shield Ethernet.
Librairie Modbus
Il est nécessaire d’installer la librairie ArduinoModbus dans l’IDE Arduino. ArduinoModbus nécessite l’installation de la librairie ArduinoRS485.
Configuration de l’adresse MAC et IP
Chaque carte Ethernet possède sa propre adresse MAC, celle du Shield Ethernet est inscrite sur le dos de la carte. L’adresse MAC de mon Shield Ethernet est ‘A8:61:0A:AE:7A:08‘.
L’adresse IP doit être sélectionnée en fonction de votre réseau local. Ici je choisis par exemple l’adresse IP ‘192.168.1.88‘.
Le programme Arduino du serveur Modbus TCP
Je me suis inspiré du programme d’exemple “EthernetModbusServerLED”, qui permet de contrôler la LED de l’Arduino Uno via un client Modbus TCP. Mais j’ai utilisé des registres de type “holding register” et non “coil” car j’ai remarqué un problème dans mon client Modbus TCP Python qui ne positionne pas correctement les valeurs de ce type de registre… J’ai également ajouté un compteur qui s’incrémente continuellement et que l’on peut lire depuis le client Modbus TCP dans le “holding register” à l’adresse 0.
/*
Modbus TCP server
Created December 2021
by Tutoduino
*/
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoModbus.h>
// MAC address of the Ethernet Shield
byte mac[] = { 0xA8, 0x61, 0x0A, 0xAE, 0x7A, 0x08 };
// IP address of the Ethernet Shield
IPAddress ip(192, 168, 1, 88);
// We use the default TCP port (502) of ModbusTcp
EthernetServer ethServer(502);
ModbusTCPServer modbusTCPServer;
const int ledPin = 7;
int counter = 0;
void setup() {
Serial.begin(115200);
Serial.println("Ethernet Modbus TCP Example");
// start the Ethernet connection
Ethernet.begin(mac, ip);
// start the Ethernet server
ethServer.begin();
// start the Modbus TCP server
if (!modbusTCPServer.begin()) {
Serial.println("Failed to start Modbus TCP Server!");
while (true) {
delay(1); // do nothing, no point running without Modbus server
}
}
// configure the LED as output
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
// configure 1 holding starting at address 0x00
modbusTCPServer.configureHoldingRegisters(0x00, 1);
// configure 1 holding starting at address 0x00
modbusTCPServer.configureHoldingRegisters(0x01, 1);
}
void loop() {
// listen for incoming clients
EthernetClient client = ethServer.available();
if (client) {
// a new client connected
Serial.println("new client");
// let the Modbus TCP accept the connection
modbusTCPServer.accept(client);
// loop while the client is connected
while (client.connected()) {
// Increment internal counter, while client is connected
counter++;
// poll for Modbus TCP requests, while client connected
modbusTCPServer.poll();
// update registers
updateRegisters();
}
Serial.println("client disconnected");
}
}
void updateRegisters() {
// If holding register #0 is set, turn on the LED
if (modbusTCPServer.holdingRegisterRead(0x00) == 1) {
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
// Write value of the counter into holding register #1
if (modbusTCPServer.holdingRegisterWrite(0x01, counter) != 1) {
Serial.println("Error while writing holding register");
}
}
Une autre librairie Modbus semble intéressante également.Il s’agit de la librairie modbus-esp8266. Cette librairie utilise bien moins de mémoire dynamique que la précédente. Voici le résultat de compilation du programme basé sur la librairie modbus-esp8266 :
Le croquis utilise 21500 octets (66%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 533 octets (26%) de mémoire dynamique, ce qui laisse 1515 octets pour les variables locales. Le maximum est de 2048 octets.
A comparer au résultat de compilation du programme basé sur la librairie ArduinoModbus :
Le croquis utilise 22246 octets (68%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 1562 octets (76%) de mémoire dynamique, ce qui laisse 486 octets pour les variables locales. Le maximum est de 2048 octets.
Voici le programme adapté pour la librairie modbus-esp8266 :
/*
Modbus TCP server
Created December 2021
by Tutoduino
Based on the library from Alexander Emelianov (a.m.emelianov@gmail.com)
https://github.com/emelianov/modbus-esp8266
This code is licensed under the BSD New License. See LICENSE.txt for more info.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <ModbusEthernet.h>
// MAC address of the Ethernet Shield
byte mac[] = { 0xA8, 0x61, 0x0A, 0xAE, 0x7A, 0x08 };
// IP address of the Ethernet Shield
IPAddress ip(192, 168, 1, 88);
// ModbusTCP instance
ModbusEthernet modbusTCP;
// Internal counter
int counter = 0;
void setup() {
Serial.begin(115200);
// configure the PIN 7 as output
pinMode(7, OUTPUT);
// start the Ethernet connection
Ethernet.begin(mac, ip);
Serial.println("Modbus TCP server is starting");
// configure as Modbus TCP server
modbusTCP.server();
// Add two holding registers
modbusTCP.addReg(HREG(0));
modbusTCP.addReg(HREG(1));
}
void loop() {
uint16_t value=0;
// Server Modbus TCP queries
modbusTCP.task();
value = modbusTCP.Reg(HREG(0));
// If holding register #0 is 1, set the LED
if (value == 1) {
digitalWrite(7, HIGH);
} else {
digitalWrite(7, LOW);
}
// Increment counter and write it into holding register #1
counter++;
modbusTCP.Reg(HREG(1),counter);
delay(50);
}
Le client Modbus TCP
Vous pouvez utiliser n’importe quel client Modbus TCP sur votre ordinateur. Il en existe de nombreux sous tous les systèmes (Linux, Windows, Mac…). Je vous laisse regarder sur Qwant, le Google Français plus respectueux des données de ses utilisateurs ;).
Pour ma part, étant sur Linux et aimant bien utiliser le langage Python, j’utilise le projet pyModbusTCP Il permet d’utiliser très simplement un client Modbus TCP en Python.
Installation du module dans un terminal Linux :
$ sudo pip install pyModbusTCP
On lance la console Python 3 dans le terminal Linux :
$ Python3
Création d’un client avec l’adresse IP du serveur sur l’Arduino, dans l’interpréteur Python3 :
from pyModbusTCP.client import ModbusClient
c = ModbusClient(host="192.168.1.88", port=502, unit_id=1, auto_open=True)
Lecture de l’état du “holding register” #0 dans l’interpréteur Python3 :
value = c.read_holding_registers(0,1)
print(value)
Positionne la valeur “1” dans le “holding register” #0 dans l’interpréteur Python3 :
c.write_single_register(0,1)
La LED s’allume, la valeur du “holding register” #0 est bien passée à 1 sur l’Arduino !
Lecture du compteur interne dans le “holding register” #1 dans l’interpréteur Python3:
c.read_holding_registers(1,1)
Attention : il n’est pas possible d’utiliser la LED_BUILTIN (PIN 13 de l’Arduino Uno) avec le Shield Ethernet !
“Arduino communicates with both the W5500 and SD card using the SPI bus (through the ICSP header). This is on digital pins 10, 11, 12, and 13 on the Uno and pins 50, 51, and 52 on the Mega. On both boards, pin 10 is used to select the W5500 and pin 4 for the SD card. These pins cannot be used for general I/O”
https://store.arduino.cc/products/arduino-ethernet-shield-2
Bonjour,
Article très intéressant, est il possible de recevoir des informations d’un capteur qui fonctionne en mode MODBUS RS485, en connaissant la trame du capteur a interroger ?
Merci d’avance