Cuprins:
2025 Autor: John Day | [email protected]. Modificat ultima dată: 2025-01-13 06:58
În trecut, am scris un ghid despre cum să construiți un computer bazat pe Z80 și am proiectat circuitul pentru a fi cât se poate de simplist, astfel încât să poată fi construit cât mai ușor. Am scris și un mic program folosind aceeași idee de simplitate. Acest design a funcționat destul de bine, dar nu am fost total mulțumit de el. Am început cu rescrierea unui program pentru acesta care permitea programarea acestuia în timpul rulării. Aceasta a fost să mă permită să testez bucăți de cod fără a fi nevoie să îl dedic EEPROM, ceea ce, la rândul meu, ar necesita să reprogramez EEPROM. Nu mi s-a părut o idee distractivă. Apoi am început să mă gândesc la spațiile de memorie. Dacă aș dori să interfațez o bucată de hardware (IO în principal), o bucată de cod ar putea depăși cantitatea de spațiu de memorie disponibilă pentru sistem. Amintiți-vă, designul a folosit doar octetul inferior al magistralei de adrese și apoi bitul inferior al octetului înalt a fost utilizat pentru a selecta între spațiile ROM și RAM. Aceasta însemna că aveam doar 253 de octeți de spațiu de utilizat. S-ar putea să întrebați de ce 253 în loc de 256. Asta pentru că noul meu cod injectează trei octeți de date la sfârșitul unui program scris (acest lucru va fi acoperit mai târziu, deoarece l-am modificat pentru a lucra la noul design).
n
M-am întors peste vechile mele scheme pentru a vedea ce se mai întâmpla. Am găsit un mic defect la circuitul de selecție a memoriei, pe care îl voi acoperi când voi ajunge acolo. Versiunea simplificată: toate cererile de scriere ar trece de fapt, deși au fost întotdeauna introduse în RAM. Probabil că acest lucru nu merită să vă îngrijorați, dar am vrut să fac acest lucru corect de data aceasta. Și odată cu asta, am început să desenez o nouă schemă. Cele două imagini atașate acestei pagini sunt înainte și după circuitul real. Am curățat atât de mult cablajul spaghetelor, încât nu este amuzant.
n
Dacă ați urmat împreună cu depunerea mea originală și intenționați să urmați împreună cu aceasta, mă veți urî. Dacă începi proaspăt, atunci ai noroc. Doar apucați părțile din listă (sau echivalentul lor) și urmați-le.
Provizii:
LM7805 - Regulator de 5 volți Z80 - CPU; creierele sistemuluiAT28C64B - EEPROM. Stocare de date „permanentă” utilizată pentru firmware-ul computerului IDT6116SA - SRAM; utilizat pentru stocarea codului de utilizator și / sau stocării datelor generaleNE555 - Ceas sistem74HC374 - Octal D-Latch cu / OE; folosit ca cip de intrare74LS273 - Octal D-Latch cu / MR; chip de ieșire TLC59211 - Cip de driver LED (utilizat astfel încât 74LS273 să poată conduce LED-uri, deoarece singur nu este capabil de ieșirea curentă) MC14572 - Acesta este un cip „Line Driver”, dar l-am găsit perfect pentru logica de control al memoriei. Are 4 invertoare și o poartă NAND și NOR construită în74LS32 - Poarta Quad SAU CD4001 - Poarta Quad NORCD4040 - Contor cu 12 etape; Desenat, dar nu este implementat divizorul de ceas (pentru rularea sistemului la viteze de ceas mai mici) 2 rezistențe de 10K Ohm - Unul este utilizat în circuitul de timer 555, deci utilizați orice valoare doriți pentru acesta 4 Rezistoare de 1K Ohm - Unul este utilizat pentru Circuit cu temporizator 555, deci folosiți orice doriți pentru el. Un altul este utilizat pentru conducerea LED-urilor, deci variați și dacă doriți 8x330 Ohm Rezistor Bus8x10K Ohm Resistor Bus11 LED-uri - Trei sunt utilizate pentru starea sistemului și celelalte opt sunt ieșiri. Pentru cele 8, am folosit un afișaj grafic cu bare (HDSP-4836) 4 condensatoare - Două sunt utilizate LM7805; 0,22 uF și 0,1 uF. Unul este pentru temporizatorul 555, deci folosiți ceea ce simțiți că este corect. Ultimul este pentru resetarea la pornire; 100uF2 N. O. Butoane - Unul este utilizat pentru intrare, celălalt pentru resetare 8 comutatoare SPST DIP - Intrare date; Am folosit Piano Key styleWire. Multă sârmă
n
NOTĂ: versiunea MC14572 prin gaură este învechită, dar versiunea SMD este încă activă (nici măcar starea „nu pentru un design nou”), deci este posibil să fie nevoie să achiziționați o placă de circuit pentru a vă permite să o utilizați. Un al doilea 74LS32 poate fi utilizat în locul MC14572 (consultați schema „circuit de selecție a memoriei” din ible-ul anterior)
Pasul 1: Prezentare rapidă a modificărilor + Scheme
Cum să citiți schemele: O săgeată îndreptată către un cip este o intrare: Intrare> -O săgeată îndreptată spre un cip este o ieșire: Ieșire <-Autobuzele folosesc o linie în loc de o săgeată: Autobuz | -
n
Majoritatea jetoanelor au fost desenate cu pinouturile lor exacte. Micul dip a fost desenat pe aceste jetoane. Majoritatea jetoanelor au, de asemenea, numere și etichete. S-ar putea să fie cam greu de citit. Creionul meu devenea plictisitor.
n
În ceea ce privește conexiunile de circuite, aspectul noului design este în mare parte neschimbat față de original. Am conectat nibble-ul inferior al byte-ului de adresă la memorie și apoi am folosit bitul low al nibble-ului superior (A12) pentru selecția RAM / ROM. Acest lucru a însemnat că spațiul ROM a trecut de la 0000-00FF până la 0000-0FFF. Spațiul RAM a trecut de la 0100-01FF la 1000-1FFF. De asemenea, am schimbat logica Memory Control pentru un design mai bun și am adăugat două LED-uri de stare noi (și unele logici de lipici). De asemenea, am desenat (dar nu am cablat) un circuit divizor de ceas. Era să îndeplinească două funcții. Funcția evidentă este de a împărți frecvența ceasului în jos. Cealaltă funcție este pentru PWM (Pulse Width Modulation), deoarece 555 nu generează unde cu 50% cicluri de funcționare. Asta nu contează cu adevărat în acest circuit, dar dacă doriți să utilizați ceasul pentru a conduce unele LED-uri, veți observa cu siguranță efectele (unul (set de) LED-uri vor fi mai slabe decât celălalt). Întregul rest al circuitelor este esențial neschimbat.
Pasul 2: CPU, memorie și control al memoriei
Aceasta este partea în care cititorii versiunii mele anterioare mă urăsc. În versiunea originală, am aruncat cam piese pe tablă într-un loc în care păreau că ar impune mici probleme cu conectarea. Rezultatul arăta ca și cum cineva ar fi aruncat o farfurie de spaghete pe el și ar fi fost ca „fire!” Am vrut să-l curăț puțin, așa că am început prin a rupe totul, în afară de CPU, RAM și ROM. Am tras aproape întregul circuit de intrare, circuitul de ieșire și logica lipiciului. Aproape că mă durea să fac, dar era necesar. Am lăsat intacte toate conexiunile de date și octetul inferior al magistralei de adrese. Am conectat apoi următorii patru biți ai magistralei de adrese (A8-A11) la cipul ROM. De această dată am avut grijă să ocolesc cipul pentru a face mai ușoară tragerea în sus pentru reprogramare. De asemenea, am sărit conexiunile de adresă până la cipul RAM.
n
Cu asta în afara drumului, a trebuit să obțin acum logica de control al memoriei conectată. În schema originală, conectasem linia procesorului / MREQ direct la / CE la ambele cipuri de memorie, apoi am conectat / WR la RAM / WE. Apoi am avut procesorul / RD și / MREQ în mod logic SAU împreună, precum și A9. În esență, a fost configurat astfel încât toate solicitările de memorie să activeze atât RAM, cât și ROM, dar A9 a fost folosit pentru a selecta care dintre cipurile / OE au fost selectate. Acest lucru a fost în regulă și totul, deoarece cipurile vor rămâne inactive până când nu va fi făcută o cerere de memorie și apoi doar unul / OE va fi activ în timpul unei cereri de citire. Acest lucru a împiedicat diafragma, dar a introdus o nuanță incomodă. Deoarece A9 a fost utilizat doar pentru a determina ce cip scoate date și pentru că CPU-ul avea acces direct la pin-ul RAM / WE, toate cererile de scriere ar trece. Acest lucru a fost în regulă pentru ROM, deoarece modul său de scriere este inhibat prin legarea / WE direct la sursa de 5V. Cu toate acestea, memoria RAM ar fi scrisă indiferent de A9. Aceasta a însemnat că o încercare de scriere într-o locație de spațiu ROM ar scrie în aceeași locație în spațiul RAM.
n
O soluție pentru acest lucru ar fi să reconectați logica de control, astfel încât CPU să aibă acces direct la pinii / chips-urilor OE și / WE și apoi să utilizeze MREQ și A12 pentru a selecta ce chips-uri / CE a fost condus. M-am dus cu această idee, dar în loc să folosesc patru porți NOR și un invertor precum designul original, am găsit un cip mic incomod care era perfect pentru sarcină. A trebuit să creez un circuit care să folosească doar porțile logice disponibile în cip, dar a fost destul de ușor. A12 se alimentează direct într-o poartă NAND și o poartă NOR. / MREQ este alimentat în poarta NOR și complimentul său este alimentat în poarta NAND. Poarta NAND este utilizată pentru a conduce / CE pentru RAM, iar ieșirea NOR este inversată și utilizată pentru a conduce ROM / CE. Acest lucru face ca / MREQ să fie scăzut înainte de selectarea oricărui cip și apoi A12 alege care este selectat. Cu această configurare, acum orice cerere de scriere pe ROM nu va face nimic. De asemenea, economisește energie deoarece este activ un singur cip în loc de ambele. În ceea ce privește cipul logic în sine, avem încă două invertoare neutilizate în interior. Unul se va obișnui mai târziu, dar vom ajunge acolo când vom ajunge acolo.
Pasul 3: LED-uri de stare ale sistemului
Înainte de a începe acest proiect, încercam să interacționez cu un anumit IC, dar aveam probleme cu el. Nu sunt sigur de ce se întâmpla, am folosit un LED de montare pe panou pentru a testa (unul dintre acele ansambluri care are un rezistor încorporat). Făcând acest lucru mi-a dat o idee de nostalgie, care este încă folosită astăzi: LED-urile de stare folosite pentru a indica dacă memoria a fost citită sau scrisă. Acesta trebuia folosit împreună cu LED-ul de intrare pe care îl aveam deja. LED-ul de intrare a fost conectat la generatorul de semnal / WAIT pentru a ne indica faptul că sistemul așteaptă intrarea (voi ajunge acolo, nu vă faceți griji). Am luat în considerare adăugarea unui LED pentru a indica o scriere IO, dar m-am gândit că LED-urile de ieșire care se schimbă ar fi deja un mare indicator al acestui lucru. Gândindu-mă la asta, s-ar putea să îl adaug încă. Cu toate acestea, consider util să știu dacă memoria este citită sau scrisă. Ei bine, oricum este util pentru depanarea programelor. De fapt, am folosit-o intens ca atare atunci când încerc să pun programul în funcțiune: „de ce scrie în memorie? Nu ar trebui să facă asta încă!”
n
Pentru a controla aceste LED-uri, am folosit poarta quad NOR. Am folosit toate porțile. Doar două au fost utilizate pentru a genera semnalele de stare, dar cipul nu are capacitățile de alimentare pentru a conduce efectiv LED-urile. Sunt capabili să scadă atât de multă putere, așa că am folosit celelalte două porți NOR ca invertoare și am conectat LED-urile ca atare. Deoarece un LED este folosit pentru a indica citirile și celălalt pentru scrieri, iar o cerere de citire și scriere nu va avea loc în același timp, am reușit să scap cu utilizarea unui singur rezistor pentru ambele LED-uri. În ceea ce privește semnalele de care aveam nevoie pentru a decoda, a fost, de asemenea, suficient de ușor. Am vrut ca toate cererile de citire a memoriei să fie indicate, așa că prima poartă NOR avea / MREQ și / RD pe intrările sale. Starea de scriere era puțin mai complicată, dar la fel de ușoară. Încă am folosit / MREQ ca o intrare, dar utilizarea / WR ca cealaltă ar provoca o nuanță minoră pe care am vrut să o evit. Ar fi indicat TOATE cererile de scriere. Îmi doream doar cele care au trecut de fapt. Deci, cum aș face asta? Amintiți-vă cum am configurat sistemul, astfel încât să se poată scrie doar memoria RAM? Am folosit RAM-urile / CE ca cealaltă intrare la poarta NOR. Aceasta înseamnă că LED-ul se va aprinde numai atunci când este selectată memoria RAM și se face o cerere de scriere. În ceea ce privește culoarea LED-ului, am ales portocaliul ca indicator de citire (dar am găsit doar galbeni) și roșu ca indicator de scriere.
Pasul 4: Intrare și ieșire
În pasul anterior, este posibil să fi observat că am adăugat deja o parte din restul componentelor pe placă. Rezervam spațiul, astfel încât să nu plasez din greșeală firele unde doream o componentă (așa că ar trebui să găsesc o nouă locație pentru componenta respectivă). Este posibil să fi observat, de asemenea, că am lăsat întrerupătoarele de intrare la locul meu și am fost conectat la șina de alimentare. Am decis că locația inițială era locul perfect și am decis să plasez LED-urile de ieșire în apropiere (deasupra). În dreapta afișajului barei este zăvorul de intrare. Deasupra se află zăvorul de ieșire, iar în stânga acestuia se află driverul LED. Am început prin conectarea afișajului la șofer, deoarece acesta a fost cel mai ușor de făcut. Apoi am conectat comutatoarele la partea de intrare a zăvorului de intrare. Apoi am conectat partea de ieșire a zăvorului de ieșire la driverul LED. Acest lucru poate părea o ordine incomodă pentru a obține aceste cabluri, dar a fost dintr-un motiv. Intrarea zăvorului de ieșire urma să fie conectată la magistrala de date, precum și ieșirea zăvorului de intrare. Ideea a fost de a conecta ieșirile de blocare de intrare la intrările de blocare de ieșire, ceea ce am făcut. Apoi tot ce trebuia să fac era să conectez mizeria aia la magistrala de date. Nu a contat unde au mers fizic aceste conexiuni, deoarece toate ar fi conectate electric. Calculatorul este aproape gata.
Pasul 5: Resetați și finalizați intrarea și ieșirea
Ne pare rău, nu există poze pentru acest pas. Consultați pasul anterior pentru fotografii.
n
Poate că ați observat în ultima poză a pasului anterior, am avut un buton verde și un alt cip logic instalat. Cipul este poarta SAU. Două porți sunt utilizate pentru a genera semnalul / WAIT. Ei bine, se generează semnalul prin OR-ing / IORQ și / RD de la procesor. Ieșirea este alimentată în a doua poartă, de unde ajunge OR din nou la un buton. Butonul aduce intrarea porții la mare, aducând astfel ieșirea la mare. Această ieșire este alimentată la procesoare / pinul WAIT. Deși nu este apăsat, un rezistor menține intrarea scăzută. Am folosit inițial un rezistor de 10K, dar LS32 punea de fapt tensiune pe intrare. Rezistorul nu l-a scăzut suficient de jos și a trebuit să-l înlocuiesc cu un 1K. Oricum, ideea este că atunci când se face o cerere de citire IO, prima și a doua poartă SAU îi spune procesorului să aștepte. Odată ce ați setat comutatoarele de intrare la orice doriți, apăsați butonul și acesta scoate CPU din condiția de așteptare. LED-ul verde de „intrare”, așa cum l-am numit într-un pas anterior, este conectat astfel încât atunci când pinul / WAIT să scadă, să se aprindă.
n
Dar încă nu am terminat. Flip flop-ul de intrare are nevoie de un semnal pentru a-l anunța când datele de intrare sunt valide și ar trebui să fie trimise la CPU. Acest ac de ceas este activ la nivel înalt. Înainte, tocmai l-am conectat la buton. Aceasta este încă o opțiune validă, dar de data aceasta am ales să o pun pe aceeași ieșire ca a doua poartă SAU. Acest CI are, de asemenea, un pin / OE care trebuie acționat. Dacă ar fi menținut sus, nu ar introduce niciodată date în autobuz. Dacă ar fi ținut jos, ar fi întotdeauna conducerea autobuzului. Pentru a remedia acest lucru, am folosit pur și simplu o a treia poartă SAU. Intrările sunt / IORQ și / RD, iar ieșirea merge direct la zăvor / OE.
n
Zăvorul de ieșire are nevoie și de știftul de ceas pentru a fi acționat. Din nou, este activ ridicat. În schema mea, am desenat a patra poartă SAU care conduce direct pinul folosind / IORQ și / WR. Aceasta a însemnat că știftul ceasului va fi ținut sus până când va fi făcută o cerere de scriere, apoi va coborî din nou, apoi din nou. Probabil că acest lucru ar fi fost în regulă, deoarece autobuzul de date ar fi avut în continuare date valide pe el imediat după încercarea de scriere, dar din punct de vedere tehnic, a fost un proiect de gunoi. Nu am observat această eroare decât după ce am luat ultimele poze, dar am rupt acea conexiune și apoi am alimentat ieșirea porții SAU într-unul din invertoarele neutilizate din logica de control a memoriei, apoi i-am conectat ieșirea la pinul de ceas.. De asemenea, am remediat schema și am găsit o altă eroare pe care am comis-o. L-am corectat și eu.
n
Cu toate acestea în sfârșit făcute, am avut o cantitate foarte mică de lucru: circuitul de resetare. Am adăugat un buton pe placă și am folosit un rezistor de 10K pentru a ține o parte înaltă. Cealaltă parte merge direct la sol. Partea susținută este ieșirea / RESET, care se îndrepta către fiecare cip cu un pin / RESET (blocarea procesorului și a ieșirii). Pentru a realiza resetarea la pornire, am adăugat un condensator la ieșirea / RESET. Ideea este că rezistența cu valoare mare ar face ca condensatorul relativ mare să se încarce încet și să mențină pinii / RESET scăzut pentru o anumită cantitate de cicluri de ceas (CPU are nevoie de patru cicluri de ceas). Probabil că puteți ghici deja care este partea negativă a acestui circuit. Este același negativ ca și versiunea anterioară, deoarece este același circuit. Când butonul este apăsat, condensatorul este scurtcircuitat prin buton. Acest lucru este rău atât pentru capac cât și pentru buton, așa că, dacă doriți să faceți construcția puțin mai permanentă, vă recomandăm să o reproiectați. Mă gândeam la un alt 555 temporizator configurat în modul monostabil. Dar odată cu aceasta, circuitul computerului este acum terminat. Yay. Acum are nevoie de programat.
Pasul 6: Programare
Programarea acestui lucru a fost un coșmar. Am construit un programator Arduino EEPROM. Nu a funcționat. Am construit un altul bazat pe designul și codificarea altcuiva. Încă nu a funcționat. M-am întors la metoda încercată și adevărată de a seta manual adresele și octeții de date manual. Cumva, am încurcat asta. Am încercat din nou și totuși am greșit. M-am întors din nou și am descoperit că este oprit cu un singur octet, așa că l-am corectat și în cele din urmă a funcționat, slavă Domnului.
n
În ceea ce privește programul real, se pare că este foarte complex și greu de urmat, dar nu este. De fapt, este destul de simplu. Jumătate din el copiază numere în jur. Cealaltă jumătate este împărțită între matematică pe 16 biți, salturi condiționale și totuși mai multe numere de copiere din jur. Așa că permiteți-mi să o parcurg și să vă spun cum funcționează.
n
Inițializarea stabilește doar câteva valori de registru pentru utilizare de către program. Bucla de program este un pic mai complexă, dar nu întreagă. În primul rând, acceptă intrarea în registrul A de pe portul 00. Apoi, registrul E este scris în memorie. În primele două bucle, registrul E conține date nedorite, așa că încercăm să le scriem în ultimii doi octeți de spațiu ROM, deoarece nu vor fi de fapt scrise; indicatorul de adresă (IY) este apoi incrementat. Valoarea stocată în D este apoi mutată în E pentru a fi scrisă în continuare. A este apoi încărcat în D și L și E este copiat în H. HL este locul în care compararea valorii are loc prin scădere și verificarea ZF (zero flag). Prima valoare comparată cu este stocată în registrele B și C. B și C sunt tratate ca un singur registru pe 16 biți, BC. Dacă valorile sunt aceleași, atunci programul sare direct în spațiul RAM, unde se presupune că se află codul utilizatorului. Dacă codul din BC nu se potrivește, atunci HL este reîncărcat cu valorile inițiale din D și E și se compară din nou cu valoarea din SP în același mod în care a fost comparat cu BC. Dacă este o potrivire, are același rezultat, dar trei octeți suplimentari sunt înregistrați în memorie. Octetii sunt un cod care face ca procesorul să revină la începutul programului său (o resetare a software-ului). Cu toate acestea, dacă cea de-a doua comparație nu a fost potrivită, programul se apropie de locul în care obține o valoare de la utilizator.
n
LD SP, EDBFH; cod exe (adaugă salt)
n
LD IY, FFEH; indicator de memorie inițial pentru stocarea codului
n
LD BC, EDC3H; cod exe (fără buclă)
n
bucla; directivă de asamblare, deci nu trebuie să știm unde se află această parte în memorie
n
IN A, (00H); obțineți datele programului
n
LD (IY + 00H), E; E conține cod de stocat
n
INC IY; mutați la următoarea locație de memorie
n
LD E, D; ld D în E
n
LD D, A; ld A în D
n
LD H, E; ld E în H
n
LD L, D; ld D în L
n
SAU A; resetează steagul de transport
n
SBC HL, BC; returnează 0 dacă a fost introdus codul exe 2
n
JP Z, 1000H; dacă da, săriți și executați programul
n
LD H, E; în caz contrar, actualizați-le la valori adecvate
n
LD L, D
n
SAU A; prima scădere poate fi setată steagul carry. Ștergeți-l
n
SBC HL, SP; returnează 0 dacă a fost introdus codul exe 1
n
JP NZ, buclă; dacă nu, repetați procesul (începând cu obținerea unei valori)
n
LD (IY + 00H), C3H; în caz contrar, injectați un cod de salt la sfârșitul programului utilizator
n
LD (IY + 01H), 00H; jump acționează practic ca o resetare a software-ului
n
LD (IY + 02H), 00H; este o resetare completă în cazul în care registrele au fost modificate
n
JP 1000H; săriți și executați programul utilizatorului