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

5 wskazówek dotyczących pisania konfigurowalnego oprogramowania układowego

ghost666 04 Jun 2023 10:18 1554 12
  • Jedną ze strategii szybkiego wdrażania nowych produktów wbudowanych na rynek jest wykorzystanie zunifikowanych platform. Mając plan działania identyfikujący rozwiązania, które zostaną wpuszczone do obiegu, np. w ciągu najbliższych kilku lat, można z powodzeniem stworzyć ww. ujęcie. Ponieważ różni klienci chcą nieco innych funkcji, konfiguracji i dostosowań, opracowywanie pojedynczych produktów jest niepraktyczne. Zamiast tego, jeśli postawi się na opcję z wieloma rozwiązaniami ze wspólnym oprogramowaniem podstawowym, które można rozszerzyć i rekonfigurować, uda się radykalnie obniżyć koszty inżynieryjne. A także skrócić czas przygotowywania nowych produktów. Przyjrzyjmy się w poniższym tekście pięciu sugestiom dotyczącym pisania konfigurowalnego oprogramowania układowego, które diametralnie poprawią jego jakość.

    Wskazówka nr 1 — zacznij od podejścia odgórnego

    Zespoły zajmujące się tym zagadnieniem często mają trudności z przyszykowaniem elastycznego oprogramowania, ponieważ myślą o tym ujęciu od podstaw. Podejście do tego od strony sprzętu stawia tenże, a nie aplikację lub użytkownika w centrum konceptu. Rezultatem jest często ściśle powiązany kod, który ma niewielką lub żadną konfigurowalność. To jest dokładnie to, czego powinniśmy unikać, aby stworzyć elastyczne oprogramowanie systemu wbudowanego.

    Inna postawa w tym zakresie może zmienić wszystko. Deweloperzy będą zmuszeni do zastanowienia się, w jaki sposób ich klienci mają zamiar korzystać z produktu. Bardzo często spojrzenie na rozwiązanie oczami różnych jednostek pozwala natychmiast określić, gdzie konieczna jest konfigurowalność. Jeden klient może potrzebować ustawienia prędkości silnika A, podczas gdy inny szybkości silnika B. W przypadku utknięcia jednostki napędowej, jedna osoba może chcieć, aby dioda LED świeciła światłem ciągłym, podczas gdy druga, aby migała z częstotliwością 5 Hz.

    Rozpoczynając od góry, od klienta (lub klientów) i przechodząc do sprzętu, można poprawić konfigurowalność tworzonego oprogramowania. Kluczowe jest jednak zorientowanie na użytkownika końcowego.

    Wskazówka nr 2 — wykorzystaj pliki konfiguracyjne

    Jeśli spojrzymy poza branżę, przekonamy się, że programiści używają na ogół plików konfiguracyjnych do dostosowania swojego oprogramowania do konkretnej aplikacji. Te mogą pomóc w dyktowaniu tego, jak ma się zachowywać tworzone rozwiązanie w odmiennych wariantach w zależności od ustawień. Oczywiście plik konfiguracyjny zapewniać może różne poziomy dostosowania oprogramowania. Poprawia to możliwości ponownego wykorzystania aplikacji i może obniżyć ogólne koszty wdrażania systemu.

    Twórcy oprogramowania układowego mogą umieszczać niektóre parametry konfiguracyjne w pamięci nieulotnej, aby umożliwić dostosowania, jednak te wpływają tylko na zachowanie systemu w czasie pracy. A co z konfiguracją, która całkowicie zmienia funkcjonowanie podstawowego kodu? Chodzi tutaj o możliwość ustawienia logiki oprogramowania wbudowanego.

    Interesującą techniką, często używaną do poprawy elastyczności, jest wykorzystanie plików konfiguracyjnych, które automatycznie formułują kod programu. Na przykład można spożytkować napisany w Pythonie zestaw dodatkowych narzędzi odczytujący plik YAML z informacjami o konfiguracji wątków i generujący kod C/C++, który jest kompilowany dla aplikacji na mikrokontrolerze. Jeśli oprogramowanie dla danego klienta wymaga niestandardowych funkcji, nowy wątek może zostać dodany do konfiguracji i automatycznie włączony do kompilacji, a sama jego logika tylko opisana ręcznie. Pomysł ten można wykorzystać do ustawienia również wszystkiego poza wątkami i zastosować dany schemat do informacji specyficznych dla produktu, takich jak liczba silników, przekaźników, obecny lub nieobecny sprzęt i tak dalej.

    5 wskazówek dotyczących pisania konfigurowalnego oprogramowania układowego
    Rys.1. Przykładowa architektura korzystania z konfiguracji i szablonów wprowadzonych do generatora, który przygotowuje kod źródłowy na podstawie ustawień.


    Jeśli rozejrzeć się po ekosystemach dedykowanych do tworzenia oprogramowania dla mikrokontrolerów, zauważymy, że większość z nich posiada wbudowaną tego rodzaju funkcjonalność, przynajmniej do pewnego stopnia. Czym innym jest np. automatyczna inicjalizacja stosu, sterty czy nawet wybranych peryferii w układzie lub konfiguracja trybów i opcji wejść/wyjść (np. w taki sposób, jak robi to STM32CubeMX dla układów STM32 czy PsoC Creator dla rodziny jednostek PSoC). Środowiska wyposażone w te funkcje często pozwalają na ustawienia z poziomu interfejsu graficznego. Zapewnia to bardzo wygodny i szybki sposób konfiguracji.

    Wskazówka nr 3 — twórz tabele konfiguracji w swoim kodzie

    Techniką, która najczęściej używana jest przez większość programistów do pisania elastycznego kodu, jest tworzenie i stosowanie tabel konfiguracyjnych. Te ostatnie są zwykle definiowane jako tablica lub struktury. Na przykład, jeśli chcemy utworzyć tabelę konfiguracji dla cyfrowego urządzenia peryferyjnego wejścia/wyjścia, można zdeterminować strukturę, która wygląda podobnie do następującego kodu (w zależności od obsługiwanych funkcji):

    Code: c
    Log in, to see the code


    Powyższe ujęcie definiuje charakterystykę pinu Dio, która przeważnie byłaby używana do jego inicjalizacji. Następnie można utworzyć tablicę struktury z informacjami konfiguracyjnymi dla każdego pinu Dio w systemie, co będzie wyglądało mniej więcej tak:

    Code: c
    Log in, to see the code


    Wszyscy powinni stosować tego rodzaju rozwiązania z kilku powodów:

    * Są czytelne dla człowieka, co świetnie sprawdza się przy przeglądach kodu;
    * Mogą być generowane przez skrypt czytający plik YAML, JSON itp.;
    * W razie potrzeby mogą być edytowane ręcznie przez człowieka;
    * Kod inicjujący jest uproszczony przez zapętlenie po tablicy;
    * Tabele są przenośne, wielokrotnego użytku i skalowalne;
    * Mogą być stosowane do konfiguracji urządzeń, jak i logiki i parametrów oprogramowania.

    Wskazówka nr 4 — wykorzystuj warstwy abstrakcji

    Jednym z problemów, które można zauważyć w przypadku wielu przykładów oprogramowania układowego, jest to, że jest ono ściśle skorelowane ze sprzętem. Często wiąże się kod swojej aplikacji bezpośrednio z urządzeniem, co utrudnia przenoszenie i ponowne używanie oprogramowania wbudowanego. Jak wielu z nas miało okazję nauczyć się podczas obecnego niedoboru chipów, że jeśli trzeba znów przygotować rozwiązanie do współdziałania z nowym układem, powrót i dalsze wykorzystanie całego kodu może być koszmarem. Aby tego uniknąć, koniecznie trzeba mieć możliwość łatwego skonfigurowania aplikacji do pracy z dowolnym sprzętem.

    Jedną z opcji dla rozwikłania tego problemu jest użycie warstwy abstrakcji. Ta tworzy interfejs dostępu do urządzenia, którego muszą przestrzegać sterowniki. Kod aplikacji wywołuje funkcje na poziomie tej abstrakcji. Pomysł ten jest znany jako zasada odwrócenia zależności. Ta jest częścią reguł SOLID dotyczących projektowania obiektowego. Używając interfejsu między sprzętem a kodem aplikacji, odwraca się dependencję, aby przełamać zależność urządzenie/rozwiązanie. A więc sprawić, aby oprogramowanie i sprzęt były bardziej konfigurowalne oraz od siebie niezależne.

    Wskazówka nr 5 — polimorfizm jest twoim przyjacielem

    Jeśli jesteś programistą C, możesz pomyśleć, że polimorfizm to słowo na coś złego lub coś, co nie istnieje w tym języku. W obu przypadkach nie miałbyś racji.

    Polimorfizm to potężna technika, która pozwala konfigurować zachowanie danej aplikacji w czasie kompilacji (polimorfizm statyczny) lub w trakcie wykonywania (polimorfizm dynamiczny). Metoda ta umożliwia jednemu interfejsowi reprezentować różne typy obiektów. Załóżmy na przykład, że oprogramowujemy produkt, który miga diodą LED. Ta ostatnia może być podłączona do sprzętu przez port Dio, do kanału PWM, do zewnętrznego ekspandera wejść/wyjść dołączonego do SPI lub I²C i tak dalej. W zależności od konkretnej wersji platformy sprzętowej lub wymagań klienta może ulec zmianie sposób jej podpięcia. Chociaż wydaje się to irytującym problemem konfiguracyjnym, programista może użyć polimorfizmu do napisania kodu aplikacji, który nie dba o sposób podłączenia. Technika ta może poprawić konfigurowalność, a ponadto możliwość ponownego użycia kodu i jego elastyczność.

    Podsumowanie

    Dzisiejsze produkty wbudowane nie są już jednorazowymi projektami, które są wytwarzane przez wiele lat. Innowacje i zmiany w technologii mają charakter wykładniczy, a zespoły muszą opracowywać kod dla platformy, który będzie można ponownie wykorzystać do wprowadzenia na rynek sporej liczby rozwiązań w nadchodzących latach. Aby sprostać tej potrzebie, trzeba uwzględnić konfigurowalność oprogramowania układowego. W niektórych przypadkach nacisk na ww. elastyczność zwiększy złożoność i koszty początkowe. A być może nawet będzie to mniej efektywne z punktu widzenia pamięci i wydajności. Jednak konfigurowalność zapewni firmom opracowującym produkty wbudowane uniwersalność i skalowalność, aby sprostać przyszłym wyzwaniom klientów.

    Źródło: https://www.embedded.com/5-tips-for-writing-configurable-firmware/

    Cool? Ranking DIY
    About Author
    ghost666
    Translator, editor
    Offline 
    Fizyk z wykształcenia. Po zrobieniu doktoratu i dwóch latach pracy na uczelni, przeszedł do sektora prywatnego, gdzie zajmuje się projektowaniem urządzeń elektronicznych i programowaniem. Od 2003 roku na forum Elektroda.pl, od 2008 roku członek zespołu redakcyjnego.
    ghost666 wrote 11809 posts with rating 9944, helped 157 times. Live in city Warszawa. Been with us since 2003 year.
  • #2
    khoam
    Level 42  
    Quote:
    Polimorfizm to potężna technika, która pozwala konfigurować zachowanie danej aplikacji w czasie kompilacji (polimorfizm statyczny) lub w trakcie wykonywania (polimorfizm dynamiczny).

    Pozwolę się nie zgodzić z tym stwierdzeniem i uważam, że polimorfizmu dynamicznego należy unikać w embedded, ze względu na niepotrzebne i nieco nieprzewidywalne zużycie pamięci dynamicznej (heap). Polimorfizm w C++ zawsze można zastąpić odpowiednio zaprojektowanymi klasami szablonowymi ze specjalizacjami.

    Dodano po 5 [minuty]:

    Quote:
    Jeśli jesteś programistą C, możesz pomyśleć, że polimorfizm to słowo na coś złego lub coś, co nie istnieje w tym języku.

    Można symulować polimorfizm w C, ale to taka zabawa w symulowanie C++, a przede wszystkim konstruktorów, destruktorów oraz VMT.
  • #4
    ghost666
    Translator, editor
    khoam wrote:
    Quote:
    Jeśli jesteś programistą C, możesz pomyśleć, że polimorfizm to słowo na coś złego lub coś, co nie istnieje w tym języku.

    Można symulować polimorfizm w C, ale to taka zabawa w symulowanie C++, a przede wszystkim konstruktorów, destruktorów oraz VMT.


    Moim zdaniem nawet trzeba część tego przenosić do C przy kodzie, który używany ma być w kilku aplikacjach. Oczywiście tutaj statyczne zabawy dyrektywami kompilatora są najlepszym rozwiązaniem, ale pewnie i dynamicznie można...
  • #5
    karol75
    Level 16  
    Tylko czy te zabawki i "nadmierna" konfigurowalność nie zaciemni kodu i przyniesie więcej złego niż dobrego?
    Jeżeli zrobimy taki kod na 10 różnych procesorów czy nie wybijemy sobie zębów wracając do projektu już po kilku miesiącach?
  • #6
    ghost666
    Translator, editor
    karol75 wrote:
    Tylko czy te zabawki i "nadmierna" konfigurowalność nie zaciemni kodu i przyniesie więcej złego niż dobrego?
    Jeżeli zrobimy taki kod na 10 różnych procesorów czy nie wybijemy sobie zębów wracając do projektu już po kilku miesiącach?


    Jeśli zrobimy to dobrze, to nie.
  • #7
    karol75
    Level 16  
    Ok. Robię sam pod siebie.
    Ale jak myślisz, czy przeciętny programista Systemów wbudowanych zrozumie rozbudowany system konfiguracyjny?
    Dołóż do tego tonę dokumentacji i mamy problem z długością wdrożenia w projekt.


    Normalne aplikacje (na pc) to konieczność z powodu złożoności systemu.
    Ale tutaj gdy cały hex ma kilkaset kilo to może być przerost formy nad treścią.

    Ostatnio pracuję nad usb w HAL (STM32) i przypuszczam że będę miał poważne problemy aby dodać do niego klasę Printer (wiele nocy nieprzespanych - aby zrozumieć cały framework).
    I do tego dorzućmy jeszcze konfigurowalność, nie daj bóg kompilację warunkową.
  • #8
    zigipl
    Level 13  
    W takim razie jakie rozwiązanie proponujesz jeśli pojawia się potrzeba wprowadzania różnych ustawień?
  • #9
    karol75
    Level 16  
    Ja preferuję podejście indywidualne do każdego projektu + pliki nagłówkowe i kod obsługi urządzenia zewnętrznego, np. wyświetlacz HD44780, grafika, czcionki itd.

    Wolę projekty proste.
    Moim zdaniem przenośność kodu między różnymi procesorami to ułuda, tak jak w przypadku JAVY lub JavaScript.
  • #10
    khoam
    Level 42  
    karol75 wrote:
    Moim zdaniem przenośność kodu między różnymi procesorami to ułuda

    Całkowicie się z tym nie zgadzam. Faktem jest natomiast, że jest to trudniejsze, ale za to oszczędza (bardzo) wiele czasu przy portowaniu na nowy MCU. Piszę to z perspektywy programowania w C++.
  • #11
    karol75
    Level 16  
    khoam wrote:
    Faktem jest natomiast, że jest to trudniejsze, ale za to oszczędza (bardzo) wiele czasu przy portowaniu na nowy MCU. Piszę to z perspektywy programowania w C++.


    Przerost formy nad treścią.
    Załóżmy dwa procesory

    ATMEGA8 i Xmega
    Od biedy się da tylko po co?

    Atmega8 i STM32F103 lub któryś Renesans.

    Inna architektura, inne środowisko itd., Da się tylko po co?

    Jest to może przydatne w przypadku jednej rodziny, ale w przypadku różnych środowisk, nie widzę sensu.
    Druga sprawa ile trzeba włożyć pracy w napisanie klas?
    Jak często zmieniasz procesor dla projektu?
    Robić framework nad frameworkiem IDE bez sensu, moim zdaniem.

    Klasy funkcjonalne robiące komunikację, obsługę urządzeń zewnętrznych, tak tutaj widzę sens.
    Dla wewnętrznych w procesorze nie widzę sensu. Zwłaszcza w dobie środowisk takich jak STM32Cube, w którym większość się wyklikuje.

    Oczywiście można tworzyć klasy zgodnie z wzorcem strategia kod dla każdego procesora, albo używać interfejsów, albo obserwator itd.
    Tylko, który programista systemów embeded łatwo się w to wdroży?

    Pamiętajmy że to zżera cykle procesora.
  • #12
    khoam
    Level 42  
    karol75 wrote:
    Tylko, który programista systemów embedded łatwo się w to wdroży?

    To jest sedno problemu. Większość programistów embedded nie zna C++ albo im się wydaje, że zna i dlatego nie używa.

    Dla procesorów 32-bitowych można projektować programy, które będą łatwo przenoszone na kolejne MCU i będą napisane w C. Jest wiele przykładów Open Source w necie, np. FreeRTOS. Natomiast używane IDE nie ma nic tu do rzeczy i może stanowić jedynie ograniczenie, jeżeli ktoś zbyt polega na jednym konkretnym, przypisanym do danej rodziny MCU. Niestety to też jest dość powszechna praktyka klikania - "klikaczy" w pierwszej kolejności zastąpią narzędzia wykorzystujące tzw. AI.

    W wypadku procesorów 8-bitowych mogę się z Tobą zgodzić. Jest zbyt wiele ograniczeń sprzętowych i programowych (toolchain) specyficznych dla danego MCU, aby projektowanie takich przenoszalnych programów miało sens.

    karol75 wrote:
    Pamiętajmy, że to zżera cykle procesora.

    karol75 wrote:
    Przerost formy nad treścią.
  • #13
    karol75
    Level 16  
    khoam wrote:

    To jest sedno problemu. Większość programistów embedded nie zna C++ albo im się wydaje, że zna i dlatego nie używa.


    Jest wielka zaleta C++ nad C powodująca enkapsulację danych i hermetyzację kodu.
    Fakt, nie tworzę "wielkich" systemów embeded, może dlatego nie widzę wielkich korzyści w konfiguralności.
    Z drugiej strony FreeRTOS to dla mnie taki oddzielny moduł jak obsługa wyświetlacza HD44780 ;-D On pracuje w warstwie wyżej niż konfiguracja portów.