Problème com Arduino-modbus TCP

Bonjour,

Depuis quelques jours j'ai un problème que je n'avais pas avant et que je ne comprends pas. Dans l'optique d'un projet j'utilise un BUS Ethernet via un shield Ethernet pour Arduino avec le protocole modbus TCP . Je connecte une ou plusieurs carte en Serveur au bus et j'utilise mon PC comme client via une interface python avec bibliotheque.

Les soucis sont les suivant :
Quand je me connecte en client depuis mon PC et que j'essaie d'avoir accès à des registres (quelque soit leur type) une fois sur 2 ça ne marche pas et et quand ça marche les valeurs sont complètement incohérentes. Des registres auquels je ne touche pas encore qui sont initialisés à 0 par défaut affichent des valeurs au hasard entre 0 et 47 000 et quelques et il en va de même pour les registres auquels j'ai affecté des valeurs précises. Des fois j'ai même des timeout error alors que j'arrive à ping n'importe quel élément du réseau Ethernet.

J'utilise le code suivant comme exemple pour vous montrer les soucis mais ce n'est pas le code de mon projet celui serait trop long et trop complexe pour mettre en évidence le problème.
Mon code Arduino pour une carte que j'utilise comme serveur est donc :


#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoModbus.h>


// Adresse MAC du shield Ethernet 
byte mac[]  = {0xA8, 0x61, 0x0A, 0xAE, 0xA8, 0x3F}; 

//Adress IP de la connection Ethernet
IPAddress ip(192, 168, 0, 102);

// Utilisation du port TCP défaut pour la com modbus
EthernetServer ethServer(502); 
ModbusTCPServer modbusTCPServer; 

const int ledPin = 7;
 
int counter = 0;



void setup() {
   
  Serial.begin(115200); 
  Serial.setTimeout(10);
  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, HIGH);
 
  // configure 50 holding starting at starting address 0x00
  modbusTCPServer.configureHoldingRegisters(0x00, 50);

//initialise 3 random of them at 3 values : 0, 100, 200

  modbusTCPServer.holdingRegisterWrite(0x20, 0); 
  Serial.print("valeur initial du holding register 0x20 : "); 
  Serial.println(modbusTCPServer.holdingRegisterRead(0x20));

  modbusTCPServer.holdingRegisterWrite(0x21, 100); 
  Serial.print("valeur initial du holding register 0x21 : "); 
  Serial.println(modbusTCPServer.holdingRegisterRead(0x21));

  modbusTCPServer.holdingRegisterWrite(0x22, 200); 
  Serial.print("valeur initial du holding register 0x22 : "); 
  Serial.println(modbusTCPServer.holdingRegisterRead(0x22));
  

}

void loop() {
 
  // listen for incoming clients
  EthernetClient client = ethServer.available();
   
  if (client) {
    // a new client connected
    Serial.println("client ok ");
 
    // 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++;
      modbusTCPServer.holdingRegisterWrite(0x23, counter);
      
      
      // poll for Modbus TCP requests, while client connected
      modbusTCPServer.poll();
      delay(500);
    }
  }
}

Le code python pour mon client est bien plus simple :

from pyModbusTCP.client import ModbusClient



import matplotlib.pyplot as plt
from time import sleep
import random as rd

c = ModbusClient(host = "192.168.0.102", port = 502 , auto_close = False, debug = True )

c.open()

c.is_open

puis j'envoie la commande

c.read_holding_registers(0x00, 50)
qui me permet de lire les 50 registres initialisés dans mon IDE Arduino.
Voilà ce que j'obtiens comme valeurs :

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8448, 1867, 1942, 1944, 148, 16388, 2, 1, 1, 36615, 18952, 513,
63753, 513, 45827, 42759, 1024, 3072, 60167, 0, 19210, 1792, 2560, 44892, 50451, 3072, 50183,
1024, 13831, 256, 768, 56064, 3092, 256, 1280, 244]

Je n'arrive pas à voir de cohérence la dedans. Sachant que par défaut si on ne fait rien un registre est initialisé à 0 . Mais même quand j'initialise manuellement ça ne change rien les valeurs ne sont pas bonnes (aux adresses 30 31 et 32 (0x20 0x21 et 0x22 en hexa) les valeurs ne correspondent pas à l'initialisation alors qu'à aucun moment je n'écrit dans ces registres.

Pour situer mon incompréhension j'ajoute que j'ai essayé pas mal de chose. Déjà le fait de supprimer en partie ou toute comme Serial aide un petit peu mais ne résout pas le problème global, c'est comme si la comme avec le port série perturbait la comme modbus tcp ce qui me paraît peu cohérent.

Je précise que je n'avais pas ce problème il y a quelque semaines mais que celui ci ne se règle pas en changeant de version ce qui me paraît tout aussi bizarre.

Cordialement
Dans l'attente de n'importe quelle piste.

:warning:
Post mis dans la mauvaise section, on parle anglais dans les forums généraux. déplacé vers le forum francophone.

Merci de prendre en compte les recommandations listées dans Les bonnes pratiques du Forum Francophone

Désolé my bad je pensais l'avoir mis dans la section francophone .

1 Like

Bonjour,
J'ai tenté aussi la comm Modbus TCP avec la librairie "officielle"
J'en suis revenu après un début de calvitie, 1 clavier en moins et une souris pendu...
Comme dirait Renaud laisse béton..

Donc j'ai repris ce que j'avais fait avec celle la : GitHub - andresarmento/modbus-arduino: A library that allows your Arduino to communicate via Modbus protocol, acting as a slave (master in development). Supports serial (RS-232, RS-485) and IP via Ethernet (Modbus IP).

No error et j'y vais comme un porc les requêtes sans delay ça marche comme ça :slight_smile:

le code arduino (c'est dans une biblio perso d'ou le pointeur

void IOs_SetupMobdus( ModbusIP *MP )
  {
    MdbPtr = MP;

   /// uint8_t IDX =22;

    for ( uint8_t IDX = 0 ; IDX < 22 ; IDX ++ ) 
    {
      MdbPtr->addHreg(IDX,IDX*2);// 1 registre commande Hreg 0 pour 16 commande logicielle
    }
  //registre 16 et 17 entree api => PC vers API
  //registre 20 et 21 sortie api => API => PC
 
  }

le code python WITHOUT DELAY lol...

   if (client.connected):
        print("connexion , etablished ...")
    try:
        for i in range(10):
            #print(i)
            #print(PLC1)

            #read_holding_registers(address: int, count: int = 1, slave: int = 0, **kwargs: any)
            #0x03 read holding register
            result = client.read_holding_registers(0,22,UNITS=1)
            
            if ( result.isError() ):
                print ("pymodbus result is Error ")        
            else:
                for h in range(HR_LEN) : 
                    print( "Passe " + str(i) + " Register " + str(h) + " => " + str( result.registers[h] ) )

    except Exception as e:
        print ("pymodbus returned Error ",e)
        print (result)

Merci beaucoup pour ta réponse je vais essayer tout ça en espérant que ça marche mais si ça fonctionne pour toi je ne vois pourquoi ça bloquerait !

Bonne soirée

J'essaie de mettre le code sur un http
si la supervision python t’intéresse je mets le projet en forme
j'ai testé avec un zelio et je bricole un esclave arduino
tu utilise qmodmaster ou équivalent ? je faisais les test avec sans problème mais dés que je suis passé sur python ça a planté apparemment les messages serait découpés mais bon un datagramme tcp c'est 1024 octets ....

Pour la librairie je te mets la déclaration des librairies et registres et la lecture écriture dans la tache de l'esclave
A charge de revanche :wink:

les libariries


#include "Arduino.h"
#include <ModbusIP.h>
#include <Modbus.h>
//constantes --------------------------------------------------------
#define  INP_MAX  8
#define  HR_CMD   16 //pc vezrs api
#define  HR_INP   20 //api vers pc
#define  HR_OUT   21 //api vers pc etat sortie

mon pointeur dans le fichier .cpp et .h (librairie perso) tu peut t en passer mais il faut un objet dans ton fichier .ino

ModbusIP *MdbPtr; et extern ModbusIP *MdbPtr; // pointeur vers objet modbus

ModbusIP mb; //dans ton fichier uno

la déclarations des registres

void IOs_SetupMobdus( ModbusIP *MP )
  {
    MdbPtr = MP;

   /// uint8_t IDX =22;

    for ( uint8_t IDX = 0 ; IDX < 22 ; IDX ++ ) 
    {
      MdbPtr->addHreg(IDX,IDX*2);// 1 registre commande Hreg 0 pour 16 commande logicielle
    }
  //registre 16 et 17 entree api => PC vers API
  //registre 20 et 21 sortie api => API => PC
 
  }

l'exploitation des registres


bool Logic()
{
  uint16_t OUT_BLOC0 = 0;
  bool a,b,c,d; //variable temporaire

  uint16_t REG16 = 0;
  a = IOs_E(0);
  b = IOs_E(1);
  REG16 = mb.Hreg(HR_CMD); //lecture du registre

  c = REG16 & ( 1 << 0 ); //bit 0 True or false that is the question ;)
  
//Sortie 0  
// a OU b
  if (a || b) 
    { 
      pcf.digitalWrite( 0 , LOW);
      OUT_BLOC0 += (1 << 0);
    }
  else { pcf.digitalWrite( 0 , HIGH ); }

//Sortie 1  
// a ET (NON B)  
  if ((a || c) & !b) 
  { 
    pcf.digitalWrite( 7 , LOW); 
    OUT_BLOC0 += ( 1 << 7 );
  } 
  else { pcf.digitalWrite( 7 , HIGH ); }

   mb.Hreg(HR_OUT,OUT_BLOC0); 
}

enfin dans le loop

void loop() {
  #if DEBUG2
    dbg_print_time( "Ligne 195 - debut loop setup " ); 
  #endif
 
  // TAche modbus TCP 
  mb.task();
  task();

}
1 Like

Salut !

Yes la supervision python m'intéresse je ne suis pas un expert du domaine de manière générale mais j'ai les connaissances de base.
J'utilisais majoritairement python et un équivalent de qmodmaster (modbuspoll ) et wireshark pour le debug. Mais rien à faire on a utilisé avec plusieurs logiciels les problèmes étaient exactement les même. Je pense que le soucis vient de la librairie Arduino.

Merci beaucoup pour la déclaration des librairies et registres et la lecture écriture dans la tache de l'esclave :slight_smile: , je peinais justement à trouver la doc dessus. Je n'ai pas vu de liste des classes et fonctions de la librairie.

Petite question après essais, je ne comprend pas trop ce que fait ta fonction logic(). Et la fonction IOs_E je ne la connais pas non plus :slight_smile: si tu as des éclaircissements je suis preneur.

Bonne journée à toi !

Bonjour,
Pour logic c'est une fonction appelé par task qui va utilise les entrées , applique les traitements logiques et actualise les sorties

task appelle une fonction de lecture des entrees IOs_LectureEntree(); puis appelle logic()
IOs_E() me renvoie l'etat 0 ou 1 de l’entrée tu peux la remplacer par digitalRead

void task()
{
  
    //dbg_print_time( "Ligne 109 - task_Light1 avant lecture " ); 
 
 //met a jour les entrees et registre    
    IOs_LectureEntree(); 
 
    //dbg_print_time( "Ligne 111 - task_Light1 apres lecture " ); 
    Logic();
    //dbg_print_time( "Ligne 111 - task_Light1 apres lecture " ); 
}

Je te mets le code , je gere les NO et NF dans IOs_LectureEntree() mais c'est encore au stade du projet. C'était pour éviter de mettre des not ! partout mais je pense le virer...

  word IOs_LectureEntree()
  {
    uint8_t IDX =0;
    uint16_t INPUT_BLOC0 = 0;

    
    for ( IDX = 0 ; IDX < INP_MAX ; IDX ++ ) 
    {
    //aquisition etat entree physique
      if( digitalRead( ENTREEARD[IDX].iInMcu ) == HIGH ) 
        { ENTREEARD[IDX].bStInp = true; } 
      else 
        { ENTREEARD[IDX].bStInp = false; }
      
    //gestion inversion etat physique et logique
      if ( ENTREEARD[IDX].bStInv == true) 
        { ENTREEARD[IDX].bLsCur = !ENTREEARD[IDX].bStInp; } 
      else 
        { ENTREEARD[IDX].bLsCur = ENTREEARD[IDX].bStInp; } 
      
      // actualise DiscreteInput 
      if ( ENTREEARD[IDX].bLsCur == true ) 
        { 
          INPUT_BLOC0 += (1 << IDX);
         // MdbPtr->discreteInputWrite(IDX , 1);
        }
      else
        {
          //MdbPtr->discreteInputWrite(IDX , 0);
        }  
     }
    // fin de la boucle 
    //actualise holding register etat des entrees ctrl -> pc
    MdbPtr->Hreg(HR_INP,INPUT_BLOC0);

    return INPUT_BLOC0;
  }

  
 //------------------------------------------------------------------------------------------------------
 // retourne l etat d uen entree 
 //------------------------------------------------------------------------------------------------------
  bool IOs_E( int DiIdx )
  {
  bool bA = false;
  if ( ( DiIdx >= 0 ) && ( DiIdx < INP_MAX ) )  
    { 
    bA =  ENTREEARD[DiIdx].bLsCur;  
    }
  return bA;
  }

Ca marche, merci beaucoup pour les précisions et ton aide en général . Mais du coup tes fonctions IOs, tu les écris toi même où elles sont inclues dans les librairies.

Et sinon dis moi si je me trompe mais j'ai l'impression que dans ton cas tu ne prends en compte que des entrées/Sorties logique de type bool (ce qui correspond à des registres coil pour le modbus).
Pour des registres de type holding/input registers il faut réaliser un autre type de traitement ?

  • IOs c'est dans une librairie perso
  • Pour modbus
    normalement on doit copier les entrées dans les registres discretes inputs et les sorties doivent être commande par les coils et les valeurs ana dans ?? (je regarderais)

MAIS
l'intelligence reste a l'automate donc on ne commande jamais directement les sorties pour des raisons de sécurité principalement on peut par contre demander la marche d'une pompe comme avec un bp; l'api contrôle l’arrêt d'urgence et actionne la pompe si c'est possible

Ensuite j'utilise les holding register car au début je n’utilisais que le zelio qui a une implémentation relative de la norme modbus malgre que ce soit du Schneider et que modicon est cree modbus...

Je suis reste sur cette solution pour le moment ça m'évite de compliquer le code du superviseur j'y reviendrais ensuite ça me permet d'utiliser les entres sorties TOR et ANA

en gros dans mon zelio j'ai 8 registre holding register
4 me servent a envoyer les commandes(TOR) et consigne(analogique) du pc vers l'api e
4 autres sont utilisés pour recuperer les etats et valeurs analogique de l'api vers le pc

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.