Buonasera, sto smanettando con un contatore di energia Modbus RTU, il protocollo del dispositivo corrisponde allo standard IEEE754 secondo il quale la parola Modbus è costruita con un bit di segno, un esponente e la cosiddetta mantissa. Lo sketch è fornito dal costruttore del dispositivo e funziona, includendo la libreria math e applicando la funzione pow leggo i registri in modo corretto.
Chiedo a qualcuno più esperto di spiegarmi la seguente parte di codice, vorrei capire come applicare su un dispositivo differente con stesso protocollo.
float convert_t5(uint32_t n)
{
uint32_t s = (n & 0x80000000) >> 31;
int32_t e = (n & 0x7F000000) >> 24;
if (s == 1)
{
e = e - 0x80;
}
uint32_t m = n & 0x00FFFFFF;
return (float)m * pow(10, e);
}
float convert_t6(uint32_t n)
{
uint32_t s = (n & 0x80000000) >> 31;
int32_t e = (n & 0x7F000000) >> 24;
if (s == 1)
{
e = e - 0x80;
}
uint32_t ms = (n & 0x00800000) >> 23;
int32_t mv = (n & 0x007FFFFF);
if (ms == 1)
{
mv = mv - 0x800000;
}
return (float)mv * pow(10, e);
}
uint32_t modbus_7m_read16(uint8_t addr, uint16_t reg)
{
uint32_t attempts = 3;
while (attempts > 0)
{
digitalWrite(LED_D0, HIGH);
ModbusRTUClient.requestFrom(addr, INPUT_REGISTERS, reg, 1);
uint32_t data = ModbusRTUClient.read();
digitalWrite(LED_D0, LOW);
if (data != INVALID_DATA)
{
return data;
}
else
{
attempts -= 1;
delay(10);
}
}
return INVALID_DATA;
}
uint32_t modbus_7m_read32(uint8_t addr, uint16_t reg)
{
uint8_t attempts = 3;
while (attempts > 0)
{
digitalWrite(LED_D0, HIGH);
ModbusRTUClient.requestFrom(addr, INPUT_REGISTERS, reg, 2);
uint32_t data1 = ModbusRTUClient.read();
uint32_t data2 = ModbusRTUClient.read();
digitalWrite(LED_D0, LOW);
if (data1 != INVALID_DATA && data2 != INVALID_DATA)
{
return data1 << 16 | data2;
}
else
{
attempts -= 1;
delay(10);
}
}
return INVALID_DATA;
}
Grazie per eventuale aito, delucidazioni e risposte.
lo IEEE754 definisce il formato per la rappresentazione de numeri con virgola mobile...quelli che in ARDUINO vengono definiti come "float" o "double" ...che in ARDUINO sono la stessa cosa...in altri casi no.
Sono numeri "lunghi" 32 BIT; ma a differenza delle variabile di tipo INT nelle float e double ogni bit ha il proprio "compito"...in questo caso:
1 8 23 lunghezza in bit
+-+--------+-----------------------+
|S| Esp. | Mantissa |
+-+--------+-----------------------+
31 30 22 0 indice dei bit
il ModBus è un protocollo per lo scambio dati (trovi documentazione e tool vari in abbondanza)...per essere sintetico...in ModBus leggi/scrivi registri a 16 BIT...se devi leggere/scrivere un float (32 BIT) devi considerare due registri contigui e "costruirti" o "disassemblare" la tua variabile a 32.
Per quanto riguarda quale registro corrisponde a cosa devi far riferimento alla documentazione specifica dell'oggetto con cui vuoi dialogare....quindi se condividi il documento possiamo darci un'occhiata.
Ciao, ho nozioni riguardo Modbus, mi sono incartato con i le variabili a 32 bit, ho allegato il codice in quanto mi confonde le idee la parte di calcolo....
se ti riferisci alle funzioni "convert_t5" e "convert_t6"...dovrebbe essere:
float convert_t5(uint32_t n) //prendo in ingresso una variabile INT a 32 BIT...quindi i famosi 2 registri combinati...e ritorno un float
{
uint32_t s = (n & 0x80000000) >> 31; // dalla INT verifico il segno per il float
int32_t e = (n & 0x7F000000) >> 24; // dalla INT estraggo l'esponente per il float
if (s == 1) // se il float deve essere negativo
{
e = e - 0x80; // modifico l'esponente
}
uint32_t m = n & 0x00FFFFFF; //dalla INT estraggo la mantissa per il float
return (float)m * pow(10, e); //assemblo il tutto per ritornare il float
}
Fino ad ora ho sempre utilizzato tool pronti per la lettura dei registri Modbus di vari dispositivi andando semplicemente a definire i parametri di comunicazione e i registri da leggere. Adesso sto sperimentando un pò a banco con un contatore di energia, avrei poi intenzione di implementare un web server per avere a disposizione i dati di lettura.
Se parto da data = modbus_7m_read32(MODBUS_7M_ADDRESS, FINDER_7M_REG_U1); Serial.println(" voltage = " + (data != INVALID_DATA ? String(convert_t5(data)) : String("read error")));
non er essere pignolo ma sto pezzo di codice per come è scritto non compilerà mai...manca una "}" per chiudere il loop()...poi c'è un for() e subito dopo la dichiarazione delle funzioni.
detto questo..."data" è una variabile locale di tipo unit32_t; la usi per conservare il valore ritornato da alcune funzioni tipo la "modbus_7m_read32"...la variabile "data" la puoi a sua volta passare alla funzione "convert_t5(data)" che trasformerà la uint32_t in una float...in questo caso il valore salvato in "data" non avrebbe senso.
prende un numero intero, non segnato, lungo 32 bit che chiama 'n', con l'operatore AND mette a zero tutti i bit meno il 32esimo bit ovvero il bit 31 (numerando da 0) e poi lo fa scorrere verso destra di 31 posizioni ... alla fine della cosa hai un numero intero che varrà 0 o 1 in funzione del bit che, in un float, identifica il segno (e che, appunto, è posizionato nel 32esimo bit a sinistra). Quindi questa riga ti ritona un numero che vale solo 0 o 1.
int32_t e = (n & 0x7F000000) >> 24;
Qui prende lo stesso numero 'n', ma questa volta si dedica ad estrarre il valore dell'esponente che va dal bit 24esimo al 31esimo (ovvero, partendo a numerare da 0, dal 23 al 30) ... per fare questo prima con il solito AND mette a zero tutti i bit che non gli interessano (dallo 0 al 22 e il 31) dopo di che fa scorrere iul tutto verso destra di 24 posizioni così da allineare i bit a destra ... in questo modo ha ottenuto un numero intero che rappresenta il valore dell'esponente e che potrà valere da 0 a 254 (sono 8 bit).
Nel reference di Arduino, in fondo, trovi tutte le spiegazioni per i vari operatori bitwise (quelli che operano sui singoli bit).
La parentesi sarà un errore di copia incolla, lo sketch sta girando e funziona, leggo una variabile sul cloud Arduino. Ripeto, voglio approfondire visto che fino ad ora dalle mie esperienze ho smanettato con applicazioni limitandomi a leggere un registro, convertirlo in decimale e visualizzarlo, c'è un mio post in cui descrivo appunto Modbus con sensori SHT20 e Wemos.
IEEE754 sto cercando di capire adesso come funziona.
@gpb01 intanto grazie per le delucidazioni, stavo leggendo il reference già segnalato da @andreaber, ovviamente poi ho bisogno come in tutte le cose di smanettare un pò.
Quindi, se voglio recuperare la parte dell'esponente, devo eliminare segno e matissa e quindi l'and lo devo fare con 0x7F800000 e non con 0x7F000000 (altrimenti perdo l'ultimo bit dell'esponente) e lo shift a sinistra deve essere di 23 bit, non di 24 ...
#include <ArduinoModbus.h>
#include <ArduinoRS485.h>
#include <WiFi.h>
#include <math.h>
#include "finder-7m.h"
#include "config.h"
#include "thingProperties.h"
const uint8_t MODBUS_7M_ADDRESS = 11;
void setup()
{
Serial.begin(9600);
digitalWrite(LEDG, HIGH);
digitalWrite(LEDB, HIGH);
digitalWrite(LED_D0, HIGH);
digitalWrite(LED_D1, HIGH);
digitalWrite(LED_D2, HIGH);
digitalWrite(LED_D3, HIGH);
delay(2000);
digitalWrite(LEDG, LOW);
digitalWrite(LEDB, LOW);
digitalWrite(LED_D0, LOW);
digitalWrite(LED_D1, LOW);
digitalWrite(LED_D2, LOW);
digitalWrite(LED_D3, LOW);
Serial.println("Arduino + 7M example: setup");
initProperties();
RS485.setDelays(MODBUS_PRE_DELAY, MODBUS_POST_DELAY);
ModbusRTUClient.setTimeout(200);
if (ModbusRTUClient.begin(MODBUS_BAUDRATE, MODBUS_SERIAL_PARAMETERS))
{
Serial.println("Modbus RTU client started");
}
else
{
Serial.println("Failed to start Modbus RTU client: reset board to restart.");
while (1)
{
}
}
setDebugMessageLevel(2);
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
ArduinoCloud.addCallback(ArduinoIoTCloudEvent::CONNECT, iotConnect);
ArduinoCloud.addCallback(ArduinoIoTCloudEvent::DISCONNECT, iotDisconnect);
ArduinoCloud.printDebugInfo();
}
const uint32_t INVALID_DATA = 0xFFFFFFFF;
void loop()
{
uint32_t data;
Serial.println("** Reading 7M at address " + String(MODBUS_7M_ADDRESS));
data = modbus_7m_read32(MODBUS_7M_ADDRESS, FINDER_7M_REG_ENERGY_COUNTER_XK_E1);
Serial.println(" energy = " + (data != INVALID_DATA ? String(data) : String("read error")));
if (data != INVALID_DATA)
{
energy = (float)data / 10000;
}
data = modbus_7m_read32(MODBUS_7M_ADDRESS, FINDER_7M_REG_RUN_TIME);
Serial.println(" run time = " + (data != INVALID_DATA ? String(data) : String("read error")));
data = modbus_7m_read32(MODBUS_7M_ADDRESS, FINDER_7M_REG_FREQUENCY);
Serial.println(" frequency = " + (data != INVALID_DATA ? String(convert_t5(data)) : String("read error")));
data = modbus_7m_read32(MODBUS_7M_ADDRESS, FINDER_7M_REG_U1);
Serial.println(" voltage = " + (data != INVALID_DATA ? String(convert_t5(data)) : String("read error")));
data = modbus_7m_read32(MODBUS_7M_ADDRESS, FINDER_7M_REG_ACTIVE_POWER_TOTAL);
Serial.println(" active power = " + (data != INVALID_DATA ? String(convert_t6(data)) : String("read error")));
for (uint32_t i = 0; i < READ_INTERVAL_SECONDS * 10; i++)
{
ArduinoCloud.update();
delay(100);
data = 0;
}
}
float convert_t5(uint32_t n) //prendo in ingresso una variabile INT a 32 BIT...
//quindi i famosi 2 registri combinati...e ritorno un float
{
uint32_t s = (n & 0x80000000) >> 31; // dalla INT verifico il segno per il float
int32_t e = (n & 0x7F000000) >> 24; // dalla INT estraggo l'esponente per il float
if (s == 1) // se il float deve essere negativo
{
e = e - 0x80; // modifico l'esponente
}
uint32_t m = n & 0x00FFFFFF; //dalla INT estraggo la mantissa per il float
return (float)m * pow(10, e); //assemblo il tutto per ritornare il float
}
float convert_t6(uint32_t n)
{
uint32_t s = (n & 0x80000000) >> 31;
int32_t e = (n & 0x7F000000) >> 24;
if (s == 1)
{
e = e - 0x80;
}
uint32_t ms = (n & 0x00800000) >> 23;
int32_t mv = (n & 0x007FFFFF);
if (ms == 1)
{
mv = mv - 0x800000;
}
return (float)mv * pow(10, e);
}
uint32_t modbus_7m_read16(uint8_t addr, uint16_t reg)
{
uint32_t attempts = 3;
while (attempts > 0)
{
digitalWrite(LED_D0, HIGH);
ModbusRTUClient.requestFrom(addr, INPUT_REGISTERS, reg, 1);
uint32_t data = ModbusRTUClient.read();
digitalWrite(LED_D0, LOW);
if (data != INVALID_DATA)
{
return data;
}
else
{
attempts -= 1;
delay(10);
}
}
return INVALID_DATA;
}
uint32_t modbus_7m_read32(uint8_t addr, uint16_t reg)
{
uint8_t attempts = 3;
while (attempts > 0)
{
digitalWrite(LED_D0, HIGH);
ModbusRTUClient.requestFrom(addr, INPUT_REGISTERS, reg, 2);
uint32_t data1 = ModbusRTUClient.read();
uint32_t data2 = ModbusRTUClient.read();
digitalWrite(LED_D0, LOW);
if (data1 != INVALID_DATA && data2 != INVALID_DATA)
{
return data1 << 16 | data2;
}
else
{
attempts -= 1;
delay(10);
}
}
return INVALID_DATA;
}
void iotConnect()
{
digitalWrite(LEDB, HIGH);
}
void iotDisconnect()
{
digitalWrite(LEDB, LOW);
}
e qui il file "finder-7m.h":
#include <ArduinoRS485.h>
#define FINDER_7M_REG_RUN_TIME 103 // Run time
#define FINDER_7M_REG_FREQUENCY 105 // Frequency
#define FINDER_7M_REG_U1 107 // Voltage U1
//#define FINDER_7M_REG_U2 109 // Voltage U2
//#define FINDER_7M_REG_U3 111 // Voltage U3
#define FINDER_7M_REG_ACTIVE_POWER_TOTAL 140 // Active Power Total (Pt)
#define FINDER_7M_REG_ENERGY_COUNTER_E1 406 // Energy counter E1
//#define FINDER_7M_REG_ENERGY_COUNTER_E2 408 // Energy counter E2
#define FINDER_7M_REG_ENERGY_COUNTER_E3 410 // Energy counter E3
//#define FINDER_7M_REG_ENERGY_COUNTER_E4 412 // Energy counter E4
#define FINDER_7M_REG_ENERGY_COUNTER_XK_E1 462 // Energy counter E1 x 1000
//#define FINDER_7M_REG_ENERGY_COUNTER_XK_E2 464 // Energy counter E2 x 1000
#define FINDER_7M_REG_ENERGY_COUNTER_XK_E3 466 // Energy counter E3 x 1000
//#define FINDER_7M_REG_ENERGY_COUNTER_XK_E4 468 // Energy counter E4 x 1000
// Calculate preDelay and postDelay in microseconds as per Modbus RTU.
//
// See MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
constexpr auto MODBUS_BAUDRATE = 19200;
constexpr auto MODBUS_SERIAL_PARAMETERS = SERIAL_8N1;
constexpr auto MODBUS_BIT_DURATION = 1.f / MODBUS_BAUDRATE;
constexpr auto MODBUS_PRE_DELAY = MODBUS_BIT_DURATION * 9.6f * 3.5f * 1e6;
constexpr auto MODBUS_POST_DELAY = MODBUS_BIT_DURATION * 9.6f * 3.5f * 1e6;
digita o incolla il codice qui
Ribadisco che le due funzioni usare per ricavare un float da un int_32 mi sembrano errate ... vengono trattati solo 7 bit per l'esponente e non 8 bit che sono invece la sua lunghezza reale!
Guglielmo pur non avendo esperienza (io) hai palesemente ragione dalle spiegazione che leggo qui e altrove, però funziona
Questo è l'output del monitor seriale:
** Reading 7M at address 11
energy = 1260632
run time = 601232
frequency = 50.00
voltage = 235.90
active power = 1058.10
** Reading 7M at address 11
energy = 1260662
run time = 601242
frequency = 50.00
voltage = 234.70
active power = 1095.60