Minikomputer 8085 - Część II

W poprzednim wpisie udało nam się zbudować działający minikomputer oraz uruchomić na nim napisany przez Davida Huntera monitor. Dla przypomnienia zbudowana przeze mnie maszyna wyglądała następująco: Minikomputer wyłaniający się z chaosu Podczas kilku kolejnych godzin pracy z komputerem, szybko okazało się że taka plątanina przewodów znacznie utrudnia debugowanie i rozwiązywanie problemów. Co gorsza przewody połączeniowe nie zapewniały dobrej jakości połączeń pomiędzy układami scalonymi. W efekcie komputer potrafił nie startować lub zawieszał się podczas wykonywania programów.

Kolejną ważną rzeczą którą pominąłem podczas budowy komputera są kondensatory odsprzęgające (ang. decoupling capacitors). Układy scalone, zwłaszcza te należące do rodziny 74LS potrafią emitować sporo zakłóceń podczas zmiany stanu. Dołączenie kondensatora foliowego lub ceramicznego o pojemności 0.1uF do linii zasilania układu pozwala zmniejszyć zakłócenia i poprawia stabilność jego działania.

Teraz nadszedł czas żeby naprawić wszystkie powyższe mankamenty. Przewody połączeniowe zastąpiłem ciętymi na wymiar przewodami jednożyłowymi. Oprócz kondensatorów odsprzęgających, dodałem do szyny zasilania również jeden duży kondensator elektrolityczny o znacznej pojemności (470uF). Generalnie dokładanie tak dużych kondensatorów nie jest zalecane bo potrafi mocno obciążyć zasilacz w momencie startu układu. Ja używam prostego zasilacza do płytek stykowych który przeżył już niejedno zwarcie, więc na razie postanowiłem zignorować ten problem. Efekt mojej pracy widać poniżej: Minikomputer tym razem z porządnymi połączeniami

Przy okazji przebudowy komputera postanowiłem wprowadzić kilka modyfikacji. Po pierwsze zastąpiłem pojedyńczy rejestr wejścia/wyjścia z projektu Huntera, dwoma rejestrami wyjściowymi i jednym wejściowym. Dzięki temu w prosty sposób będziemy mogli podłączyć do komputera zarówno 2x16 znakowy wyświetlacz LCD, jak i parę przycisków.

Warto tutaj zaznaczyć że oryginalny projekt Huntera również pozwala na integracje z wyświetlaczem LCD, jeżeli tylko wykorzystamy rejestry przesuwne 74LS595 do zwielokrotnienia liczby wyjść. Dokładnie tą samą sztuczkę wykorzystał Ben Eater w swoim programatorze EEPROM.

Ostatecznie po wszystkich modyfikacjach schemat komputera wygląda następująco: Schemat minikomputera Powyższy schemat oddaje jeden do jednego układ połączeń oraz typ układów scalonych które znalazły się na moich płytkach stykowych. Na przykład miałem pod ręką tylko jeden układ 74HCT574. Przy budowie portów wyjściowych musiałem więc wykorzystać dwa starsze układy 74HCT374, które robią co prawda to samo co 74HCT574, ale mają znacznie mnie wygodny układ wyprowadzeń. Podobnie nie posiadałem 74LS373 więc użyłem 74LS574 w połączeniu z inwerterem. Na koniec przypomnę że układy rodzin 74LS i 74HCT są ze sobą kompatybilne i można je stosować zamiennie.

Schemat minikomputera został stworzony w darmowym i popularnym wśród hobbystów programie KiCad. Podczas tworzenia schematu nie widziałem co wpisać w pole tytuł, postanowiłem więc nadać mojemu komputerowi nazwę Mikrus-85. Hurra!

Kolejny celem jaki przed sobą postawiłem była kompilacja i uruchamianie na Mikrusie programów napisanych w C. Niestety już samo znalezienie odpowiedniego kompilatora stanowiło duży problem. Small Device C Compiler mógłby być idealnym wyborem, gdyby wspierał używany przeze mnie procesor 8085. Co prawda prace nad dodaniem takiego wsparcia rozpoczęły się ponad rok temu, ale nie zaowocowały jeszcze w pełni działającym kompilatorem. Warto dodać że procesor 8085 jest wstecznie kompatybilny z procesorem 8080 (dokładnie mówiąc 8085 dodaje tylko dwie nowe instrukcje), więc kompilator C dla 8080 może być z powodzeniem wykorzystany również do generowania kodu dla 8085.

Ostatecznie postanowiłem użyć kompilatora Small-C, napisanego ponad 30 lat temu przez Chrisa Lewisa, a przywróconego do życia dzięki wysiłkom użytkownika ncb85. ncb85 oprócz kodu kompilatora udostępnił też na swoim koncie przykład jego użycia co pozwoliło mi zaoszczędzić niemało czasu.

Wracając do samego kompilatora, jest to dość stary program który powstał jeszcze przed wprowadzeniem standardu C89 (sic!). Poniżej garść ciekawostek które się z tym wiążą:

  • Nie ma typu void. Metody domyślnie zwracają typ int. :deal-with-it-parrot:
  • Brak wsparcia dla inicjalizacji wartości zmiennych globalnych.
  • Deklaracja metod musi byc wykonana w przestarzałym stylu K&R:
lcd(regSelect, data)
    char regSelect;
    char data;
{
    // code
}

Współcześnie napisalibyśmy:

void lcd(char regSelect, char data) {
    // code
}

Ponieważ nasz procesor jest 8-bitowy do dyspozycji mamy jedynie cztery typu liczb całkowitych: unsigned char, signed char, unsigned int i signed int. Operacje na 16-bitowych typach int muszą być symulowane za pomocą dostarczanej razem z kompilatorem biblioteki crun8085lib.asm.

Sam kompilator jest bardzo prosty, nie generuje prawie żadnych ostrzeżeń - łatwo więc sobie strzelić w stopę. Dla przykładu poniższa pętla jest nieskończona:

char c;
for (c = 0; c < 128; c++) {
    // do nothing
}

Od czasu do czasu kompilator kończy pracę zgłaszając Segmentation Fault. Praca z Small-C wymaga więc sporej dozy i cierpliwości, i wyrozumiałości, i wytrwałości.

Stworzenie najprostszego programu w C main() { } przy użyciu Small-C wymaga trochę więcej wysiłku niż tylko uruchomienie kompilator. W wyniku kompilacji otrzymujemy bowiem nie plik binary, ale plik źródłowy assemblera który należy jeszcze poddać translacji do kodu maszynowego. Do tego celu należy wykorzystać as8085 będący częścią pakietu ASxxxx. ASxxxx jest wciąż rozwijanym projektem posiadającym naprawdę dobrą dokumentację. Dzięki temu zbudowanie assemblera ze źródeł nie powinno sprawić nam żadnych problemów.

Czas na małą dygresję. Po starcie procesor 8085 zaczyna wykonywanie kodu programu od adresu 0x0000. Wartości rejestrów procesora, w szczególności rejestru SP odpowiedzialnego za zarządzaniem stosem nie są na starcie procesora dobrze zdefiniowane. Zanim wywołamy naszą funkcję main() powinniśmy nadać rejestrowi SP poprawną wartość. Zbudowany przeze mnie komputer posiada następującą mapę pamięci:

Adresy          | Przeznaczenie
---------------------------------
0x0000 - 0x1FFF - ROM
0x2000 - 0x3FFF - RAM
0x8000          - output port 1
0xA000          - output port 2
0xC000          - input port

Stos na 8085 podobnie jak w innych procesorach Intela rośnie w dół (w kierunku mniejszych adresów). Dodatkowo warto żeby adres stosu był parzysty, rozsądnym wyborem jest więc ustawienie SP na adres 0x3FFE a więc przedostatni dostępny adres pamięci RAM.

Ostatecznie stworzyłem plik cstart.asm odpowiedzialny za przygotowanie środowiska dla C:

;       Run time setup for Small C.
        .module CSTART
        .area   CSTART (REL,CON) ;program area CRTSO is RELOCATABLE
        .list   (err, loc, bin, eqt, cyc, lin, src, lst, md)
        .nlist  (pag)
        .globl  cstartend

        lxi h,#0x3ffe   ; Initialize stack on even address.
                        ; Stack grows downwards.
        sphl            ; Load HL into SP

        call    main    ; call main program

stop:
        hlt             ; stop processor
        jmp stop        ; interrupt can wake CPU from hlt

cstartend:
       .end

Pozostaje nam już tylko upewnić się że kod z pliku cstart.asm wyląduje pod adresem 0x0000. Do tego celu musimy 1) nadać mu unikalną nazwę CSTART 2) wykorzystać parametr linkera -b żeby wymusić pozycje obszaru CSTART w pamięci EEPROM.

Poniżej przedstawiam parametry linkera (plik eeprom.lnk):

-ioux
eeprom-img
cstart
main
-b CSTART=0
-b SMALLC_GENERATED=0+cstartend
-e

Zauważmy że kod w pliku cstart.asm kończy się deklaracją labelki cstartend. Labelkę to wykorzystujemy żeby zmusić linker to umieszczenia kodu wygenerowanego przez Small-C (SMALLC_GENERATED) zaraz po kodzie sekcji CSTART.

Jeżeli zaczniemy pisać bardziej skomplikowane programy w C to może się okazać że nasz program wymaga funkcji, znajdujących się we wspomnianej już przeze mnie bibliotece crun8085lib.asm. Na chwilę obecną po prostu kopiuje brakujące funkcje do pliku cstart.asm, postaram się to poprawić w kolejnym wpisie.

Ostatecznie wynikiem kompilacji i linkowania jest plik w formacie Intel HEX który możemy już wypalić w pamięci EEPROM.

W trakcie zabawy z Mikrusem przygotowałem prosty program który wypisuje wiadomość na ekran LCD oraz pozwala na obsługę dwóch przycisków: Obsługa LCD

Na koniec zagadka dla czytelników: W tej chwili Small-C nie pozwala nam wykorzystać zmiennych globalnych. Odczyt zmiennych jest co prawda możliwy ale zapisy do zmiennych zdają się “znikać”? Dlaczego tak się dzieje?

W następnym wpisie postaram się to naprawić. Koniec części drugiej.