Funkcja InttoFloat zwraca zero w klasie HP_modbus - co robię źle?

0

Witajcie,
Co prawda temat z Arduino, które leży gdzieś w okolicach C++, dlatego pozwoliłem sobie założyć ten wątek. Otóż, w ramach rozwijania nowych skilli, zacząłem dzielić sobie program na pliki h i cpp. Na początku poszło gładko niestety, natknąłem się na moment, gdzie trzeba przekazać parametry do funkcji i odczytać wynik. Tak w skrócie:

  • odczytuję dwa 16-bitowe rejestry Modbus, które zawierają wartość kodowaną Big Endian - to działa
  • wartość te przekazuję do funkcji, która ma je przekonwertować na float - to też działa, bo już wewnątrz tej funkcji mogę poprawnie wydrukować wcześniej odczytane wartości
  • no i niestety - funkcja zwraca mi zero...
    Budowa funkcji na pewno jest poprawna, ponieważ umieszczona w głównym pliku *.ino i wywołana np. z loop prawidłowo zmienia te wartości na float. Błąd na 100% polega na moim: złym pojmowaniu składni ? złym skonstruowaniu klasy ? Będę wdzięczny, za podpowiedź.
// klasa w pliku HP_modbus.h:
class HP_modbus {
public:
    HP_modbus();
    void begin();
    void preTransmission();
    void postTransmission();
    void czytajModbus();
    float InttoFloat(uint16_t Data0, uint16_t Data1); // ???
private:
    ModbusMaster node;
};

// funkcje w pliku HP_modbus.cpp:
// funkcja odczytuje zawartość rejstrów - dane1, dane2 są drukowane, czyli odczyt ok
void HP_modbus::czytajModbus()
{

    uint8_t odczytLicznika;
    uint16_t daneZlicznika[2];

    odczytLicznika = node.readHoldingRegisters(0x0000, 4);
    if (odczytLicznika == node.ku8MBSuccess) {
        daneZlicznika[0] = node.getResponseBuffer(0x00);
        daneZlicznika[1] = node.getResponseBuffer(0x01);
    }

    Serial.print("dane1: ");
    Serial.println(daneZlicznika[0]);
    Serial.print("dane2: ");
    Serial.println(daneZlicznika[1]);

    EM_A_power = InttoFloat(daneZlicznika[0], daneZlicznika[1]); // funkcja przekształcająca odczytane wartości na float
} // zwraca zero...

float HP_modbus::InttoFloat(uint16_t Data0, uint16_t Data1)
{

    // dane drukuje poprawnie:
    Serial.println("wejscie do funkcji");
    Serial.print("Data0: ");
    Serial.println(Data0);
    Serial.print("Data1: ");
    Serial.println(Data1);

    float x;
    unsigned long* p;
    p = (unsigned long*)&x;
    *p = (unsigned long)Data0 << 16 | Data1; //Big-endian
    return (x);
}
1
    float x;
    unsigned long* p;
    p = (unsigned long*)&x;
    *p = (unsigned long)Data0 << 16 | Data1; //Big-endian
    return (x);

To nie ma sensu (oraz jest UB). Zapewne chcesz coś w stylu:

unsigned long value = static_cast<unsigned long>(Data0 << 16) | Data1;
return value; // będzie automatyczna konwersja do float

Przy czym pojawia się pytanie czy nie będzie potrzeba aby tych 16-bitowym wartościom zmienić endianess.

0
kq napisał(a):
    float x;
    unsigned long* p;
    p = (unsigned long*)&x;
    *p = (unsigned long)Data0 << 16 | Data1; //Big-endian
    return (x);

To nie ma sensu (oraz jest UB). Zapewne chcesz coś w stylu:

unsigned long value = static_cast<unsigned long>(Data0 << 16) | Data1;
return value; // będzie automatyczna konwersja do float

Przy czym pojawia się pytanie czy nie będzie potrzeba aby tych 16-bitowym wartościom zmienić endianess.

Zadziałało, tzn. funkcja zwróciła w końcu coś innego niż zero.
Data0 516, Data1 3772 wynik 3772.0
Pozostaje kwestia konwersji - jest to odczyt napięcia, wynik powinien być w okolicach 230-240 V

0

Czy masz jakąś dokumentację czego się spodziewać w tych rejestrach?

0

Przepraszam, wyżej wpisałem odwrotnie. W tym konkretnym przykładzie odczytuję 2 rejestry int_16:
Data0: 3772
Data1: 516
To jest konkretny odczyt z sprzed chwili - jest to napięcie sieci. Po przekonwertowaniu ma nam dać float +/- 230,0 V

0

Ponawiam pytanie o dokumentację - bez tego te wartości nie mają żadnego sensu. Moje próby zgadywania się nie powiodły: https://godbolt.org/z/874YToWEW

0

Jakiej dokumentacji potrzebujesz ?

0

Musisz gdzieś mieć podane w jakim formacie czujnik podaje wartości na tym modbusie.

0

Niestety niewiele mi to mówi. Które adresy pobierasz?

0

Ok, to jest tak - odczyty które wyżej podawałem, były nieprawidłowe, widocznie muszę mieć gdzieś jeszcze błąd w samej procedurze obsługi modbus. Ale odczytałem starym programem:
Data0 dec 15648 = hex 3D20
Data1 dec 63753 = hex F909
wynik dec 0.0393
Jak wpiszemy taki ciąg hex 3D20F909 tu: https://www.scadacore.com/tools/programming-calculators/online-hex-converter/
to wszystko się zgadza - BigEndian ABCD - float 0.0393 i to powinna zwracać moja funkcja 🙂
Bardziej mnie jednak intryguje fakt, że moja funkcja Inttofloat działa w programie głównym a w klasie zwraca zero - czyli chyba samą klasę źle definiuję, albo funkcję w klasie ? Jeszcze nie do końca to wszystko rozumiem... Przy czym Twoja funkcja jakąś tam wartość zwracała... Wcześniej wyodrębniłem sobie do plików h i cpp podprogram obsługi lcd i wszystko działa, ale nim nie było nigdzie przekazywania parametrów... długa droga przede mną 😀 W każdym razie bardzo dziękuję za pomoc i na pewno odgrzebię wątek - spróbuję teraz poszerzyć wiedzę.

1

Zapisywanie wartości przez cast na błędny typ to UB i kompilator ma prawo to np. zignorować. Dlatego w moim przykładzie do zapisywania pamięci używam memcpy. Jeśli tego nie możesz zrobić to użyj char* lub std::byte

1

Ok, przebudowałem całą klasę i teraz działa - miałem złe podejście do funkcji 😀
Skoro powiedziałeś, że jest UB, to wywaliłem również starą procedurę i skorzystałem z Twojego przykładu:

memcpy(&x, &Data1, 2);
memcpy((char*)(&x)+2, &Data0, 2);

Bardzo dziękuję za naprowadzenie !

1 użytkowników online, w tym zalogowanych: 0, gości: 1