Następnym etapem rozwoju projektu jest dodanie wyświetlacza na układzie HD44780 (a nie HD77840 jak mi się wcześniej pokiełbasiło).
Jako że jest to pierwsze prawdziwe urządzenie wejścia/wyjścia uważam że to świetny moment żeby przedyskutowac jak będzie wyglądać adresowanie We/Wy w tym komputerze.
Procesor Z80 używa ośmiu bitów na liniach A0-A7 do adresowania urządzeń We/Wy. To oznacza, że potrafi bezpośrednio zaadresować 256 różnych urządzeń. Dekodowanie takiej liczby adresów byłoby jednak równie skomplikowane co zbędne, postanowiłem więc pójść w dokładnie przeciwną stronę, a mianowicie wykorzystać dokładnie jeden z tych bitów do wyboru jednego urządzenia We/Wy. Dzięki temu w celu zdekodowaniu adresu muszę dokonać koniunkcji logicznej tylko jednej linii adresowej z linią IORQ.
Tutaj ważna uwaga, ponieważ linie sterujące zarówno procesora, jak i urządzeń peryferyjnych (a także pamięci) są aktywne stanem niskim, koniunkcji logicznej dokonujemy za pomocą bramki OR a alternatywy za pomocą bramki AND, odwrotnie niż w przypadku "normalnej" logiki.
Dalej, niektóre urządzenia posiadają kilka oddzielnych kanałów, np układ DART ma dwa kanały A i B, układ HD44780 ma kanały komend i kanał danych, etc. Zdecydowałem więc że jedynie 6 starszych spośród 8 dostępnych bitów adresu będzie wykorzystywana do wyboru urządzenia We/We, podczas gdy dwa najmłodsze bity posłuża do wyboru kanału. Oznacza to, że ostatecznie będę miał możliwość połączenia sześciu fizycznych urządzeń, każde reprezentujące maksymalnie 4 urządzenia logiczne (lub inaczej mówiąc: kanały). Powinno to wystarczyć do jednoczesnego podłączenia np. wyświetlacza, klawiatury, układu DART, karty dźwiękowej, portu równoległego i pamięci masowej.
W tym schemacie adresy urządzeń fizycznych będą wyglądały następująco (od najstarszego bitu):
011111xx
101111xx
itd. aż do
111110xx
Odpowiedzialnością programisty będzie unikanie jednoczesnego adresowania dwóch lub więcej urządzeń, np:
001111xx
Teraz trochę o samym układzie HD44780. Posiada on, jak wspomniałem, kanał komend i kanał danych. Wybór kanału następuje za pomocą pinu RS (Register Select), który podłączę bezpośrednio do linii A0. W pierwszej iteracji projektu będę do urządzenia jedynie pisał, natomiast nie będę z niego czytał. Tym samym pin R/W' mogę podłączyć bezpośrednio do masy, a pin E (Enable) do linii W procesora (poprzez inwerter, jako że jest aktywny stanem wysokim).
Czytanie z urządzenia jest jednak również ważną częścią komunikacji, której na dłuższą metę nie da się uniknąć. Nawet jeśli nie interesuje mnie sczytywanie znaków które są wyświetlane w danym momencie, zdecyduję się raczej na odczytywanie flagi BUSY. Flaga ta jest użyteczna ponieważ układ potrzebuje pewnego czasu aby wykonać każdą operację, a flagą sygnalizuje że nie jest jeszcze gotowy na przyjęcie kolejnego rozkazu czy danej. Przy częstotliwości zegara procesora rzędu 1Hz nie ma to znaczenia, ponieważ jednak procesor ma wkrótce dostać "prawdziwy" zegar, będzie zmuszony niekiedy czekać na układ HD44780.
Dostępne opcje to albo czekanie bezwarunkowe (maksymalne czasy wymagane do zakończenia danej operacji przez wyświetlacz są w jego karcie katalogowej), co oznaczałoby marnowanie czasu na czekanie nawet gdy nie jest ono konieczne. To byłoby akceptowalne, ale niezbyt eleganckie. Alternatywą jest właśnie sprawdzanie flagi BUSY.
Niestety, o ile większość urządzeń peryferyjnych i pamięci posiada linię adresującą Chip Select, Chip Enable czy występującą pod jakąś podobną nazwą, i osobne linie strobujące dla odczytu i zapisu: R i W, o tyle HD44870 na jedną linię R/W' i jedną linię strobującą E. Z mojej (ograniczonej) wiedzy na temat tego układu wynika, że wszystkie linie oprócz E muszą być ustawione na odpowiednie wartości przed podaniem wygnału strobującego na pin E. Oznacza to, że nie mogę użyć sygnału WR z procesora jednocześnie do strobowania i wyboru kierunku komunikacji pinem R/W (przynajmniej nie bez jakiegoś sposobu opóźnienia sygnału strobującego, np przerzutnikiem D). Co gorsza, odczyt nie byłby w takim wypadku w ogóle możliwy bo sygnał strobujący pojawiałby się na pinie E w zupełnie niewłaściwym momencie.
Wymyśliłem więc, że pin R/W' podłączę do linii A1 procesora, a strobować pin E będę za pomocą sygnału będącego alternatywą logiczną linii RD i WR procesora. Innymi słowy kierunek komunikacji jest wybierany linią adresową, a aktywowanie któregokolwiek z sygnałów RD lub WR spowoduje aktywację pinu E wyświetlacza. Adresy portów będą więc wyglądały następująco:
Kod: Zaznacz cały
LCD_CMD_WR equ 11110100b ; write command port of the LCD
LCD_DAT_WR equ 11110101b ; write data port of the LCD
LCD_CMD_RD equ 11110110b ; read command port of the LCD
LCD_DAT_RD equ 11110111b ; read data port of the LCD
Ponownie, odpowiedzialność za pisanie tylko do portów dla których A0 ma wartość 0 i czytanie jedynie z portów gdzie A0 ma wartość 1 spada na programistę.
Ostatnia uwaga dotyczy fizycznej realizacji alternatywy logicznej linii RD i WR. Jak pisałem wcześniej, w logice odwrotnej (gdzie sygnały są aktywne stanem niskim) alternatywy dokonujemy za pomocą bramki AND. Jednak jako że mam już na płytce bramki OR i NOT, zrealizuję bramkę AND za pomocą bramki OR odwracając wszystkie sygnały wejściowe i wyjściowe.
Testy będę przeprowadzał w czwartek, gdyby do tego czasu ktoś zauważył, że to co napisałem nie ma sensu lub nie ma prawa działać, proszę o komentarz.
Listing istotnych fragmentów programu:
Kod: Zaznacz cały
; resets the LCD display
lcd_init:
LD C, LCD_CMD_WR
LD A, 00001111b ; init: set display on, cursor on, blinking on
OUT (C), A
LD A, 00111000b ; set data length: 8b, 2 lines
OUT (C), A
RET
; writes a null-terminated string to the LCD screen
; IX: address of the string
lcd_wriStr:
LD C, LCD_DAT_WR
CALL wriStr
RET
; writes a null-terminated string to an output port
; IX: address of the string
; C: port number
wriStr:
LD A,(IX+0)
CP 0
RET Z
OUT (C),A
INC IX
JR wriStr