Arduino Modbus Master

Ciao a tutti!

Nell'ultimo periodo sto cercando di approfondire la comunicazione Modbus su Arduino, utilizzando transceiver RS485 e moduli LoRa @868MHz.

Per quanto riguarda la configurazione di Arduino come slave, ho seguito attentamente il seguente esempio

e tutto funziona a dovere, anche via radio.

Ora vorrei invece capire come funziona la programmazione del master, che, se non vado errato, dovrebbe consentirmi di interrogare lo slave (nel caso dell'esempio uso la funzione 6 del Modbus per ruotare il servo ed accendere led) senza utilizzare il SW installato sul pc, ma appunto utilizzando i soli Arduino.

Purtroppo però non ho idea di come posso fare ad ottenere questo risultato; avete qualche idea? Se possibile, vorrei usare la libreria ModbusRtu.h , ma ovviamente ogni consiglio è più che ben accetto.

Grazie mille!

>fizio: in conformità al REGOLAMENTO, punto 13, il cross-posting è proibito (anche tra lingue diverse) per cui, il tuo post duplicato nella sezione Inglese del forum (post per di più scritto in Italiano, cosa NON permessa) è stato cancellato.

Ti prego, per il futuro, di evitare di aprire più post sullo stesso argomento in sezioni differenti del forum. Grazie.

Guglielmo

ciao...hai dato un occhio agli esempi?...il "simple_master" mi sembra chiaro...se invece non lo fosse dicci dove incontri problemi o hai dubbi

gpb01:
>fizio: in conformità al REGOLAMENTO, punto 13, il cross-posting è proibito (anche tra lingue diverse) per cui, il tuo post duplicato nella sezione Inglese del forum (post per di più scritto in Italiano, cosa NON permessa) è stato cancellato.

Ti prego, per il futuro, di evitare di aprire più post sullo stesso argomento in sezioni differenti del forum. Grazie.

Guglielmo

Ciao a tutti, scusate il ritardo ma non mi arrivano le email di update dei topic!! Mi sono accorto poco dopo di aver sbagliato "lingua" ed ho provato a cancellarlo...ma niente...mi dava sempre errore. Ecco perchè poi l'ho riscritto anche qui. Pardòn!

ORSO2001:
ciao...hai dato un occhio agli esempi?...il "simple_master" mi sembra chiaro...se invece non lo fosse dicci dove incontri problemi o hai dubbi

Ciao e grazie...come già detto non ho visto la tua risposta, altrimenti avrei risposto molto prima!

Nel frattempo ho fatto qualche passetto in avanti seguendo anche altri tutorial e rompendo le scatole a Google in lungo e in largo. Ho configurato due Arduino Uno, uno fa da master, l'altro da slave...fondamentalmente la prova è accendere il led di uno slave, ed ecco i codice

master

#include <ModbusRtu.h>

uint16_t au16data[3]; //!< data array for modbus network sharing  Array
uint8_t u8state; //!< machine state

Modbus master(0,0,13);
modbus_t telegram;
unsigned long u32wait;

void setup() {
  Serial.begin(9600);
  master.begin(9600); // baud-rate at 19200
  master.setTimeOut(9600); // if there is no answer in 2000 ms, roll over
  u32wait = millis() + 1000;
  u8state = 0; 
}
void loop() {
  switch( u8state ) {
  case 0: 
    if (millis() > u32wait) u8state++; // wait state
    break;
  case 1: 
    telegram.u8id = 1; // slave address
    telegram.u8fct = 6; // function code (this one is registers read)
    telegram.u16RegAdd = 0; // start address in slave
    telegram.u16CoilsNo = 1; // number of elements (coils or registers) to read
    telegram.au16reg = au16data; // pointer to a memory array in the Arduino
    master.query( telegram ); // send query (only once)
    u8state++;
    break;
  case 2:
    master.poll(); // check incoming messages
    if (master.getState() == COM_IDLE) {
      u8state = 0;
      u32wait = millis() + 100; 
    }
    break;
  }
  au16data[0]=analogRead(0);   questo è il "comando" vero e proprio che mi consente di accendere il led lato 
                                                slave
  }

slave

#include<ModbusRtu.h>
uint16_t modbus_array[] = {0,0,0};
Modbus bus;

void setup() {
  Serial.begin(9600);
  delay(1000);
  pinMode(9, OUTPUT);
  bus = Modbus(1,0,13);
  bus.begin(9600);
}

void loop() {
  bus.poll(modbus_array,sizeof(modbus_array)/sizeof(modbus_array[0]));
  
  if (modbus_array[0] == 0){
    digitalWrite(9,LOW);
  } else {  
     digitalWrite(9,HIGH);           
    }
}

I problemi/dubbi rimangono ancora sulla parte master, ovvero:
a) non ho ben capito la parte l' analog(0) che ho commentato su;
b) Se io volessi scrivere un determinato valore...supponiamo che il led dello slave si accenda solo se invio un intero >10...come dovrei fare?
c) come conseguenza al punto b)...vorrei replicare in sostanza quanto fatto in questo esempio: RS485 MODBUS Serial Communication using Arduino UNO as Slave
(lato master ovviamente, dello slave il codice mi è chiaro)

Grazie...

ciao..provo a ad essere breve...
lato master crei un array di int che conterrà le informazioni che vuoi inviare od inviare (scrivere) o che riceverai (leggere)...infatti ad uno degli attributi di "telegram" passi il puntatore dell'array ...per essere letto o scritto.
Sempre in "telegram" decidi funzione (leggere o scrivere), registro di partenza e numero di registri.
Nel lato slave ti crei sempre un array che conterrà o le informazioni da passare o i "comandi" ricevuti.
L'indice "0" delle due array è sempre il primo registro gestisto...e dovrebbe corrispondere al registro "0" di partenza.
quindi se vuoi che il tuo LED si accenda al valore "10" ed è il registro tre lato master farai:

    au16data[3] = 10;
    telegram.u8id = 1; // slave address
    telegram.u8fct = 6; // function code (this one is registers read)
    telegram.u16RegAdd = 3; // start address in slave
    telegram.u16CoilsNo = 1; // number of elements (coils or registers) to read
    telegram.au16reg = au16data; // pointer to a memory array in the Arduino
    master.query( telegram ); // send query (only once)

lato slave farai:

  bus.poll(modbus_array,sizeof(modbus_array)/sizeof(modbus_array[0]));
 
  if (modbus_array[3] == 10){
    digitalWrite(9,LOW);
  }
etc...

le due array conviene averle della stessa dimensione

ORSO2001:
ciao..provo a ad essere breve...
lato master crei un array di int che conterrà le informazioni che vuoi inviare od inviare (scrivere) o che riceverai (leggere)...infatti ad uno degli attributi di "telegram" passi il puntatore dell'array ...per essere letto o scritto.
Sempre in "telegram" decidi funzione (leggere o scrivere), registro di partenza e numero di registri.
Nel lato slave ti crei sempre un array che conterrà o le informazioni da passare o i "comandi" ricevuti.
L'indice "0" delle due array è sempre il primo registro gestisto...e dovrebbe corrispondere al registro "0" di partenza.
quindi se vuoi che il tuo LED si accenda al valore "10" ed è il registro tre lato master farai:

    au16data[3] = 10;

telegram.u8id = 1; // slave address
    telegram.u8fct = 6; // function code (this one is registers read)
    telegram.u16RegAdd = 3; // start address in slave
    telegram.u16CoilsNo = 1; // number of elements (coils or registers) to read
    telegram.au16reg = au16data; // pointer to a memory array in the Arduino
    master.query( telegram ); // send query (only once)




lato slave farai:


bus.poll(modbus_array,sizeof(modbus_array)/sizeof(modbus_array[0]));

if (modbus_array[3] == 10){
    digitalWrite(9,LOW);
  }
etc...



le due array conviene averle della stessa dimensione

Grazie per essere stato così chiaro ed esaustivo...ci lavoro...

Ciao, ho seguito le tue indicazioni e tutto funziona a dovere.

Ora sto cercando di fare un passettino in avanti, ovvero da un master voglio comandare 2 slave. Di conseguenza ho aggiunto un secondo pacchetto telegram con stessi parametri (funzione, numero di registri...) in cui cambia solo l'id dello slave.

Anche in questo caso funziona (invia i pacchetti in due momenti separati, la prima volta interroga lo slave 1, la seconda lo slave 2...se volessi inviarli contemporaneamente?).

Ho provato quindi ad associare al primo valore dell'array=au16data[0] un valore letto da seriale, ovvero:

    if(Serial.read()=='i'){
    au16data[0]=1;}
    if(Serial.read()=='f'){
      au16data[0]=0;}

Legge correttamente 'i' (i led dei due slave si accendono solo se digito tale carattere da seriale), ma sull' 'f' niente risposta...dove sto sbagliando?

Riporto comunque lo sketch completo di master e dello slave

master

#include <ModbusRtu.h>
#include <SoftwareSerial.h>

uint16_t au16data[]={0,0,0}; //!< data array for modbus network sharing  Array
uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query

SoftwareSerial mySerial(3,5);
Modbus master(0);
modbus_t telegram[2];
unsigned long u32wait;

void setup() {
  Serial.begin(9600);
  master.begin(&mySerial, 9600); // baud-rate at 
  master.setTimeOut(9600); // if there is no answer in 2000 ms, roll over
  u32wait = millis() + 1000;
  u8state = 0; 
}
void loop() {
  switch( u8state ) {
  case 0: 
    if (millis() > u32wait) u8state++; // wait state
    break;
  case 1: 
    if(Serial.read()=='i'){
    au16data[0]=1;}
    if(Serial.read()=='f'){
      au16data[0]=0;}

    telegram[0].u8id = 1; // slave address
    telegram[0].u8fct = 6; // function code (this one is registers read)
    telegram[0].u16RegAdd = 0; // start address in slave
    telegram[0].u16CoilsNo = 1; // number of elements (coils or registers) to read
    telegram[0].au16reg = au16data; // pointer to a memory array in the Arduino

    telegram[1].u8id = 2; // slave address
    telegram[1].u8fct = 6; // function code (this one is registers read)
    telegram[1].u16RegAdd = 0; // start address in slave
    telegram[1].u16CoilsNo = 1; // number of elements (coils or registers) to read
    telegram[1].au16reg = au16data; // pointer to a memory array in the Arduino
    
   master.query( telegram[u8query] ); // send query (only once)
    u8state++;
    u8query++;
  if (u8query > 2) u8query = 0;
    break;
  case 2:
    master.poll(); // check incoming messages
    if (master.getState() == COM_IDLE) {
      u8state = 0;
      u32wait = millis() + 100; 
    }
    break;
  }
 }

slave (uguale per entrambi, cambia solo l'id)

#include<ModbusRtu.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(3,5);
uint16_t modbus_array[] = {0,0,0};
Modbus bus(1,5,13);

void setup() {
  Serial.begin(9600);
  delay(1000);
  pinMode(9, OUTPUT);
  bus.begin(&mySerial,9600);
}

void loop() {
  bus.poll(modbus_array,sizeof(modbus_array)/sizeof(modbus_array[0]));
  
  if (modbus_array[0] == 1){
    digitalWrite(9,HIGH);
  } else {  
     digitalWrite(9,LOW);           
    }
}

Nel frattempo c'ho buttato dentro anche la software serial... :slight_smile:

Grazie.

Aggiungo anche...

Ho fatto una prova di lettura di un singolo registro (funzione 3): ho collegato ad uno slave un sensore di temperatura (tmp36 dell'arduino kit) e al master un lcd che in teoria dovrebbe farmi vedere il valore ottenuto...di seguito i rispettivi codici:
master

#include <ModbusRtu.h>
#include <SoftwareSerial.h>
#include<LiquidCrystal.h>   //Library for using 16x2 LCD display
LiquidCrystal lcd(8,9,10,11,12,13);

uint16_t au16data[3]; //!< data array for modbus network sharing  Array
uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query

SoftwareSerial mySerial(3,5);
Modbus master(0,5,7);  //5=rs485 DI; 7=rs485 DE-RE
modbus_t telegram;
unsigned long u32wait;

void setup() {
  Serial.begin(9600);
  master.begin(&mySerial, 9600); // baud-rate at 
  master.setTimeOut(9600); // if there is no answer in 2000 ms, roll over
  u32wait = millis() + 1000;
  u8state = 0; 
  lcd.begin(16,2);                //Lcd set in 16x2 mode 16colonne x 2righe
  lcd.print("Modbus");     //Welcome Message sulla prima riga
  lcd.setCursor(0,0);
  delay(3000);
  lcd.clear();
}
void loop() {
  switch( u8state ) {
  case 0: 
    if (millis() > u32wait) u8state++; // wait state
    break;
  case 1:   
    telegram.u8id = 1; // slave address
    telegram.u8fct = 3; // function code
    telegram.u16RegAdd = 0; // start address in slave
    telegram.u16CoilsNo = 1; // number of elements (coils or registers) to read
    telegram.au16reg = au16data; // pointer to a memory array in the Arduino

   master.query(telegram); // send query (only once)
    u8state++;
    u8query++;
  if (u8query > 5) u8query = 0;
    break;
  case 2:
    master.poll(); // check incoming messages
    if (master.getState() == COM_IDLE) {
      u8state = 0;
      u32wait = millis() + 100; 
    }
    break;
  }
   au16data[0] = digitalRead(7);
   delay(1000);
   lcd.setCursor(0,0); 
   lcd.print("valore: ");
   lcd.print(au16data[0]);
 }

slave

#include<ModbusRtu.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(3,5);
uint16_t au16data[3];
Modbus bus(1,5,13);      // 5=rs485 DI ; 15=rs485 DE-RE
unsigned long tempus;
int8_t state = 0;

void setup() {
  Serial.begin(9600);
  delay(1000);
  bus.begin(&mySerial,9600);
  tempus = millis() + 100; 
}

void loop() {
  bus.poll(au16data,sizeof(au16data)/sizeof(au16data[0]));
  if (state > 4) { 
  tempus = millis() + 50; //Tiempo actual + 50ms
  }  
  au16data[0]=analogRead(A0);
  Serial.println(au16data[0]);
  delay(1000);          
}

Grazie ancora...

ciao...premetto che non ho letto per intero i tuoi post...comunque per inviare un comando (scrittura) per commandare in un colpo solo tutti gli slave devi usare l'ID "0"...questo funge da broadcast e tutti accetteranno quel comando...ovviamente tutti quelli che sapranno cosa fare del comando inviato...ed ovviamente se la libreria lo implementa ...ma penso di si.
in questo caso NON c'è risposta da parte degli slave.
Diversamente arduino fa le cose in modo sequenziale...una dopo l'altra...la contemporaneità è un'illusione data dalla velocità di esecuzione del programma (quando fatto bene).

un consiglio...dato che la memoria è scarsa...usa un solo oggetto telegram e cambia di volta in volta l'ID che t'interessa etc etc

ORSO2001:
ciao...premetto che non ho letto per intero i tuoi post...comunque per inviare un comando (scrittura) per commandare in un colpo solo tutti gli slave devi usare l'ID "0"...questo funge da broadcast e tutti accetteranno quel comando...ovviamente tutti quelli che sapranno cosa fare del comando inviato...ed ovviamente se la libreria lo implementa ...ma penso di si.
in questo caso NON c'è risposta da parte degli slave.
Diversamente arduino fa le cose in modo sequenziale...una dopo l'altra...la contemporaneità è un'illusione data dalla velocità di esecuzione del programma (quando fatto bene).

un consiglio...dato che la memoria è scarsa...usa un solo oggetto telegram e cambia di volta in volta l'ID che t'interessa etc etc

Buongiorno,
sto provando ad usare questa libreria e c'è una cosa che mi sfugge (sono alle prime armi con modbus e poco esperto con arduino). Partendo dagli esempi della libreria come l'advanced_slave, non capisco in che modo si possa decidere la tipologia di registro dei dati da scambiare; se ad esempio nello slave ho dei valori che devono essere letti dal master, come faccio fare in modo che siano input registers piuttosto che holding registers o discrete inputs o coils?
In definitiva nello slave si può decidere il tipo di registro?
Grazie

ciao gt4020,

nell'esempio viene creato un array dal nome au16data lunga 9.
in quest'array si decide quale posizione sarà dedicata ai coils e quale ai registri (vedi funzione io_poll)...poi lato master deciderai in quali posizioni di questa array leggere o scrivere.

in pratica con un array di 9 elementi è come se avessi i registri dallo 0 all'8...di cui i primi 2 (0 ed 1) sono per le coils (lo 0 in lettura e la 1 in scrittura) ; il terzo ed il quarto (2 e 3) sono scrittura per le analogwrite...le ultime 3 sono solo in lettura di vari valori.

quindi a te decidere cosa e come esporre verso il master.

spero sia chiaro

Quindi per vedere se ho capito:

bitWrite -> significa che è un "Discrete input"
bitRead -> significa che è un "Coil"
analogRead -> significa che è un "Input register"
analogWrite -> significa che è un "Holding register"

E' giusto il tipo di registro che ho dedotto?

ciao,

no...in primis...questa libreria risolve/distingue, lato slave, la funzione modbus che utilizzi nel seguente switch (non lo riporto tutto):

    case MB_FC_READ_COILS:
    case MB_FC_READ_DISCRETE_INPUT:
    case MB_FC_READ_REGISTERS:
    case MB_FC_READ_INPUT_REGISTER:
        au8Buffer[ NB_HI ]      = highByte(telegram.u16CoilsNo );
        au8Buffer[ NB_LO ]      = lowByte( telegram.u16CoilsNo );
        u8BufferSize = 6;
        break;
    case MB_FC_WRITE_COIL:
        au8Buffer[ NB_HI ]      = ((au16regs[0] > 0) ? 0xff : 0);
        au8Buffer[ NB_LO ]      = 0;
        u8BufferSize = 6;
        break;
    case MB_FC_WRITE_REGISTER:
        au8Buffer[ NB_HI ]      = highByte(au16regs[0]);
        au8Buffer[ NB_LO ]      = lowByte(au16regs[0]);
        u8BufferSize = 6;
        break;
    case MB_FC_WRITE_MULTIPLE_COILS: // TODO: implement "sending coils"

come vedi le funzioni di lettura sono comuni e vengono gestite allo stesso modo...per la scrittura fa i distinguo in modo da gestire il singolo BIT di un registro od il valore completo della int16_t.

è per questo che dicevo che tu decidi quali registri saranno riferiti a coils e quali a valori "numerici".

le istruzioni che hai riportato, che sono di arduino indipendentemente dal modbus, servono a:

bitWrite -> scrivere il valore del singolo bit di una variabile
bitRead -> leggere il valore del singolo bit di una variabile
analogRead -> leggere il valore di un ingresso analogico e salvarlo in una variabile
analogWrite -> scrivere il valore di una variabile in una uscita analogica

Perdonami se approfitto della tua pazienza e disponibilità ma mi sfugge ancora qualcosa (sempre riferito allo sketch di esempio):

void io_poll() {
  // digital inputs -> au16data[0]
  // Lee las entradas digitales y las guarda en bits de la primera variable del vector
  // (es lo mismo que hacer una máscara)
  bitWrite( au16data[0], 0, digitalRead( 2 )); //Lee el pin 2 de Arduino y lo guarda en el bit 0 de la variable au16data[0] 
  bitWrite( au16data[0], 1, digitalRead( 3 ));
  bitWrite( au16data[0], 2, digitalRead( 4 ));
  bitWrite( au16data[0], 3, digitalRead( 5 ));

Qui nel registro 0 scrive lo stato di 4 digital input su 4 bit, giusto?
Ecco quello che mi sfugge è: questo registro 0 è un coil, un input register o un holding register? Come, chi e dove si determina questo aspetto?
Per chiarire ancora meglio: se io volessi scrivere, come sopra, lo stato di 8 input digitali su un holding register, come lo dovrei scrivere?
Parlo sempre su un arduino modbus SLAVE.
Grazie ancora

ciao,

nessun problema...spero solo di riuscire ad essere chiaro...

genericamente parlando...è sempre dal lato slave che si decide cosa e come esporre verso l'esterno; cioè allo slave, di solito, si allega il portocollo scambio dati che dice (esempio) dal registro 0 al registro 9 si leggono e o scrivono le coils; dal registro 11 al 100 si leggono o scrivono registri "numerici".
Inoltre ne documento di solito sono dichiarate le funzioni modbus supportate (mica tutti gli slave supportano tutte le funzioni)...oppure, come mi è capitato, viene indicato il limite dei registri che si possono legger eo scrivere in un colpo solo (nel mio caso erano 20 anzichè i 240 classici)...quindi se invierai una richiesta con una funzione non supportata lo slave ti ritornerà un messaggio di eccezione.

con questa libreria, salvo modifiche, non riesci a distingure se il master sta chiedendo una o l'altra funzione; infatti alcune sono raggruppate (vedi lo switch del post precedente).

Quel che devi tener presente è che se usi le funzioni per i coil tu considererai i singoli BIT di un registro; se invece userai le funzioni per gli holding od input register leggerai il valore numerico dei registri.

quello che riesci a fare tu è rendere dei registri effettivamente utili in sola lettura od in lettura/scrittura...non tramite assegnazione di una proprietà del registro ma da come lo userai nel programma.

scusa la domanda...hai letto/studiato il protocollo modbus (RTU, TCP, ASCII)? cioè hai capito come si "formula" una richiesta e come si compone la relativa risposta?

Grazie mille, con le tue spiegazioni e dopo aver provato di persona lo sketch di esempio, ora ho capito come lavora. Grazie ancora.

Buongiorno, volevo chiedere se è possibile possibile istanziare su una stessa scheda (nella fattispecie un esp32 che sto usando come modbus master) anche una parte slave (ovviamente su un'altra seriale). In pratica vorrei che la mia scheda sia contemporaneamente master su una rete 485 e slave su un altra. Si può fare? Si può attingere dagli stessi registri?