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ć ?
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ę.
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() (;
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.
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.
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.
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.
Dzięki panowie za pomoc Widzę że dyskusja na wyższy poziom weszła ... 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
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?
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ć" ?