Pytanie:
Skompresuj plik .hex dla mikrokontrolera
Danial
2019-10-12 15:53:18 UTC
view on stackexchange narkive permalink

Obecnie piszę program w AVR Studio , oto kompilacja * Wykorzystanie pamięci: *

  Urządzenie: atmega32
    Program: 9304 bajtów (zapełnienie w 28,4%)
    (.text + .data + .bootloader)
    Dane: 334 bajty (pełne 16,3%)
    (.data + .bss + .noinit)
 

Ponieważ używam mikrokontrolera ATmega32 , wydaje się, że nie stanowi to problemu, ale chcę użyć tego samego kodu i dla mikro kontrolera ATmega8 -kontroler.

Więc chcę zmniejszyć rozmiar programu mniej niż 8192 bytes.

Jak mogę to zrobić?

Czy używasz biblioteki zmiennoprzecinkowej?
@Danial to nie rozwiązuje twojego problemu, ale zauważę tylko, że plik .hex jest tylko formatem do przechowywania kodu programu. Są tylko tekstem, więc dobrze się kompresują.Ale to nie to, co kończy się we flashu MCU, to rzeczywisty kod programu.Więc _ to_ chcesz zmniejszyć rozmiar, a nie plik .hex jako taki.
Siedem odpowiedzi:
#1
+15
Oldfart
2019-10-12 15:59:56 UTC
view on stackexchange narkive permalink

Możesz nie skompresować kod szesnastkowy, możesz tylko spróbować go zmniejszyć.

  • Chcesz spróbować innego?ustawienia kompilatora (maksymalna optymalizacja i optymalizacja pod względem rozmiaru)
  • Przejrzyj kod źródłowy i zobacz, co można zoptymalizować lub pominąć.
  • Sprawdź, czy nie jest pobierany niepotrzebny kod biblioteki. (Nie powinno, ale kto wie)

Dobra uwaga od Jeroena3: Sprawdź, czy potrzebujesz / masz zmiennoprzecinkowe.Szczególnie funkcje takie jak printf czasami pobierają kod zmiennoprzecinkowy, aby poradzić sobie z printf ("% f" ... . Jeśli nigdy nie używasz liczb zmiennoprzecinkowych, nienie potrzebuję tego.

#2
+11
Michel Keijzers
2019-10-12 16:04:03 UTC
view on stackexchange narkive permalink

MCU nie może wykonać skompresowanego kodu.

Jest jednak kilka rzeczy, które możesz zrobić:

  • Zamiast korzystać z pełnowartościowych funkcji bibliotecznych, utwórz samodzielnie niektóre lub wszystkie funkcje;w ten sposób możesz zoptymalizować funkcje biblioteki, które w większości są (zbyt) elastyczne dla Twoich konkretnych potrzeb.
  • Usuń zduplikowany kod z własnego kodu.Użyj parametrów dla prawie powielonego kodu, aby był elastyczny.
  • Użyj najmniejszego typu dla stałych, a zwłaszcza stałych tablic.
  • Usuń „oczywisty” martwy kod (kod, który nigdy nie może zostać wykonany), zobacz uwagę Jeroen3 i Dakkaron poniżej.(sprawdź, czy dzieje się tak dla pełnych funkcji lub części funkcji).Zobacz też ten link.
  • Zmniejsz ciągi (jeśli używasz wielu instrukcji print, zminimalizuj te stałe ciągi, jeśli to możliwe).
Usunięcie martwego kodu to [funkcja konsolidatora] (https://renenyffenegger.ch/notes/development/languages/CC-plus-plus/GCC/options/Wl/index): ** - Wl, --gc-sekcja ** co dotyczy również nieużywanych funkcji bibliotecznych.
@Jeroen3 Dzięki, nie wiedziałem, że to takie sprytne.Zaktualizowałem moją odpowiedź (z komentarzem do twojego imienia).
@Jeroen3 Działa to tylko dla „twardego” martwego kodu, czyli kodu, w przypadku którego kompilator może wywnioskować, że jest martwy.Nie działa w przypadku martwego kodu, którego kompilator nie może określić, np.jakiś kod, który jest wykonywany tylko wtedy, gdy funkcja jest wywoływana z określonym parametrem, ale funkcja nigdy nie jest wywoływana z tym parametrem.Dlatego zdecydowanie przydatne jest użycie sprawdzania konsolidatora, ale przydatne jest również ręczne analizowanie kodu pod kątem martwego kodu.
@Dakkaron Dzięki ... dodałem Twoje imię do komentarza (właściwie miałem coś takiego jak to co napisałeś wcześniej).
#3
+8
Michael Karas
2019-10-12 19:41:12 UTC
view on stackexchange narkive permalink

Oprócz doskonałych sugestii zawartych w innych odpowiedziach tutaj, chcę skomentować, że może istnieć ogromna różnica w tym, ile kompilatorów (i linkerów) może zoptymalizować kod.

Kilka lat temu pracowałem w firmie, w której produkt korzystał z ATMega8. Kiedy przybyłem na scenę, ten produkt miał trzy różne wersje kodu źródłowego, aby zapewnić oddzielne zestawy funkcji dla różnych konfiguracji produktu. Kod źródłowy został skompilowany przy użyciu taniego kompilatora C, a każdy zestaw kodu ledwo mieści się w 8 kilobajtach pamięci FLASH urządzenia.

Zleciłem firmie zakup wysokiej klasy kompilatora od firmy dobrze znanej w społeczności AVR. Następnie zacząłem pracować nad kodami źródłowymi oprogramowania i ustawiłem opcje kompilatora pod kątem maksymalnej optymalizacji.

Kiedy skończyłem, wszystkie opcje produktu zmieściły się w jednym obrazie, który był mniejszy niż 8 KB. W rzeczywistości było wystarczająco dużo miejsca, aby dodać do kodu programowy UART przeznaczony wyłącznie do transmisji, tak aby oprogramowanie mogło wyrzucić wewnętrzne informacje, które były używane do kalibracji parametrów produktu. Wyjście UART zostało wyzwolone, gdy sygnał 28 V został przyłożony do jednego z kanałów A / D przez dzielnik napięcia. Wyzwalacz był potrzebny, ponieważ oprogramowanie wyjściowe UART wykorzystywało GPIO, które normalnie było sygnałem, który był wyjściem z produktu.

Dobra historia.Kompilatory mogą wykonać dobrą robotę, ale wysiłek człowieka może w tym pomóc.W podobny sposób przejąłem projekt z wykorzystaniem 8-bitowego procesora PIC od klienta, w którym różne wersje zostały rozdzielone w trzech podobnych kodach źródłowych - pamięć programu była prawie pełna dla każdej wersji (8k pamięci programu, około 85% zapełniona).Połączyłem kody, zoptymalizowałem sam kod C, sprawdzając wygenerowany kod asemblera i dodałem wiele funkcji z czasem (około 98% pełnych).Rzucenie na to lepszego kompilatora mogłoby trochę pomóc, ale nie tak bardzo, jak optymalizacja kodu.
#4
+8
Graham
2019-10-13 01:02:23 UTC
view on stackexchange narkive permalink

Pierwszym krokiem do jakiejkolwiek optymalizacji jest dowiedz się, co to robi .

Pierwszym krokiem powinno być skłonienie konsolidatora do zrzucenia adresu każdego identyfikatora w kompilacji.To wszystko funkcje i wszystkie zmienne.Twój linker powinien również być w stanie raportować rozmiary funkcji;prawdopodobnie nie będzie robić rozmiarów zmiennych, ale możesz je wywnioskować z adresu następnej zmiennej na liście.

Gdy już wiesz, dokąd zmierza Twoja przestrzeń, możesz coś z tym zrobić.Szanse są całkiem dobre, rozwiązanie będzie oczywiste, gdy zaczniesz patrzeć na to, co jest największe.

Dopóki się nie zorientujesz, jesteś po prostu ślepym strzelcem, a to nigdy nie jest dobry plan.

#5
+6
filo
2019-10-13 15:39:09 UTC
view on stackexchange narkive permalink

Nie ma „praktycznego” sposobu na uruchomienie skompresowanego kodu na odbiorniku AVR, więc problem polega na tym, że „jak zoptymalizować rozmiar oprogramowania sprzętowego”.

Sztuczki Toolchain (tj. nie musisz modyfikować swojego kodu):

  1. Jaki jest poziom optymalizacji kompilatora? W gcc opcja optymalizacji pod kątem minimalnego rozmiaru nazywa się -Os

  2. Istnieje funkcja zwana optymalizacją czasu łącza, która umożliwia dalszą optymalizację pod kątem rozmiaru. Jest już wspomniany w tej odpowiedzi.

  3. Konsolidator może zoptymalizować nieużywane dane i symbole. Jest włączana za pomocą -Wl, - gc-sekcja -funkcja-sekcje -fdata

  4. Włącz -mcall-prologues . Wyjaśnione tutaj.

Ogólne sztuczki związane z kodem:

  1. Uruchom nm -S --size-sort -t d your_output_file.elf . To polecenie pokaże, jak duży jest każdy symbol (symbol w języku konsolidatora oznacza dane lub kod). Możesz wtedy dowiedzieć się, gdzie jest największa możliwość optymalizacji.

  2. Spróbuj znaleźć fragmenty kodu, które mogłyby same stać się funkcjami.

  3. Unikaj używania printf . Jeśli potrzebujesz tylko przekonwertować liczby całkowite na łańcuchy, itoa jest opcją. Możesz również sprawdzić xprintf, który jest lżejszą alternatywą dla standardowego printf.

  4. Jeśli używasz liczb zmiennoprzecinkowych (zmiennoprzecinkowych, podwójnych) - spróbuj przekonwertować kod na liczby całkowite. Na przykład, jeśli potrzebujesz 2 miejsc po przecinku, możesz użyć prostego skalowania przez 100 (tj. 2,5 staje się 250). Każda operacja zmiennoprzecinkowa (tak prosta jak float x = a + b ) na AVR ściąga stos kodu biblioteki, ponieważ procesor nie obsługuje takich operacji sprzętowo.

#6
+4
sktpin
2019-10-14 19:50:09 UTC
view on stackexchange narkive permalink

Jeśli szukasz sposobów na zmniejszenie rozmiaru kodu programu - oprócz tego, że linker optymalizujący kompilator & odciął niektóre i nie używa standardowych funkcji bibliotecznych, jak zauważyli inni, zależy to również od składu kodu programu jak duży będzie.

  • najpierw spróbuj znaleźć sposób na pokazanie, które części programu są najbardziej urażające pod względem rozmiaru. Niestety nie jestem zaznajomiony z AVR studio, ale tutaj, user skeeve, post # 7 wymienia rozmiary pojedynczych plików obiektowych. Jeśli Twój program ma kilka modułów, będziesz przynajmniej wiedział, które z nich są największe i są dobrymi kandydatami do ręcznej optymalizacji .
    • Dlaczego ręczna refaktoryzacja kodu? Jeśli możesz uzyskać mniejszy kod w ten sposób, w przeciwieństwie do używania do tego optymalizacji kompilatora, debugowanie (na przykład przechodzenie przez kod za pomocą GDB lub czegoś podobnego) będzie działać znacznie lepiej niż wtedy, gdy optymalizator tworzy wyraźną różnicę między rzeczywistym wykonanym kodem a kod źródłowy, dzięki czemu łatwiej będzie go śledzić
  • oto kilka wskazówek: zobacz rozdział „3 porady i wskazówki dotyczące zmniejszenia rozmiaru kodu”
  • unikaj kodu „kopiuj & wklej”
    • tj. użyj procedury dla sekwencji działań, które musisz wykonać w więcej niż jednym miejscu, a następnie wywołaj ją z tych miejsc
    • może się to wydawać oczywiste („Używałem funkcji, prawda!”), ale warto poszukać miejsc w kodzie, które wykonują prawie dokładnie to samo, wykonują za każdym razem kilka kroków, a wszystko to zależy tylko od kilka parametrów, które mogą być argumentami funkcji
    • Uwaga dodatkowa: są też inne powody, dla których warto to zrobić: rutynowo używaj procedur
  • (bardziej desperacko, walcząc o kilka dodatkowych bajtów ...) rozważ konwersję większego przełącznika / wielkości liter lub bloków if..else..if..else ... (z których wywoływane są funkcje) na tablicę const zwskaźniki funkcji.Działa tylko wtedy, gdy wszystkie funkcje mają tę samą sygnaturę, tj. Ten sam typ wskaźnika, a obliczenie indeksu tablicy na podstawie wartości decydującej o tym, co zostanie wywołane, nie powoduje samoistnego dodania dużej ilości kodu.Indeksowanie tablicy i wywoływanie wskaźnika funkcji może generować mniejszy kod niż wiele przełączników / przypadków lub if / else dla tego samego scenariusza.(zrobiło to dla mnie - chociaż na 32-bitowym Microblaze. YMMV)
#7
+2
Dvidunis
2019-10-13 02:34:56 UTC
view on stackexchange narkive permalink

Jeśli Twój projekt składa się z wielu plików źródłowych (jak większość projektów), możesz również spojrzeć na Link Time Optimization (powszechnie w skrócie LTO).

Wprowadza dodatkowe optymalizacje między obiektami (w czasie łączenia, jak sugeruje nazwa).

Możesz poszukać konkretnej opcji „Optymalizacja czasu łącza” / „LTO” w swoim IDE lub poszukać miejsca na dodanie flag kompilatora.

Jeśli Twój kompilator jest oparty na GCC lub Clang, możesz dodać flagę -flto podczas kompilacji i czasu łączenia (zarówno CFLAGS, jak i LDFLAGS, jeśli dotyczy).

Może to zoptymalizować całe bloki kodu (wewnątrz funkcji), które nie są wywoływane, lub zoptymalizować je pod kątem określonego wzorca wejściowego.

Pamiętaj, że jeśli korzystasz z biblioteki standardowej, musisz się upewnić, że skompilujesz ją za pomocą LTO, aby uzyskać duże oszczędności.

Więcej informacji o LTO można znaleźć tutaj: https://en.wikipedia.org/wiki/Interprocedural_optimization
I przykład z jądra Linuksa tutaj: https://lwn.net/Articles/744507/



To pytanie i odpowiedź zostało automatycznie przetłumaczone z języka angielskiego.Oryginalna treść jest dostępna na stackexchange, za co dziękujemy za licencję cc by-sa 4.0, w ramach której jest rozpowszechniana.
Loading...