Modbus RTU IEEE754

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.

ciao

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....

ciao

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
}

Buonasera, ok adesso è più chiaro.
Vedi mappa Modbus allegata
Pagine da Mbus-7M24-7M38_v2 (1).pdf (42,7 KB)
Pagine da Modbus-7M24-7M38_v2_30062021.pdf (156,6 KB)

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.

Non ho capito i numeri 31 e 24: indicano i bit da leggere per esponente e mantissa?

ciao

per il significato di ">> xx" vedi QUESTO link...se hai dubbi chiedi.

Serve a fare lo shift di x bit a sinistra ( << ) o a destra ( >> ).

In questo caso però non capisco esattamente:

uint32_t s = (n & 0x80000000) >> 31

provo a spiegare, assegna ad s il valore di "AND" tra n e 0x80000000 il risultato lo sposta a destra di 31 bit?

Ma "n" cosa è?

se guardi la funzione "n" è la variabile in ingresso...

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")));

n = data?

e questa "cosa" dove la metteresti?

E' una parte precedente dello sketch

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;
    }

    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++)


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;
}

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.

Comunque, per chiarti un po' le idee ...

uint32_t s = (n & 0x80000000) >> 31;

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).

Guglielmo

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ò.

Mmmm ... però NON mi torna qualche cosa nella parte ESPONENTE ... che è di 8 bit e non 7 come tratta quella formula ...

SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM

S = Segno (1 bit), E = bit dell'esponente (8 bit), M = bit della mantissa (23 bit)

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 ... :roll_eyes:

Vi torna o sto sbagliado io ???

Guglielmo

Qui lo sketch intero:

#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

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