[RISOLTO] Comunicazione Modbus RTU problemi lettura dati

Ciao,
sto cercando di comunicare tramite adattatore TTL/RS485 con un flussimetro con std modbus RTU, di per sè la comunicazione funziona, riscontro però dei problemi nella conversione dei dati e nella velocità di lettura, procedendo con ordine:
-Arduino Mega
-Convertitore TTL/RS485 (half-duplex, con MAX1346)
-Flussimetro nativo full-duplex (un red-y da banco),
-comunicazione con 9600,8 N,2
-cablaggio da half-duplex a full-duplex

Il mio codice è questo:

//riadattamento dell'esempio "basic" della libreria
#include <ModbusMaster.h>


// instantiate ModbusMaster object
ModbusMaster node;
int del=1000;
unsigned long previousMillis = 0; 
void setup()
{
  // use Serial (port 0); initialize Modbus communication baud rate
  Serial1.begin(9600,SERIAL_8N2);
  Serial.begin(9600);
  // communicate with Modbus slave ID 2 over Serial (port 0)
  node.begin(141, Serial1);
}


void loop()
{
  static uint32_t i;
  uint8_t j, result;
  uint16_t data[6];
  i++;
  
  // set word 0 of TX buffer to least-significant word of counter (bits 15..0)
  //node.setTransmitBuffer(0, lowWord(i));
  
  // set word 1 of TX buffer to most-significant word of counter (bits 31..16)
  node.setTransmitBuffer(1, highWord(i));
  
  // slave: write TX buffer to (2) 16-bit registers starting at register 0
  //result = node.writeMultipleRegisters(0, 2);
  
  // slave: read (6) 16-bit registers starting at register 2 to RX buffer

  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis >= del){
    previousMillis = currentMillis;
    result = node.readHoldingRegisters(0, 4);
  
    if (result == node.ku8MBSuccess){
      Serial.print(node.getResponseBuffer(0));
      Serial.print(" ");
      Serial.print(node.getResponseBuffer(1));
      Serial.print(" ");
      Serial.print(node.getResponseBuffer(2));
      Serial.print(" ");
      Serial.println(node.getResponseBuffer(3));
    }
  }
}

Non riesco a capire come convertire il valore ricevuto in decimale (con ModbusPoll converto il valore riportato nella terza colonna,16869,da F32 a big-endian e ottengo il valore corretto di temperatura 26.2 °C)

questo un esempio di output a seriale:
17:39:12.066 -> 0 0 16869 44424
17:39:13.073 -> 0 0 16869 44424
17:39:14.080 -> 0 0 16869 44424
17:39:15.049 -> 0 0 16869 44424
17:39:16.058 -> 0 0 16869 44424
17:39:17.064 -> 0 0 16869 44424
17:39:18.071 -> 0 0 16869 44424
17:39:19.074 -> 0 0 16869 44424
17:39:20.082 -> 0 0 16869 44424
17:39:21.091 -> 0 0 16869 45548
17:39:22.062 -> 0 0 16869 45548
17:39:30.076 -> 0 0 16869 46672
17:39:31.082 -> 0 0 16869 46672
17:39:32.094 -> 0 0 16869 46672

In allegato c'è una spiegazione dei registri, fornita dal produttore.
Vorrei capire come poter fare a trasformare i dati inviati dal flussimetro in valori numerici, purtroppo non sono molto ferrato nel campo, ho letto che la conversione dovrebbe essere fatta secondo lo standard IEEE754 ma non so proprio come iniziare.
Qualcuno può aiutarmi?
una seconda domanda, come potete vedere dal timestamp molto spesso ci sono degli arresti improvvisi di qualche secondo,che si ripetono ad intervalli quasi regolari, non capisco perchè..dove può stare il collo di bottiglia?C'è forse un modo migliore leggere i valori dei registri?


Grazie,
Paolo

ciao,

devi usare le union...tipo:

union F32 {
  float result;
  uint16_t a[2];
};

F32 temp;

temp.a[0] = node.getResponseBuffer(3);
temp.a[1] = node.getResponseBuffer(2);
Serial.print(temp.result);

Ciao ORSO2001,
grazie della risposta, testata e funziona, nel frattempo avevo trovato l'alternativa con i puntatori ma mi è di difficile comprensione.

Ho visto che con la union posso fare il contrario cioè da float a int

union F32 {
  float result;
  uint16_t a[2];
};

F32 temp;
float temperatura= 24.91;
temp.result = temperatura; 

 Serial.println(temp.a[1],HEX);
 Serial.println(temp.a[0],HEX);

che è fantastico così ho trovato la soluzione anche per scrivere nei registri, in teoria.

Adesso però mi sorgono dei dubbi.
Praticamente per ottenere un valore decimale da 2 byte esadecimali secondo la convenzione big endian basta solo ordinarli nel modo corretto? E' la union che fa la conversione da uint16 a float32 secondo lo IEEE754 o non centra niente?

Scusa le domande stupide ma come detto non è il mio forte, mi sto facendo un pò di cultura di base ma il percorso è lungo...
grazie ancora

ciao,

giusto per chiarire (spero)...in arduino una "int" od "unsigned int" comprendono 2 byte (16 bit totali)...diversamente da altre piattaforme dove sono a 4 byte (32 bit totali).
Sempre in arduino le "float" e le "double" sono a 4 byte (32 bit totali)...dove le "double" in altre piattaforme sono a 8 byte (64 bit totali)....lascio a te approffondire l'argomento in questione tra "segno", "esponente" e "mantissa".

la differenza tra "big endian" e "Littel endian" è l'ordine con cui vengono trasferiti e/o salvati i byte trasmessi di una variabile multi byte...per esempio il valore 0x1234, che è una int avente due byte..di cui uno avente "valore" 0x12 ed il secondo 0x34 vengono così trattati:

LE = posizione[0] 0x34; posizione[1] 0x12
BE = posizione[0] 0x12; posizione[1] 0x34

quindi c'è la combinazione dei byte da AB a BA...anche qui ti lascio approfondire per le combinazione a più byte, come nel tuo caso, da tipo ABCD a CDAB oppure DCBA etc...

le union sono usate per gestire lo stesso spazio della memoria allocata con "significati" diversi...nell'esempio che ti ho postato la union ha 2 campi..un float (4 byte...32 bit) e un array di 2 u_int (2x2=4 byte 32 bit); questo ti permette, senza l'uso dei puntatori, di allocare dove vuoi.

spero sia chiaro ( e di non aver sparato stupidagini)

Grazie, inizio a capire..o credo :slight_smile:
Una domanda, è normale che nella conversione si perda la precisione dei decimali gia alla seconda cifra dopo la virgola?
Se io genero i primi 50 numeri con step di 0.1 con il seguente codice

union F32 {
  float result;
  uint16_t a[2];
};

float setpoint=0.000; //variabile di setpoint

void setup(){

  Serial.begin(9600);
}


void loop(){
  for(int i;i<50;i++){
    F32 write_data;
    setpoint=setpoint+0.100;
    write_data.result=setpoint;
    Serial.print(setpoint);
    Serial.print(" ");
    Serial.print(write_data.a[1],HEX);
    Serial.print(" ");
    Serial.println(write_data.a[2],HEX);  
  }

ottengo questo risultato (prime 3 colonne), se prendo il risultato della scomposizione in int (visualizzati in hex) e li converto con un convertitore tipo questo:

il risultato, 4 colonna non è più preciso alla prima cifra decimale:
0,10 3DCC 8200 0,09985733
0,20 3E4C 8200 0,199714661
0,30 3E99 8200 0,299819946
0,40 3ECC 8200 0,399429321
0,50 3F00 8200 0,501983643
0,60 3F19 8200 0,5996399
0,70 3F33 8200 0,7012024
0,80 3F4C 8200 0,798858643
0,90 3F66 8200 0,900421143
1,00 3F80 8200 1,00396729
1,10 3F8C 8200 1,09771729
1,20 3F99 8200 1,19927979
1,30 3FA6 8200 1,30084229
1,40 3FB3 8200 1,40240479
1,50 3FC0 8200 1,50396729
1,60 3FCC 8200 1,59771729
1,70 3FD9 8200 1,69927979
1,80 3FE6 8200 1,80084229
1,90 3FF3 8200 1,90240479
2,00 4000 8200 2,00793457
2,10 4006 8200 2,10168457
2,20 400C 8200 2,19543457
2,30 4013 8200 2,30480957
2,40 4019 8200 2,39855957
2,50 401F 8200 2,49230957
...

Che è un pò quello che vedo leggendo il setpoint dal flussimetro dopo averlo modificato tramite writeMultipleResgister()

Se leggi il reference vedi che la Serial.print() prevede un secondo parametro che è il numero di decimali. Se tu non specifichi nulla, la Serial.print(), da sola, usa di base 2 decimali e quindi ... automaticamente arrotonda a 2 decimali. Se vuoi vedere il valore con più decimali devi specificare quanti ne vuoi.

Guglielmo

P.S.: prova leggere anche QUESTA discussione ... c'era un problema simile al tuo :smiley:

Ciao Guglielmo, grazie per la risposta e scusa il ritardo per la mia:)

Ho letto il thread che mi hai suggerito, e ho trovato anche un sito per la conversione da decimale a binario ieee754 in un altro post; questo il sito IEEE-754 Floating Point Converter

Quello che non mi torna è che l'errore che vedo non è dovuto al troncamento della Serial.print()
in quanto già in binario (sotto forma di esadecimale) la conversione restituita dalla union presenta
un errore già sulla seconda cifra decimale che è ben lontano dall'errore indicato nel sito sopra.

Per esempio
conversione di decimale 1.20 in hex:
Arduino (output che ho allegato alla Reply#4): 3F99 8200
Sito (qualunque) per conversioni:3F99 999A

se adesso converto(sempre con sito internet) nuovamente il risultato ottenuto in decimale con i due metodi ottengo:
Arduino:1.19927979
Sito:1.20

Nel sito che ho linkato l'errore di conversione è irrisorio: 4.76837158203125E-8.

Da dove proviene questo errore di conversione di Arduino?

paolo_cristoforetti:
Nel sito che ho linkato l'errore di conversione è irrisorio: 4.76837158203125E-8.

... che oltre ogni immaginabile precisione di Arduino !

Ti ricordo che su Arduino, come dice il reference, la "precisione" è:

The float data type has only 6-7 decimal digits of precision. That means the total number of digits, not the number to the right of the decimal point. Unlike other platforms, where you can get more precision by using a double (e.g. up to 15 digits), on the Arduino, double is the same size as float.

Inoltre viene chiaramente detto:

Floating point numbers are not exact, and may yield strange results when compared. For example 6.0 / 3.0 may not equal 2.0. You should instead check that the absolute value of the difference between the numbers is less than some small number.

Questo ti fa capire che i float, su Arduino, sono piuttosto soggetti ... ad arrotondamenti e scarsa precisione ... ::slight_smile:

Guglielmo

certo, non mi aspettavo una precisione cosi eccezionale. È anche vero che come riportato nella reference una precisione di 6digits dovrebbe permettermibdi avere il numero corretto arrotondando alla seconda cifra, o sbaglio?

Mah ... se (6.0 / 3.0) può NON dare 2.0 ... ho alcuni dubbi su quali siano gli arrotondamenti ... ::slight_smile:

Guglielmo

Ciao Guglielmo, grazie della pazienza :slight_smile:
Premesso che non voglio polemizzare ma solo capire..cosa si intende per "può non dare", quale può esere il fenomeno che genera l'errore in modo, a questo punto, in modo casuale?

Floating point numbers are not exact, and may yield strange results when compared. For example 6.0 / 3.0 may not equal 2.0. You should instead check that the absolute value of the difference between the numbers is less than some small number.

Quanto è grande questo "small number"?

Se la precisione nelle conversioni è così bassa, che senso a nella serial print avere la possibilità di estendere a più decimali la visualizzazione?

Se banalmente carico questo sketch:

float a=6.0, b=3.0;
float c;
bool e;

void setup() {
  Serial.begin(9600);
  c=a/b;
  Serial.print(c,1);
  Serial.print(" - - ");
  Serial.print(c,10);
  e=(a/b)==2.0;
  Serial.print(" - - ");
  Serial.print(e);
}

void loop() {
 
}

L'output mi fà supporre che l'errore non si manifesta...

@nid69ita
Ciao,
la quarta colonna è il risultato che ottengo convertendo l'esadecimale delle colonne 2-3 utilizzando il sito internet https://www.scadacore.com/tools/programming-calculators/online-hex-converter/ (che è il primo che ho trovato).
Non fa quindi parte dell'outpit dell'Arduino, scusa l'imprecisione.

>paolo_cristoforetti: QUESTA è la rappresentazione utilizzata su Arduino per i float.

Guglielmo

Strano, usare la union per vedere le 2 word del float NON fa nessuna "conversione"
Ti fa vedere come il linguaggio C memorizza a bit un float (nel tuo caso le 2 word).

AGGIORNAMENTO: scusa ma hai visto l'errore enorme che hai fatto?
Al post#4 non hai notato che il secondo HEX è sempre 8200 ??
In C gli indici degli array partono da 0, quindi tra quadre per a 0 e 1 NON 1 e 2

AGGIORNAMENTO: scusa ma hai visto l'errore enorme che hai fatto?
Al post#4 non hai notato che il secondo HEX è sempre 8200 ??
In C gli indici degli array partono da 0, quindi tra quadre per a 0 e 1 NON 1 e 2

:roll_eyes:
non mi ero proprio accorto di questo...svista imperdonabile (ho programmato un pò per lavoro in VBA ed è la stessa cosa).
Però, visto che richiamo un indice superiore alla dimensione dichiarata dell'array non dovrebbe dare errore?

Comunque adesso torna tutto:

union F32 {
  float result;
  uint16_t a[2];
};

float setpoint=0.000; //variabile di setpoint
  int i=0;
void setup(){

  Serial.begin(9600);
}


void loop(){

  for(i;i<50;i++){
    F32 write_data;
    F32 read_data;
    setpoint=setpoint+0.100;
    write_data.result=setpoint;
    Serial.print(setpoint);
    Serial.print(" ");
    Serial.print(write_data.a[0],HEX);
    Serial.print(" ");
    Serial.print(write_data.a[1],HEX);
    read_data.a[0]=write_data.a[0];
    read_data.a[1]=write_data.a[1];
    Serial.print(" ");
    Serial.println(read_data.result,5);
    
  }
}

....
3.10 6663 4046 3.10000
3.20 CCC9 404C 3.20000
3.30 332F 4053 3.30000
3.40 9995 4059 3.40000
3.50 FFFB 405F 3.50000
3.60 6661 4066 3.60000
3.70 CCC7 406C 3.70000
3.80 332D 4073 3.80000
3.90 9993 4079 3.90000
4.00 FFF9 407F 4.00000
4.10 3330 4083 4.10000
4.20 6663 4086 4.20000
4.30 9996 4089 4.30000
4.40 CCC9 408C 4.40000
4.50 FFFC 408F 4.50000
4.60 332F 4093 4.60000
4.70 6662 4096 4.70000
4.80 9995 4099 4.80000
4.90 CCC8 409C 4.90000
5.00 FFFB 409F 5.00000

Comunque, se io converto 3.5 con questo sito:
https://www.h-schmidt.net/FloatConverter/IEEE754.html
ottengo come HEX questo valore:(SITO)= 4060 0000

Ritornando in decimale con il secondo sito

ottengo:

Fonte Hex fonte Decimale convertito
SITO 40 60 00 00 3.5
ARDUINO 40 5F FF FB 3.49999881

c'è comunque un errore nella conversione, ma meno marcato

paolo_cristoforetti:
Però, visto che richiamo un indice superiore alla dimensione dichiarata dell'array non dovrebbe dare errore?

MAI, in C al massimo il compilatore può dare un warning.
Il C non controlla se vai fuori da ultimo elemento di un array. Devi tu fare il controllo.
Questo perché il C è un linguaggio senza molti controlli/features di linguaggi tipo VB o Java. E' usato per creare gli altri linguaggi e S.O., essendo basico, poi il programmatore implementa i controlli che vuole.

P.S. in VB/VBA in base ad "Option Base {0 | 1}" puoi avere gli indici che iniziano da 0 o da 1.
Anche in base alla dichiarazione: Dim v(0 to 4) as integer, z(1 to 5) as integer

Bene,
grazie per le varie delucidazioni e scusate per il tempo perso per un errore.. :cold_sweat:

Sbagliare humanum est :smiley: