Implementacja konsoli Chip-8 w mikrokontrolerze STM32F0

Pierwszym pomysłem na realizację emulatora był emulator konsoli Atari2600. Implementacja tego emulatora na komputerze PC działała całkiem sprawnie, jednak po przeniesieniu programu na mikrokontroler STM32F051R8T6 płynność działania spadła do poziomu uniemożliwiającego korzystanie z niego. Spowodowane było to zegarem o niskiej częstotliwości oraz małą ilością dostępnej pamięci RAM wykorzystywanego układu. Z pomocą przyszedł projekt konsoli Chip-8, który jest maszyną wirtualną stworzoną w celu ułatwienia procesu tworzenia i uruchamiania prostych gier na starszym sprzęcie (lata ’70 ubiegłego wieku).

 

 

W poniższym projekcie przedstawiony został proces tworzenia emulatora platformy do gier (Chip-8), zaczynając od implementacji w języku C++ pod systemem Windows, a następnie przenosząc projekt na mikrokontroler STM32F0. Platformą na jakiej został zrealizowany przedstawiony projekt jest zestaw STM32F0DISCOVERY  oraz moduł wyświetlacza KamodTFT2.

Chip-8 jest prostą maszyną wirtualną. Charakteryzuje się on 8-bitowym procesorem dysponującym 4 kilobajtami pamięci. Do dyspozycji jest 16 rejestrów 8-bitowych oznaczanych od V0 do VF. Przy czym rejestr VF ma specjalne zastosowanie przy operacjach arytmetycznych. Dodatkowo do dyspozycji jest 16-bitowy rejestr indeksowy oznaczany literą I – służy on do adresowania pamięci.

Procesor posiada również stos, jednak umieszczony jest on poza pamięcią. Mieści on 16 dwubajtowych (16-bitowych) adresów. Wykorzystywany jest do przechowywania adresów powrotu przy wywoływaniu funkcji.

Maszyna dysponuje dwoma licznikami taktowanymi ze stałą częstotliwością 60 Hz. Liczniki te liczą w dół, dopóki nie osiągną wartości 0. Jeden z nich przeznaczony jest do generowania opóźnień (delay timer), natomiast drugi do generowania prostych dźwięków (sound timer).

Maszyna Chip-8 wyposażona jest w prosty monochromatyczny wyświetlacz o rozdzielczości 64×32 piksele. Procesor rysuje po nim używając duszków (sprite) będących obrazami o szerokości 8 pikseli i wysokości od 1 do 15 pikseli, które są nakładane na tło. Rysowanie odbywa się w specyficzny sposób, gdyż piksele wstawiane na ekran są za pomocą operacji XOR – umożliwia to wykrywanie kolizji, jednak niesie ze sobą przykry efekt – migotanie ekranu. W pierwszych 512 bajtach pamięci umieszczona jest tablica znaków 0-9 i a-f wyświetlanych na ekranie.

Chip-8 zawiera 35 instrukcji, każda o długości dwóch bajtów. Dokładniejsze ich omówienie zostanie przedstawione w dalszej części. Na rysunku 1 przedstawiono zrzut ekranu stworzonej aplikacji podczas jednej z rozgrywek.

 

Implementacja konsoli Chip-8 w mikrokontrolerze STM32F0

Rys. 1. Działająca aplikacja

Tworzenie aplikacji dla komputera PC

W tym momencie, po zapoznaniu się z podstawami budowy Chip-8, można przejść do tworzenia emulatora tego procesora przeznaczonego dla komputera PC. Należy zaznaczyć, że ze względu na małą złożoność nie jest to trudny proces.

List. 1. Klasa reprezentująca obiekt procesora

W pierwszym kroku została utworzona klasa reprezentująca obiekt procesora. W ramach deklaracji zostały zawarte wszystkie rejestry układu, pamięć oraz tablica reprezentującą wyświetlaną zawartość ekranu. W konstruktorze klasy wykonywane jest czyszczenie pola pamięci, wyświetlacza oraz stosu. Proces ten jest realizowany jest poprzez zapis do powyższych elementów wartości 0.

List. 2. Konstruktor klasy CPU

Kolejnym krokiem jest wczytanie do pamięci procesora programu, który będzie wykonywany. W przypadku komputera PC proces ten realizowany jest przy użyciu funkcji fopen, która wczytuje zawartość pliku do pamięci procesora rozpoczynając od adresu 512.

Po stworzeniu szkieletu maszyny można przejść do tworzenia funkcji realizujących operację procesora. Składa się ona z trzech części:

  1. Pobranie instrukcji z pamięci.
  2. Dekodowanie instrukcji.
  3. Wykonanie.

Pierwszym działaniem jest pobranie instrukcji. Jak zostało zaznaczone wcześniej jedna instrukcja składa się z dwóch bajtów. Pierwsza część decyduje o rodzaju realizowanej operacji, natomiast druga jest parametrem dla wykonywanej funkcji. Należy zwrócić uwagę, że w zależności od operacji długość bitów określających operację może mieć różną długość. Operacja pobrania instrukcji z pamięci została przedstawiona na listingu poniżej.

List. 3. Pobranie instrukcji i parametru z pamięci układu

Kolejnym krokiem, po pobraniu instrukcji, jest jej zdekodowanie. Opis wszystkich instrukcji można znaleźć pod adresem http://en.wikipedia.org/wiki/Chip-8 (opcode table). Najłatwiejszą metodą realizacji jest zastosowanie instrukcji switch. W przedstawionym programie zastosowano inne podejście. Zamiast analizowania pełnych rozkazów przeprowadzane jest sprawdzenie stanu 4 starszych bitów pierwszego bajtu będącego rozkazem:

W zależności od zawartości tych bitów wykonywane są odpowiednie działania. Przykładowo dla przypadku 0h należy sprawdzić drugi bajt instrukcji, gdyż w ten sposób mogą być zakodowane 3 różne instrukcje. Dekodowanie tych instrukcji zostało przedstawione na listingu poniżej.

List. 4. Dekodowanie przykładowych instrukcji

Podążając powyżej przedstawionym schematem działań należy opisać pozostałe operacje procesora.

List. 5. Dekodowanie oraz wykonanie najważniejszych operacji

Warta uwagi jest operacja wyświetlania danych na ekranie przedstawiona na poniższym listingu.

List. 6. Dekodowanie oraz wykonanie operacji wyświetlającej zawartość ekranu

Kod pozostałych funkcji nie został przedstawiony ze względu na fakt, że są to funkcje realizujące proste operacje arytmetyczne i logiczne. Trzy z nich pobierają stan wejść z klawiatury i działają analogicznie jak funkcja 3xkk (SE – pomiń jeżeli Vx = kk). W dołączonym kodzie źródłowym programu dla komputera PC można zapoznać się z pełnym programem, którego najważniejsze elementy zostały omówione powyżej.

 

Implementacja projektu na platformie STM32

W ramach implementacji na platformie STM32 należy zrealizować obsługę: instrukcji, licznika odliczającego 60 razy na sekundę, wejść z klawiatury do tworzonej maszyny i na końcu obsłużyć wyświetlanie obrazu. W ramach działania programu w pierwszym kroku tworzony jest obiekt klasy CPU, który realizuje wszystkie działania układu Chip-8.

Jak zostało wspominane wcześniej w ramach projektu został wykorzystany zestaw STM32F0DISCOVERY. Natomiast do wyświetlania zastosowano wyświetlacz KamodTFT2, którego sposób obsługi jest dobrze opisany na stronie STM32.eu w wielu projektach. Ogólno dostępna biblioteka została wzbogacona o dodatkowe poprawki służące przyśpieszeniu transferu danych do LCD oraz obsługujące czcionki do wyświetlania tekstu. Jako klawiaturę zastosowano 6 mikroprzełączników zmontowanych na płytce uniwersalnej podłączonej bezpośrednio pod piny mikroprocesora. Do przechowywania danych wstępnie planowano zastosowanie karty SD, jednak rozmiar plików wykonywalnych dla maszyny wirtualnej jest na tyle mały, że zrezygnowano z tego pomysłu.  Zamiast tego użyto pamięci Flash z interfejsem SPI w postaci układu SST25VF080B. Na rysunku poniżej przedstawiono schemat połączonych ze sobą elementów.

 

Fot. 3. Widok działającego układu

Rys. 2. Schemat połączeń płytki STM32F0DISCOVERY z elementami zewnętrznymi

Aplikacja została zrealizowana przy wykorzystaniu środowiska ARM-MDK firmy Keil. Wynika to z faktu, że TrueStudio Lite w przypadku układów bazujących na rdzeniu CortexM0 ma ograniczenie tworzonego programu do 8 kB, które jest zbyt małą ilością pamięci dla przedstawionego emulatora. Jednak nie obyło się bez problemów także w przypadku Keila. Niestety w darmowej wersji nie wspiera on języka C++. Spowodowało to konieczność pisania programu w języku C i zrezygnowanie z klas na rzecz struktur i zastosowanie kilku sztuczek. Pierwszym etapem prac było stworzenie programu realizującego wewnętrzne operacje maszyny wirtualnej. Warto zwrócić tutaj uwagę na rozmiar stosu, gdyż przy początkowych pracach jego rozmiar ustawiony był na zbyt niską wartość, co w szczególnych przypadkach wywoływało obsługę wyjątku.

Kolejnym etapem było obsłużenie wyświetlacza TFT. Procedura ta została zrealizowana jako funkcja aktualizująca zawartość tablicy przedstawiającej zawartość wyświetlacza. W czasie jej wykonywania odbywa się także aktualizacja zawartości wyświetlanej na TFT. Kolejnym elementem jest funkcja odczytującą stan linii, do których podpięte są przyciski sterowania. Przy wyborze portu warto spojrzeć do dokumentacji, aby upewnić się czy dany pin nie jest wykorzystywany przez inne peryferia na płytce (jak dioda lub port JTAG/SWD). Przy tworzeniu projektu nie została zwrócona na to uwaga, co sprawiło duże problemu podczas debugowania programu. Do obsługi licznika maszyny wirtualnej został użyty licznik SysTick, który odlicza 500 razy na sekundę (z taką prędkością działa maszyna wirtualna). Dodatkowo w ramach licznika SysTick generowany jest przebieg licznika programowego z częstotliwością 60 Hz.

 

Fot. 3. Widok działającego układu

Fot. 3. Widok działającego układu

Do przechowywania plików z grami użyta została zewnętrzna pamięć Flash, z którą komunikacja odbywa się przez magistralę SPI. Dla wygody przechowywania plików w pamięci został przygotowany prosty system plików. Do przygotowania zawartości zapisywanej w pamięci Flash został przygotowany program dla komputera PC, który łączy ze sobą wszystkie dane i tworzy tablicę z danymi zawierającą informacje o ich rozmiarze, nazwie i położeniu w pamięci:

Samo działanie maszyny Chip-8 na mikrokontrolerze jest analogiczne do działania na komputerze PC. W przypadku mikrokontrolera dodano proste menu pozwalające wybrać konkretną grę.

Jakub Czekański


Do pobrania