Spisu treści:
2025 Autor: John Day | [email protected]. Ostatnio zmodyfikowany: 2025-01-13 06:58
W przeszłości napisałem przewodnik, jak zbudować komputer oparty na Z80 i zaprojektowałem obwód tak, aby był jak najbardziej uproszczony, aby można go było zbudować tak łatwo, jak to tylko możliwe. Napisałem też mały program, wykorzystując tę samą ideę prostoty. Ten projekt działał całkiem dobrze, ale nie byłem z niego do końca zadowolony. Zacząłem od przepisania do niego programu, który pozwalał na programowanie w czasie wykonywania. Miało to pozwolić mi przetestować fragmenty kodu bez konieczności dedykowania go do EEPROM, co z kolei wymagałoby ode mnie przeprogramowania EEPROM. Nie brzmiało to dla mnie jak fajny pomysł. Potem zacząłem myśleć o przestrzeniach pamięci. Gdybym chciał sprzęgnąć kawałek sprzętu (głównie IO), kawałek kodu mógłby potencjalnie przekroczyć ilość dostępnej dla systemu przestrzeni pamięci. Pamiętaj, że projekt używał tylko dolnego bajtu szyny adresowej, a następnie niższy bit starszego bajtu był używany do wyboru między przestrzeniami ROM i RAM. Oznaczało to, że miałem do wykorzystania tylko 253 bajty miejsca. Możesz zapytać, dlaczego 253 zamiast 256. To dlatego, że mój nowy kod wstrzykuje trzy bajty danych na końcu napisanego programu (zostanie to omówione później, gdy zmodyfikowałem go, aby działał na nowym projekcie).
n
Wróciłem do moich starych schematów, aby zobaczyć, co jeszcze się dzieje. Znalazłem małą wadę w obwodzie wyboru pamięci, którą omówię, gdy tam dotrę. Uproszczona wersja: wszystkie żądania zapisu faktycznie przeszły, chociaż zawsze były umieszczane w pamięci RAM. To chyba nie było nic wartego zmartwienia, ale tym razem chciałem zrobić to porządnie. I z tym zacząłem rysować nowy schemat. Dwa zdjęcia dołączone do tej strony są przed i po rzeczywistym obwodzie. Wyczyściłem tyle przewodów spaghetti, że to nie jest zabawne.
n
Jeśli podążyłeś za moim pierwotnym zgłoszeniem i planujesz podążać za tym, znienawidzisz mnie. Jeśli zaczynasz od nowa, masz szczęście. Po prostu chwyć części z listy (lub ich odpowiedniki) i postępuj zgodnie z instrukcjami.
Kieszonkowe dzieci:
LM7805 - 5 V regulatorZ80 - procesor; mózgi systemuAT28C64B - EEPROM. „Stałe” przechowywanie danych wykorzystywane do oprogramowania sprzętowego komputeraIDT6116SA - SRAM; służy do przechowywania kodu użytkownika i/lub ogólnego przechowywania danychNE555 - Zegar systemowy74HC374 - Octal D-Latch z /OE; używany jako układ wejściowy74LS273 - Octal D-Latch z /MR; układ wyjściowyTLC59211 - układ sterownika LED (używany, aby 74LS273 mógł sterować diodami LED, ponieważ sam nie jest zdolny do generowania prądu wyjściowego) MC14572 - To jest układ „Line Driver”, ale uznałem go za idealny dla logiki sterowania pamięcią. Posiada 4 inwertery oraz wbudowaną bramkę NAND i NOR74LS32 - Quad OR gateCD4001 - Quad NOR gateCD4040 - 12-stopniowy licznik tętnień; Narysowany, ale nie zaimplementowany dzielnik zegara (do uruchamiania systemu przy niższych częstotliwościach zegara)2 Rezystory 10K Ohm - Jeden jest używany w obwodzie timera 555, więc użyj dowolnej wartości 4 Rezystory 1K Ohm - Jeden jest używany dla 555 obwód czasowy, więc używaj tego, co chcesz. Inny służy do sterowania diodami LED, więc zmień go również, jeśli chcesz. Diody LED magistrali rezystorowej 8x330 Ohm magistrali rezystorowej 8x10K Ohm - trzy są używane do stanu systemu, a pozostałe osiem to wyjścia. W przypadku 8, użyłem wskaźnika słupkowego (HDSP-4836)4 Kondensatory - dwa są używane w LM7805; 0,22 uF i 0,1 uF. Jeden jest dla timera 555, więc używaj tego, co uważasz za słuszne. Ostatni dotyczy resetowania po włączeniu; 100uF2 NIE Przyciski - Jeden służy do wprowadzania danych, drugi do resetowania8 Przełączniki DIP SPST - Wejście danych; Użyłem drutu w stylu Piano Key. Dużo i dużo drutu
n
UWAGA: wersja przewlekana MC14572 jest przestarzała, ale wersja SMD jest nadal aktywna (nawet status „nie dla nowego projektu”), więc może być konieczne zakupienie płytki drukowanej, aby móc z niej korzystać. Drugi 74LS32 może być użyty zamiast MC14572 (patrz schemat „obwód wyboru pamięci” z poprzedniej tabeli)
Krok 1: Szybki przegląd zmian + schematy
Jak czytać schematy:Strzałka skierowana w chip to wejście:Wejście >-Strzałka skierowana w stronę przeciwną do chipa to wyjście:Wyjście <-Busy używają linii zamiast strzałki:Bus |-
n
Większość żetonów została wylosowana z ich dokładnymi pinoutami. Na tych żetonach został narysowany mały dołek. Większość żetonów ma również numery pinów i etykiety. Mogą być ciut trudne do odczytania. Mój ołówek się stępił.
n
Jeśli chodzi o połączenia obwodów, układ nowego projektu pozostaje w większości niezmieniony w stosunku do oryginału. Podłączyłem dolny bit starszego bajtu adresu do pamięci, a następnie użyłem dolnego bitu górnego bitu (A12) do wyboru pamięci RAM/ROM. Oznaczało to, że przestrzeń ROM wzrosła z 0000-00FF do 0000-0FFF. Przestrzeń RAM wzrosła z 0100-01FF do 1000-1FFF. Zamieniłem również logikę sterowania pamięcią na lepszy projekt i dodałem dwie nowe diody LED stanu (i trochę logiki kleju). Narysowałem również (ale nie okablowałem) obwód dzielnika zegara. Miał pełnić dwie funkcje. Oczywistą funkcją jest dzielenie częstotliwości zegara w dół. Druga funkcja służy do modulacji szerokości impulsu (PWM), ponieważ 555 nie generuje fal przy 50% cyklu pracy. To naprawdę nie ma znaczenia w tym obwodzie, ale jeśli chcesz użyć zegara do sterowania niektórymi diodami LED, na pewno zauważysz efekty (jedna (zestaw) diod LED będzie ciemniejsza niż druga). Cała reszta obwodów jest zasadniczo niezmieniona.
Krok 2: Sterowanie procesorem, pamięcią i pamięcią
To jest ta część, w której czytelnicy mojej poprzedniej wersji mnie nienawidzą. W oryginalnej wersji po prostu wrzuciłem części na planszę w miejscu, w którym wyglądały, jakby nie sprawiały problemu z podłączeniem. Wynik wyglądał, jakby ktoś zrzucił na niego talerz spaghetti i był jak „druty!” Chciałem to trochę posprzątać, więc zacząłem od zgrywania wszystkiego oprócz procesora, pamięci RAM i ROM. Podciągnąłem prawie cały obwód wejściowy, obwód wyjściowy i logikę kleju. Prawie mnie to zabolało, ale było to konieczne. Zostawiłem wszystkie połączenia danych nienaruszone i niższy bajt szyny adresowej. Następnie podłączyłem kolejne cztery bity magistrali adresowej (A8-A11) do układu ROM. Tym razem zadbałem o obejście chipa, aby ułatwić przeprogramowanie. Przeskoczyłem również połączenia adresowe do układu RAM.
n
Pomijając to, musiałem teraz podłączyć logikę sterowania pamięcią. Na oryginalnym schemacie podłączyłem linię /MREQ procesora bezpośrednio do /CE do obu układów pamięci, a następnie podłączyłem /WR do /WE pamięci RAM. Następnie miałem logicznie LUB procesora /RD i /MREQ razem, a także A9. Zasadniczo został on skonfigurowany tak, aby wszystkie żądania pamięci aktywowały zarówno pamięć RAM, jak i ROM, ale A9 został użyty do wyboru, który z chipów / OE został wybrany. To było w porządku, a wszystko dlatego, że chipy pozostawały nieaktywne, dopóki nie zostanie wysłane żądanie pamięci, a wtedy tylko jeden /OE będzie aktywny podczas żądania odczytu. To zapobiegło przesłuchom, ale wprowadziło niezręczny niuans. Ponieważ A9 był używany tylko do określenia, który układ wysyłał dane, a procesor miał bezpośredni dostęp do pinu /WE pamięci RAM, wszystkie żądania zapisu przeszły. To było w porządku dla ROM, ponieważ tryb zapisu jest wstrzymywany przez powiązanie /WE bezpośrednio z zasilaniem 5V. Pamięć RAM byłaby jednak zapisywana niezależnie od A9. Oznaczało to, że próba zapisu do miejsca w pamięci ROM spowoduje zapis w tej samej lokalizacji w pamięci RAM.
n
Jednym z rozwiązań tego problemu byłoby ponowne okablowanie logiki sterującej, aby procesor miał bezpośredni dostęp do pinów /OE i /WE chipów, a następnie użycie MREQ i A12 do wyboru, które chipy /CE są sterowane. Poszedłem z tym pomysłem, ale zamiast czterech bramek NOR i falownika, jak w oryginalnym projekcie, znalazłem niezręczny mały chip, który idealnie nadawał się do tego zadania. Musiałem stworzyć obwód, który używałby tylko bramek logicznych dostępnych w chipie, ale to było dość łatwe. A12 zasila bezpośrednio bramkę NAND i bramkę NOR. /MREQ jest podawany do bramki NOR, a jego komplement jest podawany do bramki NAND. Bramka NAND jest używana do sterowania /CE dla pamięci RAM, a wyjście NOR zostaje odwrócone i używane do sterowania ROM /CE. To sprawia, że /MREQ musi być niski, zanim zostanie wybrany któryś z chipów, a następnie A12 wybierze, który z nich zostanie wybrany. Przy tej konfiguracji żadne żądania zapisu do pamięci ROM nic nie zrobią. Oszczędza również energię, ponieważ aktywny jest tylko jeden chip zamiast obu. Jeśli chodzi o sam układ logiczny, to w środku nadal mamy dwa nieużywane falowniki. Jeden przyzwyczai się później, ale dotrzemy tam, gdy tam dotrzemy.
Krok 3: Diody LED stanu systemu
Zanim zacząłem ten projekt, próbowałem połączyć się z pewnym układem scalonym, ale miałem z nim problemy. Niepewny, co się dzieje, użyłem diody LED do montażu na panelu do sondowania (jeden z tych zespołów, który ma wbudowany rezystor). To dało mi nostalgiczny pomysł, który jest nadal używany do dziś: diody LED stanu używane do wskazywania, czy pamięć jest odczytywana, czy zapisywana. Miał być używany w połączeniu z diodą wejścia, którą już miałem. Dioda wejścia została podłączona do generatora sygnału /WAIT, aby wskazać nam, że system czeka na wejście (dojdę tam, nie martw się). Rozważałem dodanie diody LED wskazującej zapis IO, ale pomyślałem, że zmiana diod wyjściowych byłaby już świetnym wskaźnikiem tego. Myśląc o tym, mogę jeszcze to dodać. Niemniej jednak uważam, że warto wiedzieć, czy pamięć jest odczytywana, czy zapisywana. Cóż, i tak przydaje się do debugowania programu. W rzeczywistości intensywnie używałem go jako takiego, próbując uruchomić mój program: „dlaczego to zapisuje w pamięci? To jeszcze nie powinno tego robić!”
n
Do sterowania tymi diodami wykorzystałem bramkę quad NOR. Użyłem wszystkich bramek. Tylko dwa zostały użyte do wygenerowania sygnałów stanu, ale chip nie ma możliwości zasilania, aby faktycznie sterować diodami LED. Są w stanie zatopić tyle mocy, więc wykorzystałem pozostałe dwie bramki NOR jako falowniki i jako takie podłączyłem diody LED. Ponieważ jedna dioda LED służy do wskazywania odczytów, a druga do zapisów, a żądanie odczytu i zapisu nie wystąpi w tym samym czasie, udało mi się uniknąć użycia tylko jednego rezystora dla obu diod. Jeśli chodzi o sygnały, które musiałem zdekodować, to również było dość łatwe. Chciałem, aby wszystkie żądania odczytu pamięci były wskazywane, więc pierwsza bramka NOR miała na swoich wejściach /MREQ i /RD. Status zapisu był trochę trudniejszy, ale równie łatwy. Nadal używałem /MREQ jako jednego wejścia, ale użycie /WR jako drugiego spowodowałoby drobny niuans, którego chciałem uniknąć. Wskazałby WSZYSTKIE żądania zapisu. Chciałem tylko tych, które faktycznie przeszły. Więc jak mam to zrobić? Cóż, pamiętasz, jak mam skonfigurowany system, aby można było zapisać tylko pamięć RAM? Użyłem RAM/CE jako drugiego wejścia do bramki NOR. Oznacza to, że dioda LED będzie się świecić tylko wtedy, gdy wybrana jest pamięć RAM i wykonywane jest żądanie zapisu. Pod względem koloru diody LED wybrałem pomarańczowy jako wskaźnik odczytu (ale znalazłem tylko żółte) i czerwony jako wskaźnik zapisu.
Krok 4: Wejście i wyjście
W poprzednim kroku mogłeś zauważyć, że dodałem już niektóre pozostałe komponenty do płyty. Zarezerwowałem miejsce, aby przypadkowo nie ułożyć przewodów tam, gdzie chciałem komponent (więc musiałbym znaleźć nową lokalizację dla tego komponentu). Być może zauważyłeś również, że zostawiłem przełączniki wejściowe na miejscu i podłączyłem je do szyny zasilającej. Uznałem, że oryginalna lokalizacja jest idealnym miejscem i postanowiłem umieścić wyjściowe diody LED w pobliżu (powyżej). Po prawej stronie wyświetlacza słupkowego znajduje się zatrzask wejściowy. Powyżej znajduje się zatrzask wyjściowy, a po jego lewej stronie znajduje się sterownik LED. Zacząłem od podłączenia wyświetlacza do sterownika, ponieważ było to najłatwiejsze do zrobienia. Następnie podłączyłem przełączniki do strony wejściowej zatrzasku wejściowego. Następnie podłączyłem stronę wyjściową zatrzasku wyjściowego do sterownika LED. To może wydawać się niezręcznym rozkazem, aby je podłączyć, ale było to z jakiegoś powodu. Wejście zatrzasku wyjściowego miało być podłączone do szyny danych oraz wyjście zatrzasku wejściowego. Pomysł polegał na połączeniu wyjść zatrzasku wejściowego z wejściami zatrzasku wyjściowego, co zrobiłem. Wtedy wszystko, co musiałem zrobić, to podłączyć ten bałagan do magistrali danych. Nie miało znaczenia, gdzie fizycznie przebiegały te połączenia, ponieważ wszystkie byłyby połączone elektrycznie. Komputer jest już prawie gotowy.
Krok 5: Resetowanie i kończenie wejścia i wyjścia
Przepraszamy, brak zdjęć dla tego kroku. Zapoznaj się z poprzednim krokiem dla zdjęć.
n
Być może zauważyłeś na ostatnim zdjęciu z poprzedniego kroku, że miałem zielony przycisk i zainstalowałem inny układ logiczny. Chip to bramka OR. Do generowania sygnału /WAIT wykorzystywane są dwie bramki. Cóż, sygnał jest generowany przez OR-ing /IORQ i /RD z procesora. Wyjście jest podawane do drugiej bramki, gdzie ponownie trafia do OR do przycisku. Przycisk powoduje, że wejście bramki jest wysokie, a tym samym wyjście jest wysokie. To wyjście jest podawane na pin procesora /WAIT. Gdy nie jest wciśnięty, rezystor utrzymuje stan niski na wejściu. Początkowo używałem rezystora 10K, ale LS32 faktycznie podawał napięcie na wejście. Rezystor nie spadł wystarczająco nisko i musiałem go wymienić na 1K. W każdym razie pomysł polega na tym, że gdy wykonywane jest żądanie odczytu IO, pierwsza i druga bramka OR nakazują procesorowi czekać. Po ustawieniu przełączników wejściowych na dowolne, naciskasz przycisk i powoduje to wyjście procesora z warunku oczekiwania. Zielona dioda „wejście”, jak to nazwałem we wcześniejszym kroku, jest podłączona tak, że gdy pin /WAIT jest niski, zapala się.
n
Ale jeszcze nie skończyliśmy. Przerzutnik wejściowy wymaga sygnału, aby wiedzieć, kiedy dane wejściowe są prawidłowe i powinny zostać wysłane do procesora. Ten pin zegarowy jest aktywny w stanie wysokim. Wcześniej po prostu podłączaliśmy go do przycisku. Jest to nadal ważna opcja, ale tym razem zdecydowałem się umieścić ją na tym samym wyjściu, co druga bramka OR. Ten układ scalony ma również pin /OE, który należy napędzać. Gdyby był trzymany wysoko, nigdy nie wprowadzałby danych do magistrali. Jeśli trzymany nisko, zawsze będzie kierował autobusem. Aby to naprawić, po prostu użyłem trzeciej bramki OR. Wejścia to /IORQ i /RD, a wyjście trafia bezpośrednio do /OE zatrzasku.
n
Zatrzask wyjściowy również wymaga wysterowania pinu zegara. Ponownie jest aktywny na wysokim poziomie. Na moim schemacie narysowałem czwartą bramkę OR bezpośrednio napędzającą pin za pomocą /IORQ i /WR. Oznaczało to, że pin zegarowy będzie utrzymywany wysoko, dopóki nie zostanie wysłane żądanie zapisu, a następnie przejdzie w dół, a następnie ponownie w górę. To prawdopodobnie byłoby w porządku, ponieważ magistrala danych nadal zawierałaby prawidłowe dane natychmiast po próbie zapisu, ale z inżynierskiego punktu widzenia był to projekt śmieci. Nie zauważyłem tego błędu, dopóki nie zrobiłem ostatnich zdjęć, ale zerwałem to połączenie, a następnie podałem wyjście bramki OR do jednego z nieużywanych falowników z logiki sterowania pamięcią, a następnie podłączyłem jego wyjście do pinu zegara. Naprawiłem też schemat i znalazłem kolejny błąd, który popełniłem. Ja też to poprawiłem.
n
Kiedy to wszystko w końcu zostało zrobione, miałem bardzo małą pracę do wykonania: obwód resetowania. Dodałem przycisk do płytki i użyłem rezystora 10K, aby utrzymać jedną stronę wysoko. Druga strona idzie bezpośrednio na ziemię. Strona utrzymywana wysoko to wyjście /RESET, które trafiło do każdego układu z pinem /RESET (procesor i zatrzask wyjściowy). Aby wykonać reset po włączeniu, dodałem kondensator do wyjścia /RESET. Chodzi o to, że rezystor o dużej wartości spowodowałby powolne ładowanie stosunkowo dużego kondensatora i utrzymywanie niskich pinów /RESET przez pewną liczbę cykli zegara (procesor potrzebuje czterech cykli zegara). Prawdopodobnie już się domyślasz, jaka jest ujemna strona tego obwodu. To ten sam minus, co poprzednia wersja, ponieważ to ten sam obwód. Po naciśnięciu przycisku kondensator jest zasadniczo zwarty przez przycisk. Jest to niekorzystne zarówno dla czapki, jak i przycisku, więc jeśli chcesz, aby twoja konstrukcja była trochę bardziej trwała, możesz ją przeprojektować. Myślałem o kolejnym 555 timerze ustawionym w trybie monostabilnym. Ale z tym, obwód komputerowy jest teraz gotowy. Tak. Teraz trzeba go zaprogramować.
Krok 6: Programowanie
Programowanie tego było koszmarem. Zbudowałem programator Arduino EEPROM. To nie zadziałało. Zbudowałem kolejny na podstawie cudzego projektu i kodowania. Nadal nie działało. Wróciłem do sprawdzonej metody ręcznego ustawiania adresów i bajtów danych. Jakoś to schrzaniłem. Spróbowałem ponownie i nadal się myliłem. Wróciłem jeszcze raz i odkryłem, że jest przesunięty o jeden bajt, więc poprawiłem to i dzięki Bogu w końcu zadziałało.
n
Jeśli chodzi o rzeczywisty program, wygląda na to, że jest bardzo złożony i trudny do naśladowania, ale tak nie jest. Właściwie to całkiem proste. Połowa z tego to kopiowanie liczb. Druga połowa jest dzielona między 16-bitową matematykę, skoki warunkowe i jeszcze więcej kopiowania liczb. Pozwól, że przejdę przez to i powiem ci, jak to działa.
n
Inicjalizacja ustawia tylko niektóre wartości rejestru do wykorzystania przez program. Pętla programu jest nieco bardziej złożona, ale niewiele. Najpierw przyjmuje dane wejściowe do rejestru A na porcie 00. Następnie rejestr E zostaje zapisany do pamięci. W pierwszych dwóch pętlach rejestr E zawiera niepotrzebne dane, więc próbujemy zapisać je w ostatnich dwóch bajtach przestrzeni ROM, ponieważ w rzeczywistości nie zostaną zapisane; wskaźnik adresu (IY) jest następnie zwiększany. Wartość przechowywana w D jest następnie przenoszona do E w celu późniejszego zapisania. A jest następnie ładowane do D, a L i E jest kopiowane do H. HL to miejsce, w którym porównywanie wartości odbywa się poprzez odejmowanie i sprawdzanie ZF (flaga zero). Pierwsza porównywana wartość jest przechowywana w rejestrach B i C. B i C są traktowane jako jeden 16-bitowy rejestr BC. Jeśli wartości są takie same, program przeskakuje prosto do przestrzeni RAM, gdzie zakłada się, że znajduje się kod użytkownika. Jeśli kod w BC nie jest zgodny, HL jest ponownie ładowany z początkowymi wartościami z D i E i jest ponownie porównywany z wartością w SP w ten sam sposób, w jaki był porównywany z BC. Jeśli jest zgodny, ma ten sam wynik, ale w pamięci zapisywane są trzy dodatkowe bajty. Bajty to kod, który powoduje, że procesor wraca do samego początku swojego programu (reset oprogramowania). Jeśli jednak drugie porównanie nie było zgodne, program zapętla się do miejsca, w którym pobiera wartość od użytkownika.
n
LD SP, EDBFH; kod exe (dodaje skok)
n
LD IY, FFEH; wskaźnik pamięci początkowej do przechowywania kodu
n
LDBC, EDC3H; kod exe (bez pętli)
n
pętla; dyrektywa asemblera, więc nie musimy wiedzieć, gdzie w pamięci znajduje się ta część
n
w A, (00H); pobierz dane programu
n
LD (IY+00H), E; E zawiera kod do zapisania
n
INC IY; przejdź do następnej lokalizacji w pamięci
n
LD E, D; ld D ile to E
n
LD D, A; ld A do D
n
LDH, E; ld E ile to H
n
LDL, D; ld D ile to L
n
LUB A; zresetuj flagę przeniesienia
n
SBC HL, BC; zwraca 0, jeśli wprowadzono kod exe 2
n
JP Z, 1000H; jeśli tak, przejdź do programu i uruchom go
n
LDH, E; w przeciwnym razie odśwież je do odpowiednich wartości
n
LD L, D
n
LUB A; pierwsze odejmowanie mogło mieć ustawioną flagę przeniesienia. Wyczyść to
n
SBC HL, SP; zwraca 0, jeśli wprowadzono kod exe 1
n
JP NZ, pętla; jeśli nie, powtórz proces (zaczynając od uzyskania wartości)
n
LD (IY+00H), C3H; w przeciwnym razie wstrzyknij kod skoku na końcu programu użytkownika
n
LD (IY+01H), 00H; skok w zasadzie działa jak reset oprogramowania
n
LD (IY+02H), 00H; jest to pełny reset w przypadku modyfikacji rejestrów
n
JP 1000H; przejdź do i uruchom program użytkownika