Gdyby spytać o to generalnie mężczyzn to pewnie każdy z nich powiedziałby, że tak. Problem w tym, że podczas odpowiadania raczej nie myślą oni o mikrokontrolerach… Rozważmy zatem te nasze mikrokontrolery. Czy to dobrze jest mieć dwa jądra, czy lepiej mówiąc – rdzenie?
Artykuł ten powstał na podstawie mojego LIVE #1, a także jest jego uzupełnieniem.
2-rdzeniowe procesory
Wielordzeniowość w procesorach komputerowych znamy już od dawna. Pamiętam jak wchodziły Dual-Core od Intela. Kupując w tamtym okresie nowy komputer sprzedawca w sklepie rozsypywał magiczny pył jakby to była nie wiadomo jaka technologia NASA. Jak to dwa procesory w jednym?! Równo 2 razy szybszy niż dotychczas?! Ciężko było w to uwierzyć.
Dzisiaj raczej nikt nawet nie mrugnie okiem, gdy takie AMD wypuszcza procesor z 32 rdzeniami. Raczej jest to już dla nas norma.
Trochę inaczej jest w świecie mikrokontrolerów. Tutaj to WOW, które miałem będąc sporo młodszym dopiero się zaczyna. Może nie jest to już tak wielkie WOW, ale wciąż jestem pod wrażeniem.
Postęp technologiczny w embedded
Pisząc aplikacje na komputery przyzwyczailiśmy się już, że mają one “nieograniczone” zasoby. Inaczej jest na mikrokontrolerach. Tutaj dotychczas mieliśmy jeden rdzeń i musieliśmy pisać programy tak, aby sensownie żonglowały zasobami i czynnościami.
W ostatnich latach branża embedded zdecydowanie wyszła z migania diodą czy akwizycją danych z czujników i poszła w bardziej skomplikowanym kierunku. Pojawiły się początkowo wyświetlacze. Najpierw proste, monochromatyczne graficzne. Później pierwsze kolorowe, głównie w telefonach. Pamiętam reklamę Siemensa S65, która krzyczała, że jego wyświetlacz ma AŻ 13 cm² powierzchni! Dzisiaj parsknęlibyśmy śmiechem, ale wtedy to już był niezły wyczyn dla małych mikrokontrolerów.
Teraz nauczeni smartfonami chcemy budować nasze drobne urządzenia mikrokontrolerowe również z wyświetlaczami o ogromnej rozdzielczości. Szybko okazuje się, że jakoś te nasze twory nie chodzą tak płynnie jakbyśmy chcieli.
Wszystko przez to, że rysowanie po takich ekranach zajmuje masakryczną ilość czasu i zasobów. Już wyświetlacze o niskiej rozdzielczości, ale sterowane po SPI cały ekran rysują “wieczność”. A przecież potrzebujemy jeszcze gdzieś w koło nadal korzystać z czujników. Jeszcze gorzej jest gdy w czasie rzeczywistym musimy sterować silnikami.
STM32H7
Jednym z ciekawych rozwiązań jest wydzielenie zadań “multimedialnych” od tych “sterujących”. I chyba właśnie do tego zostały stworzone 2-rdzeniowe STM32 z serii H7. Rdzeń główny to Cortex-M7, który ma mnóstwo ficzerów multimedialnych. Drugi to dobrze znany Cortex-M4.
Obydwa rdzenie zamknięte są w jednej obudowie. Peryferia są pojedyncze, ale dostęp do nich mają wszystkie jaja. Można więc decydować, który czym się zajmuje.
Jakiś czas temu na swoim pierwszym LIVE pokazywałem jak rozpocząć zabawę z płytką Nucleo z STM32H745.
Płytkę taką kupisz u mnie w sklepie.
Co taki mikrokontroler ma dokładnie? Nie chcę się rozpisywać, bo można to zobaczyć w sklepie, ale z takich najważniejszych informacji to:
- 480 MHz (M7) i 240 MHz (M4)
- 2 MB Flash, 1 MB SRAM
- Ethernet
- DCMI
- FMC
- SDIO
- Quad SPI
- DAC
- Kontroler TFT, akceleratory graficzne, sprzętowy JPEG
Jest tego oczywiście dużo więcej, ale wymieniłem tylko ciekawsze rzeczy.
Na płytce Nucleo dostajemy oczywiście programator. Tutaj jest ST-Link V3, więc najnowsza konstrukcja. Pozwala on pracować na dwóch rdzeniach. Jak skonfigurować projekt i uruchomić swój pierwszy program?
Tworzenie projektu, konfiguracja peryferiów
Pisanie firmware na taki dwurdzeniowy mikrokontroler jest dosyć specyficzne. Tak naprawdę musisz napisać dwa “niezależne” programy. Każdy ma swój kod inicjujący, swój wektor przerwań, a co za tym idzie własny NVIC. Projekt taki będzie się składał z dwóch podprojektów. Jak to ogarnąć? No na szczęście mamy STM32CubeMX! On zrobi za nas praktycznie większość tego, czego potrzebujemy.
W Cube wystarczy w trakcie tworzenia nowego projektu wybrać nasz mikrokotroler lub jeszcze lepiej dokonac wybory po płytce, jeśli korzystasz z Nucleo. Po utworzeniu projektu pojawi nam się oczywiście widok mikrokontrolera oraz drzewa peryferiów.
Widok jest nam dosyć znany jednak otwierając drzewko po lewej stronie zobaczysz, że jest trochę różnic w porównaniu do jednordzeniowych mikrokontrolerów.
Po pierwsze zobaczysz właśnie dwa NVICi. Po jednym dla każdego rdzenia.
Z innych ciekawych rzeczy przy używaniu Cube dla 2-rdzeniowców jest to, że w ustawieniach peryferiów jest podział na Cortex-M7 i Cortex-M4. Różnie ten podział wygląda. Weźmy najpierw takie GPIO.
Każdy z pinów, który wybierzemy możemy przypisać do jednego z rdzeni lub pozostawić “Free”. Niestety z tego co przetestowałem, to nie możemy przypisać jednego pinu GPIO do użytku przez dwa rdzenie. Pozostawienie pinu jako “Free” jedynie rezerwuje nam go w Cube, ale nie ma później go w kodzie dla żadnego z rdzeni. Nawet inicjalizacji.
Tak wygląda przypisanie poszczególnych diod z Nucleo dla rdzeni.
Zerknijmy jeszcze na przykład do I²C. Wchodząc do ustawień I2C1 pierwsze co widzimy to mała tabelka.
Odczarujmy ją! Zacznę od PowerDomain. To jest informacja, w której domenie zasilania całego MCU znajduje się ten interfejs. Przydatna rzecz jeśli chcemy optymalizować energię.
W Runtime contexts wybieramy to, który rdzeń będzie mógł korzystać z I2C1. Tutaj jest trochę inaczej niż przy GPIO bo możemy zaznaczyć, że zarówno M7 jak i M4 może komunikować się przez ten interfejs. Jeśli zaznaczymy obydwa pojawia się coś nowego!
Pojawił się wybór Initializera. Wybieramy który z rdzeni ma odpowiadać za całą inicjalizację tego interfejsu. Tutaj ważna informacja. Kto inicjuje, ten później obsługuje przerwania. Wybierając więc M7 jako inicjatora, przerwania będą przychodziły na NVIC1.
Reszta ustawień I²C jest w zasadzie taka sama jak w wypadku pojedynczego rdzenia.
Teraz przydatna wskazówka 🙂 Jeśli chcesz na szybko ustalić który rdzeń, z jakiego interfejsu ma skorzystać, kto inicjuje oraz w jakiej domenie napięciowej się znajduje, to są takie dwa fajne przełączniki pod ikonką zębatki. Warto je zaznaczyć. Wtedy ujrzymy taki piękny widok przy drzewku interfejsów.
Szybko można wyklikać rdzenie, inicjalizatora oraz dobrać odpowiednio używane interfejsy domenami jeżeli trzeba. Bajka!
Zegary
Zmorą każdego początkującego programisty STM32 jest ustawienie zegarów. Oczywiście Cube nam to ułatwia i to jeszcze jak! Taki potwór jak STM32H745 intuicyjnie powinien mieć tych ustawień jeszcze więcej niż zwykły jednojajowiec. Zobaczmy.
Jest tego całkiem sporo! Jednak spodziewałem się, że będzie tego więcej 🙂 Zaznaczyłem najważniejsze zegary, czyli taktowanie naszych rdzeni M7 i M4. Jak widać zegary naszych rdzeni są ze sobą powiązane. Dzięki temu drzewo zegarów nie jest tak zagmatwane.
Poniżej głównego zegara jest masa multiplekserów do wyboru taktowania peryferiów. Będziemy się tym martwić tylko wtedy, gdy któregoś będziemy chcieli użyć.
Generowanie kodu
Jak wygląda kod dla dwurdzeniowca? Dla “normalnego” mikrokontrolera generujemy i mamy cały projekt ładnie podzielony na Inc, Src, Drivers, inne rzeczy oraz oczywiście main.c w którym dzieje się wszystko.
Po wygenerowaniu takiego projektu dla H745 mamy de facto dwa projekty podłączone w jeden większy. Dzieje się tak dlatego, że tak naprawdę pisząc na 2 rdzenie piszemy dwa osobne programy! Lądują one w inne miejsce na pamięci Flash.
Taki złożony projekt wygląda następująco.
Co tu widzimy? Mamy jeden duży projekt hierarchiczny o nazwie LIVE1. Agreguje on dwa inne projekty. Jeden LIVE1_CM4, który jest dedykowany rdzeniowi M4 oraz analogicznie dla rdzenia M7 jest LIVE1_CM7.
Każdy z projektów ma swój main.c oraz swój zestaw bibliotek. Budowane są one oddzielnie. Nic z jednego projektu nie przenika do drugiego.
To dlatego, chociażby jeśli wybrałeś GPIO dla jednego rdzenia, nie będziesz miał definicji pinu w drugim.
Początkowo może być ciężko się poruszać po takim projekcie z uwagi właśnie na identyczne nazwy plików. Moja propozycja jest taka, aby podzielić okno edytora IDE na pół i po jednej stronie trzymać pliki tylko dla jednego z rdzeni.
Domyślnie pamięć Flash w mikrokontrolerze podzielona jest na pół dla dwóch rdzeni. Dla każdego z nich zarezerwowane jest 1024K.
Inaczej jest z RAMem. Dla M7 jest 128K RAM i 64K ITCRAM. M4 ma aż 288K RAMu.
Podczas LIVE padło pytanie o to czy można zmienić te wielkości. Oczywiście zmienić. Gdzie? W plikach linkera! W każdym projekcie są pliki z rozszerzeniem *.ld.
W nich można przesunąć na przykład programy w pamięci Flash i sprawdziłem to na żywo. Dzięki temu nie musimy się martwić tym, że sporą część Flasha “zmarnujemy” na mniej obfity kod dla jednego z rdzeni. Proste i skuteczne.
Podsumowanie
Co prawda nie odpowiedziałem jeszcze dobitnie na pytanie z tytułu wpisu, ale można podskórnie już poczuć, że dwa rdzenie mogą być lepsze. Napewno jeśli chodzi o wydajność, bo trudność pisania kodu na takie stworki nam mocno rośnie. Takie dwa projekty naraz mogą nie raz dać popalić!
W kolejnym wpisie pomigam diodami oraz pokażę Ci, jaki jest mini rollercoaster z tym, aby taki program wrzucić do mikrokontrolera i go debugować! Jeśli już nie możesz się doczekać migania diodami na dwa rdzenie to zrobiłem to na LIVE, więc zapraszam Cię również do nagrania.
Jeśli artykuł Ci się spodobał, kup coś u mnie! https://sklep.msalamon.pl/
Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podyskutować na ten temat, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.
7 komentarzy
Paweł · 02/10/2020 o 07:25
Witam
Pragnę wszystkich uczulić, w jakie gniazdo wkładają micro USB przy pierwszym uruchomieniu.- Praktyka wykazuje że można się pomylić, choć drugie gniazo jest typu micro AB (jeszcze pomyślałem “ale koślawe”).
W manualu wyraźnie jest napisane które gniazdo do czego służy, ale kto by tam zgłębiał manuale kiedy dostaje taką przesyłkę.
Po prawidłowym podłączeniu pobieżnie sprawdziłem część wyjść i działanie obu rdzeni. Wszystko wydaje się być w porządku z samym microkontrolerem.
Nie wiem jeszcze, czy Micro AB USB działa. Po pierwszej aplikacji z jego użyciem wyjaśni się czy mam jeszcze sprawną płytkę.
Piotr · 01/10/2020 o 15:50
Super artykuł!
Pytanie odnośnie przypadkowego wykorzystania GPIO jako wyjście zarówno dla M4 jak i M7 – czy Cube nas ostrzega przed taką sytuacją?
Mateusz Salamon · 01/10/2020 o 16:55
Nie ustawisz dla dwóch naraz. Możesz ustawić jako “Free” i wtedy żaden core nie ma go zdefiniowanego 🙂
Maciej · 01/10/2020 o 00:53
Świetnie, że zająłeś się serią H7. Czekam na następne artykuły 🙂
Mateusz Salamon · 01/10/2020 o 10:08
Dzięki! Na pewno się pojawią kolejne, bo to fajny układ 🙂
Marcin · 30/09/2020 o 21:15
W jaki sposób 2 rdzenie komunikują się między sobą?
Mateusz Salamon · 01/10/2020 o 10:07
Póki co poznałem dwa sposoby, które również sprawdziłem na LIVE na YouTube i będę je opisywał w kolejnych artykułach. Pierwszy to semafory sprzętowe. Trochę podobnie jak w RTOS. Drugi to specjalna pamięć RAM dedykowana do współdzielenia między rdzeniami.