Arduino Modbus TCP

5
(6)

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.

Arduino Uno Ethernet Shield Modbus TCP
Arduino Uno équipé du Shield Ethernet 2

Librairie Modbus

Il est nécessaire d’installer la librairie ArduinoModbus dans l’IDE Arduino. ArduinoModbus nécessite l’installation de la librairie ArduinoRS485.

Ajout de la librairie ArduinoModbus

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‘.

Adresse MAC du Shield Ethernet Arduino

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‘.

Configuration de l’adresse MAC et IP du Shield Ethernet

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)
Exécution des commandes du client Modbus TCP dans l’interpréteur Python 3

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

Votre avis compte !

Note moyenne : 5 / 5. Nombre de votes : 6

Pas encore de vote pour ce tutoriel

Désolé si cet article ne vous a pas intéressé

Merci de commenter afin que je puisse l’améliorer.

Dites-moi comment améliorer cette page.