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

5 kroków do zaprojektowania wbudowanej architektury oprogramowania - część 4

ghost666 29 Dec 2022 08:29 867 1
  • W ostatnich trzech artykułach omówiono pięć kroków wymaganych do zaprojektowania wbudowanej architektury oprogramowania. Do tej pory dzielono ją na zależną i niezależną od sprzętu. Ponadto zbadano, w jaki sposób wczesna identyfikacja zestawów danych może pomóc poprawić bezpieczeństwo urządzeń i pomóc w naturalnej dekompozycji.

    Pięć kroków projektowania, które prezentowane są w tej serii, obejmuje:

    1. Oddziel ją;
    2. Zidentyfikuj i śledź zestawy danych;
    3. Rozłóż system na czynniki pierwsze;
    4. Zaprojektuj interfejs i komponenty;
    5. Symuluj, iteruj i skaluj.

    W tym artykule przedstawiono czwarty etap tworzenia wbudowanej architektury oprogramowania: projektowanie interfejsu i komponentów.

    Przypomnijmy, na szybko, poprzednie ujęcia. We wcześniejszym artykule przyjrzeliśmy się, jak rozłożyć system, którego zestawy danych zostały zidentyfikowane, na domeny i zadania. Uprzedni diagram, który został stworzony podzielił aplikację na bezpieczne i niezabezpieczone domeny, niezależne i zależne od sprzętu (oddzielone warstwą abstrakcji), a następnie na operacje. Dekompozycja systemu wyglądała jak na rysunku 1.

    5 kroków do zaprojektowania wbudowanej architektury oprogramowania - część 4
    Rys.1. Przykładowy rozkład zadań wbudowanego systemu sterowania silnikiem.


    Ze względu na ograniczenia przestrzenne i czasowe związane z formatem tego artykułu przemilczymy detale, że istnieją dodatkowe kroki w procesie architektury. Te opisują, jak sprawić, by operacje wchodziły w interakcje. Chcemy jednakże zbadać, w jaki sposób możemy podjąć jedno z zadań i zaprojektować interfejsy oraz komponenty. Przyjrzyjmy się, jak możemy zdefiniować oba wskazane dla operacji sterowania silnikiem w omówionym systemie.

    Krok #4 – projekt interfejsu i komponentów

    Tworzenie komponentów i interfejsów to czynności ściśle ze sobą powiązane. W rzeczywistości, jeśli spojrzymy na standardową definicję tego pierwszego znajdziemy następujący opis: „Komponent oprogramowania to jednostka składu z interfejsami określonymi w wymaganiach i wyraźnymi zależnościami kontekstowymi”. Nie można nawet zdefiniować tego ujęcia bez omawiania interfejsów! Te ostatnie są tym, czego programiści używają do interakcji z danym komponentem. Dlatego sensowne jest, abyśmy zaczęli rozkładać nasze zadanie motoryczne na komponenty, a następnie definiować interfejsy dla każdego z nich.

    Definiowanie składników oprogramowania

    Twórcy oprogramowania wbudowanego mogą korzystać z wielu różnych metod i technik, aby zidentyfikować komponenty, które będą układać się razem w celu realizacji pożądanego zachowania zapewnianego przez zadanie. W prawie każdej operacji preferowane jest rozbicie systemu na warstwowy diagram oprogramowania, który zaczyna się od sterowników niskiego poziomu i prowadzi aż do kodu aplikacji. Rysunek 2 pokazuje przykład tego, jak taki diagram mógłby wyglądać dla zadania motorycznego.

    5 kroków do zaprojektowania wbudowanej architektury oprogramowania - część 4
    Rys.2. Przykładowe ułożenie komponentów w strukturze oprogramowania.


    Cel każdego z tych ujęć jest prawdopodobnie oczywisty, ale na wszelki wypadek pozwólmy sobie zdefiniować, co robi każde z nich:

    * pwm_drv — sprzętowy sterownik peryferyjny do modulacji szerokości impulsu dostępny w mikrokontrolerze;
    * Warstwa abstrakcji — zrywa zależność między sterownikiem silnika a sprzętem. Dzięki temu można użyć sterownika PWM do napędzania silnika lub zastąpić pwm_drv sterownikiem zewnętrznego układu scalonego;
    * motor_drv — sterownik przeznaczony do sterowania silnikiem. Nie ma zależności sprzętowych. Jedyną jest warstwa abstrakcji;
    * motor_sm — maszyna stanów, która śledzi aktualny status silnika oraz pożądany;
    * motor_app — funkcje wsparcia specyficzne dla aplikacji. Może to być zbieranie danych telemetrycznych, sprawdzanie błędów itp.;
    * motor_task — komponent przechowujący aktualny kod zadania motorycznego. Jest on zależny od innych komponentów silnika. Zadanie motoryczne może obejmować interakcje aplikacji z RTOS, takie jak semafory, kolejki, muteksy i logikę zdarzeń.

    Razem te ujęcia mają wszystkie niezbędne zachowania, aby otrzymać polecenie, które przestawia maszynę stanów kontrolującą silnik, a następnie go uruchamia. Ciekawą rzeczą w tej dekompozycji jest to, że wszystko jest samowystarczalne, jeśli zmieniają się aspekty łańcucha sterowania silnikiem, takie jak nowy układ sterownika, wymagania aplikacji itp. Przy odpowiednich interfejsach konieczna jest minimalna modyfikacja innych komponentów.

    Definiowanie interfejsów

    W przypadku zadania sterowania silnikiem istnieją dwie kategorie interfejsów, które musimy zdefiniować. Po pierwsze, mamy taki od zadań, który odbiera polecenia z innych komponentów aplikacji, które mówią mu, co silnik powinien robić, vide MOTOR_ON lub MOTOR_OFF. Po drugie, są też takowe dla każdego komponentu. Przyjrzyjmy się im po kolei.

    Projekt interfejsu zadań motorycznych

    Podczas definiowania interfejsu do komunikacji z zadaniem motorycznym warto spojrzeć wstecz na zestawy danych, które zilustrowaliśmy na rysunku 1. Widzimy, że operacja kontrolera wchodzi w interakcję z motoryczną. Aby to pierwsze powiedziało drugiemu, co powinien robić silnik potrzebnych jest kilka informacji:

    * Stan silnika;
    * Kierunek jego obrotów;
    * Prędkość jego obrotów.

    Można nawet dołączyć do tych danych identyfikator silnika, jeśli chcemy, aby system był skalowalny do zarządzania wieloma układami napędzającymi. Na przykład mogą istnieć aplikacje, w których maszyna stanów steruje silnikiem za pomocą komunikatów, ale użytkownik również może zmienić status przy ich użyciu. W takich sytuacjach można nawet dołączyć identyfikator zadania, aby operacja motoryczna wiedziała, co żąda sterowania silnikiem.

    Z perspektywy interfejsu danych można zdefiniować strukturę zasobów, które będą wykorzystywane do interfejsu zadań sterowania i motoryki. Można zapisać ją od razu w C, co pozwala stworzyć coś takiego:

    Code: c
    Log in, to see the code


    Struktura MotorMessage_t definiuje interfejs danych niezbędny do wydania zadania motorycznego w celu wykonania praktycznej pracy. Sposób, w jaki w systemie przekazywane są te informacje do operacji motorycznej, będzie się różnić w zależności od potrzeb projektu. Na przykład można użyć kolejki komunikatów, bufora danych lub innego mechanizmu. Są to jednak szczegóły, o których decyduje programista, a nie architekt oprogramowania.

    Definiowanie interfejsów komponentów

    Istnieje kilka różnych sposobów zaprojektowania interfejsu dla tych komponentów. Po pierwsze, można wziąć karteczkę samoprzylepną i określić funkcje, które naszym zdaniem są nam potrzebne. Następnie można użyć jakiegoś narzędzia do tworzenia diagramów UML i wykorzystać diagram klas, nawet jeśli nie będziemy pisać kodu zorientowanego obiektowo. Finalnie, można zorganizować jakiś podstawowy kod i opracować testy, które zmuszą developerów do zbudowania interfejsu dla zachowania komponentu. W praktyce programistycznej często używa się kombinacji wszystkich trzech. Projekt zaczyna się od prostych notatek. Te następnie zapisywane są w języku UML, a gdy dokumentacja ta jest już gotowa do przygotowania komponentu stosuje się diagram UML do prowadzenia testów, dalej rozwijając projekt danego interfejsu.

    Diagramy klas są świetne, ponieważ pozwalają określić atrybuty, operacje i wzajemne relacje (moduły i komponenty też mogą tak działać). Przykład pokazano na rysunku 3, który przedstawia wstępny projekt elementów zadania motorycznego. Jest kilka rzeczy, na które trzeba zwrócić uwagę. Po pierwsze, Motor Task korzysta z aplikacji silnika, maszyny jego stanu i sterowników. Nie ma tu relacji dziedziczenia. Widzimy, że sterownik silnika posiada interfejs silnika. Aplikacja silnika używa wyliczenia MotorError_t, ale poza tym komponenty są oddzielone od siebie i nie wchodzą ze sobą w interakcje.

    Następnie definiowane są operacje oraz jakie funkcje lub metody są potrzebne poszczególnym komponentom. Na przykład widać, że sterownik silnika ma dwie funkcje: inicjalizację i sterowanie. Polecenie silnika przyjmuje typ komunikatu silnika ze wszystkimi informacjami do wydawania komendy silnikowi, co odbywa się za pośrednictwem interfejsu silnika (warstwa abstrakcji).

    Wreszcie można zobaczyć dodatki, które obsługują te interfejsy. Pokazane są wyliczenia MotorError_t, używane przez aplikację silnikową. Mówiąc dokładniej jest kilka wyliczeń, które należałoby zdefiniować, ale dla uproszczenia diagramu pominięto je.

    5 kroków do zaprojektowania wbudowanej architektury oprogramowania - część 4
    Rys.3. Przykładowy projekt interfejsu pierwszego przebiegu z wykorzystaniem diagramów klas.


    Kiedy przeanalizujemy diagram klas, można zobaczyć, że zadanie motoryczne wchodzi w interakcję z komponentami niższego poziomu i steruje ostatecznym zachowaniem silnika. Po pierwsze, informacje docierają do zadania motorycznego przez MotorQ. Następnie uruchamia ono maszynę stanu. Na koniec wywołuje ono aplikację silnikową w poszukiwaniu błędów. A także wykorzystuje wynik ze stanem do wydawania poleceń silnikowi przez sterownik. To dużo do wydobycia z diagramu klas, który prawdopodobnie nie jest oczywisty. Czy zatem w naszej architekturze czegoś nie ma?

    Czego brakuje w tym projekcie?

    To, co zaprezentowano powyżej wystarczy, żeby zacząć wdrażać kod aplikacji silnikowej. Jednakże więcej diagramów i czasu może radykalnie poprawić przejrzystość. Na przykład, można rozważyć dodatkowe diagramy UML, takie jak sekwencji, aby pokazać wymagania czasowe dla interakcji zadania sterującego z operacją motoryczną. Można również stworzyć diagram sekwencji, aby programiści zrozumieli, w jaki sposób zadanie motoryczne oddziałuje na inne komponenty. Na przykład, czy powinno ono wydawać polecenie silnikowi na końcu zadania, czy też jest wykonywane jako pierwsze, aby zminimalizować jitter? Jaka jest logika postępowania w przypadku wystąpienia błędu? Czy silnik powinien dalej pracować, czy zostać zatrzymany?

    Na ogół okaże się, że potrzeba co najmniej trzech do czterech diagramów UML, aby w pełni opisać jedno zadanie, tak aby programiści mogli je wykonać i zakodować. Przyjrzeliśmy się tylko kilku. Co oznacza, że ​​jest więcej do zrobienia. Należy dobrze to przyswoić — te elementy opisane powyżej, to już dużo — można zacząć opracowywać testy i wykorzystywać programowanie oparte na nich, aby dalej rozwijać te interfejsy i zrozumienie systemu. To może być droga, którą należy teraz podążać. Jednak wiadomo również wystarczająco sporo, aby zacząć, a gdy wdrażane będą szczegóły pojawią się pytania, które doprowadzą do zmian w początkowej architekturze.

    Projekt architektury oprogramowania krok 4 — wnioski

    Jak widać w powyższym artykule, możemy wykorzystać diagramy UML i diagramy stosów komponentów, aby je zidentyfikować i zdefiniować ich interfejsy. Ponadto diagramy klas mogą być doskonałym narzędziem dla projektantów do tworzenia tych ostatnich. Trzeba koniecznie pamiętać, że chociaż ujęcie projektowe toczy się zgodnie z prostym pięcioetapowym procesem rozwijania architektur, te kroki są tylko pewnym uproszczonym przewodnikiem i nie obejmują całej historii. Architekci oprogramowania często muszą nurkować głębiej i eksplorować peryferia tego, o czym do tej pory tylko wspomniano w ramach tych artykułów, aby wypełnić luki i szczegóły na poszczególnych etapach.

    W następnym materiale omówiony zostanie ostatni krok tworzenia architektury oprogramowania: symulacja, iteracja i skalowanie oprogramowania.

    Źródło: https://www.embedded.com/5-steps-to-designing-an-embedded-software-architecture-step-4/

    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 11498 posts with rating 9731, helped 157 times. Live in city Warszawa. Been with us since 2003 year.
  • #2
    _johnny_
    Level 8  
    Na rysunku 1 i 2 sa na czarno warstwy abstrakcji. Jesli mowimy o rysunku 2 chodzilo o wydzielenie interfejsu dla kazdego z komponentow to oczywiste , jednakze na rysunku 1 mowa jest o jednej wielkiej klasie dla calego projektu obejmujaca mechanike dzialania poszczeglych komponentow(z rys 2)? Czy struktura tego programu jest dalej podobna do np Arduino - linkujemy wszystkie bibloteki na wstepie i obslugujemy w jednym miejscu np w loop?