Arduino Modbus su RS485

Salve a tutti,
Ho provato a cercare un po' di informazioni su come sfruttare questo protocollo. Ho raccolto un po' di idee, ma la moltitudine di informazioni, anche molto diverse, trovate in rete, mi hanno anche un po' confuso. Devo utilizzare l'arduino (UNO R3) come master per leggere temperatura ed umidità da uno slave.
Scusatemi se quelle che seguono saranno domande abbastanza banali per i più esperti, ma lo slave è abbastanza costoso e, pertanto, ho paura di sbagliare qualcosa e danneggiarlo. Quindi vorrei essere sicuro di effettuare i collegamenti corretti.

Per effettuare il collegamento ho acquistato un DSD TECH SH-U12.
Ad una estremità è dotato di collegamenti GND, RX0, TX0 e 5V0, dall'altra di collegamenti GND, A+, B-
I primi 4 pin pensavo di collegarli all'arduino come segue:
sh-u12 GND ____ GND arduino (master)
sh-u12 RX0 ____ pin 10 arduino (master)
sh-u12 TX0 ____ pin 11 arduino (master)
sh-u12 5V0 ____ pin 5V ardino (master)

Lo strumento è dotato di porta seriale descritta come "COM1 serial interface RS485 (galvanically isolated)" nella quale ho a disposizione due collegamenti:

  • TxD+/RxD+
  • TxD-/RxD-

Suppongo quindi, correggetemi se sbaglio, che i collegamenti tra DSD TECH SH-U12 e slave (lo strumento) debbano essere di questo tipo:
sh-u12 A+ ____ TxD+/RxD+ slave
sh-u12 B- _____ TxD-/RxD- slave

L'altro GND forse dovrei tenerlo scollegato? Sullo slave non ho un ingresso per il ground.

Riguardo al protocollo Modbus, lo slave può essere configurato come segue:

  • Baud rate 9600, 19200, 38400
  • Data format 8 - 1- no parity

Gli indirizzi su cui devo leggere i dati di temperatura ed umidità sono i seguenti:

  • 0x11E9
  • 0x11F3

In base a quanto leggo in rete, è consigliabile tenere i pin RX/TX dell'arduino liberi, ed utilizzare la libreria SoftwareSerial per impostare altri due pin come seriale aggiuntiva (ho scelto il 10 e l'11).

Nella speranza che quanto scritto sopra sia tutto corretto, vi chiedo ulteriore aiuto per comprendere quanto segue

  1. oltre all'indirizzo a cui devo estrapolare i dati, mi serve sapere altro dello slave? Lo strumento è dotato di un manuale dedicato al protocollo Modbus molto ricco di informazioni. Molte di esse però non capisco se mi interessano. Quella che più mi suscita dubbi è quella dei "Function codes", così descritta: "The functions described in the following (from the Modbus Standard) are available for extracting measured values, device and process data, and for writing data."
    Nel presente capitolo vengono forniti dei codici esadecimali a cui sono associati delle funzionalità
  • 03 o 04, reading n words
  • 06, writing one word
  • 10 writing n words
    Sono necessari per estrapolare i dati di temperatura ed umidità dallo slave?
  1. avete per caso da segnalarmi un esempio di sketch che svolge una funzione similare a quello che devo scrivere io, al fine di prenderne spunto? Ho trovato di tutto, ma non quello che mi serve. Eppure leggere soltanto dei dati dovrebbe essere la cosa più semplice da fare, con questo protocollo.

Lo strumento è il seguente:

Grazie in anticipo a chi potrà aiutarmi.

Il RS485 descrive solo la parte fisica del collegamento. Ci sono 2 linne di dati, una l'opposto dell altro che mana i dati in una direzione. Perché 2 o piú dispositivi possano dialogare serve un protocollo.
Il MODBUS é un protocollo di come vengono mandati i dati che si basa sul RS485 come hardware.

Ciao Uwe

Non è che lo strumento sia tutto funzionante e programmabile via touchscreen,e sulla linea bus puoi leggere solo i dati?

Interfaces such as Modbus (master/slave), PROFIBUS, PROFINET IO device, or Ethernet with web server can be used for the communication with superordinate systems <<<<superordinate systems??? :o

zonalimitatore:
Non è che lo strumento sia tutto funzionante e programmabile via touchscreen,e sulla linea bus puoi leggere solo i dati?

Interfaces such as Modbus (master/slave), PROFIBUS, PROFINET IO device, or Ethernet with web server can be used for the communication with superordinate systems <<<<superordinate systems??? :o

Lo strumento è programmabile tramite USB e software proprietario.
Quello che mi consente di fare è scegliere quale porta utilizzare per il protocollo modbus, se sia master o slave, baud rate, ecc.

Ho contattato il produttore e mi ha fornito gli indirizzi ai quali leggere umidità e temperatura (i valori che ho riportato nel topic iniziale).

Edito per completare la mia risposta: sì, credo che io possa solo leggere dati ed è ciò che comunque mi interessa. Non ho necessità di controllarlo tramite il master, almeno per ora.
Il Jumo è collegato ad una macchina che opera dei cicli di lavoro molto complessi, utilizzando delle rampe di temperatura. Quello che voglio fare è estrarre i dati in tempo reale, visto che il software proprietario mi consente solo di scaricarli una volta terminato il processo (che dura anche 36 ore).
Sto cercando di avere un monitoraggio in real-time, da remoto, di quel che accade. Oltre a temperatura ed umidità relativa, devo salvare in un file JSON anche altri parametri, tramite sensoristica non connessa al Jumo.

Ho iniziato a lavorarci un po' sopra
Per ora ho solo collegato l'adattatore RS485 all'arduino e preparato una bozza di sketch.
L'adattatore che ho a disposizione monta un max13487.
In base a quanto ho trovato in rete (ma non son sicuro che l'informazione sia attendibile), a differenza dei più diffusi adattatori max485, il max 13487 non necessita di avere un controllo del "flusso di linea" (non so se è un termine improprio, l'ho tradotto dalla fonte in inglese che ho trovato).
Ciò dovrebbe significare che, non avendo la connessione DE/RE, non devo preoccuparmi di inizializzarla (almeno credo).

Pertanto, utilizzando la libreria ModbusMaster, reperita al link sottostante, credo di poter ignorare le linee di codice che fanno riferimento a DE/RE.

Quanto segue è un frammento di codice copiato dall'esempio disponibile nella libreria.

#define MAX485_DE      3
#define MAX485_RE_NEG  2

// instantiate ModbusMaster object
ModbusMaster node;

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

void setup()
{
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  // Modbus communication runs at 115200 baud
  Serial.begin(115200);

  // Modbus slave ID 1
  node.begin(1, Serial);
  // Callbacks allow us to configure the RS485 transceiver correctly
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
}

In aggiunta, il max13487 differisce dal max485 per le seguenti specifiche:
Low High

  • MAX 485: -A 3.3V 2.3V
  • MAX 485: -B 1.3V 3.3V
  • MAX13487: -A 2.3V 1.3V
  • MAX13487: -B 2.3V 3.5V

Il progetto dal quale ho reperito quest'ultima informazione analizzava le differenze tra i due adattatori, perché vi era la necessità di collegare un master ad uno slave, con differenti adattatori interconnessi tra loro.
Non ho però la più pallida idea se, nel mio caso specifico, io possa ignorare questa informazione. Deduco e spero di sì.

Sulla base di queste mie considerazioni, ho creato uno sketch di prova, molto semplice.

Faccio alcune premesse.
Nel manuale dello strumento (Jumo Dicon Touch) è indicato che i dati che andrò ad estrarre sono dei float. Gli indirizzi da cui estrarre le informazioni li conosco, come ho indicato nel topic iniziale (0x11E9 e 0x11F3).
Lo sketch di seguito l'ho per ora testato senza collegarlo al Jumo.
Funziona, nel senso che l'adattatore è accesso ed il led TX blinka secondo la cadenza stabilita dallo sketch.
Come potrete vedere dallo sketch, i bytes impostati nella funzione readInputRegisters() sono 4.
Ho impostato questo valore prendendolo dalla legenda della Modbus address table del Jumo, che di seguito riporto:

Bit x, Bit no. x (bit 0 is the least significant bit)
Bool, Boolean value (TRUE or FALSE); can be read or written as a word. The value range is 0 to 1.
Byte, 1 byte = 8 bits; can be read or written as word. The value range is 0 to 255.
Word, 1 word = 2 bytes = 16 bits
Int32, Integer (32 bits) = 2 words
Uint32, Unsigned integer (4 bytes) = 32 bits = 2 words
Long, Long integer (4 bytes) = 32 bits = 2 words
Float, Floating-point value (4 bytes) acc. to IEEE 754. This device saves the floating-point values in the format Float MSB.
Char[60], Text with 60 characters, with 2 characters in each word
Bit field 32, Bit field 32 bits long

E' corretta la mia osservazione?

Ho anche delle riserve sull'uso della funzione readInputRegisters().
Ho dedotto che fosse quella che mi servisse guardando l'esempio fornito con la libreria; non so se sono io che non trovo indicazioni, ma mi sembra che nel manuale della libreria non sia riportata la lista delle funzioni disponibili.

Questo il mio sketch

#include <ModbusMaster.h>
#include <SoftwareSerial.h>

SoftwareSerial RS485Serial(10, 11); //RX, TX

// instantiate ModbusMaster object
ModbusMaster node;

void setup() {
  Serial.begin(9600);
  RS485Serial.begin(9600);
  node.begin(1, RS485Serial); // Modbus slave ID 1
}

void loop() {
  float T; // temperature (°C)
  float RH; // relative humidity

  //Readings
  T = node.readInputRegisters(0x11E9, 4);
  delay(500);
  RH = node.readInputRegisters(0x11F3, 4);
  delay(1000);

  //Outputs
  Serial.println(T);
  Serial.println(RH);
}

Commenti e/o suggerimenti in merito?

Riesumo il topic perché dopo un po' di tempo ho fatto dei progressi, ma senza utilizzare Arduino.
Ho preferito comprarmi un convertitore USB-to-RS485 per capire come il protocollo Modbus funzioni, testando la comunicazione tramite pc.
Devo dire di aver capito come comunicare con il mio slave, ma ho chiare difficoltà a collegarmi con Arduino.

Il codice dello sketch che avevo postato tempo fa non andava bene.
La funzione che devo usare per leggere i dati è la readHoldingRegisters(), in cui devo specificare l'indirizzo a cui leggere. Oltretutto, la funzione in questione non estrapola i dati, ma semplicemente restituisce un valore che dovrebbe essere indicativo di lettura correttamente avvenuta o fallita.

Di seguito incollo il nuovo codice, scritto basandomi sull'uso della libreria scaricata al seguente indirizzo.

Tuttavia, non capisco perché non riceva alcuna risposta dallo slave.
Quando invia la richiesta di lettura vedo blinkare il led TX sull'adattatore RS485, ma quello RX rimane spento.

Qualcuno sa darmi consigli?

#include <ModbusMaster.h>
#include <SoftwareSerial.h>

SoftwareSerial RS485Serial(10, 11); //RX, TX

// instantiate ModbusMaster object
ModbusMaster node;

void setup() {
  Serial.begin(9600);
  RS485Serial.begin(9600);
  node.begin(1, RS485Serial); // Modbus slave ID 1
}

void loop() {
  uint8_t result;
  uint16_t data[6];

  //Readings
  result = node.readHoldingRegisters(0x11E9, 2);
  delay(1000);

  if (result == node.ku8MBSuccess)
  {
    for (j = 0; j < 6; j++)
    {
      data[j] = node.getResponseBuffer(j);
    }
  }

  //Output
  Serial.println(data);

}

Seguo con interesse perchè voglio collegare periferiche RS-485 di Arduino anche su RS-485 di PLC Mitsubishi.

Intanto mi guardo i video di Paolo Aliverti su Youtube dove prima ha spiegato i collegamenti RS-485 e poi il protocollo Modbus..

Ho contattato la casa produttrice dell'adattatore SH-U12, chiedendogli supporto. Mi han risposto che la loro scheda è completamente automatizzata e che, al contrario del max485, non necessita di alcuna inizializzazione prima e dopo l'invio della richiesta allo slave.

Ho visto anche io i video di Aliverti, ma si limita al controllo dello slave e non allo scambio dati con il master.

Domani, sperando di aver tempo, farò altri test. Ho anche un adattatore max485 da provare.

Ho cambiato l'adattattore con un max485 e ora tutto funziona.
Ho utilizzato i collegamenti classici che si trovano anche in rete

MAX485 to ARDUINO
VCC to 5V
GND to GND
DE to PIN 3
RE to PIN 4
R0 to 10
DI to 11

La libreria Modbus che ho utilizzato è la stessa che si puà installare dall'Arduino IDE o scaricare da GitHub - 4-20ma/ModbusMaster: Enlighten your Arduino to be a Modbus master

Di seguito incollo il codice che ho scritto e nel quale ho implementato la convesione HEX to FLOAT che ho trovato in un altro sketch nella sezione inglese.
La sketch è pensato per mettere l'Arduino "in ascolto". Su richiesta invio la stringa "r" tramite seriale e l'Arduno legge dallo slave, per poi stampare sulla seriale i dati convertiti in float.
Questa struttura mi è utile per collegare l'Arduino ad un RaspberryPI e comandarlo tramite Node-Red.

Spero possa essere d'aiuto a qualcuno. Se servono spiegazioni, visto che nel codice mancano commenti, sono a disposizione.

#include <ModbusMaster.h>
#include <SoftwareSerial.h>

SoftwareSerial RS485Serial(10, 11); //RX, TX
ModbusMaster node;

uint32_t REG[2] = {0x11E9, 0x11F3};

#define MAX485_DE  3
#define MAX485_RE  4

void preTransmission()
{
  digitalWrite(MAX485_RE, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE, 0);
  digitalWrite(MAX485_DE, 0);
}

//------------------------------------------------
// Convent 32bit to float
//------------------------------------------------
float HexTofloat(uint32_t x) 
{
  return (*(float*)&x);
}

uint32_t FloatTohex(float x) 
{
  return (*(uint32_t*)&x);
}
//------------------------------------------------

void setup() 
{
  Serial.begin(9600);
  RS485Serial.begin(9600);
  node.begin(1, RS485Serial); // Modbus slave ID 1
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
  
  pinMode(MAX485_RE, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  
  digitalWrite(MAX485_RE, 0);
  digitalWrite(MAX485_DE, 0);
}

void loop() 
{
	if (Serial.available())
	{
	   String request = Serial.readStringUntil(13);
		if (request[0] == 'r')
		{
			uint8_t k = 0;
			uint32_t data[2];
			float sensors[2];
			for (k = 0; k < 2; k++)
			{
				float i = 0;
				uint8_t result,j;
				uint32_t value = 0; 
				result = node.readHoldingRegisters(REG[k], 2);
				delay(500);
				if (result == node.ku8MBSuccess)
				{
					for (j = 0; j < 2; j++)
					{
						data[j] = (node.getResponseBuffer(j));
					}
          value = data[1];
          value = value << 16;
          value = value + data[0];
          sensors[k] = HexTofloat(value);
				} 
				else
				{
					Serial.println("Connect modbus fail");
					delay(1000);
				}
			}
			Serial.print(sensors[0]); // print relative humidity
			Serial.print(",");
			Serial.println(sensors[1]); // print temperature
		}
	}
}

Ottimo !