Skocz do zawartości

[C] Organizacja kodu w projektach embedded


dirrack

Pomocna odpowiedź

Cześć,

Dostałem ostatnio do ogarnięcia i uporządkowania całkiem spory kawał kodu w C na procesor Texas Insturments (w tym przypadku seria Tiva z Cortex-M4 ale nie ma to tu większego znaczenia). Program na bibliotekach std (podobnie jak w STM32 stdperiph). Kod ma około 4000 linii i bardzo ciężko się w nim odnaleźć, odłączać konkretne peryferia, modyfikować etc. ze względu na ogromne spaghetti, które się wytworzyło. A potrzeba modyfikacji jest, bo jest to działający od lat element projektu na uczelni. Nawet chcąc pisać kod od początku mija się to z celem bez wiedzy na temat tego jak się za to zabrać.
Tu dochodzimy do sedna: jak profesjonalnie podchodzi się w firmach czy w projektach badawczych do organizacji kodu w projektach na mikrokontrolery? Dla projektów obiektowych są wzorce projektowe, zasada SOLID etc. ale czym kierować się przy pisaniu na uC?
Poza ogólnymi zasadami, borykam się także z pomniejszymi drobnymi pytaniami odnośnie dobrych praktyk:

  1. Czy monolit jest typową praktyką?
  2. Czy sensowne jest używanie zmiennych globalnych (np wartości, które coś konfigurują, wymieniają dane między funkcjami), czy np.: lepiej przekazywać wskaźniki do tych danych między funkcjami?
  3. W przypadku gdy chcemy odłączyć jakiś podzespół, załóżmy jakiś czujnik temperatury dla przykładu , to lepiej tworzyć jakąś flagę czy lepszym wyborem będą dyrektywy procesora.
  4. Gdy wyodrębniamy jakiś element kodu do zewnętrznej funkcji, warto by operował na wskaźnikach czy normalnie przyjmował kopie danych i zwracał daną? Wiem, że popadanie w optymalizacyjną paranoje nie ma czasem sensu, ale być może ktoś bardziej doświadczony jest w stanie odpowiedzieć co warto stosować, a co jest przesadą.

Chętnie też przyjmę wszelkie materiały odnośnie optymalizacji czy organizacji kodu w odniesieniu do systemów wbudowanych godne polecenia, a być może i ktoś ma przykład jakiegoś projektu, który jest wykonany "zgodnie ze sztuką". Zachęcam wszystkich do wymiany doświadczeń i przemyśleń odnośnie sprawy i z góry dziękuję za odpowiedzi, bo jak się domyślam temat nie jest łatwy ani oczywisty a celowo mówie ogólnikowo, żebyśmy mogli skorzystać na nim wszyscy. Pozdrawiam i zapraszam do dyskusji! 🙂
 

Link do komentarza
Share on other sites

Z literatury to najbardziej uniwersalnym klasykiem jest chyba Czysty kod i Czysta architektura Roberta C. Martina. Każdy znajdzie tam coś ciekawego dla domeny, w której się porusza. Wiele problemów, które spotyka się w codziennej pracy, ktoś już wcześniej rozwiązał więc czemu by z nich nie korzystać. 

Wiele ciekawych informacji (nt. wielu spraw o które pytasz) znajdziesz na blogu @GAndaLF np. https://ucgosu.pl/2019/01/podstawy-architektury-embedded-warstwy-i-moduly/

Dnia 24.04.2020 o 18:58, dirrack napisał:

Czy monolit jest typową praktyką?

Pewnie (niestety) tak. Lepiej byłoby zapytać "Czy monolit jest dobrym podejściem?" I tak pewnie patrząc, na kod z którym musisz pracować, zapewne odpowiesz, że nie. 4000 linii kodu to nie jest wcale dużo, a jak sam piszesz "bardzo ciężko się w nim odnaleźć, odłączać konkretne peryferia, modyfikować etc. ze względu na ogromne spaghetti, które się wytworzyło." Wprowadzenie modułów wymaga nieco więcej zastanowienia się nad tym co i jak się pisze, ale daje więcej możliwości i jest bardziej podatne na wprowadzanie zmian w końcowym rozwiązaniu. Moduły możesz testować jednostkowo, monolit raczej będzie ciężko.

Ale to też zależy co robisz, jak pracujesz nad szybkim projektem, to zależy na Ci na jak najszybszym wykonaniu zadania a nie estetyce. Jak pracujesz nad projektem, który będzie utrzymywany kilka lat, to tam warto poświęcić więcej czasu na dobre zaprojektowanie architektury podatnej na zmiany.

Dnia 24.04.2020 o 18:58, dirrack napisał:

Czy sensowne jest używanie zmiennych globalnych (np wartości, które coś konfigurują, wymieniają dane między funkcjami), czy np.: lepiej przekazywać wskaźniki do tych danych między funkcjami?

 Pewnie każdy na swojej drodze nauki programowania spotkał się ze stwierdzeniem że użycie zmiennych globalnych jest złe, kilka linków:

Dnia 24.04.2020 o 18:58, dirrack napisał:

W przypadku gdy chcemy odłączyć jakiś podzespół, załóżmy jakiś czujnik temperatury dla przykładu , to lepiej tworzyć jakąś flagę czy lepszym wyborem będą dyrektywy procesora.

Użycie flagi wiąże się z narzutem, który wynika z konieczności jej sprawdzenia, wykorzystanie dyrektyw preprocesora skutecznie wytnie Ci z użycia dany fragment kodu, ale może doprowadzić do sytuacji, że wyłączony kod z użycia przestanie być aktualizowany równolegle z "główną" gałęzią i tak przy odblokowaniu jakiegoś fragmentu kodu możemy się nagle natknąć na błędy kompilacji, np spowodowane zmianą nazwy jakiejś zmiennej. Oczywiście wszystko zależy od zastosowania, natomiast użycie dyrektyw jest bardzo częstą praktyką. W C++17 mamy konstrukcję if constexpr, która może być ciekawą alternatywą.

Dnia 24.04.2020 o 18:58, dirrack napisał:

Gdy wyodrębniamy jakiś element kodu do zewnętrznej funkcji, warto by operował na wskaźnikach czy normalnie przyjmował kopie danych i zwracał daną? Wiem, że popadanie w optymalizacyjną paranoje nie ma czasem sensu, ale być może ktoś bardziej doświadczony jest w stanie odpowiedzieć co warto stosować, a co jest przesadą.

Przy podstawowych typach dobrą praktyką jest przekazywanie danych przez wartość, przy złożonych zalecane jest przekazywanie wskaźników lub referencji (to już w C++). Nikomu nie są potrzebne tymczasowe kopie przekazywanych argumentów. Ale to oczywiście też zależy od kontekstu. Naturalnie spodziewamy się, że funkcja ma jakieś wejście i jakieś wyjście.

Jeśli dobrze rozumiem, to zastosowanie o które pytasz - np. konfiguracja jakiejś struktury w jakieś funkcji, najczęściej będzie zrealizowana właśnie przez przekazanie wskaźnika (lub referencji) na obiekt tej struktury. I takie podejście jest stosowanie np. w StdPeriph czy HAL od STM32.  

 

Tyle ode mnie, mam nadzieję, że zachęci to innych do wypowiedzi 😛 

Edytowano przez Matthew11
  • Pomogłeś! 1
Link do komentarza
Share on other sites

Po pierwsze - jeżeli 4000 linii ma cały projekt to jeszcze nie jest zbyt duży. Jeżeli tyle ma pojedynczy plik, jego edycja jest już mocno uciążliwa. Chociaż zdarzają się i kilkukrotnie większe pliki. Jednak już przy 4000 na cały projekt widać pewne problemy projektowe i właśnie ich doświadczasz 😄 Te problemy to zwykle brak dokumentacji i nie wiadomo jaki jest cel danego kodu, brak sensownego podziału na elementy składowe - podział jest zwykle wymuszony np. przez peryferia, długie funkcje robiące wiele rzeczy na raz, często z jakimiś dziwnymi konstrukcjami. Jeżeli to projekt na uczelni, który ma już swoje lata to pewnie wszystkie te rzeczy występują i może jeszcze różne inne.

 

Dla embedded niestety nie ma jasno sprecyzowanych wzorców co do których wszyscy się zgadzają i stosują je w praktyce. Jest raczej wolna amerykanka i kod bardziej podchodzący pod antywzorce. Ja od jakiegoś czasu już staram się szukać takich wzorców na własny użytek i jakoś je generalizować do całego embedded i opisuję to właśnie na https://ucgosu.pl jak wspomniał @Matthew11

W moich materiałach znajdziesz coś na ten temat tutaj:

https://ucgosu.pl/materialy-do-nauki-embedded/

https://ucgosu.pl/projekt-embedded/

Robię też na YouTube livestreama, gdzie poruszam te tematy:

I ostatnio szykuję szkolenie online z C.

To tyle z autopromocji, teraz przechodząc do odpowiedzi:

1. Czy monolit jest typową praktyką?

Rozumiem, że chodzi Ci o podział monolit/mikroserwisy jak w aplikacjach webowych? Tutaj najbliższym odpowiednikiem mikroserwisów (czy ogólnie serwisów na różnych serwerach) jest stosowanie kilku procesorów. I odpowiedź brzmi - zależy od projektu. Bardzo dobrze sprawdzają się konstrukcje w stylu dedykowany procesor do obsługi wyświetlacza graficznego, czy obsługi innych zadań mało krytycznych a wymagających dla procesora. Możliwa też jest opcja z jednym procesorem i RTOSem, albo jakieś inne podejście do wielozadaniowości na jednym procesorze (np. przerwania, maszyny stanów).

 

2. Czy sensowne jest używanie zmiennych globalnych (np wartości, które coś konfigurują, wymieniają dane między funkcjami), czy np.: lepiej przekazywać wskaźniki do tych danych między funkcjami?

Generalnie zmienne globalne to zło. Wskaźniki do zmiennych globalnych, czy nawet settery gettery to też zło, tylko bardziej ukryte. Mamy dokładnie te same problemy (współbieżność, modyfikacja z wielu miejsc, problemy z zależnościami). Mi się podoba koncepcja interfejsów opisujących konkretne czynności wystawianych przez moduł zamiast dawania na zewnątrz kontroli nad szczegółami implementacyjnymi. To może szczególnie zaboleć przy większym refactorze.

Obrazując koncepcję interfejsu - załóżmy, że sterujemy silnikiem.

ZŁA PRAKTYKA: set direction, set speed, set flags itd.

DOBRA PRAKTYKA: konkretne funkcje: move_forward(speed), stop() itd

W ten sposób ukrywamy informacje, że jest jakaś zmienna direction i jakieś flagi. Potem jak zmieni nam się koncepcja to nie musimy przerabiać połowy innych modułów bo z ich perspektywy funkcje wysokopoziomowe zostają te same.

Osiągnięcie czegoś takiego jest trudne i często wymaga dostosowywania się na bieżąco wraz z wzrostem naszej wiedzy o problemie - dlatego nie bój się wprowadzać zmian.

 

3. W przypadku gdy chcemy odłączyć jakiś podzespół, załóżmy jakiś czujnik temperatury dla przykładu , to lepiej tworzyć jakąś flagę czy lepszym wyborem będą dyrektywy procesora.

Chodzi Ci o nie wywoływanie jakiejś części kodu? Tutaj opcji jest więcej - możesz też na przykład korzystać z usuwania niewykorzystywanych funkcji przez linker. Ale generalnie - musisz sobie odpowiedzieć na pytanie, czy chcesz na sztywno odłączać podczas kompilacji, czy dynamicznie zmieniać w trakcie działania programu. W drugim przypadku musisz bazować na konfiguracji jakimiś zmiennymi. Pozostaje natomiast kwestia obudowania tego czytelnymi interfejsami.

 

4. Gdy wyodrębniamy jakiś element kodu do zewnętrznej funkcji, warto by operował na wskaźnikach czy normalnie przyjmował kopie danych i zwracał daną? Wiem, że popadanie w optymalizacyjną paranoje nie ma czasem sensu, ale być może ktoś bardziej doświadczony jest w stanie odpowiedzieć co warto stosować, a co jest przesadą.

Jeżeli przekazujesz argumenty przez kopię i funkcja nie ma wewnętrznego stanu zachowanego między wywołaniami - możesz ją bezpiecznie używać przy wielowątkowości. To na pewno duży plus. Kolejna kwestia ze wskaźnikami jest taka, że masz całą gamę dodatkowych błędów, które możesz przypadkiem popełnić - nadpisanie nie swoich danych, przekazanie wskaźnika w kilka miejsc na raz, nadpisywanie wskaźnika wykorzystywanego właśnie przez hardware, nullpointery, unaligned access itd. Dlatego tak jak mówił @Matthew11 - przez wskaźnik przekazujesz duże obiekty jak tablice, czy struktury. A przy pojedynczych zmiennych lepiej dawać argument przez wartość, a modyfikację zwracać jako wynik.

 

Jako referencyjny projekt polecam kod PW-Sat2 - satelity rozwijanego przez studentów Politechniki Warszawskiej i pisanego w C++:

https://pw-sat.pl/

https://github.com/PW-Sat2/PWSat2OBC

 

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

Nawiązując do interfejsów, o których wspomniał @GAndaLF. W C++ nie mamy zapewnionego przez standard konceptu interfejsu jak w innych językach (np. Java), ale możemy taki stworzyć stosując klasy abstrakcyjne (te, których obiektów nie możemy stworzyć), nawiązując do przykładu sterowania silnikiem przedstawionego przez @GAndaLF możemy napisać coś takiego:

struct IMotorDriver
{
    virtual ~IMotorDriver() = default;
    
    virtual void moveForward(int speed) = 0;
    virtual void stop() = 0;
};

Taki interfejs reprezentuje naszą abstrakcję jaką jest nasz sterownik silnika (bez szczegółów implementacji). Nasz docelowy sterownik musi ten interfejs zaimplementować w jakiś sposób (to już zawiera pełno szczegółów implementacyjnych) nadpisując metody interfejsu (override lub final). Więc robimy coś takiego:

class MotorDriver : public IMotorDriver 
{
    // ...
    void stop() final
    {
        speed = 0;
        setPwm();
    }
    // ...
};

Następnie w kodzie odwołujemy się do konkretnego obiektu naszego sterownika za pośrednictwem referencji lub wskaźnika na typ IMotorDriver, np. jeśli przekazujemy go gdzieś dalej do naszej logiki, np:

void someLogic(IMotorDriver& _motorDriver)
{
    _motorDriver.moveForward(100);
    _motorDriver.stop();
}

Dzięki temu, jeśli założenia projektowe ulegną zmianie i konieczne będzie sterowanie silnikiem innego rodzaju lub cokolwiek innego, zmiany dokonujesz jedynie przez stworzenie nowej klasy np. MotorDriverA. A pozostała część Twojego programu nie będzie wymagała żadnych modyfikacji z Twojej strony, poza miejscem, w którym tworzysz obiektu sterownika:

// MotorDriver motorDriver;
MotorDriverA motorDriver;

Pełny przykład z wykorzystaniem interfejsów: https://godbolt.org/z/dhGTd4

Natomiast należy pamiętać, że takie rozwiązanie niesie za sobą pewny narzut związany z koniecznością utworzenia vtable - ale możliwe jest zastosowanie optymalizacji

Edytowano przez Matthew11
  • Lubię! 1
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Bardzo mnie cieszy, że poświęciliście na to czas i się wypowiedzieliście na temat i dziękuje Wam za to serdecznie @Matthew11 i @GAndaLF. 🙂
Dzięki za rady i polecone materiały, choć część już za mną, między innymi książki Uncle Boba i blog GAndaLFa w dużej części. Nie da się go ominąć w polskim internecie szukając w tematach systemów wbudowanych. 😄 I całe szczęście, że taki blog istnieje! 🙂


Odnosząc się trochę do tego co napisaliście, a głównie do tego gdzie temat nie jest dla mnie wyczerpany: 

Dnia 27.04.2020 o 23:32, Matthew11 napisał:

4000 linii kodu to nie jest wcale dużo

W takim razie pytanie brzmi: ile to jest dużo przy jakimś typowym mikrokontrolerze z Cortex-M4? Nie wnikając w to co to jest konkretnie "typowy", chodzi mi o jakieś widełki.

Dnia 27.04.2020 o 23:32, Matthew11 napisał:

Pewnie każdy na swojej drodze nauki programowania spotkał się ze stwierdzeniem że użycie zmiennych globalnych jest złe

Ja oczywiście słyszałem to jakieś 1000 razy. Ale nadal dostaje projekty (też w pracy), przykłady, kursy (też na uczelni) i tak dalej, które korzystają ze zmiennych globalnych. Siłą rzeczy zaczynam się zastanawiać, czy to z lenistwa piszących czy może faktycznie, jest to dobry pomysł w specyficznych przypadkach.

2 godziny temu, GAndaLF napisał:

Rozumiem, że chodzi Ci o podział monolit/mikroserwisy jak w aplikacjach webowych?

Tu chodziło mi o raczej monolit w sensie programu w jednej dużej części: main, pare funkcji i jeden plik. Choć zupełnie nie pomyślałem o tym, że w większym systemie ma sens podział na osobne moduły. Jednak od strony jednego dużego pliku odpowiedział mi Matthew11.

2 godziny temu, GAndaLF napisał:

Generalnie zmienne globalne to zło. Wskaźniki do zmiennych globalnych, czy nawet settery gettery to też zło, tylko bardziej ukryte. Mamy dokładnie te same problemy (współbieżność, modyfikacja z wielu miejsc, problemy z zależnościami).

O ile z RTOSem jestem w stanie sobie wyobrazić jak ogarnąć współbieżność przez jakiś mutex czy semafor to w przypadku programu bez systemu operacyjnego nie wiem jak to ugryźć. Hipotetyczny przykład: dostajemy jakieś dane w przerwaniu, ale potem je w jakiś sposób przetwarzamy już w while(1) - filtrujemy, skaujemy - cokolwiek, zakładam, że jest tych operacji tak dużo, że nie ma sensu to w przerwaniu. Jak uchronić się przed nadpisaniem danych w trakcie operacji na danych? Bufor cykliczny? Kopiowanie zmiennych? To jedyne co mi przychodzi na myśl, czy dobrze myślę?

3 godziny temu, GAndaLF napisał:

3. W przypadku gdy chcemy odłączyć jakiś podzespół, załóżmy jakiś czujnik temperatury dla przykładu , to lepiej tworzyć jakąś flagę czy lepszym wyborem będą dyrektywy procesora.

Chodzi Ci o nie wywoływanie jakiejś części kodu?

Tu raczej chodzi mi o sytuacje, że chcę testować działanie jakiegoś elementu systemu wyłączając wpływ innych modułów na niego odłączając wykonywanie kodu odpowiedzialne za powiązane funkcjonalności. Np.: pomiar temperatury i filtrowanie temperatury albo sterowanie grzałką komory, w której umieszczony jest czujnik.

Pojawiły mi się w trakcie tej krótkiej dyskusji jeszcze dwa pytania, być może jesteście w stanie mi pomóc.

  1. Jak bez systemu operacyjnego mierzyć obciążenie procesora? Widziałem opcje ze zmienną pomocniczą czy miganiem GPIO jak choćby: https://www.embedded.com/how-to-calculate-cpu-utilization/ lub http://shadetail.com/blog/ghetto-cpu-busy-meter/ ale co ma sens? Ja bym podpiął analizator pod GPIO i miałbym ten pomiar w czasie rzeczywistym bez dużego obciążania procesora. 
  2. Optymalizacje. Pakowali mi zawsze do głowy, jeszcze w technikum i nawet na uczelni różne zasady odnośnie optymalizacji: a to przyrównuj do 0 bo szybsze niż inne porównywania, nie rób tyle funkcji, bo dużo skoków, przesunięcia bitowe zamiast dzielenia itd. Potem przeczytałem artykuł GAndaLFa. Rozumiem, że początkujący powinien to olać i jak najszybciej zapomnieć o takich problemach a zająć się nimi gdy będzie źle albo będzie potrzeba większej szybkości. Czy tak? Myślę, że to jest częsty błąd początkujących, że dowiadują się wielu rzeczy na raz i przykładają swoją uwagę do rzeczy niekoniecznie istotnych, bo jeszcze nie mają doświadczenia. Proszę więc o odpowiedź ludzi z doświadczeniem, na co nie warto zwracać uwagi (i nie tylko o optymalizacje chodzi)? 🙂

 

Link do komentarza
Share on other sites

(edytowany)
11 godzin temu, dirrack napisał:

W takim razie pytanie brzmi: ile to jest dużo przy jakimś typowym mikrokontrolerze z Cortex-M4? Nie wnikając w to co to jest konkretnie "typowy", chodzi mi o jakieś widełki.

Odpowiedź na to pytanie jak zwykle brzmi - to zależy. Ale możemy posłużyć się wspomnianym projektem PW-Sat2 OBC i dokonując jego analizy za pomocą narzędzia cloc. Co prawda używają innego rodzaju mikrokontoler, ale... gdy uruchomimy go dla głównego katalogu dostaniemy:

$ cloc .

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
C/C++ Header                   585          19137          50052         148464
C++                            452          10969           2407          47387
C                               81           8236          18267          34174
Python                         250           3854            715          10390
CMake                          163            824             17           3235
Markdown                        14            194              0            478
Assembly                         1             28            102            208
Mustache                         1              1              0             63
-------------------------------------------------------------------------------
SUM:                          1547          43243          71560         244399
-------------------------------------------------------------------------------

Prawie ćwierć miliona linii, jak pokopiemy dalej możemy stwierdzić, że istotne foldery to /src i /libs/drivers. Pomijamy katalog /libs/external czyli zewnętrze biblioteki używane w projekcie wtedy:

$ cloc libs/drivers/ src/

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
C++                             91           1902            217           9428
C/C++ Header                    81           1503           4259           4240
CMake                           28            122              0            556
-------------------------------------------------------------------------------
SUM:                           200           3527           4476          14224
-------------------------------------------------------------------------------

Prawie 14 tysięcy linii głównego kodu. Jak policzymy ile linii przypada średnio na jeden plik to mamy ~80 linii kodu na plik pomijając puste linie i komentarze. Łatwiej przejrzeć 80 linii kodu niż 4000 w jednym pliku.

Natomiast liczba linii kodu to żadna miara jakości projektu, jedne są duże drugie małe, jednak istotne jest to, żeby taki projekt dało się łatwo czytać i łatwo modyfikować niezależnie ile linii kodu na niego się składa.

11 godzin temu, dirrack napisał:

Ale nadal dostaje projekty (też w pracy), przykłady, kursy (też na uczelni) i tak dalej, które korzystają ze zmiennych globalnych. Siłą rzeczy zaczynam się zastanawiać, czy to z lenistwa piszących czy może faktycznie, jest to dobry pomysł w specyficznych przypadkach.

O ile te zmienne globalne nie są współdzielone między wątkami to jeszcze nie ma tragedii. Gdy wchodzą wątki i jednoczesny dostęp do danych gdzie jeden z nich jest operacją zapisu wtedy niestety mamy do czynienia z undefined behaviour. Kopiąc dalej, gdy mamy do czynienia z procesorami, które posiadają pamieć cache np. seria K66 NXP - użycie zmiennych globalnych w niektórych przypadkach może zwolnić nasz system - polecam obejrzeć prezentację Scott Meyers: Cpu Caches and Why You Care

11 godzin temu, dirrack napisał:

Hipotetyczny przykład: dostajemy jakieś dane w przerwaniu, ale potem je w jakiś sposób przetwarzamy już w while(1) - filtrujemy, skaujemy - cokolwiek, zakładam, że jest tych operacji tak dużo, że nie ma sensu to w przerwaniu. Jak uchronić się przed nadpisaniem danych w trakcie operacji na danych? Bufor cykliczny? Kopiowanie zmiennych?

W zasadzie to sam sobie odpowiedziałeś, możesz do tego jeszcze zaprzęgnąć DMA. Generalnie w takich przypadkach musisz zrobić testy i dobrać rozmiary buforów do konkretnych warunków pracy. Nie ma jednego generycznego rozwiązania, które będzie działać w każdym przypadku.

11 godzin temu, dirrack napisał:

Tu raczej chodzi mi o sytuacje, że chcę testować działanie jakiegoś elementu systemu wyłączając wpływ innych modułów na niego odłączając wykonywanie kodu odpowiedzialne za powiązane funkcjonalności. Np.: pomiar temperatury i filtrowanie temperatury albo sterowanie grzałką komory, w której umieszczony jest czujnik.

Dwie najprostsze opcje to jak już zostało wspomniane - preprocesor i flagi. W C++17 masz if constexpr - ma zalety obu opcji i brak ich wad. Jeszcze innych podejściem może być przeniesienie testowania na PC i mockowanie modułów, które chcesz testować. Mockowanie peryferiów MCU może być trudne, ale z reguły znasz wartości wejściowe/wyjściowe, więc możesz stworzyć testy i wspomniany pomiar temperatury i jej filtrowanie - czyli Twój kod - przetestować bez użycia sprzętu. I właśnie tutaj wychodzi czy Twój kod jest modułowy. Takie podejście wymaga oczywiście dużo więcej pracy, ale gdy coś nie działa, a Ty wiesz, że Twój kod do obsługi czegoś działa w warunkach testowych, to wtedy wiesz że szukasz problemu w konfiguracji sprzętu, jego działaniu itp. Innymi słowy zmniejszasz sobie zakres poszukiwań.

11 godzin temu, dirrack napisał:

Jak bez systemu operacyjnego mierzyć obciążenie procesora? Widziałem opcje ze zmienną pomocniczą czy miganiem GPIO jak choćby: https://www.embedded.com/how-to-calculate-cpu-utilization/ lub http://shadetail.com/blog/ghetto-cpu-busy-meter/ ale co ma sens?

Tak użycie GPIO, to dość częsta praktyka i jej narzut jest znikomy, a na pewno policzalny. Niektóry procesory mają rejestry zliczające cykle zegara - więc jest to jakaś alternatywa. 

11 godzin temu, dirrack napisał:

Proszę więc o odpowiedź ludzi z doświadczeniem, na co nie warto zwracać uwagi (i nie tylko o optymalizacje chodzi)?

Lepiej jest odpowiedzieć na pytanie "na co ZWRACAĆ uwagę?" - tutaj z pomocą przychodzą wszystkie dobre praktyki dla danego języka, nie wiem ile jest materiałów dotyczących języka C, ale za to C++ ma masę różnych list np: Core Guidelines, są też różnego rodzaju standardy jak np. MISRA C/C++ gdzie oprócz konkretnej reguły znajdziesz też wyjaśnienia z przykładami i to jest bardzo konkretna i rzetelna wiedza. Z dostępnych publicznie (dla C++) to np. JSF czy AUTOSAR. Znając dobre praktyki jednocześnie wiesz czego masz unikać. 

Edytowano przez Matthew11
  • Lubię! 1
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.