logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Freemodbus - zapis liczby w formacie float (Read Holding registers)

malina555 08 Gru 2017 19:20 867 10
  • #1 16877145
    malina555
    Poziom 14  
    Witam. Odpaliłem właśnie bibliotekę freemodbus na avr i potrzebuje informacji w jaki sposób zapisać w rejestrach Read Holding registers liczbę float. Rejestry Read Holding registers w tej bibliotece są uint32_t.

    Ogólnie w programie działam na liczbach całkowitych.

    Przykładowo mam taką wartość rzeczywistą pewnej zmiennej (5,25) to mnożę ją razy 100 i zapisują do zmiennej uint32_t zmienna = 525;

    Ale w rejestrach Read Holding registers chciałbym aby ta liczba była zapisana jako zmiennoprzecinkowa. W jaki sposób taką konwersję przeprowadzić ?
  • #2 16877325
    Freddie Chopin
    Specjalista - Mikrokontrolery
    malina555 napisał:
    Rejestry Read Holding registers w tej bibliotece są uint32_t.

    Z pewnością nie są - wszystko jest albo bitem albo uint16_t.

    Format float dla MODBUS nie jest w żaden sposób ustandardyzowany, wiec generalnie możesz sobie wybrać którą wersję wolisz.

    http://store.chipkin.com/articles/modbus-floating-point-encoding

    Można np. tak:

    uint16_t words[2];
    float value = 123.456f;
    memcpy(words, &value, sizeof(words));


    Potem już sobie tylko words przepisać do dwóch kolejnych rejestrów. Można zamiast do tablicy pomocniczej kopiować od razu do bufora podawanego przez bibliotekę.
  • #3 16877769
    Eagle
    Poziom 24  
    Jaki jest powód aby kopiować dane float'a zamiast rzutować czyli zmienić interpretację?

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #4 16877881
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Eagle napisał:
    Jaki jest powód aby kopiować dane float'a zamiast rzutować czyli zmienić interpretację?

    Rzutowanie i odczyt jest "undefined behaviour", w przeciwieństwie do używania memcpy(), które zresztą w tego typu przypadkach (stały i niewielki rozmiar) jest przez kompilator optymalizowane do kilku instrukcji bez wywoływania właściwej funkcji. Reasumując - używając normalnego kompilatora robiac takie rzutowanie nie zyskujesz absolutnie nic względem memcpy(), którego działanie jest w pełni zdefiniowane w tego typu sytuacji.

    W niektórych przypadkach cwaniackie próby optymalizacji mogą wręcz dać przeciwny efekt
    https://godbolt.org/g/yJT5wX
    Sam spróbuj zrobić tak, żeby taka "optymalna wersja" z rzutowaniem była choć tak samo mała jak wersja z memcpy() (;
  • #5 16877948
    Eagle
    Poziom 24  
    Cytat:
    A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.



    https://godbolt.org/g/czPZwk


    Kopiujesz tylko 2 bajty sizeof(words), a po zamianie na sizeof(float) jest nieco inaczej, ale istotnie ta konstrukcja jest wykrywana w większości platform, choć dla ARM wołane jest memcpy.

    edit: https://godbolt.org/g/Chmpkn choć robi to samo co 4 bytowe memcpy, to nie oddaje intencji.
  • #6 16878175
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Eagle napisał:
    Kopiujesz tylko 2 bajty sizeof(words)

    No tak, pozostałość z wcześniejszych wersji tego kodu, kiedy words było zadeklarowane w ciele funkcji jako `uint16_t words[2];`. Tutaj poprawiona wersja
    https://godbolt.org/g/rKJwV8
    Jest jeszcze krótsza niż tamta, wiec tylko wzmacnia mój argument.

    Dyskusja jest mało sensowna. Jak widzisz sam, użycie memcpy() jest szybsze niż - intuicyjnie niby lepsze - rzutowanie i kopiowanie ręcznie. Przy okazji rzutowanie i kopiowanie jest "defined" tylko w niektórych przypadkach, za to memcpy() absolutnie zawsze (abstrahując od przypadków nieprawidłowego użycia typu "buffer overflow" czy "null pointer dereference"), wiec nie trzeba się nad tym zastanawiać. Jakkolwiek byś napisał ten kod, to wersja z memcpy() jest krótsza i bardziej czytelna. Po prostu używanie rzutowania nie ma absolutnie żadnych zalet w takim akurat przypadku, wiec nie wiem o co kruszymy kopię?

    Eagle napisał:
    istotnie ta konstrukcja jest wykrywana w większości platform, choć dla ARM wołane jest memcpy

    Jest to kwestia użytych flag kompilatora, ponieważ w rzeczywistości konstrukcja taka dla ARM daje identyczny efekt jak na każdej innej platformie.

    08000494 <v1(float, unsigned short*)>:
    memcpy(words, &x, sizeof(x));
    8000494:	6008 	str	r0, [r1, #0]
    }
    8000496:	4770 	bx	lr
    
    08000498 <v2(float, unsigned short*)>:
    words[1] = ((uint16_t*)&x)[1];
    8000498:	f3c0 430f 	ubfx	r3, r0, #16, #16
    words[0] = ((uint16_t*)&x)[0];
    800049c:	8008 	strh	r0, [r1, #0]
    words[1] = ((uint16_t*)&x)[1];
    800049e:	804b 	strh	r3, [r1, #2]
    }
    80004a0:	4770 	bx	lr
    80004a2:	bf00 	nop


    Jeśli do godbolta wpiszesz `-mcpu=cortex-m3 -mthumb -O2` dla flag kompilatora, to sam zobaczysz że jest identycznie.

    Przy okazji kolejna wada rzutowania:

    main.cpp: In function 'void v2(float, uint16_t*)':
    main.cpp:39:33: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
    words[0] = ((uint16_t*)&x)[0];
    ^


    W wersji z memcpy() oczywiście takiego problemu nie ma.

    Eagle napisał:
    edit: https://godbolt.org/g/Chmpkn choć robi to samo co 4 bytowe memcpy, to nie oddaje intencji.

    I to jest właśnie idealny przykład kodu który jest skrajnie niebezpieczny. Nikt nie powiedział, że tablica uint16_t jest wyrównana tak aby można było do niej coś wpisać jako jedno uint32_t. Tyle kombinacji, żeby osiągnąć zbliżoną długość jak memcpy(), z tym że z bardzo poważnym błędem... No i po co? Może dla AVR taka operacja nie jest "undefined", ale powiedz mi sam, gdzie jest zysk ze stosowania rzutowania zamiast użycia przeznaczonej właśnie do tego celu funkcji? Przeniesiesz kiedyś ten kod na inną platformę - jako część większego projektu czy jakiejś biblioteki - i będziesz przez pół dnia szukał dlaczego w 50% przypadków dostajesz śmieci a nie to czego oczekiwałeś (ewentualnie w jednym na 100 przypadków program będzie się wieszał, bo h4x0rskie rzutowanie niewyrównanych odpowiednio wskaźników akurat doprowadzi do zamazania adresu powrotu lub czegoś w tym stylu).

    Powtarzam główny motyw przewodni - rzutowanie nie ma ŻADNYCH zalet w stosunku do memcpy() - w najlepszym przypadku może być jedynie tak samo szybkie, zwykle jest wolniejsze, to czy zadziała zależy od platformy i szczęścia, zapis jest dłuższy i mniej czytelny. No chyba że w kategorii zalet rozpatrujemy to, że kod z rzutowaniem wygląda bardziej pr0 i każdy jak taki kod widzi to od razu czuje większy respekt przed autorem.
  • #7 16878222
    Eagle
    Poziom 24  
    Punkt dla ciebie, przyjmuję uzasadnienie.
  • #8 16878382
    Konto nie istnieje
    Konto nie istnieje  
  • #9 16878490
    malina555
    Poziom 14  
    Dzięki panowie za pomoc :) Widzę że dyskusja na wyższy poziom weszła :D ... Jak już jesteśmy w temacie to jeszcze chciałbym mieć konwersję w drugą stronę czyli zęby móc aktualizować sobie wartość tą wartość "value". Tutaj w tą stronę przez rzutowanie czy też memcpy ? i drugie pytanie czy z tej biblioteki freemodbus można wydobyć info (jakąś flagę?) że danych rejestr został "zaktualizowany" ? Chodzi mi o to żeby tego nie robić np w timerze ciągle
  • #10 16878551
    Freddie Chopin
    Specjalista - Mikrokontrolery
    malina555 napisał:
    Jak już jesteśmy w temacie to jeszcze chciałbym mieć konwersję w drugą stronę czyli zęby móc aktualizować sobie wartość tą wartość "value". Tutaj w tą stronę przez rzutowanie czy też memcpy ?

    Użyj memcpy() tyle że po prostu w drugą stronę - jak w oryginalnym przypadku, tak i tutaj rzutowanie nie ma żadnych zalet za to jest dużo bardziej niebezpieczne (bo akurat float w zasadniczej większości przypadków jest wyrównany tak jak uint16_t, za to już odwrotna sytuacja nie jest prawdziwa).

    malina555 napisał:
    i drugie pytanie czy z tej biblioteki freemodbus można wydobyć info (jakąś flagę?) że danych rejestr został "zaktualizowany" ? Chodzi mi o to żeby tego nie robić np w timerze ciągle

    Jeśli master (np. komputer PC) zapisze przez Modbus jakiś rejestr w Twoim urządzeniu, to przecież FreeMODBUS wywołuje wtedy stosownego callbacka, np. eMBRegHoldingCB(), przy czym ostatni argument (mode) wskazuje że chodzi o zapis. Samo wywoływanie funkcji eMBPoll() niestety musi się odbywać cyklicznie i nic na to nie poradzisz. No chyba że pytasz tak naprawdę o coś innego?
  • #11 16881996
    malina555
    Poziom 14  
    Wymajstrowałem to w ten sposób, wygląda na to że jest dobrze bo wartości się zgadzają, ale pewnie da się to wykonać w prostszy sposób :)

    Zrobiłem 2 zmienne:
    uint8_t flaga
    int mod_adres

    W pętli głównej dopisałem taki fragment:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    i w:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod



    Teraz mi wypada wyrzucić tą konwersję na float jest do funkcji i tym "if" dołożyć warunek że wykonywane to będzie tylko jeśli adres się zgadza i zrobić kilka takich if-ów do wszystkich wartości które potrzebuje ustawiać :) Chyba że ktoś ma pomysł jak to "udoskonalić" ? ;)
REKLAMA