Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

STM32F1/F4 (Eclipse) - Tworzenie biblioteki dynamicznej-podział programu

Leszek7Sz 25 Sep 2014 14:45 1608 16
Nazwa.pl
  • #1
    Leszek7Sz
    Level 10  
    Witam
    Środowisko:
    Eclipse Kepler, GCC ARM, OpenOcd
    Procesor: STM32F1/F4

    Przesiadłem się niedawno na ARMy. Tworzę projekt, który niestety bardzo się rozrasta. Zajmuje już ok 300k, a to początek. Zawiera dużo grafik, czcionek oraz stałym procedur (obsługa stosu TCP, obsługa grafiki-emWin, Fat32 itp). Te części programu są stałe. Problemem jest czas, jaki zajmuje każdorazowe programowanie procesora.
    W jaki sposób stworzyć bibliotekę, którą będę mógł jednorazowo wpisać w zadany obszar procesora? Potem tylko kompilacja mniejszych fragmentów programu, które będą korzystały z biblioteki na stałe wpisanej do procesora. Przez to osiągamy krótszy czas programowania i debagowania.
    Próbowałem używać -fpic przy kompilacji i -shared przy linkowaniu ale bez rezultatu. Szukam już dwa dni.

    Proszę o pomoc i pozdrawiam.
  • Nazwa.pl
  • #2
    Freddie Chopin
    MCUs specialist
    Rozwiązanie tego problemu jest tak skomplikowane, że raczej przyzwyczaiłbym się do czasu programowania, albo coś z nim zrobił (; Opcji na przyspieszenie jest wiele - np. podczas testów, gdy nie potrzebujesz stosu TCP/IP i systemu plików, bo akurat testujesz GUI, to po prostu wyłącz część projektu. Inną opcją jest mniej debuggowania, więcej myślenia (; Oczywiście nie piszę tego, żeby Cie obrazić czy coś - zauważyłem po prostu po sobie, że z czasem więcej czasu poświęca się na przemyślenie problemu, aby tym samym nie trzeba było potem rozwiązania tak wnikliwie testować czy debuggować. Czasem wręcz zdarza się, że przez cały dzień nie czuję potrzeby programowania układu, tylko po kawałku dopisuję funkcjonalność której potrzebuję.

    Jeśli koniecznie chcesz "po Twojemu", to na pewno najłatwiej będzie wyrzucić z projektu grafiki/czcionki (czyli generalnie duże bloki danych), które tylko odczytujesz - wrzucając je pod konkretny adres w pamięci, w kodzie po prostu do funkcji podajesz ten konkretny adres i tyle. Zrobienie tego samego z kodem jest bardzo skomplikowane, sama flaga -fpic niczego nie załatwi i pewnie sama w sobie nawet nie działa bez wielu innych opcji. -shared na 99% nie działa w przypadku mikrokontrolera.

    4\/3!!
  • #3
    Leszek7Sz
    Level 10  
    Dzięki za odpowiedz. Tego się właśnie bałem.

    Jak na razie używam dla plików bmp, czcionek itp. atrybutu
    __attribute__((section (".addr")))
    i linkuję
    -Wl,--section-start=.addr=adres;
    i programuję procesor. Potem ręcznie wpisuję adresy do właściwego programu. Jest to niestety bardzo uciążliwe. Jakakolwiek zmiana wymaga odczytywania adresów z pliku .map i ponowne wpisanie jej do właściwego programu. Miałem nadzieję, że dla uP ARM jest taka możliwość jak w innych systemach.
    A co do czasu programowania i myślenia, to zaczynałem przygodę z uP w 83 roku. Wtedy w trakcie kompilowania programu potrafiłem zaparzyć kawę - wynik kompilacji-error (literówka). Wtedy nie było automatycznego sprawdzania składni. To dopiero uczyło myślenia.

    Jeszcze raz dzięki. Dużo się od Ciebie Freddie nauczyłem. "Bawię" się ARM-ami od lipca tego roku i jest to duży przeskok. Przeszedłem od 8080, poprzez Z80, '51, AVR i teraz ARM.
    Pozdrawiam
  • Nazwa.pl
  • #4
    tadzik85
    Level 38  
    Lepiej napisać dobrze skrypt linkera.
    Utworzyć kopię elfa i wywalić z niej sekcje stale. Które są już zaprogramowane.
    W ten sposób miałbyś wgrywany tylko wybrany fragment przez ciebie.

    Można by pokusić się o 2 wersje linkowania. w jednym skrypcie miałbyś sekcje typu NOLOAD w drugim nie.
  • #5
    Leszek7Sz
    Level 10  
    Robię podobnie. Ale mimo wszystko, jak trzeba zmienić grafikę, czcionkę itp, to zabawa jest od nowa - adresy trzeba wpisać aktualne.
    Nie do końca rozumiem jak z elf-a wyrzucić sekcje stałe.
    W tej chwili robię tak, że kompiluję i tworzę osobny plik do programowania z grafikami, czcionkami itp z ustawionym na sztywno adresem i programuję procesor. Następnie we właściwym programie przypisuję ręcznie adresy tych sekcji (wpisuję ręcznie adresy grafik, czcionek) i kompiluję, Dzięki temu program zajmuje ok. 30kB.

    Czy jest jakaś inna metoda? Najbardziej kłopotliwe jest ręczne wpisywanie adresów. Jak to zrobić automatycznie?
  • #6
    tadzik85
    Level 38  
    Tworzysz sekcję np dla grafiki.
        .pic1 (NOLOAD) :
        {
            . = ALIGN(4);
            *(.pic1*)
        } > ADR AT > ADR

    Tego typu.
    W drugim linkerze tak samo poza NOLOAD.
    Wywołujesz 2 razy linkowanie z rożnymi skryptami otrzymując 2 elfy.
    A grafikę umieszczasz po prostu w tej sekcji i wszystko dzieje się z automatu.

    Żadnych przepisywać sprawdzania adresów.

    Nie testowałem, ale powinno działać.
  • #7
    Leszek7Sz
    Level 10  
    Dzięki. Spróbuje jutro i opiszę dokładnie procedurę dla potomnych :D
  • #8
    tadzik85
    Level 38  
    Leszek7Sz wrote:
    Dzięki. Spróbuje jutro i opiszę dokładnie procedurę dla potomnych :D


    Ważne by każdemu elementowi nadać adres (przez memory i umieszczenie sekcji w niej albo przez .=ADRES co każdą sekcję wejściową ). I zagwarantować by obiekty mieściły się w sekcjach. Więc lepszą uważam metodą 1. Linker sam wypluje info jak sekcja się nie zmieści w MEMORY.

    Jeśli z jakiejś przyczyny GDB nie obsłuży sekcji typu NOLOAD, co jest mało prawdopodobne tworzymy z tych elfów hexy.
    Programujemy procesor innym programem np. STLinkUtility (działa z linii poleceń).
    I uruchamiamy debugowanie bez ładowania programu.
    Można pokusić się o uruchomienie programowania przez STLink Utility i uruchomienie openocd przez jakiś skrypt podpięty do External tools Eclipsa.

    2 opcja: zamiast generowania 2 elfów, generujemy 2 hexy jeden pełen 2-gi tylko zawierający sekcję text (opcja -j dla objcopy).
    W 1 opcji lekkim hazardem było by liczyć ze kod w obu elfach będzie identyczny.
  • #9
    Freddie Chopin
    MCUs specialist
    Proponowałbym po prostu te dane stałe wyrzucić na sam koniec pamięci flash (uwaga - najlepiej zrobić tak, żeby dane zaczynały się na początku strony!) - wtedy Twój program może się rozrastać w miarę dowolnie, bo jeśli dojdziesz do ostatniej strony (lub stron), to tak czy siak będziesz musiał ciągle programować wszystko. Dzięki takiemu umieszczeniu, grafiki są na stałych adresach, niezależnych od programu. Jeśli jakaś grafika się często zmienia, to można ja wyrzucić do osobnej strony i podmieniać tylko ją - wtedy nie zmieniają się też adresy pozostałych grafik. Jeśli wszystkie grafiki się trochę zmieniają (np. ich rozmiar zmienia się o kilka-kilkanaście-kilkadziesiąt bajtów), to można je umieścić na adresach będących wielokrotnością jakiejś wartości (np. na adresach podzielnych przez 1024) - wtedy niewielkie zmiany rozmiaru będą rzadziej powodowały zmianę ich umieszczenia (trzeba tylko zwrócić uwagę na bloki o rozmiarze bardzo zbliżonym do tego mnożnika).

    Inną opcją jest generowanie ze skryptu linkera automatycznych adresów tych bloków danych - tak jak na podstawie skryptu linkera kompilator zna adresy __text_start, __main_stack_end itd. - dodaj coś takiego dla grafik (wymaga to osobnych sekcji dla każdej, lub ręcznego linkowania plików obiektowych po nazwie, a nie "*.o"), w kodzie korzystaj z tych adresów i problem przestawiania przestanie istnieć.

    4\/3!!
  • #10
    Leszek7Sz
    Level 10  
    Dzięki podpowiedzi tadzik85 udało się rozwiązać problem.

    Opisywana metoda pozwala umieszczać w stałym obszarze pamięci FLASH dane (bitmapy, czcionki itp) które zajmują dużo miejsca (rzędu kilkuset kB) i nie są często zmieniane. Dzięki tej metodzie, wielkość kodu wynikowego wpisywanego do procesora redukujemy do kilkudziesięciu kB. Czas programowania procesora po wprowadzeniu zmian w uruchamianym kodzie skrócił się z kilkunastu sekund do 2-3. I w dodatku nie trzeba ręcznie wprowadzać adresów grafik, tablic czy funkcji.

    W pliku skryptu linkera dzielimy pamięć rom na dwie części.
    Przykład dla STM32F103VC pliku link.ld

    Code: c
    Log in, to see the code



    Podział należy dokonać uwzględniając jaką część kodu umieścimy w rom (aktualnie testowany fragment programu), a jaką w część, którą zaprogramujemy tylko raz (grafiki, czcionki) - w obszarze rom1. Na szczęście linker informuje nas o przekroczeniu wielkości pamięci i korygujemy to na bieżąco.

    Następnie dopisujemy w skrypcie:

    Code: c
    Log in, to see the code

    Bardzo ważne, aby powyższy fragment umieścić po wszystkich deklaracjach obszarów korzystających z rom. W przeciwnym wypadku linker umieści częśc kodu za naszym stałym obszarem i po użyciu (NOLOAD) w obszar rom1 zostaną wpisane wartości 00 i nie zmniejszy się wielkości kodu programu, a nasz wcześniej zaprogramowany obszar FLASH zostanie skasowany.

    W kodzie programu deklarujemy duże obiekty lub już sprawdzone funkcje z atrybutem:
    __attribute__((section (".stalyobszar")))

    przykladowo:

    Code: c
    Log in, to see the code


    linkujemy i programujemy procesor.
    Następnie zmieniamy w skrypcie (lub używamy drugiego skryptu) linijkę:

    Code: c
    Log in, to see the code


    I to wszystko. Po kolejnym linkowaniu, kod z atrybutem __attribute__((section (".stalyobszar"))) nie jest ładowany do procesora, ale zachowane są adresy funkcji lub innych bloków danych. Linker nie informuje nas o zmianie wielkości kodu, ale wystarczy sprawdzić wielkość pliku .bin.

    Jeśli zajdzie potrzeba poprawienia kodu umieszczonego w segmencie .stalyobszar, to wystarczy w skrypcie linkera wykasować (NOLOAD), zlinkować, zaprogramować itd...

    Podziękowania dla tadzik85 i freddiego.
  • #11
    tadzik85
    Level 38  
    Użyj znaczników syntax. Ciut chaotycznie opisałeś metodę.
    Nie ma za co. Mam nadzieje, że nie tylko tobie się przyda.
    Powtórzę należy pamiętać by nie dopuścić do sytuacji gdy zmienna i stalą część kodu znajdzie się w jednej stronie pamięci flash.
  • #12
    Freddie Chopin
    MCUs specialist
    Leszek7Sz wrote:
    I to wszystko.

    ... jest źle (; Zastanów się, co się stanie, jeśli któraś z funkcji które umieszczasz w tym stałym obszarze wywołuje jakąś inną funkcje, która jest w obszarze "nie-stałym". A raczej na pewno jakąś w końcu wywołuje, np. memcpy() lub memset(), które mogą być wywoływane przez kompilator niejawnie np. przy inicjalizacji struktur czy tablic... Naprawdę podtrzymuję to co napisałem na początku - taki podział jest bardzo skomplikowany do prawidłowej realizacji i działa tylko jeśli w całym kodzie nic nie zmienisz... No chyba że Ty przed każdym debuggowaniem - nawet jeśli kod się nie zmienił - programujesz układ, no to wtedy faktycznie "działa".

    To że teraz Ci to działa to przypadek. W końcu przestanie.

    4\/3!!
  • #13
    tadzik85
    Level 38  
    Hm. No OK, Ale w przypadku danych, przeszkód brak.
    Co do kodu to faktycznie.

    Myśl. A jakby biblioteki nawet standardowe dodać do części stałej? A zdaje się można zrobić to przez linker. Umieścić tam stosy, FATFS itp do najniższej warstwy włącznie?
  • #14
    Freddie Chopin
    MCUs specialist
    Tylko po co tak kombinować? Dla zyskania 10-ciu sekund w skali dnia? Obstawiam, że w tym projekcie o którym mowa tak czy siak grafika zajmuje większość miejsca, więc przeniesienie tego do części stałej (co jest do zrobienia prosto i poprawnie) na pewno da spory zysk - większy niż przenoszenie kupy malutkich funkcji czy kilkunastu bibliotek, czego trzeba cały czas pilnować i sprawdzać... A przecież wystarczy tylko jedno, JEDNO, programowanie wsadem w którym te adresy funkcji byłyby pomieszane, żeby stracić te wszystkie drogocenne sekundy zarobione na tej "optymalizacji" - debuggowanie takiego kodu przez kilka minut i zastanawianie się "WTF?" powinno skutecznie wyleczyć zapędy do takich pomysłów.

    Ciekawe czy autor ma choć włączoną optymalizację kodu na poziomie co najmniej -O2 albo i -Os? Co z flagami do usuwania nieużywanych funkcji i danych: -ffunction-sections i -fdata-sections oraz --gc-sections?

    Generalnie myślę też, że cała ta dyskusja i te eksperymenty trwały już więcej niż sumaryczny zysk z tych zmian na cały miesiąc... A tak naprawdę efekt jest mizerny, bo niepoprawny i działa jedynie przypadkiem.

    4\/3!!
  • #15
    Leszek7Sz
    Level 10  
    Tak jak pisałem na początku, projekt zawiera dużo grafik (animowanych) oraz własnych czcionek. Celowo używam własnych czcionek, gdyż te z GUI nie da się przenosić w dowolny obszar. Zajmuje to kilkaset kB. To dla takich danych potrzebuję używać tej metody. Napiszecie, że grafikę można umieścić np. na dysku SD. Ale przy animacji się to nie sprawdza, za duże opóźnienia.
    Poza tym, kody funkcji też można przenieść w obszar stały, ale z głową.
    Sam kod programu zajmuje ok 70kB (nawet bez przenoszenia do obszaru stałego) i programowanie procesora wtedy nie stanowi problemu.
    Będę testował w praktyce, nie w teorii i wkrótce napiszę o moich doświadczeniach.

    Dla wiadomości. Używam optymalizacji -O2 oraz opcje jak napisał Freddie
    Co do czasu straconego na eksperymenty, to zajmuję się programowaniem codziennie i dla mnie ten czas jest cenny. Patrzę w perspektywie długiego czasu a nie w skali miesiąca. Może też komuś przyda się ta metoda i zaoszczędzi czas, jeśli będzie z niej korzystał z głową.

    Pozdrawiam
  • #16
    Freddie Chopin
    MCUs specialist
    Leszek7Sz wrote:
    oza tym, kody funkcji też można przenieść w obszar stały, ale z głową.

    Musiałbyś robić binarne porównanie wygenerowanego obszaru po każdej kompilacji, żeby się upewnić, że wciąż jest taki sam jak był. Bez takiej automatycznej kontroli jedna sesja debuggowania "błędnego" kodu zniweczy wszystkie twoje oszczędności czasowe. Kontrola rozmiaru niczego nie daje, bo rozmiar może być identyczny, ale kod już nie - przecież gdy zmieni się adres jakiejś funkcji wywoływanej z Twojego stałego obszaru, to rozmiar sumaryczny nie ulegnie zmianie.

    Liczba funkcji które kompilator wywołuje niejawnie jest dosyć spora - wymienione już memset() oraz memcmp(), funkcje matematyczne (w tym niektóre operacje na liczbach całkowitych!), całkiem sporo funkcji odwołuje się do globalnych danych (m.in. errno, ale nie tylko). W C++ sytuacja oczywiście robi się jeszcze ciekawsza, bo tam każdy praktycznie operator albo deklaracja zmiennej może się okazać funkcją.

    Poza tym, skoro kodu jest 70kB, a grafiki około 230kB, to co zyskasz przenosząc kilka funkcji do tego stałego obszaru? Żeby je przenieść i być pewnym, że nie będzie problemu muszą to być proste (żeby nie powiedzieć "tępe") funkcje które nie wywołują funkcji z innych modułów. Pół sekundy? Ja wiem, że te sekundy wydają się długie, jednak po zsumowaniu tego czasu za np. jeden dzień, wychodzą zwykle jakieś śmieszne wartości o które nie warto walczyć, np. 5 minut... Piszę z doświadczenia, bo każdemu wydaje się, że programuje układ 1000 razy dziennie, a potem okazuje się, że ta liczba ma max 2 cyfry i pierwsza z nich wcale nie jest taka wysoka...

    4\/3!!
  • #17
    Leszek7Sz
    Level 10  
    Quote:
    Poza tym, skoro kodu jest 70kB, a grafiki około 230kB, to co zyskasz przenosząc kilka funkcji do tego stałego obszaru?


    Tak, masz całkowitą rację i nie przenoszę funkcji do tego obszaru. Istnieje duże niebezpieczeństwo graniczące z pewnością, że linker kiedyś namiesza w adresach i czas stracony na szukanie przyczyny zniweczy wcześniejsze oszczędności. Tak jak pisałem post wyżej, umieszczam tam tylko grafiki, czcionki. Może niepotrzebnie napisałem o przenoszeniu funkcji w stały obszar i tym samym trochę namieszałem.

    Quote:
    bo każdemu wydaje się, że programuje układ 1000 razy dziennie


    Specyfika projektu polega tym, że muszę testować działanie programu na "żywym" układzie - procesor dostaje dane i rozkazy z komputera poprzez Ethernet, sprawdza czujniki pomiarowe,pobiera pomiary z zewnętrznych przetworników ADC i na to wszystko musi reagować w czasie rzeczywistym. Procesor programuję czasami kilkadziesiąt razy dziennie, więc zaoszczędzenie nawet kilku sekund za każdym razem daje mi dużo.

    Dzięki za wszelkie uwagi.