Jak połączyć trzy bajty w całość? Chcę odczytać 24bitowy rejestr i niestety wywala mi jakiś błąd przy kompilacji że nie mogę przesunąć bajtu na 16 pozycję
Moja literówka, a ktoś tu bezmyślnie skopiował... I nawet nie sprawdził czy dobrze naskrobałem ani nie pomyślał... tylko Ctrl+C, Ctrl+V Ale świątecznie mam dobre serce jeszcze na takie zajączki:
to ja jeszcze dodam, że zdecydowanie ładniej jest uzywać typów uint8_t zamiast unsigned char oraz uint32_t zamiast unsigned long.
Są to typedef'y dodane do standardu C99 języka C:
http://en.wikipedia.org/wiki/Inttypes.h#inttypes.h
Jest to też poprawnie zdefiniowane dla avr-gcc w bibliotece avr-libc - plik inttypes.h - zauważ, że wszystkie funkcje z avr-libc operują właśnie na typach z inttypes.h, nigdy na zwykłych char, int czy long.
Dzięki stosowaniu tych typedef'ów twój program będzie przenośny (kto wie, może w przyszłości będziesz chciał kompilowac innym kompilatorem), poza tym w programach dla ośmiobitowców to naprawdę lepiej wygląda i łatwiej się taki kod odczytuje, wszystko widać jak na tacy.
A tak w ogóle, to nie lepiej od razu zdefiniować wartosc_rej jako unię? Stracisz (pozornie, bo twoja funkcja i tak zwraca uint32_t, bo nie ma przecież typu uint24_t ) 1 bajt RAMu, ale jeśli to zmienna lokalna, to nie ma dużego znaczenia.
Za to nie będziesz musiał sam robić przesuwania, będzie zrobione za ciebie.
Kod: C / C++
Zaloguj się, aby zobaczyć kod
Nie musisz nic przesuwac, zostalo to za ciebie zrobione na etapie kompilacji. Musisz tylko wiedziec, ze wartosc_rej.bajt_skladowy[0] to najstarczy bajt zmiennej wartosc_rej.pelna_wartosc - wynika to z tego, jak elementy tablicy bajt_skladowy[4] są ułożone w pamięci.
W jednym poście polecanie typedefów jako mechanizmów na przenośność i dostępu do bajtów int'a przez unię
Mogę rozbić na 2 posty Chodzi Ci o padding? Czy o kolejność ułożenia bajtów tablicy w pamięci? Bo to drugie jest akurat w C zdefiniowane bardzo wyraźnie:
http://en.wikipedia.org/wiki/Row-major_order
would be laid out contiguously in linear memory as
1 2 3 4 5 6
Myślisz, że dla jednowymiarowych jest inaczej? Że dla A[3] = {1,2,3}; nie będzie:
1 2 3 ?
Bo ja jestem pewien, że będzie właśnie 1 2 3.
Gdyby tak nie było, nazwa tablicy nie byłaby jednocześnie adresem jej elementu o indeksie 0. Nie miałoby sensu odwoływanie się do elementów tablicy poprzez wskaźnik z przesunięciem.
I tu kompilator nie ma nic do rzeczy. To jest wbudowane w C.
To że Ty jesteś czegoś pewien lub co ja myślę ma się nijak do standardu,
na który przecież się powołujesz.
Tak samo jak tablice do reprezentacji liczby całkowitej.
Znajdź jak w standardzie ułożone są bajty w liczbie 32 bitowej.
Zacytuj i wtedy podyskutujemy.
To że Ty jesteś czegoś pewien lub co ja myślę ma się nijak do standardu,
na który przecież się powołujesz.
Tak samo jak tablice do reprezentacji liczby całkowitej.
Znajdź jak w standardzie ułożone są bajty w liczbie 32 bitowej.
Zacytuj i wtedy podyskutujemy.
Albert
Co do tablic to standard wyraźnie to określa, co do ułożenia bajtów - niespodzianka
no tak... pomyślałem o sposobie reprezentacji tablicy w pamięci, a endiany jakoś umknęły .
Podsumowując - przenośnie nie będzie.
Natomiast, przy założeniu, że kompilator będzie zawsze ten sam i architektura ta sama, można wykorzystać unię i uprościć sobie zapis.
Poza tym, u djlukasa byłyby 3 rzutowania na longi, 2 operacje przesunięcia bitowego longów oraz suma logiczna 3 odpowiednio poprzesuwanych longów. Nie sprawdzałem, jak to jest np. przez gcc optymalizowane, ale raczej dłużej by się wykonywało i więcej pamięci programu zajęło niż operacje na uni - tylko 3 zapisy pojedynczych bajtów i 1 odczyt 4 bajtów.
Ale mam jeszcze jedną prośbę. Potrzebuję odczytywać z różnych rejestrów o różnych długościach (8,16,24 bity). Jak napisać tą funkcję aby była ogólna a żebym nie musiał mieć trzech różnych funkcji odczytujących?
Jedna funkcja może zwracać tylko jeden typ wartości, ale możesz zrobić np. coś takiego (pierwsza rzecz jaka mi przychodzi do głowy):
przekazywać do funkcji przez referencję strukturę, w której 1 pole to unia (taka jak pisałem poprzednio) wartosc_odczytana a drugie pole, uint8_t liczba_bajtow, to informacja o tym, ile bajtów chcesz odczytać.
Czyli funkcja zwracałaby void a przyjmowała wskaźnik do struktury i operowała bezpośrednio na tych strukturach. W funkcji liczba zapisów byłaby uzależniona od wartości liczba_bajtow. Zamiast void funkcja mogłaby też zwracać jakiś kod błędu - np. gdybyś próbował przekazać do funkcji, że trzeba odczytać więcej niż 4 bajty.
Po wywołaniu funkcji z unii w strukturze odczytujesz na bazie liczba_bajtow tyle bajtów ile trzeba.
W każdym razie jest to mocno przekombinowane i w ogóle nie wydajne - trzeba tworzyć nadmiernie duże struktury a potem jeszcze warunkowo na nich operować - szkoda RAMu i czasu CPU.
Polecam ci jednak stworzenie 3 małych oddzielnych funkcji. Przykład implementacji tego co chciałeś podałem tylko po to, żeby pokazać, że to raczej bez sensu, zwłaszcza w przypadku tak mało skomplikowanego zadania.
Ale mam jeszcze jedną prośbę. Potrzebuję odczytywać z różnych rejestrów o różnych długościach (8,16,24 bity). Jak napisać tą funkcję aby była ogólna a żebym nie musiał mieć trzech różnych funkcji odczytujących?
Ale mam jeszcze jedną prośbę. Potrzebuję odczytywać z różnych rejestrów o różnych długościach (8,16,24 bity). Jak napisać tą funkcję aby była ogólna a żebym nie musiał mieć trzech różnych funkcji odczytujących?
Mirek podał ci rozwiązanie z makrami, skuteczne, ale makra są ryzykowne. To samo zrobisz klasyczną funkcją w C. Po prostu piszesz funkcję zwracającą najdłuższy typ, robisz ją inline lub static inline i zostawiasz resztę kompilatorowi. Po to ma optymalizator, aby się wykazał Automatycznie ci skróci zwracany rezultat, tak aby pasował do spodziewanego wyniku funkcji.
Po to ma optymalizator, aby się wykazał Automatycznie ci skróci zwracany rezultat, tak aby pasował do spodziewanego wyniku funkcji
W przypadku takiej funkcji lub makra (zgadzam się, że makra są ryzykowne - no i kiepsko się je debugguje) djlukas musiałby najpierw i tak wykonać tyle odczytów ile potrzebuje, a więc ręcznie wklepać odczyty i opóźnienia, a dopiero potem wywołać funkcję sklejającą, np. w taki sposób:
Kod: C / C++
Zaloguj się, aby zobaczyć kod
Jeśli dobrze rozumiem intencje djlukas, chce on wykorzystywać jedną funkcję do całości operacji, łącznie z czytaniem tylu bajtów z kostki ADE7763 ile trzeba.
Makro mirka będzie szybkie, tak samo oczywiście funkcja inline, której przewagą jest możliwość debuggowania, ale wg mnie trochę szkoda zachodu na takie podejście w tym wypadku, bo cały zysk z funkcji straci się na wcześniejsze spreparowanie parametrów wywołania funkcji.
Jeśli dobrze rozumiem intencje djlukas, chce on wykorzystywać jedną funkcję do całości operacji, łącznie z czytaniem tylu bajtów z kostki ADE7763 ile trzeba.
Makro mirka będzie szybkie, tak samo oczywiście funkcja inline, której przewagą jest możliwość debuggowania, ale wg mnie trochę szkoda zachodu na takie podejście w tym wypadku, bo cały zysk z funkcji straci się na wcześniejsze spreparowanie parametrów wywołania funkcji.
No właśnie, musimy domyślać się co autor miał na myśli więc być może proponowane rozwiązania są zupełnie nietrafione. Niemniej wracając do funkcji z 3 argumentami, gcc potrafi tworzyć wyspecjalizowane funkcje automatycznie na podstawie argumentów. Jeśli stworzysz funkcję 3-argumentową i w kodzie znajdzie się wywołanie z którego kompilator będzie wiedział, że 2 argumenty są bez znaczenia (lub ich wpływ jest możliwy do określenia na etapie kompilacji) to stworzy wyspecjalizowaną funkcję jednoargumentową zupełnie transparentnie.
Niestety jestem początkujący w tej dziedzinie i nie chciałbym sobie utrudniać zadania więc pozostanę przy tym co napisałem. Zrobiłem trzy oddzielne funkcję i o dziwo działają. Czy mogą one zostać?
Mam kolejną prośbę. Jak zapamiętać odczytane wartości w pamięci tak abym po wyłączeniu zasilania ich nie stracił? Chodzi mi dokładnie o wartość energii, którą odczytuję z układu ADE. Nie za bardzo rozumiem noty katalogowej przetwornika i nie wiem czy to w nim jest ta energia akumulowana i tylko AVR ją odczytuje czy do AVR'a są przesyłane próbki co jakiś czas i ja je muszę dodawać do siebie i zapisywać do pamięci?
Coś mniej więcej takiego, rzutowania sobie dodaj, jeśli jest możliwość zwrócenia 0 (a pewnie jest) lepiej włożyć tam jakiś bufor, a retVal użyć do oznaczenia błędów. Funkcja ma na celu tylko pokazanie jak to można rozwiązać, nie wnikam w to, czy te delaye są słuszne czy nie. Tak z góry zapowiadam, bo ostatnio coś nerwowi ludzie są na tym forum Jakby na AVR były generowane sensownie zmienne przesunięcia to kod można trochę upiększyć
gcc potrafi tworzyć wyspecjalizowane funkcje automatycznie na podstawie argumentów. Jeśli stworzysz funkcję 3-argumentową i w kodzie znajdzie się wywołanie z którego kompilator będzie wiedział, że 2 argumenty są bez znaczenia (lub ich wpływ jest możliwy do określenia na etapie kompilacji) to stworzy wyspecjalizowaną funkcję jednoargumentową zupełnie transparentnie
No tak, ale chyba tylko w przypadku -O3, prawda? Wydaje mi się, że dla -O1 gcc się aż tak nie rozpędza, ale popraw mnie, jeśli się mylę.
Sprawdzam sobie raz na jakiś czas jak gcc kompiluje niektóre rzeczy dla różnych ustawień optymalizacji. Pamiętam, że byłem w szoku, że dla -O0 gcc nie używa nawet niektórych instrukcji asemblera - wystawiasz coś na rejestr PORTx a gcc wstawia instrukcję sts lub std zamiast out.
djlukas, jeśli chcesz zwiększyć szybkość wykonywania swojego kodu, dodaj dla gcc flagę -On, gdzie n to poziom optymalizacji. Do debuggowania polecam -O1, natomiast ostatecznie zastosuj -O3.
-O1 pewnie masz już ustawione, bo inaczej funkcje z delay.h nie działałyby poprawnie.
Jeśli tworzysz swój program w AVR Studio lub Atmel Studio, to pewnie nie korzystasz z własnego makefile'a i ustawienia optymalizacji masz w opcjach projektu, w odpowiedniej zakładce.
A co do twego kodu - jeśli możesz kopiuj kod ze swego programu i wrzucaj go w tagach SYNTAX a nie jako obrazek. Łatwiej się będzie do niego odnosić i komentować.
Czemu twoja pierwsza funkcja zwraca unsigned long zamiast unsigned char?
Druga unsigned long zamiast unsigned short? Lepiej, żeby każda zwracała tylko tak dużą zmienną jak trzeba... a w razie potrzeby to co funkcja zwraca i tak sobie możesz zrzutować na większy integer.
Poza tym - środkowa funkcja nie zadziała poprawnie, bo nie zrzutowałeś wartosc_rej1, czyli nie zrobiłeś znowu tego, o czym były 2 pierwsze odpowiedzi.
gcc potrafi tworzyć wyspecjalizowane funkcje automatycznie na podstawie argumentów. Jeśli stworzysz funkcję 3-argumentową i w kodzie znajdzie się wywołanie z którego kompilator będzie wiedział, że 2 argumenty są bez znaczenia (lub ich wpływ jest możliwy do określenia na etapie kompilacji) to stworzy wyspecjalizowaną funkcję jednoargumentową zupełnie transparentnie
No tak, ale chyba tylko w przypadku -O3, prawda? Wydaje mi się, że dla -O1 gcc się aż tak nie rozpędza, ale popraw mnie, jeśli się mylę.
Sprawdzam sobie raz na jakiś czas jak gcc kompiluje niektóre rzeczy dla różnych ustawień optymalizacji. Pamiętam, że byłem w szoku, że dla -O0 gcc nie używa nawet niektórych instrukcji asemblera - wystawiasz coś na rejestr PORTx a gcc wstawia instrukcję sts lub std zamiast out.
No oczywiście, że odpowiednie optymalizacje należy włączyć. Ale pytanie, po co korzystać z czegoś innego niż O3? Przecież to kompletnie nie ma sensu, jeszcze o ile do debugowania z O0 czasami się stosuje (ale trzeba uważać) to w gotowym programie O0, O1 czy O2 nie mają zastosowań (ale będę niezmiernie wdzięczny jeśli ktoś mi je wskaże).
O0 to brak optymalizacji, więc kompilator generuje kod tak jak mu to wskazują bezpośrednio INSNy.
Dodano po 1 [minuty]:
djlukas napisał:
Mam kolejną prośbę. Jak zapamiętać odczytane wartości w pamięci tak abym po wyłączeniu zasilania ich nie stracił? Chodzi mi dokładnie o wartość energii, którą odczytuję z układu ADE. Nie za bardzo rozumiem noty katalogowej przetwornika i nie wiem czy to w nim jest ta energia akumulowana i tylko AVR ją odczytuje czy do AVR'a są przesyłane próbki co jakiś czas i ja je muszę dodawać do siebie i zapisywać do pamięci?
Wykorzystaj wewnętrzną pamięć EEPROM (nagłówek eeprom.h). Lub dołącz zewnętrzną pamięć FRAM - jeśli zapisy miałyby być częste i w krótkim czasie wykończyć EEPROM.
A co z Os ? O3 niby generuje najszybszy kod, Os najmniejszy, ale rozmiar nijak nie przenosi się na wydajność ? Czy w przypadku O3 kompilator szuka "szybszych" instrukcji?
no właśnie, -O1 ma wg mnie sens, jeśli chcesz sobie ładnie móc debuggować a nie chcesz potem pod koniec musieć pamiętać o tym, żeby zmieniać na -03. Jeśli twój uC się ze wszystkim czasowo wyrabia dla -O1, a nie musisz np. minimalizowac poboru energii (bo wtedy oczywiście zależy ci na tym, żeby wszystko co trzeba wykonać jak najszybciej i pójść spać), to czemu nie stosować -O1?
No i tak jak pisze gaskoin - czasem przydaje się -Os. Raz musiałem tego użyć w Atmega8 - zadania, które miałem do wykonania nie wymagały super szybkości, za to zająłem sobie sporo flasha na różne stringi i byłem w końcu zmuszony dać -Os, żeby się całość programu w ogóle zmieściła. Był to jednorazowy, krytyczny przypadek - uC został wybrany dużo wcześniej a po pewnym czasie program się nadmiernie rozrósł (interakcja z użytkownikiem za pomocą menu wysyłanego po rs'ie - stąd te stringi we flashu, których sumaryczny rozmiar i tak ograniczyłem do minimum).
Cytat:
Czy w przypadku O3 kompilator szuka "szybszych" instrukcji?
Już w przypadku -O1 użyje odpowiednich, szybszych instrukcji asemblera tam gdzie może - dobrym przykładem jest właśnie operowanie na przestrzeni adresowej IO, czyli na adresach 0-0x3f.
Inny przypadek: globalne flagi warto jest przechowywać np. w rejestrze typu GPIO. Np. w przypadku at90can128 jest GPIOR pod adresem 0x1e, a dla adresów 0-0x1f mają zastosowanie instrukcje sbi i cbi.
Wówczas ustawianie globalnej flagi to po prostu instrukcja sbi a zerowanie to cbi - wszystko wykonuje się w 1 cyklu zegara, dzięki czemu, gdy jakaś flaga jest (a zazwyczaj tak bywa) ustawiana w jakimś ISR a ta sama albo jakaś inna z tego samego rejestru zmieniana gdzieś w main(), nie trzeba się martwić o to, czy w trakcie zmiany w main() może wejść przerwanie i przekłamać operację.
Bo gdy nie jest używana instrukcja sbi, to musi być, w najlepszym wypadku, in, ori, out. Jeśli po in a przed out wejdzie przerwanie, które zmienia ten rejestr, to mamy przekłamany zapis.
A wracając do -O3:
- gcc decyduje sam, czy opłaca się pewne funkcje nie zadeklarowane jako inline przerobić na inline
- potrafi rozpakować krótkie pętle for na szeregi pojedynczych cykli pętli wstawione "ręcznie" jeden pod drugim
- włącza coś takiego jak "predictive-commoning", czyli wg manuala:
Cytat:
-fpredictive-commoning
Perform predictive commoning optimization, i.e., reusing computations (espe-
cially memory loads and stores) performed in previous iterations of loops. This option is enabled at level‘-O3’
A co z Os ? O3 niby generuje najszybszy kod, Os najmniejszy, ale rozmiar nijak nie przenosi się na wydajność ? Czy w przypadku O3 kompilator szuka "szybszych" instrukcji?
Masz rację, piszę w pośpiechu i potem takie kwiatki wychodzą, oczywiście miało być Os
Dodano po 5 [godziny] 28 [minuty]:
Ostry23: wszystko prawda, tylko po co dawać coś innego niż Os? Nawet debugując tylko w wyjątkowych przypadkach daje się O0, poziomów O1-O3 po prostu nie ma sensu stosować.
wszystko prawda, tylko po co dawać coś innego niż Os?
No tu się wybitnie nie zgodzę.
Ja prawie zawsze stosuję ostatecznie -O3, bo prawie zawsze zależy mi na szybkości wykonania a nie na minimalizacji zajętości flasha.
Ja bym właśnie powiedział, że -Os stosuje się w wyjątkowych przypadkach.
Mój przykład, nad którym obecnie działam:
uC XMEGA 32A4. Musi wykonać akwizycję w 8 kanałach ADC, częstotliwość próbkowania 8kHz na kanał. Każda próbkę trzeba obrobić w jednym automacie i odpowiednio szybko wypracować wynik przetwarzania. Oprócz tego uC parsuje i wykonuje polecenia z 1 kanału rs'owego - przychodzą asynchronicznie i trzeba jak najszybciej odpowiedzieć. Poza tym co 1ms muszę wysyłać na 1 spi ramkę pewnego protokołu do innego procka. Drugie spi jest stosowane do sterowania rejestrami przesuwnymi, ale to akurat mogę robić rzadko, raz na 100ms.
Do tego jest mnóstwo innych, pobocznych zadań... parę automatów - m.in. automat podmieniający dane w eepromie, przy czym są 3 (asynchroniczne względem siebie) źródła danych, które trzeba podmieniać (np. przychodzi rozkaz po rs'ie).
Przy takiej mnogości zadań do wykonania i konieczności obsługi przerwań od rs'a, 2 spi, 4 kanałów sprzętowych ADC oraz całej obróbki w głównej pętli na niczym mi tak nie zależy jak na szybkości wykonywania. Tym bardziej, że obróbkę wyników z adc robię na pełnych 12 bitach i naprawdę nie chcę nigdzie tracić zbyt dużo cykli zegara, bo zaczynam się zbliżać do granic wydajności procka.
Przy zastosowaniu -Os mogę sobie co najwyżej pomarzyć o wyrobieniu się ze wszystkim na czas (tu krytyczne jest to wypychanie dużych ramek po spi co ms, przy czym te ramki trzeba spreparować na bazie mnóstwa innych stanów w programie; no i oczywiście akwizycja i obróbka danych z ADC).
I na serio nie rozumiem po co w takim razie optować za -Os? Wg mnie jest przydatne, ale tylko wtedy, gdy zaczyna brakować flasha, np. gdy mamy w nim coś stablicowane, albo tak jak pisałem - dużo stringów.
A czy kolego przejrzałeś wygenerowany kod? O3 niby pisze, że optymalizuje pod kątem prędkości ale to opis ze starych czasów jest i obecnie niewiele ma wspólnego z rzeczywistością. Dla różnych programów różnice między O3 a Os sięgają... jednego taktu zegara - zresztą odpowiednie testy są w AVR-libc pokazane, także nawet nie trzeba samemu testować, jeśli nie wierzysz (Link).
Kolejna rzecz - pisząc w C nie masz kontroli nad wygenerowanym kodem, więc pisanie programów, które działają wyłącznie dlatego, że akurat jakoś magicznie jeden takt się wygospodarował jest sporym nadużyciem. Taki program będzie działał przypadkiem tylko, wystarczy zmienić wersję kompilatora i wszystko pójdzie w maliny. Poza tym atrybuty dotyczące poziomu optymalizacji można przypisać poszczególnym funkcjom, możesz sobie więc jedną skompilować z O0, inną z O3, a jeszcze inną z Os. Niemniej sekcje krytyczne czasowo po prostu trzeba napisać jako wstawkę w ASM, a nie liczyć na cud. Kolejna sprawa - poziomy -Ocośtam to tylko zbiór szczegółowych opcji optymalizacji. Oprócz nich istnieje wiele innych, jak np. strict-X, unroll-loops i dziesiątki innych. które mają większy impakt na prędkość kodu niż przełączenie między O3 a Os.
Natomiast twój przykład "zajętego" procka pokazuje, że albo masz źle napisany program (proc można odciążyć używając np. DMA do akwizycji danych, albo event system), albo kompletnie źle dobrałeś procesor i musisz robić gimnastykę, żeby tuszować błędy projektu.
Oczywiście zapewne się z tym nie zgodzisz, więc poproszę o pokazanie jakiegoś kodu, który uwiarygadnia twoją tezę.