Dwurdzeniowe dsPIC33CH w praktyce

Dzięki uprzejmości firmy Microchip stałem się posiadaczem zestawu dsPI33CH Curiosity Development Board (rysunek 1). Jest to testowa platforma mająca na pokładzie dwurdzeniowy mikrokontroler dsPIC33CH128MP508. Wielordzeniowe mikroprocesory nie są czymś niezwykłym, ale w przypadku mikrokontrolerów takie rozwiązania dopiero zaczynają się pojawiać.

Rysunek 1. Moduł dsPIC33CH Curiosity Development Board

Architektura mikrokontrolera dwurdzeniowego z rodziny sdPIC33CH została pokazana na rysunku 2. W jednej strukturze umieszczono dwa praktycznie niezależne od siebie mikrokontrolery. Każdy z nich ma swoje magistrale pamięci i układów peryferyjnych, swój zestaw układów peryferyjnych i układ programowania taktowania. Wspólne elementy to oscylator przebiegu taktującego oraz dzielone między siebie wyprowadzenia. Oba rdzenie łączy też możliwość wzajemnej wymiany danych poprzez moduł MSI – Master Slave Interface oraz kanały DMA.

Mikrokontroler dsPIC33CH128MP508 jest bogato wyposażony w pamięć i układy peryferyjne. Do dyspozycji jest 128 kB pamięci programu Flash i 16 kB pamięci danych RAM. Trzeba jednak pamiętać, że pamięć Flash jest współdzielona i musi pomieścić kod rdzenia Master i kod rdzenia Slave. Na kod programu rdzenia Slave przewidziano 24 kB pamięci PRAM. W przypadku, kiedy cała pamięć przeznaczona dla rdzenia Slave jest wykorzystana na kod rdzenia Master, można wykorzystać maksymalnie 104 kB pamięci Flash. Do pamięci PRAM jest na żądanie rdzenia MASTER przepisywany kod z wydzielonego obszaru pamięci Flash.

Rysunek 2. Architektura mikrokontrolera rodziny dsPIC33CH

Podział układów peryferyjnych pomiędzy rdzenie został pokazany w tabeli 1.

Tabela 1. Zestawienie układów peryferyjnych dsPIC33CH128MP508

Rdzeń Flash RAM 12-bit ADC Timery SCCP CAN FD SENT UART

 

SPI/I2S I2C
Master 128kB 16kB 1 1 8 1 2 2 2
Slave 24kB (PRAM) 4kB 3 1 4 1 1

 

Rdzeń QEI CLC PTG CRC PWM komparator PGA Current Bias

 

REFO
Master 1 4 1 1 4 1 1 1
Slave 1 4 8 3 3 0 1

Projekt w środowisku MPLAB X IDE

Projekt w środowisku MPLAB X IDE wykorzystujący dwa rdzenie dla mikrokontrolerów dwurdzeniowych to w praktyce dwa standardowe projekty tworzone osobno dla rdzenia Master i rdzenia Slave i tak skonfigurowane, żeby ze sobą współpracowały.

Prace nad projektem dla rdzenia Master rozpoczynamy standardowo od polecenia New Project (File -> New Project) – rysunek 3. Z rozwijanej listy wybieramy mikrokontroler dsPIC33CH128MP508 zamontowany w module dsPIC33CH Curiosity Development Board.

Rysunek 3. Wybór mikrokontrolera

Dla każdego mikrokontrolera z rodziny dwurdzeniowych mikrokontrolerów dsPIC33CH umieszczono na liście dwa elementy. Dla projektu rdzenia Master nazwa mikrokontrolera jest taka jak jego nazwa katalogowa – dla naszego przypadku jest to dsPIC33CH128MP508. Poza tym na liście jest umieszczony dodatkowy element z sufiksem S1 przeznaczony dla projektów rdzenia Slave. Dokładniej zostanie to opisane później, przy okazji tworzenia projektu dla rdzenia Slave.

Kolejnym krokiem wykonywanym w kreatorze projektu jest wybór programatora/debuggera. Na płytce modułu jest wbudowany debugger wyposażony w złącze microUSB. Złącze to jest wykorzystywane do połączenia z komputerem i równolegle do zasilania modułu. Jeżeli w trakcie tworzenia projektu moduł jest połączony z komputerem przez USB, to na liście Select Tools pojawia się do wyboru opcja Stater Kits (PKOB) i należy ja wybrać. – rysunek 4.

Rysunek 4. Wybór programatora/debuggera

Debugger pozwala na programowanie pamięci Flash i debugowanie jednego rdzenia. Jest możliwe jednoczesne użycie dwu sprzętowych debuggerów podłączonych do dwu portów USB i debugowanie dwu rdzeni jednocześnie. Schematycznie zostało to pokazane na rysunku 5. Porty komunikacji debugger->mikrokontroler (linie PGC i PGD) można wybrać w trakcie pracy nad projektem. Na płytce dsPIC33CH Curiosity Development Board umieszczono złącze J15 S1PGx3 (slave debug only) przeznaczone do podłączenia debugera do debugowania kodu rdzenia Slave.

Rysunek 5. Debugowanie kodu dla dwu rdzeni jednocześnie

W kolejnych krokach kreatora wybieramy wersję kompilatora XC16. W trakcie pisania tego artykułu dla dsPIC33CH wymagana była wersja co najmniej V1.36. Wybór nazwy projektu i jego katalogu kończy pracę kreatora. Od jakiegoś czasu kreator projektu w MPLAB IDE nie tworzy szkieletu programu użytkownika i nie ma w nim żadnych plików źródłowych. Użytkownik może oczywiście samodzielnie umieszczać własne pliki źródłowe w strukturze projektu, ale lepszym pomysłem jest wykorzystanie do tego celu konfiguratora projektu dostępnego w MPLAB X IDE w postaci wtyczki MCC.

Konfigurowanie ustawień dla rdzenia Slave – element Slave Core

Wybranie mikrokontrolera z rodziny dsPIC33CH powoduje, że MCC „wie” o potrzebie konfigurowania projektu dla dwu rdzeni. Po uruchomieniu w oknie Device Resources -> Peripherials na liście oprócz układów peryferyjnych dostępnych dla rdzenia Master jest umieszczony element Slave Core pozwalający skonfigurować część ustawień rdzenia Slave dla pracy dwu-procesorowej – rysunek 6.

Rysunek 6. Lista układów peryferyjnych z elementem Slave Core

Po dodaniu Slave Core do projektu MCC możemy konfigurować pracę rdzenia Slave za pomocą zakładek:

  • Master Slave Interface – konfiguracja pracy interfejsu MSI,
  • Slave ICD – wybór wyprowadzeń sygnałów debuggera ICD rdzenia Slave,
  • Slave Clock – konfiguracja taktowania rdzenia Slave,
  • Slave Watchdog – konfiguracja Watchdoga.

Rysunek 7. Konfigurowanie rdzenia Slave

Konfigurowanie MSI (rysunek 8) polega na wyborze kanału (kanałów), długości bufora do transmisji i kierunku przesyłania danych (master->slave lub slave->master). Zaznaczenie opcji Interrupt powoduje, że MSI będzie zgłaszał przerwania w trakcie pracy. Ważne jest też konfigurowanie zachowania się rdzenia Master po wykonaniu zerowania mikrokontrolera. Na rysunku zerowanie zostało tak skonfigurowane, że zerowanie rdzenia Master powoduje również zerowanie rdzenia Slave i po zerowaniu rdzeń Slave jest blokowany – musi zostać odblokowany przez program wykonywany przez rdzeń Master.

Rysunek 8. Konfigurowanie MSI i zerowania

Wspominana już możliwość niezależnego debugowania rdzenia Slave i podłączenia niezależnego debugera jest konfigurowana w oknie Slave ICDrysunek 9. Jeżeli chcemy debugować rdzeń Slave na płytce powinniśmy wybrać PGC3 i PGD3.

Rysunek 9. Konfiguracja podłączenia debugera rdzenia Slave

Układ taktowania rdzenia Slave ma swój własny preskaler i układ PLL. Źródło sygnału dla układu taktowania wybiera się w oknie Slave Clock. Można tu wybrać wewnętrzny szybki oscylator FRC, oscylator LPRC, sygnał z zewnętrznego generatora lub wbudowanego oscylatora kwarcowego. Można również wyprowadzić sygnał taktujący na wyprowadzenie OSC2 mikrokontrolera (Clock Pin Configuration), i włączyć opcję Enable Clock Switching. Ta ostatnia, po wykryciu braku sygnału zegarowego na przykład z generatora zewnętrznego lub oscylatora kwarcowego, pozwala układowi taktowania na automatyczne przełączenie się na wewnętrzny oscylator FRC.

Rysunek 10. Konfiguracja taktowania

Ostatnia czynnością konfiguracyjną modułu Slave Core jest ustawienie działania układu watchdoga dla rdzenia Slave.

Wróćmy na chwilę do rysunku 2. Oba rdzenie mikrokontrolera są dość konsekwentnie izolowane od siebie. Wspólnym elementem jest interfejs komunikacyjny MSI (Master Slave Interface) oraz współdzielone wyprowadzenia mikrokontrolera. Współdzielenie jakichkolwiek zasobów jest zawsze źródłem potencjalnych konfliktów przy próbie uzyskania do nich dostępu. Jeżeli wyprowadzenie jest skonfigurowane jako wejściowe, to konflikty nie występują, bo oba rdzenie mogą bez zakłóceń jednocześnie odczytywać jego stan. W przypadku wyprowadzenia skonfigurowanego jako wyjściowe trzeba wykonać konfigurację przypisania wyprowadzenia wyjściowego dla rdzenia Slave wykorzystując do tego celu element Pin Module konfiguratora MCC.

Rysunek 11. Konfigurowanie wyjść dla rdzenia Slave

Wszystkie opisane konfiguracje rdzenia Slave reprezentowane w MCC za pomocą elementu Slave Core są zapisywane do bitów konfiguracyjnych (bezpieczników) i muszą być potem eksportowane do pamięci odpowiadającej za konfiguracje rdzenia Slave.

Projekt dla rdzenia Slave

Dwa niezależne rdzenie wymagają projektu złożonego z dwu różnych, niezależnych od siebie projektów dla każdego z rdzeni. Możemy sobie wyobrazić przestrzeń adresową pamięci programu Flash mikrokontrolera z dwoma obszarami: pierwszy zawiera kod dla rdzenia Master wygenerowany przez projekt o nazwie Master, a drugi kod dla rdzenia Slave wygenerowany przez projekt o nazwie Slav. Zostało to pokazane na rysunku 12.

Rysunek 12. Transfer kodu do pamięci PRAM

Przy odpowiedniej konfiguracji dwu połączonych projektów, projekt dla rdzenia Master umieszcza w pamięci Flash mikrokontrolera swój kod na początku przestrzeni adresowej, a za nim kod dla rdzenia Slave Jeżeli korzystamy z MPLAB X IDE z wtyczką MCC i wykonamy konfigurację według przedstawionego przepisu, to wszystko powinno się wykonać automatycznie.

Procedurę łączenia dwu projektów zaczynamy od nadania nazwy projektu Slave w oknie Slave Project Name elementu Slave Core – w naszym przypadku będzie to nazwa Slav. Kiedy wszystkie ustawienia dotyczące rdzenia Slave są gotowe, to je zapisujemy klikając na Save Master Settings. – rysunek 13.

Rysunek 13. Zapisanie ustawień i nadanie nazwy projektowi dla rdzenia Slave

Teraz możemy się przygotować do dodania projektu dla rdzenia Slave. Najpierw musimy utworzyć projekt o nazwie takiej jaką nadaliśmy w oknie Slave Project Name – w naszym przypadku „Slav” – rysunek 13. Jak pamiętamy, dla takiego projektu musimy wybrać z listy mikrokontrolerów taki sam mikrokontroler, ale z sufiksem S1. W naszym przypadku będzie to dsPIC33CH256MP508S1 – rysunek 14.

Rysunek 14. Wybór mikrokontrolera dla projektu dla rdzenia Slave

Jest to niezbędne, żeby MCC wiedział, że projekt ma być konfigurowany dla rdzenia Slave i udostępnił mechanizmy wymiany konfiguracji pomiędzy oboma projektami. Kiedy projekt dla rdzenia Slave jest już utworzony (na tym etapie nie musi być konfigurowany), to można go dodać do projektu dla rdzenia Master. W projekcie Master MCC tworzy katalog Slave. Po kliknięciu prawym klawiszem myszy można dodać do niego wcześniej utworzony projekt o wymaganej nazwie Slav tak jak to zostało pokazane na rysunku 15.

Rysunek 15. Dodanie projektu Slav do projektu dla rdzenia Master

W tym momencie mamy dwa ściśle ze sobą powiązane projekty, które będą jednocześnie kompilowane z poziomu projektu Master. Jak wiemy, nowo utworzony projekt nie zawiera żadnych plików źródłowych. Przechodzimy teraz do projektu Slav i uruchamiamy MCC. Konfigurowanie układów peryferyjnych i taktowania odbywa się standardowo i jego opis pominę. Będzie nas za to interesowała konfiguracja modułu Master Core umieszczonego w oknie Project Resources. Jedyną czynnością jaką możemy i powinniśmy tu zrobić, to wczytać konfigurację wykonywaną dla rdzenia Slave w wykonywaną projekcie dla rdzenia Master. Dla przypomnienia ta konfiguracja obejmowała moduł Master Slave Interface, wyprowadzeń ICD, źródła sygnału taktowania i układu watchdog’a. Na rysunku 16 zostało pokazane wczytanie tej konfiguracji.

Rysunek 16. Wczytywanie konfiguracji rdzenia Slave ustawianej w projekcie dla rdzenia Master

W oknie Master Core nie możemy niczego zmienić, możemy tylko obejrzeć to ustawiliśmy w projekcie Master. Żeby wykonać zmiany konfiguracji trzeba to zrobić w projekcie dla Master i tam zapisać. Potem trzeba ponownie wczytać ta konfigurację do projektu Slave, tak jak to zostało pokazane powyżej.

Kiedy konfiguracja rdzenia Slave (projekt Slav) jest wykonana to uruchamiamy generowanie kodu przez MCC i teoretycznie oba projekty są gotowe do kompilacji. Przed tym jeszcze trzeba przejść do właściwości projektu Master i wybrać kategorię Slaves. W oknie Slave Projects powinien się wyświetlić nasz dodany projekt Slav. Zaznaczamy tu opcję Build i w trakcie kompilacji najpierw zostanie skompilowany projekt Master, a po nim projekt Slav. W polu Address możemy podać adres od którego linker umieści kod dla rdzenia Slave w pamięci Flash.

Rysunek 17. Kategoria Slaves w oknie właściwości projektu Master

Przy ustawieniach z rysunku 17 uruchomienie kompilacji projektu Master spowoduje skompilowania obu projektów: Master i Slav i umieszczenie kodu dla procesora Slave w pamięci Flash od adresu 0x100000. Jeżeli chcemy, żeby kod dla Slave mógł być debugowany, to zaznaczamy opcję Debug w oknie Slave Cores.

Przykładowy program

Konfiguracja układów peryferyjnych, taktowania i watchdoga przez wtyczkę MCC to pierwszy i ważny krok przy pracy nad aplikacją. Pokażę teraz jak wykorzystać skonfigurowane projekty do napisania prostej aplikacji wykorzystującej oba rdzenie i komunikację pomiędzy nimi. Komunikacja będzie się opierała o moduł MSI i mechanizm wymiany danych przez mailbox.

Wróćmy na chwilę do rysunku 8 i okna Slave Reset Configuration. Ustawiliśmy zerowanie procesora Slave razem z zerowaniem procesora Master i blokowanie pracy procesora Slave po jego zerowaniu. Oznacza to ,że wykonanie zerowania procesora Master automatycznie zeruje procesor Slave i jest on po tym zerowaniu zatrzymany. Wiemy też że po procedurze zerowania procesora Master powinno być wykonane przepisanie kodu dla procesora Slave. Na rysunku 1 jest pokazany fragment programu procesora Master wykonujący się po zerowaniu. Po inicjalizacji układów peryferyjnych, układu przerwań i taktowania (SYSTEM_Initialize()) są wykonywane funkcje SLAVE1_Program() i SLAVE1_Start()listing 1.

Listing 1. Inicjalizacja mikrokontrolera, przepisanie kodu do PRAM i uruchomienia rdzenia Slave

Obie te funkcje wywołują skompilowane funkcje biblioteczne, tak jak to zostało pokazane na listingu 2.

void SLAVE1_Start()

Listing 2. Funkcje SLAVE1_Start() i Slave1_Program()

W tym momencie kod rdzenia Slave jest w pamięci programu PRAM i rdzeń pracuje. Rozpoczynamy programowanie transmisji przez mailbox z Master do Slave. Na listingu 3 są pokazane deklaracje struktur bufora nadawania i odbioru, inicjalizacja bufora nadawania i zerowanie bufora odbioru.

Listing 3. Deklaracje i inicjalizacja buforów mailbox

Teraz musimy wykonać sekwencję wymiany danych pomiędzy rdzeniami. Wykorzystamy protokół wymiany danych oparty o mechanizm przerwań. Pierwszym krokiem sekwencji jest wysłanie danych do bufora Mailbox przez protokół A skonfigurowany do transmisji Master->Slave. Wykonuje to funkcja SLAVE1_protocolAWrite ().

Procesor Master może zgłaszać przerwanie bezpośrednio do procesora Slave przez ustawienie bitu MTSIRQ. Również procesor Slave może zgłaszać bezpośrednio przerwanie do procesora Master przez ustawienie bitu STMIRQ.

Po zapisaniu bufora Master zgłasza przerwanie przez ustawienie bitu MTSIRQ – listing 4.

Listing 4. Funkcja zgłaszania przerwania przez Master

Po ustawieniu MTSIRQ procesor Master synchronizuje zegar taktujący transmisją z zegarem procesora Slave. Procesor Slave generuje przerwanie przez ustawienie bitu MSIMIF. Teraz Slave musi potwierdzić rdzeniowi Master przyjęcie przerwania przez ustawienie bitu MTSIACK. Na listingu 5 jest pokazana funkcja void MASTER_InterruptRequestAcknowledge() ustawiająca MTSIACK.

Listing 5. Ustawienie MTSIACK

Na listingu 6 przedstawiono fragment programu realizujący przesłanie danych z rdzenia Master do rdzenia Slave wykorzystujący protokół oparty na zgłaszaniu przerwań i przesyłaniu potwierdzeń.

Listing 6. Sekwencja wysyłania danych przez Master

Po stronie rdzenia Slave musimy czekać na zgłoszenie przerwania, wysłać potwierdzenie, czekać na zakończenie sekwencji i ponownie wysłać potwierdzenie. Jeżeli wszystko pójdzie dobrze, to można przeczytać dane z bufora ProtocolA mailboxlisting 7.

Listing 7. Sekwencja odbierania danych przez Slave

W trakcie konfiguracji MSI został zdefiniowany drugi kanał transmisji ProtocolB od Slave do Master. Możemy teraz wysłać dane ze Slave do Master. Sekwencja jest praktycznie taka sama, różni się tylko bitami, które trzeba ustawić i testować. Wysyłanie danych przez Slave rozpoczyna się od zapisania bufora danych kanału ProtocolB, a potem jest zgłaszane przerwanie do Master, oczekiwanie na potwierdzenie i zakończenie sekwencji – listing 8.

Listing 8. Przesłanie danych przez rdzeń Slave do rdzenia Master

Te dane rdzeń Master odbiera według takiej samej zasady. Najpierw czeka na przerwanie, potem wysyła potwierdzenie, czeka na kończenie sekwencji i wysyła ponowne potwierdzenie – listing 9.

Listing 9. Odczytanie danych z rdzenia Slave

Kompletny program wykonuje następujące czynności:

  • Master przesyła do rdzenia Slave 16-bitową wartość,
  • Slave odsyła do rdzenia Master 16 bitowa wartość,
  • Slave sprawdza czy odebrana wartość jest zgodna z oczekiwaną – jeżeli tak to zapala diodę D2,
  • Master sprawdza, czy odebrana wartość od Master jest zgodna z oczekiwaniem i jeżeli tak to zapala diodę D1.

Praktyczne próby przeprowadzone z modułem dsPIC33CH Curiosity Development Board zakończyły się sukcesem. Opisane powyżej tworzenie projektów dla rdzenia Master i rdzenia Slave i konfigurowanie obu rdzeni za pomocą MCC pozwoliły na napisanie prostej aplikacji „dwurdzeniowej” wykorzystującej komunikację w protokole wykorzystującym przerwania i moduł Master Slave Interface pracującego jako Mailbox. Wszystko to daje solidne podstawy do pisania własnych bardziej lub mniej rozbudowanych aplikacji wykorzystujących dwa wydajne rdzenie i możliwość wymiany danych pomiędzy nimi. Oczywiście program testowy miał za zadanie sprawdzić możliwość działania mikrokontrolera i w praktyce może być trudny do zastosowania w bardziej wymagających zastosowaniach. Jego wadą jest blokowanie programu w pętlach nieskończonych przy testowaniu oczekiwania na przerwania lub na potwierdzenia. To bardzo prosty sposób na zablokowanie działania aplikacji jak coś pójdzie nie tak jak powinno. Najlepiej gdyby transmisja pomiędzy rdzeniami całkowicie opierała się na przerwaniach i pracowała w tle programów głównych.

Jest to moje pierwsze praktyczne zetknięcie się z mikrokontrolerem dwurdzeniowym. Jestem pod wrażeniem pracy jaką Microchip wykonał , żeby można było w miarę szybko skonfigurować projekty dla obu rdzeni i uruchomić miedzy nimi komunikację. Co równie ważne, zmiany w konfiguracjach są łatwe i szybkie do wykonania. Tradycyjnie, oprócz sprzętu (modułu z wbudowanym lub zewnętrznym programatorem/debugerem), wszystko jest dostępne i nieodpłatne. dsPIC33CH jest specyficzną rodziną nastawiona na wykonywanie algorytmów sterowania, w tym sterowania silnikami DC i układami przetwornic DC/DC. Mam nadzieję, że w miarę szybko to rozwiązanie zacznie migrować do uniwersalnych 16–bitowych jednostek PIC24 i stanie się bardziej popularne i coraz tańsze.

O autorze

Absolwent Wydziału Elektroniki Politechniki Wrocławskiej, współpracownik miesięcznika Elektronika Praktyczna, autor książek o mikrokontrolerach Microchip i wyświetlaczach graficznych, wydanych nakładem Wydawnictwa BTC.
Zawodowo zajmuje się projektowaniem zaawansowanych systemów mikroprocesorowych.