Post on 07-Jun-2015
description
1. Dichiarazione delle variabili (e delle costanti)
La dichiarazione delle variabili è generalmente la prima cosa da fare quando si vuole scrivere la parte software di un compito d’esame. Se si vuole specificare un valore specifico o una costante, quale può essere l’indirizzo a cui si trova, per esempio, una certa risorsa di I/O o il PIC, sarà necessario fare uso della parola EQU: es. Display EQU 80H Questo fa comodo in quanto, all’interno del codice, diventerà facile ed espressivo effettuare operazioni coi comandi IN e OUT: es. OUT Display AX (manda la word presente in AX verso BufferOUT, cioè verso 80H) Per quale tipo di dispositivi bisogna specificare l’indirizzo in questo modo?
• Per i dispositivi di I/O coinvolti nelle varie operazioni (ad es. una centralina ABS1, un pulsante, un componente 244, un componente 373);
• per il PIC (che dovrà essere anche programmato a parte); • per tutti quei dispositivi, insomma, che sono attaccati al bus dati e che hanno necessità di
essere attivati/disattivati (o comunque interfacciati). NOTA: In genere si utilizza EQU per indicare l’indirizzo o la locazione di un dispositivo di I/O, ma non il dato che tale dispositivo può fornire o l’informazione che esso stesso deve gestire: per quello bisognerà definire una variabile (cioè un tipo “dato”, v. di seguito). EQU può anche utilizzato anche per definire le parole di comando da dare al PIC (le OCW e le ICW): es. MASKING EQU FFH ; tutte le interruzioni sono mascherate 8259_OCW1 EQU C1H ; alla locazione C1H è possibile “scrivere” OCW1 …. MOV AL, FFH ; scriviamo in OCW1 passando per un registro OUT 8259_OCW1, AL Bisognerà invece utilizzare una sintassi del genere es. BUF_IN DB 1024 LedFlag DB 1 (il numero che segue DB è una lunghezza in byte2) per le variabili o i dati, ad es.:
• un flag di fine routine; • un dato caratteristico del problema o, comunque, un dato comunicato da (o da comunicare
a) un dispositivo di I/O (ad es. la velocità angolare di una ruota1, il valore di un display3); • un buffer o un vettore che dev’essere riempito (ad es. in una comunicazione seriale4); • un puntatore che può cambiare valore nel corso dell’esecuzione del programma; • un indice (che di sua natura è qualcosa di variabile).
Si noti che quando nel programma principale si dovranno richiamare le costanti (definite con EQU) o le variabili (definite con DB, DW, etc…): 1 Vedi compito del 7 aprile 2007. 2 Dovremo scegliere 1 se abbiamo un byte, 2 se abbiamo una word (in alternativa si poteva scrivere WORD DW 1, in quanto DW ritaglia uno spazio di memoria in word), 1024 se abbiamo un buffer di 1 KB, etc… Se dobbiamo specificare N kilobytes conviene utilizzare la notazione N*1024 dopo DB. 3 Vedi compito del 7 dicembre 2006. 4 Vedi compito dell’11 gennaio 2007.
• dovremo richiudere fra parentesi quadre [ … ] le variabili, ad es. Index DB 1 …
MOV [Index], SI …
• scrivere così come sono (senza parentesi) le costanti, ad es. InterfacciaParallelo EQU 80H … IN AL, InterfacciaParallelo …
Par superfluo ricordare che, se dobbiamo allocare spazio per una variabile, dobbiamo essere sicuri che tale spazio sia sufficiente. Ad es. se vogliamo memorizzare da qualche parte l’informazione riguardate i secondi da visualizzare sul display di un orologio, un byte sarà sufficiente (i secondi vanno da 0 a 59 e con 8 bit riusciamo a fare tutto). 2. Il PIC (8259)
Il PIC, nome in codice 8259, è un componente della famiglia Intel che si occupa di gestire il meccanismo delle interruzioni: esso è in grado di gestire fino a 8 richieste di interruzione, corrispondenti ai piedini (IR0… IR7). Ad essi andranno a finire i segnali di quei dispositivi (pulsanti, clock, etc..) che devono essere gestiti ad interrupt. Alcune caratteristiche:
• può essere utilizzato singolarmente o in cascata (soluzione avanzata che non tratteremo): se preso da solo gestisce fino a 8 interruzioni mentre nella configurazione più complicata può gestirne fino a 64;
• si tratta di un dispositivo programmabile: possiamo cioè variarne opportunamente il comportamento;
• si interfaccia sia col bus dati (8 bit) che con quello degli indirizzi (1 bit); • è un dispositivo bifronte (rivolto sia verso il processore che verso i dispositivi periferici); • il nocciolo del dispositivo ruota attorno ad alcuni registri ad 8 bit:
o IMR (Interrupt Mask Register): permette di mascherare le richieste di interruzione provenienti da uno o più dispositivi (0 = non mascherato, 1 = mascherato);
o IRR (Interrupt Request
Register) tiene traccia dei dispositivi che hanno fatto una richiesta e che non sono riusciti ancora ad averne riscontro;
o ISR (Interrupt Service
Register): tiene traccia di quali sono le procedure di servizio in corso (se la richiesta i-esima viene servita viene resettato il bit corrispondente in IRR e settato quello in ISR).
SEQUENZA DI INTERRUZIONE:
1. una richiesta arriva all’8259;
2. viene posto ad 1 il corrispondente bit del registro IRR per segnalare che c’è stata una richiesta;
3. si valuta se la richiesta dev’essere eseguita o meno controllando l’IMR; 4. viene avanzata la richiesta di interruzione al processore (attraverso INT); 5. se IF vale 1 il processore conferma la ricezione della richiesta e invia un primo
segnale di INTA; 6. l’8259 fa la cernita di quali sono i dispositivi in attesa di essere serviti e seleziona
quello con priorità più alta; 7. forza ad 1 il corrisponde bit in ISR; 8. forza a 0 il corrispondente bit in IIR; 9. il processore invia un secondo impulso su INTA; 10. l’8259 scrive sul data bus il codice del dispositivo che ha fatto richiesta di
interruzione; 11. il processore riceve il messaggio e attiva la procedura di servizio dell’interruzione.
L’8259 è in grado di riconoscere, se adeguatamente programmato, quali siano le richieste più urgenti (cioè più prioritarie): di default, le richieste che giungono su IR0 sono le più prioritarie e quelle su IR7 le meno prioritarie (maggiore è i e minore è la priorità). Nella modalità di funzionamento detta Fully nested mode, un interrupt più prioritario può sempre interrompere l’esecuzione della procedura di servizio di un interrupt meno prioritario, mentre una richiesta su una linea IRi a minore priorità non viene servita fino al termine delle procedure di servizio associate alle precedenti (e più prioritarie) richieste. Il riconoscimento delle richieste agli IRi (cioè ai piedini) può avvenire a livello oppure sul fronte di salita:
• se rileviamo a fronte l’interrupt viene generato a seguito di una transizione di un ingresso da 0 a 1. Viene quindi generato un interrupt in corrispondenza di ogni fronte positivo di un segnale d’ingresso IRi;
• se rileviamo a livello l’interrupt viene riconosciuto se viene generato un livello logico alto di un ingresso Iri. A differenza della gestione a fronte, se il valore di IRi non si porta a valore logico basso prima dell’invio del comando EOI verrà generato un altro interrupt.
Si noti che, affinché l’interrupt sia riconosciuto come tale, occorre che IRi permanga al valore logico 1 almeno fino al primo fronte negativo del segnale INTA*: al primo abbassamento di quest’ultimo, infatti, il PIC congela lo stato delle richieste e non ne accetta più; sul secondo abbassamento, invece, viene emesso l’interrupt type associato alla richiesta più prioritaria attiva. 3. Programmazione del PIC
La programmazione del PIC passa attraverso la definizione di alcuni comandi di inizializzazione inviati dal processore. In particolare, vengono spedite:
• le ICWs (Initialization Command Words): sono inviate all’inizializzazione. L’ordine delle ICW è fisso e c’è una ben precisa sequenza con la quale devono essere inviate (l’ordine è prestabilito e immutabile).
o ICW1 5 ha alcuni bit notevoli: D3 (LTIM) specifica se il PIC dev’essere sensibile a fronte [= 0] o a livello [=1]; D1 (SNGL) specifica se il dispositivo è in cascata con dispositivi analoghi [=0] o meno [=1]; D0 (IC4) serve per far capire all’8259 se ci sarà una ICW4.
5 L’8259 ha un unico bit d’indirizzo A0; la sequenza ICW1 è riconoscibile rispetto a tutte le altre perché ha D4 = 1.
o ICW2 riguarda la necessità per il processore di comunicare all’8259 le informazioni necessarie per poter poi generare i codici dei dispositivi che hanno fatto richiesta di interruzione. Dopo che l’8259 ha avanzato una richiesta di interruzione al processore, questo - attraverso i due impulsi di INTA - vorrà ricevere un codice (associato al dispositivo che ha fatto la richiesta) che gli permetta di sapere chi ha fatto l’interruzione. Chi dice all’8259 quali siano questi codici è la ICW2 secondo un meccanismo che prevede alcuni vincoli su quali siano questi codici: i codici devono essere contigui fra di loro (numerati in sequenza) e devono iniziare ad un valore multiplo di 8. La parola ICW2 contiene i 5 bit più significativi, comuni agli 8 codici; i 3 bit meno significativi corrispondono all’indice del piedino IR0...7 a cui è connesso il dispositivo. Es: all’inizializzazione del PIC passiamo la stringa
ADDRESS EQU 1FH = 0001 1111 Con questa riga di codice diciamo all’8259 che i cinque bit più significativi sono a 11111. La parola ICW2 avrà quindi valore 1111 1XXX = F8H e gli interrupt types (specificati dai tre bit a X) andranno da F8H a FFH.
o ICW3 viene usata solo quando l’8259 è connesso in cascata.
o ICW4 non è obbligatoria da inviare: se non viene inviata, tuttavia, alcune funzionalità non vengono attivate. Bit notevoli: D4 (SFNM � Special Fully Nested Mode), D1 (AEOI � Automatic End of Interrupt).
• le OCWs (Operation Command Words): possono essere inviate in qualsiasi momento per modificare la modalità di funzionamento del dispositivo. Possono essere inviate in qualunque ordine.
o OCW1 permette al programmatore di caricare un valore nel registro di maschera: in questo modo si possono mascherare (ovvero ignorare) le richieste di interruzione provenienti dai dispositivi. La OCW1 viene caricata direttamente in IMR: se l’i-esimo bit di OCW1 vale 1 si forza infatti ad 1 il bit corrispondente di IMR (e si maschera perciò la rispettiva richiesta).
o OCW2 permette di programmare in maniera diversa attività o aspetti legati alle priorità e all’EOI (End Of Interrupt). Ogni OCW2 permette di fare un’operazione su un canale (ovvero su uno dei piedini IR0..7 su cui arrivano le richieste di interruzione). Possiamo ad esempio voler segnalare all’8259 che è terminata la procedura di servizio della richiesta di interruzione associata ad un certo piedino: nel caso in cui il PIC sia stato spettato con il bit di AOEI = 0 (v. ICW4), è infatti a carico della CPU provvedere a resettare il bit del registro ISR corrispondente all’interrupt appena servito. Per avvisare il PIC mandiamo quindi una OCW2 apposita.
o OCW3 permette di eseguire una serie di operazioni che permettono sostanzialmente di leggere i registri del processore.
Quando in un esercizio è necessario programmare il PIC, bisogna scrivere la seguente sequenza, in cui andiamo a ripescare il caso del compito del 28 marzo 2007. ; Indirizzi [Questa parte non richiede sforzo di ragionamento, basta aggiungere le quantità tra parentesi all’indirizzo esadecimale (“base”) cui è mappato il PIC (nell’esempio 90H)]
ICW1 EQU 90H Base (+ 0) ICW2 EQU 91H Base (+ 1) ICW4 EQU 91H Base (+ 1) OCW1 EQU 91H Base (+ 1) OCW2 EQU 90H Base (+ 0)
; Parole di comando RESET EQU 13H ; questo è fisso, serve a segnalare che l’interrupt viene ; rilevato sul fronte ADDRESS EQU 1FH ; qui vanno messi i bit più significativi dell’interrupt type ; 1F = 11111 � ICW2 1111 1000 = F8H EN_AEOI EQU 1H ; Servirà per definire l’AEOI disabilitato (3H = abilitato) MASK EQU F8H ; Questa parte va scelta con attenzione: in genere si sceglie
; di mascherare i bit relativi ai piedini non utilizzati: ; nell’esempio usiamo 3 piedini (IR0, IR1, IR2) e ; mascheriamo tutti gli altri 1111 1000 = F8H
EOI EQU 20H ; da inviare per segnalare la fine della routine
; Inizializzazione CLI OUT ICW1, RESET ; mettiamo in ICW1 0001 0011 OUT ICW2, ADDRESS ; mettiamo in ICW2 0001 1111 OUT ICW4, EN_AEOI ; mettiamo in ICW4 0000 0001 (no AEOI) OUT OCW1, MASK ; mettiamo in OCW1 1111 1000 (maschera) STI
4. Interrupt vs. polling
Tutti i sistemi a microprocessore eseguono operazioni di input/output: questo fa scaturire una questione relativa a quando e con che cadenza il processore debba andare a prendere un nuovo dato da un dispositivo I/O (ad es. da una porta seriale). Il problema della sincronizzazione fra le varie periferiche è quindi suscettibile di due possibili soluzioni:
• polling: il processore legge periodicamente il registro di stato del dispositivo tramite un ciclo software, il quale scandisce di continuo il registro di stato e quant’altro serva a capire quale sia lo stato del dispositivo periferico. Es. secsGoesOn: CMP [Setting],1 ; è premuto il pulsante set? JE exit JMP secsGoesOn Questa operazione culmina nel momento in cui il dispositivo richiede il servizio (nell’es. [Setting] va ad 1). Es(2). MOV DX, prSTAT ; prSTAT = stato della periferica (stampante) pr_n_ready: IN AL, DX ; legge lo stato della stampante TEST AL, sERR JZ pr_error ; errore
TEST AL, sBUSY JZ pr_n_ready ; busy: acquisizione periodica dello stato della stampante
VANTAGGI: meccanismo semplice, non si richiede particolare supporto hardware, basta che il dispositivo abbia un suo registro di stato attraverso il quale comunicare col processore: SVANTAGGIO: il processore spreca tantissimi cicli per fare l’operazione di controllo, inutile dal punto di vista della computazione. Se abbiamo più elementi connessi a polling il
processore deve scandire tutti gli elementi e spreca tantissimi cicli; questo implica inoltre che un dispositivo debba attendere che il processore interroghi il suo registro di stato prima di essere servito, ma se i dispositivi sono molto può essere che debba aspettare a lungo (elevata latenza). La tipica configurazione di un dispositivo gestito a polling, ad es. un pulsante, è quella a fianco (il pulsante SET può scrivere sul bus quando viene abilitato da CS_SET# e IORDC#). Si noti che il nostro pulsante comunica direttamente col bus dati (mentre un dispositivo gestito ad interrupt dev’essere collegato al PIC 8259).
• interrupt: il processore non interroga i dispositivi di I/O, ma sono questi ultimi a contattare il processore tramite una richiesta di interruzione (interrupt). Esiste infatti un meccanismo supportato in hardware attraverso il quale le periferiche fanno sapere al processore che sono pronte ad essere servite (v. meccanismo delle interruzioni). VANTAGGI: il meccanismo dell’interrupt permette al processore di gestire liberamente il suo tempo tra un’interruzione e l’altra. Questa soluzione è quindi più performante, in quanto non fa sprecare al microprocessore neanche un ciclo per fare le insistenti e spesso inconcluse richieste a polling. Inoltre, possiamo stabilire criteri di mascheramento e priorità tramite un’opportuna programmazione del PIC (v. relativo paragrafo). La gestione ad interrupt è particolarmente adatta ai sistemi multitasking. SVANTAGGI: dobbiamo fornire supporto hardware e prevedere un controllore che si faccia carico della gestione dell’interrupt; la realizzazione è più complessa rispetto al caso polling. Esempio di flusso di programma che esegue un’operazione di I/O seguendo lo schema ad interrupt:
NOTA: l’interrupt handler trasferisce un solo dato alla volta! Spesso negli esercizi capita di risolvere problemi di “riempimento buffer”6, i quali devono essere risolti trasferendo - nel caso specifico - un byte alla volta.
6 Compito dell’11 gennaio 2007.
5. Meccanismo delle interruzioni
Quando il processore decide di servire una richiesta d’interruzione ciò che fa è scatenare una procedura di servizio di interruzione che blocca il flusso del programma in esecuzione. Al termine della procedura (a causa della quale abbiamo lanciato l’interrupt) il programma viene riattivato da punto esatto in cui era stato interrotto, ossia in corrispondenza dell’istruzione successiva a quella eseguita per ultima. Nel generico sistema a microprocessore se abbiamo n dispositivi periferici, allora n sarà il numero degli “attori” in grado di poter avanzare una richiesta di interruzione; il processore però ha solo un piedino, quindi ci sarà bisogno di un controllore delle interruzioni che faccia da controllore e da “schedulatore” delle richieste di interrupt. Grazie a questo controllore (è il PIC 8259) vi sarà la possibilità di decidere se alcune richieste debbano essere ignorate, o se vi siano diversi stadi di priorità. Il processore si interfaccia quindi col controllore e non direttamente con i dispositivi di interrupt. Nei sistemi Intel il processore dispone di due segnali:
• INT: viene portato alto per segnalare al processore che un dispositivo ha fatto richiesta di interruzione;
• INTA: segnale di uscita del processore, il quale segnala di aver percepito la richiesta e richiede il codice del dispositivo.
Si ha anche un flag IF (Interrupt Flag), il quale permette di abilitare/disabilitare le richieste di interruzioni: questo bit può essere modificato via software attraverso le istruzioni CLI (interdiciamo le interruzioni) e STI (riabilitiamo le interruzioni). L’IF viene automaticamente posto a 0 all’attivazione di una procedura di servizio dell’interruzione. Problemi importanti:
• Come fa il processore a capire chi ha avanzato una richiesta di interrupt?
La CPU richiede al controllore chi è stato attraverso un ciclo di bus apposito; come risposta, il controllore passa al microprocessore un certo codice che permette di individuare univocamente chi ha fatto la richiesta.
• Una volta individuato il
dispositivo, come fa il sapere
quale procedura di servizio
dev’essere attivata?
La soluzione passa attraverso una tabella (IVR, Interrupt Vector Table, v. figura a destra), che mette in corrispondenza i codici dei vari dispositivi con le relative procedure. La IVR risiede in memoria principale e associa a ciascun codice di interruzione l’indirizzo della corrispondente procedura di servizio. Ogni entry di questa tabella viene detta interrupt type; un interrupt type è un numero naturale a 8 bit, quindi esistono 82 (256) possibili tipi di interruzione (0… 255). Ogni elemento della tabella occupa 4 byte, per cui la tabella avrà dimensione pari a 1 KB (M[0…3FFH]).
SCHEMA DEL PROTOCOLLO DI INTERRUZIONE 1. Un dispositivo richiede un servizio attraverso una richiesta di interruzione (mandata al PIC
e non al BUS dati come avviene nel caso polling); 2. il PIC decide se quella richiesta ha da essere servita o meno, e in caso positivo inoltra quella
(sola) richiesta al processore; 3. il processore riceve la richiesta e, al termine dell’istruzione che sta eseguendo in quel
momento, la rileva. Se la richiesta ha le caratteristiche per essere servita, il programma in esecuzione viene interrotto;
4. viene interrogato il controllo delle interruzioni per sapere il codice del dispositivo che ha avanzato la richiesta (si passa attraverso il piedino INTA, che sta per INTerrupt Acknowledge);
5. il PIC risponde passando al microprocessore il tipo di interruzione che dev’essere servita;
6. il processore accede alla IVT e legge l’indirizzo della procedura d i servizio che dev’essere attivata;
7. parte la procedura di servizio riguardante il dispositivo che ha fatto la richiesta; 8. terminata la procedura, il processore ritorna al punto in cui era stato interrotto.
LO STESSO SCHEMA DAL PUNTO DI VISTA DEL PROCESSORE (INTEL) 1. Un dispositivo esterno invia una richiesta di interruzione sul pin INTR; 2. durante l’ultimo periodo di clock di un’istruzione il processore rileva la presenza di un
valore 1 su INT; 3. il processore invia un impulso su INTA per segnalare che la richiesta è stata rilevata; 4. il processore invia un secondo impulso di INTA per chiedere di leggere sul data bus il
codice del dispositivo; 5. il processore legge dal data bus il codice n del device che ha richiesto l’interrupt (1 byte); 6. il processore salva nello stack il valore del registro dei flag e l’indirizzo di ritorno (CS e IP); 7. … azzera IF; 8. … accede all’interrupt vector table per prelevare l’indirizzo della procedura di servizio; 9. la procedura va in esecuzione; 10. al termine della procedura il processore ripristina dallo stack il registro dei flag e l’indirizzo
di ritorno, tornando ad eseguire il programma interrotto.
6. Interfacce di I/O
Il sottosistema di I/O consente la comunicazione fra il calcolatore ed il mondo esterno. Fanno parte del sottosistema i dispositivi (unità di I/O) per la comunicazione uomo/macchina (video, stampanti, terminali) e quelli utilizzati per la memorizzazione permanente delle
informazioni (unità a disco, nastri magnetici), nonché la rete. Tutte le unità di I/O sono collegate al bus di sistema mediante dispositivi detti interfacce di I/O.
Il generico schema di un’interfaccia di I/O è quello visibile a fianco: grazie ad esso, l’interfaccia svolge una funzione di adattamento tra la modalità di trasferimento dei dati utilizzata all’interno del sistema (cicli di bus) e quella utilizzata dall’unità di I/O.
Negli esercizi vi sono due principali modi per interfacciare un dispositivo di I/O: • tramite un 244: un 244 è un driver 3-state (schematizzabile come una “batteria di buffer”) a 8
bit strutturato in 2 gruppi da 4 bit, abilitati da EN1* ed EN2*. Esempio pratico - Interfacciamento di 8 interruttori (switch) Quando dobbiamo lavorare con gli interruttori (dai quali ad un certo punto è necessario rilevare il valore) è necessario usare un 244, prestando attenzione al fatto che il primo gruppo di bit è regolato da EN1* e il secondo da EN2* (nell’esempio non vi è bisogno di fare distinzione, ma in un compito d’esame tutto può succedere). Nell’esempio vediamo come sono stati interfacciati gli 8 interruttori mappati a 80 H; il software riportato nel riquadro si occupa di leggere lo stato degli interruttori tramite il comando IN. Altri casi in cui è stato usato un 244: trasduttore di velocità in input (non c’è necessità alcuna di memorizzare, bisogna semplicemente leggere il dato) e in generale in quasi tutti i dispositivi che comunicano in input.
• attraverso un 373: questo componente è un latch a 8 bit con uscite 3-state; campiona sul fronte negativo del clock (il valore viene mantenuto fino a quando CK non torna a valore logico alto) e quando CK = 1 l’uscita riproduce l’ingresso. I latch hanno il pregio di mantenere in memoria il dato.
Esempio pratico - Interfacciamento di un display a 7 segmenti Un display a sette segmenti (v. figura pagina precedente) può essere visto ai morsetti come costituito da 7 led. L’attivazione di un segmento corrisponde all’accensione del led relativo. Risulta quindi sbagliato utilizzare un buffer 3-state (244) per interfacciare il display al sistema. In questo caso, infatti, il valore non viene mantenuto (memorizzato). Per fare ciò occorre necessariamente usare un registro o un latch. Altri casi in cui è stato utilizzato un 373: in generale nei dispositivi che comunicano in output.
Alcuni aspetti importanti sulle interfacce di I/O:
• generalmente un’interfaccia di I/O dispone di un insieme di registri interni a cui la CPU deve accedere. Una volta selezionato il chip (chip select CS* basso) i segnali di indirizzo (A[0.. n-1], v. figura in alto pagina precedente) consentono la selezione fra i suoi differenti registri interni (analogamente a quanto già visto per i chip di memoria). Per accedere ai registri interni è necessario scrivere una certa configurazione binaria (control word7) sul registro di controllo. Generalmente si usa la direttiva EQU per definire degli identificatori associati alle costanti che rappresentano gli indirizzi dei registri e la control word: es. ControlWord EQU D4H
BufferInRegister EQU 80H • le interfacce di I/O devono poter funzionare secondo un certo insieme di modalità
differenti (ad es. un’interfaccia per comunicazioni seriali asincrone RS232 deve poter scambiare dati con un modem impiegando trame e frequenze differenti). Conseguentemente le interfacce di I/O sono tipicamente programmabili: sono presenti cioè al loro interno un certo numero di registri di controllo che vengono scritti dalla CPU all’atto dell’inizializzazione del dispositivo per impostare la modalità di funzionamento desiderata. Ad esempio, questa potrebbe essere una porzione di programma in grado di occuparsi di inizializzare tutte le periferiche programmabili presenti nel sistema: MOV AL, ControlWord OUT ControlRegister, AL (ControlRegister è l’indirizzo del relativo spazio di I/O)
• strettamente associata alla funzionalità di adattamento fra calcolatore e l’unità di I/O è la presenza all’interno dell’interfaccia di registri di
appoggio (“buffer”) utilizzati nei trasferimenti dei dati da CPU ad unità di I/O e viceversa. Per inviare un dato all’unità di I/O la CPU effettua una scrittura del dato su un buffer dell’interfaccia, da cui poi quest’ultima si occupa di trasferire il dato all’unità di I/O. Per prelevare un dato dall’unità di I/O la CPU effettua una lettura da un buffer dell’interfaccia, su cui quest’ultima ha precedentemente appoggiato il dato proveniente dall’unità di I/O. Esempi:
o Trasferimento dati: INPUT IN AL, BufferIn (AL contenuto del registro mappato a “BufferIn”) MOV DatiIn[SI], AL (casella indice SI vettore DatiIn AL) INC SI (incrementiamo l’indice del vettore)
7 Una parola di controllo è un byte che viene trasmesso alla porta di controllo attraverso il bus dati.
o Trasferimento dati: OUTPUT MOV AL, DatiOut[SI] (AL casella indice SI vettore DatiOut) OUT BufferOut, AL (AL contenuto del registro mappato a “BufferOut”) INC SI (incrementiamo l’indice del vettore)
Si noti che in entrambi gli esempi le interfacce trasferiscono un dato alla volta! • di norma le unità di I/O lavorano in modo asincrono rispetto al funzionamento della CPU e
sono molto più lente di quest’ultima. Si rende quindi necessario introdurre all’interno dell’interfaccia di I/O un qualche meccanismo di sincronizzazione fra l’attività svolta dalla CPU e quella svolta dall’unità di I/O. Per maggiori delucidazioni si rimanda ai paragrafi introduttivi sulle politiche di interrupt e polling, nonché a quello successivo (con esempi software);
• tipicamente le interfacce di I/O dispongono di registri di stato (es. bufferFull, ledOn, deviceReady…) tramite cui vengono rese disponibili alla CPU tutte le informazioni necessarie per la sincronizzazione con l’unità di I/O. Ogni qual volta il programma ha necessità di leggere lo stato della periferica (ad es. per controllare se ci sono stati errori), il programmatore userà un istruzione del tipo: IN AL, StatusRegister (caricamento in AL del contenuto del registro mappato dove indica StatusRegister)
• in aggiunta a ciò i registri di stato situati all’interno di un’interfaccia di I/O sono spesso utilizzati per segnalare alla CPU il verificarsi di eventuali condizioni di malfunzionamento o errore (es. errore di parità nelle comunicazioni seriali).
7. Interrupt vs. polling: qualche esempio software
Esempio 1 (polling)
In questo esempio viene mostrato il flusso di un programma che esegue una’operazione di input a polling: si nota infatti che viene consultato il registro Status per vedere se il buffer è stato completamente riempito (BIF = 1). Solo al riempimento totale del buffer si inizia a leggere il buffer e a portare in memoria i dati (in DatiIn), sempre un elemento alla volta. Soltanto al termine dell’operazione di I/O il ciclo terminerà di effettuare il suo trasferimento.
Esempio 2 (polling)
Il problema che esaminiamo consiste nel:
• trasferire i byte provenienti da un’interfaccia d’ingresso (mappata a 80H) verso una d’uscita (mappata a 100H);
• memorizzare i dati ricevuti in un vettore BUF_IN di 1 KB;
• chiamare la procedura CALCOLA quando BUF_IN è pieno. Vi sono due registri importanti:
• BIF (Buffer In Full) � posizione 0; • BOE (Buffer Out Empty) � posizione 1.
E disponiamo di quattro registri interni: • 00: Buffer IN • 01: Buffer OUT • 10: Status • 11: Control
PUNTO 1: mappiamo le variabili e le costanti Abbiamo tantissime costanti, visto che ci fa comodo dare nomi simbolici a tutti gli indirizzi dei vari registri. IN_BufferIN EQU 80H ---- ---- ---- ---- OUT_BufferOUT EQU 101H IN_Status EQU 82H OUT_Status EQU 102H IN_Control EQU 83H OUT_Control EQU 103H IN_ControlWord EQU 33H OUT_ControlWord EQU 62H MASK_BIT0 EQU 1H MASK_BIT1 EQU 2H BUF_IN DB 1024 Risoluzione del problema a polling: Main: MOV AL, IN_ControlWord ; programmazione interfacce: OUT IN_Control, AL ; mandiamo al dispositivo le direttive per MOV AL, OUT_ControlWord ; effettuare l’input/output OUT OUT_Control, AL MOV SI, 0 ; inizializzazione indice buffer in memoria Poll_IN: IN AL, IN_Status ; polling sull’interfaccia d’ingresso AND AL, MASK_BIT0 ; si fa l’AND con 1: in questo modo ; rispettiamo la maschera che abilita solo IR0 CMP AL, 0 ; se ancora non si ha niente JE Poll_IN ; ricomincia il ciclo ; altrimenti… IN AL, IN_BufferIN ; leggiamo un nuovo dato MOV BL, AL ; copiamo AL in BL MOV BUF_IN[SI], BL ; e scriviamo il dato in memoria INC SI ; incrementiamo l’indice CMP SI, 1024 ; letto il KB? JNE Poll_OUT ; se non l’abbiamo ancora letto, passiamo a ; Poll_OUT (trasferimento dati in uscita) MOV SI, 0 ; sennò abbiamo finito di trasferire i dati: ; si resetta l’indice SI per il prossimo giro
CALL CALCOLA ; chiamiamo la procedura CALCOLA Poll_OUT: IN AL, OUT_Status ; polling sull’interfaccia d’uscita AND AL, MASK_BIT1 CMP AL, 0 JE Poll_OUT OUT OUT_BufferOUT, BL ; invio in uscita del dato ricevuto JMP Poll_IN ; ho inviato: pronto a ricevere
Schematicamente: MAIN:
Main: MOV AL, IN_ControlWord ; programmazione interfacce: OUT IN_Control, AL ; mandiamo al dispositivo le direttive per MOV AL, OUT_ControlWord ; effettuare l’input/output OUT OUT_Control, AL MOV SI, 0 ; inizializzazione indice buffer in memoria
• programmiamo le interfacce e spediamo le relative control word con due comandi di OUT. In questo modo diamo le direttive necessarie affinché tutto funzioni come voluto. Quasi sempre nel main capita di dover inizializzare delle variabili, oppure qualche indice che servirà per scorrere il buffer.
POLL_IN: Poll_IN: IN AL, IN_Status ; polling sull’interfaccia d’ingresso AND AL, MASK_BIT0 ; si fa l’AND con 1: in questo modo ; rispettiamo la maschera che abilita solo IR0 CMP AL, 0 ; se ancora non si ha niente JE Poll_IN ; ricomincia il ciclo ; altrimenti… IN AL, IN_BufferIN ; leggiamo un nuovo dato MOV BL, AL ; copiamo AL in BL MOV BUF_IN[SI], BL ; e scriviamo il dato in memoria INC SI ; incrementiamo l’indice CMP SI, 1024 ; letto il KB? JNE Poll_OUT ; se non l’abbiamo ancora letto, passiamo a ; Poll_OUT (trasferimento dati in uscita) MOV SI, 0 ; sennò abbiamo finito di trasferire i dati: ; si resetta l’indice SI per il prossimo giro CALL CALCOLA ; chiamiamo la procedura CALCOLA
• in questa parte compaiono le tipiche istruzioni per il trasferimento dati (input) e si effettuano alcuni controlli: quello tipico del polling (si ha qualcosa da ricevere? se sì procedi, sennò ricontrolla) e quello di fine lettura del KB (se abbiamo finito di leggere allora abbiamo anche finito di trasferire quindi non saltiamo a Poll_OUT ma procediamo con CALCOLA).
POLL_OUT: Poll_OUT: IN AL, OUT_Status ; polling sull’interfaccia d’uscita AND AL, MASK_BIT1 CMP AL, 0 JE Poll_OUT OUT OUT_BufferOUT, BL ; invio in uscita del dato ricevuto JMP Poll_IN ; ho inviato: pronto a ricevere
• queste righe di codice sono dedicate al trasferimento del dato in uscita: anche qui è presente un’operazione di polling (questa volta sull’interfaccia d’uscita). Si fa uso di BL, in precedenza creato come duplicato di AL, perché quest’ultimo registro viene modificato e poi fatto passare attraverso l’operazione logica di AND.
Esempio 3 (interrupt) Possiamo complicare a piacimento la situazione rispetto a quella trattata nell’esempio 2, supponendo che il sistema debba contemporaneamente gestire un orologio.
Lo scopo è quello di mostrare quanto tempo è trascorso dall’avvio del sistema; è inoltre disponibile un’onda quadra con frequenza 1 Hz. Casi come questo sono tipicamente trattati ad interrupt; si sa che lo scorrere dei secondi avviene con una cadenza tale che rende inutile far continuamente chiedere al processore (magari ogni microsecondo): “Scusa, ma è scattato il
secondo?”. Si perderebbero inutilmente tantissimi cicli di clock che possono essere molto meglio impiegati. Ecco allora come si agisce:
• si collega all’ingresso INT della CPU il segnale a 1 Hz, in modo che si abbia un’interruzione al secondo;
• si memorizzano ore minuti e secondi in delle variabili in memoria: Ore DB 0 Minuti DB 0 Secondi DB 0 (NOTA: con questa sintassi si inizializzano delle variabili di 1 byte inizialmente poste a zero)
• si interfaccia il nostro orologio (mappato a 200H), il quale dispone di tre registri contenenti ore, minuti e secondi correnti: CLOCK_H EQU 200H CLOCK_M EQU 201H CLOCK_S EQU 202H
Programmazione ad interrupt per l’orologio CLOCK_INT : PUSH AX ; all’inizio di ogni routine di interrupt bisogna ; effettuare un “prologo”, durante il quale andiamo ; a collocare sullo stack le variabili di contesto che ; dovranno essere ripristinate al termine nella fase ; detta “epilogo” (il termine della procedura)
INC [Secondi] ; facciamo andare avanti i secondi (abbiamo avuto ; un interrupt dal segnale di onda con periodo 1 s CMP [Secondi], 60 ; abbiamo fatto un minuto? JNE End ; se no, vai a End, altrimenti… MOV [Secondi], 0 ; bisogna resettare i secondi e INC [Minuti] ; aumentare i minuti di uno! CMP [Minuti], 60 ; abbiamo fatto un’ora? JNE End ; se no, vai a End, altrimenti… MOV [Minuti], 0 ; bisogna resettare i minuti e INC [Ore] ; aumentare le ore di uno! CMP [Ore], 24 ; abbiamo fatto un giorno? JNE End ; se no, vai a End, altrimenti… MOV [Ore], 0 ; resetta le ore!
End: MOV AL, [Secondi] ; dobbiamo aggiornare il display: … secondi….
OUT CLOCK_S, AL ; MOV AL, [Minuti] ; dobbiamo aggiornare il display: … minuti….
OUT CLOCK_M, AL ; MOV AL, [ore] ; dobbiamo aggiornare il display: … ore! OUT CLOCK_H, AL POP AX ; ripristino del contesto (faccio il pop di tutto ciò ; che avevamo push-ato nel prologo): questa parte ; viene detta “epilogo” IRET ; con questo comando usciamo dalla routine di ; interrupt Programmazione ad interrupt per l’interfaccia Main: CLI ; Disabilitazione interrupt (all’inizializzazione ; non devono disturbare!)
MOV AL, IN_ControlWord ; Programmazione interfacce tramite parole di ; comando OUT IN_Control, AL ; spediamo la prima parola MOV AL, OUT_ControlWord OUT OUT_Control, AL ; spediamo la seconda parola MOV [1k_done], 0 ; Inizializzazione variabili: la variabile ; [1k_done] indica al main l’avvenuta ; ricezione di 1 KB MOV [Index], 0 ; indice del vettore a 0 STI ; Abilitazione interrupt
NOTA: spesso il main viene racchiuso da CLI - STI perché contiene l’inizializzazione del programma, che non dev’essere interrotta per nessun motivo.
Loop: CMP [1k_done], 1 ; Sincronizzazione: hai finito di trasferire? JNE Utile ; Se no, torna al lavoro utile… MOV [1k_done], 0 ; altrimenti resetta la variabile 1k_done CALL CALCOLA ; e chiama la procedura “CALCOLA”
Utile: … ; Il main in attesa che le operazioni di I/O … ; vengano completate può eseguire delle … ; operazioni utili… JMP Loop
… … … RX_INT: PUSH AX ; PROLOGO
PUSH SI MOV SI, [Index] ; Mettiamo l’indice corrente nel registro SI IN AL, IN_BufferIN ; Lettura del nuovo dato MOV BUF_IN[SI], AL ; Scrittura in BUF_IN (memoria) del dato ricevuto INC [Index] ; incremento dell’indice
CMP [Index], 1024 ; Letti 1K dati? JNE send_OUT ; se no, bisogna spedire MOV [Index], 0 ; altrimenti resettiamo l’indice MOV [1k_done], 1 ; e segnaliamo di aver terminato il KB di dati
send_OUT: OUT OUT_BufferOUT, AL ; Invio in uscita del dato ricevuto POP SI ; EPILOGO POP AX RETI
Si noti che questa routine di interrupt (ma si tenga presente che non viene conteggiato il main) è più snella della sua controparte polling. 8. Comunicazioni seriali
Esistono due modalità diverse di trasferimento dei dati tra due sistemi, distinte dal numero di segnali che connettono il trasmettitore col ricevitore:
• comunicazione parallela: maggiore velocità (ad es. vengono spediti 8 bit alla volta), maggior costo. Viene adottata quando la distanza tra ricevitore e trasmettitore è relativamente ridotta;
• comunicazione in serie: minore velocità (viene spedito un bit alla volta), minor costo, viene usata laddove la distanza tra il trasmettitore e il ricevitore è significativa (per es. superiore al metro).
Sincronismo di ricezione
In una trasmissione seriale il dispositivo ricevente, per poter decodificare ed interpretare correttamente i dati ricevuti, deve sapere:
• quando campionare la linea per identificare il valore di ciascun bit (deve cioè acquisire il sincronismo di clock)…
• … quando inizia e finisce ciascun gruppo di bit o carattere (sincronismo di carattere), • … nonché quando inizia e finisce ciascun blocco d’informazione (sincronismo di frame).
Inoltre: • dev’essere in grado di conoscere il formato dei frames; • deve lavorare alla stessa frequenza del trasmettitore (e avere uguale bit-rate);
Problema di conversione S/P e P/S
Il mondo dei sistemi a microprocessore è un mondo che lavora in parallelo (v. bus, memorie…), ma noi dobbiamo interfacciarlo col mondo seriale della linea; se andiamo a vedere cosa succede quando si realizza una trasmissione seriale osserveremo che il trasmettitore riceverà dal processore (in parallelo) il dato da inviare, lo serializzerà e poi lo invierà. Lo shift register, ovvero
colui che si incarica di effettuare la conversione P/S, è quindi un componente fondamentale in questo processo. Questi bit, una volta trasmessi, verranno presi dal ricevitore che dovrà lavorare nel senso opposto (parallelizzarli per mandarli sul bus). Alla fine di questa operazione il dato sarà parallelizzato e fornito al sistema ricevente. Trasmissioni seriali sincrone e asincrone
Possiamo adottare due soluzioni: • sincrona: trasmettitore e ricevitore condividono uno stesso segnale di clock (avente
frequenza pari al bit-rate) che aiuta loro ad individuare dove stiano temporalmente i bit. Più precisamente, il trasmettitore e il ricevitore si scambiano i dati su un canale ed il segnale di temporizzazione su una seconda linea. È la più semplice tra le due modalità (sincrona e asincrona), ma bisogna prestare attenzione al fatto che il segnale fisico di clock può subire consistenti ritardi e/o sfasamento se i dispositivi in collegamento sono lontani: per questo si usa collegare in questo modo due dispositivi vicini (spesso sulla stessa scheda). Durante i periodi di inattività il trasmettitore invia dei caratteri, detti di sincronismo; il loro scopo è infatti quello di mantenere il sincronismo di carattere fra i due dispositivi che, grazie a questa strategia, continuano a sapere dove finisce un carattere e inizia quello successivo, rendendo superflua la ri-sincronizzazione. Proprio perché sono a tutti gli effetti occupati dalla trasmissione di carattere, tali periodi di inattività hanno una durata multipla di quella necessaria per la trasmissione di un singolo carattere;
• asincrona: trasmettitore e ricevitore non condividono alcun clock8 e quindi c’è bisogno di sincronizzarli in qualche altro modo: in particolare, il trasmettitore genererà e invierà al ricevitore un unico segnale contenente sia le informazioni di temporizzazione che i dati; il ricevitore, di contro, utilizzerà questo segnale sia per rifasare il proprio clock tramite un circuito PLL (Phase Lock Loop) sia per estrarre i dati. Inoltre, nel caso asincrono, la sincronizzazione è in termini più di fase che di frequenza, visto che i dispositivi ricevono i segnali di clock alla stessa frequenza nominale. Questo è possibile dal momento che essi ricevono (in fase di programmazione) una costante k (detta fattore di scalamento), fondamentale perché attraverso di essa i due dispositivi acquisiscono l’informazione sulla frequenza cui avviene la trasmissione: in particolare, un bit viene trasmesso ogni k colpi di clock. NOTA: nella trasmissione asincrona, se il trasmettitore non trasmette nulla, la linea è vuota (contrariamente a ciò che avviene nella trasmissione sincrona).
Caso esemplificativo di
comunicazione seriale9
1. la linea è inattiva; 2. start bit (è uno soltanto e va al valore logico basso): serve per far capire quando inizia la trama; 3. data bits: qui è contenuto il dato vero e proprio; 4. parity bit: fornisce una indicazione sul verificarsi o
8 ATTENZIONE - Non è che non ci sia il clock: il clock - anzi, i clock, uno per componente - ci sono eccome… il problema è condividerli visto che non sono già in partenza sincronizzati fra i componenti. 9 Per maggiori dettagli si faccia riferimento alla figura.
meno di un errore di trasmissione; 5. stop bit: fanno capire al ricevitore che la trama è finita.
Dispositivo 8250 e protocollo RS-232
Il dispositivo 8250 appartiene alla categoria degli UART (Universal
Asynchronous Receiver and Transmitter). Trattasi inoltre di un dispositivo programmabile: l’utente può infatti specificarne le caratteristiche di funzionamento attraverso opportune parole di controllo. Supporta l’RS-232, protocollo/standard elettrico per le comunicazioni seriali usato spesso nelle comunicazioni coi modem, e supporta la trasmissione asincrona.
In figura a destra vediamo lo schema dell’interfaccia e possiamo distinguere alcuni fondamentali gruppi di bit:
• segnali di indirizzo A[0..2]; • collegamento al bus dati
D[0..7]; • chip select (CS); • comandi di read e write (per
operazioni di lettura e scrittura) sui registri interni;
• il segnale INT per richiamare l’attenzione del processore.
Abbiamo però anche dei registri nonché dei segnali “nuovi”:
• registri di DATO o receiver buffer register (RBR): qui va a finire il dato una volta ricevuto dalla linea
seriale. Il bit meno significativo del registro contiene il bit ricevuto per primo; o transmitter holding register (THR): è il registro in cui il processore scrive il dato da
trasmettere sulla linea, una volta serializzato (è il registro simmetrico del RBR). Il bit meno significativo del registro contiene il bit che dev’essere trasmesso per primo;
• registri di STATO o line status register (LSR): è un registro di sola lettura che serve per conoscere lo stato
della linea; � LSR0: va a 1 quando è stato ricevuto un nuovo carattere ed è disponibile il RBR; � LSR1: va a 1 in corrispondenza di un errore di overrun, cioè quando in ricezione
l’8250 ha acquisito un carattere, l’ha scritto nel buffer e tuttavia il processore non
è stato sufficientemente veloce a leggere questo dato cosicché ne è arrivato un altro che l’ha sovrascritto;
� LSR2: bit di parità (è a 1 quando c’è un errore di parità); � LSR3: server per il frame error, quando l’8250 si aspetta di ricevere un 1 e invece
riceve 0 (non rileva il bit di stop); � LSR4: va a 1 quando l’8250 non riceve nulla (linea inattiva); � LSR5: quando è 1 il dato nel registro è pronto;
o interrupt identification
register (IIR): identifica la richiesta di interruzione;
• registri di CONTROLLO o line control register
(LCR): è accessibile in lettura e serve a capire lo stato delle richieste di interruzione nonché per forzare alcuni parametri significativi che caratterizzano il modo di funzionamento (ad es. formato della trama); � L0 e L1: determinano numero di bit per carattere; � L2: definisce il numero di bit di stop (1,5 carattere a 5 bit, 2 se sono più di 5); � L3: riguarda bit di parità (ed è posto a 1 se si desidera il bit di parità);
o interrupt enable register (IER): grazie ad esso il processore più decidere (in maniera selettiva) se l’8250 può scatenare richieste di interruzione; le interruzioni (ad es. buffer di ricezione pieno, buffer di trasmissione vuoto, errore di ricezione, etc.) vengono gestite con un certo criterio di priorità: di default, le richieste di interruzione a priorità più bassa sono bloccate se pende una richiesta a priorità più alta (la priorità massima è quella a livello zero);
o division latch register (DLR): è registro a 16 bit strutturato in 2 registri da 8 bit, (DLL e DLM); attraverso questi registri comunichiamo il parametro k di scalamento e quindi determiniamo il bit-rate (sia per la trasmissione che per la ricezione). Più precisamente, il dispositivo ricava il proprio bit rate come:
bit-rate = frequenza di pilotaggio / (16* costante di tempo) La costante di tempo dice di quante volte dev’essere scalata la frequenza di pilotaggio: si veda la tabella per determinarla in base al caso (la tabella è stata calcolata sulla frequenza F = 1,8432 MHz).
Infine, l’8250 ha un insieme di segnali di controllo utilizzabili per interfacciarsi con un modem secondo le modalità dello standard RS-232.
9. Comunicazioni seriali: un po’ di codici Anzitutto, ecco due esempi di mapping nello spazio di I/O per le interfacce 8250:
REGISTRO COM1 COM2
RBR,THR,DLL 03F8H 02F8H IER,DLM 03F9H 02F9H IIR 03FAH 02FAH LCR 03FBH 02FBH MCR 03FCH 02FCH LSR 03FDH 02FDH MSR 03FEH 02FEH
Ecco come avviene l’inizializzazione dell’interfaccia COM1: ; La procedura inizializza COM1 : bit-rate 9600, 8 bit per carattere, parità pari, 1 stop bit ini_com1 PROC FAR PUSH AX ; prologo PUSH DX
; DLAB = 1 per accedere a DLM e DLL MOV DX, LCR ; mettiamo 80H = 1000 0000 MOV AL, 80H ; in LCR in modo da forzare DLAB = 1 (DLAB è il bit LCR8) OUT DX, AL
; bit rate = 9600 -> DLL = 000CH (diviso in 00 DLM e 0C DLL) MOV DX, DLM MOV AL, 00H OUT DX, AL MOV DX, DLL MOV AL, 0CH OUT DX, AL
; 8 bit per car., 1 stop bit, parità pari, DLAB = 0
; � LCR = 1BH = 0001 1100 MOV DX, LCR MOV AL, 1BH OUT DX, AL
; “clear” di IER (= 00H) per disabilitare le interruzioni MOV DX, IER MOV AL, 00H OUT DX, AL
; lettura iniziale per svuotare il buffer di ricezione MOV DX, RBR IN AL, DX POP DX ; epilogo POP AX RET ini_com1 ENDP
Lettura e scrittura su COM1 in modalità “polling” read_com1 PROC FAR ; la procedura legge un carattere da COM1 ; e lo restituisce in AL
PUSH DX
; attesa che il buffer di ricezione sia pieno: il bit 0 ; del registro LSR fornisce lo stato del buffer di ; ricezione ( 1 = pieno, 0 = vuoto )
MOV DX, LSR wait_pieno: IN AL, DX
CMP AL, 01H (� 0000 0001) JZ wait_pieno
; lettura del carattere ricevuto MOV DX, RBR IN AL, DX POP DX RET read_com1 ENDP
write_com1 PROC FAR ; la procedura scrive su COM1 il carattere passato ; in AL
PUSH DX PUSH AX
; attesa che il buffer di trasmissione sia vuoto: ; il bit 5 del registro LSR fornisce lo stato del buffer ; di trasmissione (1 = vuoto, 0 = pieno)
MOV DX, LSR
wait_vuoto: IN AL, DX CMP AL, 20H (� 0010 0000) JZ wait_vuoto
; scrittura del carattere da trasmettere MOV DX, THR POP AX OUT DX, AL POP DX RET write_com1 ENDP
TRASFERIMENTO DATI
L’operatore MOV copia un dato da una posizione all’altra.
FORMA: MOV destinazione, sorgente
NOTE:
• l’operando sorgente non viene modificato.
• l’operando sorgente può essere un registro, una locazione in memoria o una costante.
• l’operando di destinazione può essere un registro o una locazione in memoria.
COMBINAZIONI NON PERMESSE e RESTRIZIONI:
• gli operandi devono avere lo stesso numero di bit;
• IP non deve mai comparire;
• CS non può essere destinazione;
• i due operandi non possono essere due valori in memoria (bisogna obbligatoriamente
passare da un registro).
MODI DI INDIRIZZAMENTO
1. REGISTER (mediante registro)
L’operando1 è un registro specificato nell’istruzione.
Es. MOV BX, AX (sposta in BX il contenuto di AX)
2. IMMEDIATE (mediante immediato)
L’operando è una costante espressamente indicata.
Es. MOV BH, 07H (2) (sposta 07H in BH)
3. DIRECT (indirizzamento diretto)
L’operando si trova in una locazione di memoria.
Es(1). MOV AX, [0100]
Spostiamo in AX il contenuto di DX+0100, DX è il segmento di default
Es(2). MOV AX, TABLE
TABLE è una variabile che indica tale locazione di memoria.
Es(3). MOV AX, TABLE+1
Il processore prende TABLE, ricava l’offset in cui quest’ultima variabile si trova in memoria e gli
somma 1. Il risultato è l’offset di ciò che si vuole spostare in AX.
Es(4). MOV AX, TABLE[2]
Il processore prende TABLE, ricava l’offset in cui quest’ultima variabile si trova in memoria e gli
somma 2. Il risultato è l’offset di ciò che si vuole spostare in AX.
4. SEGMENT OVERRIDE
Il registro di default è sempre DS, ma utilizzando l’operando “ : ” possiamo utilizzare un
segmento diverso come riferimento per il calcolo degli indirizzi.
Es. MOV AX, ES:VAR2 (prendi ciò che c’è all’indirizzo di base ES e offset VAR2 e metti in AX)
VAR2 si trova in un segmento il cui inizio è memorizzato nel registro AS (non più DS).
1 L’operando può generalmente essere un registro, oppure una costante presente nell’espressione, una quantità in memoria o infine
un valore di I/O. 2 BH e 07H sono quantità a 8 bit.
5. REGISTER INDIRECT (indirizzamento indiretto mediante registro base o registro indice)
L’offset è contenuto in un registro base (ad es. BX, BP) oppure in un registro indice (ad es. DI, SI).
Es. MOV AX, [BX]
L’operando viene preso nella cella di memoria il cui offset si trova in BX.
NOTA: se non indicato diversamente, i registri base ai quali va aggiunto l’offset sono DS (per BX,
SI, DI) e SS (per BP).
6. BASE RELATIVE (indirizzamento indiretto mediante registro base, con tanto di
displacement)
L’offset dell’operando è ottenuto sommando il contenuto di BX (o BP) a un displacement
(nell’esempio è 4); per le regole sui registri base vedi il punto 5.
Es. MOV AX, [BX+4]
7. DIRECT INDEXED
L’operando è in memoria, ma l’offset è la somma di un offset in una variabile più un displacement.
Es. MOV AX, TABLE[DI]
Su usa come offset quello di TABLE sommato al contenuto di DI.
8. BASE INDEXED (indirizzamento indiretto mediante registro base e registro indice)
L’offset è costituito dalla somma di:
• contenuto di BX o BP (registro base);
• contenuto di SI o DI (registro indice);
• un campo opzionale.
ES. MOV AX, TAB[BX][DI]
Offset: offset di TAB + contenuto di BX + contenuto di DI.
DEFINIZIONE DI COSTANTI
In fase di assemblaggio del programma, EQU permette di definire simboli che rappresentano
valori specifici.
FORMATO: nome EQU espressione
Es. X EQU 1024
ISTRUZIONI ARITMETICHE E OPERAZIONI ELEMENTARI
Operano su numeri interi binari senza (o con segno) a 8 bit o a 16 bit. Il processore 8086 supporta
anche il formato BCD.
ADD formato: ADD dest, sorg
SUB formato: SUB dest, sorg
Quel che accade è che si effettua dest + (o -) sorg e il risultato viene messo in dest (mentre sorg
rimane immutato).
RESTRIZIONI:
• non è lecito specificare come operandi due locazioni di memoria (al massimo uno): perciò,
se proprio dobbiamo sommare due valori in memoria, dobbiamo obbligatoriamente
passare attraverso un registro;
• gli operandi devono essere dello stesso tipo e della stessa dimensione (o entrambi byte o
entrambi word).
NOTA:
• il flag CF assume il significato di bit di riporto.
ADC e SBB effettuano la somma (e la sottrazione) a 32 bit riconducendo il tutto a due somme
(sottrazioni) su 16 bit (bisogna però tenere conto del riporto o del prestito).
INC e DEC permettono di incrementare o decrementare un operando. Queste due istruzioni
possono essere integrate con l’istruzione NEG (che cambia il segno dell’operando agendo a
complemento a 2).
MUL (interi senza segno) e IMUL (interi con segno) sono due comandi che prevedono un unico
operando esplicito. L’altro operando è implicito nell’istruzione stessa, perché si utilizza
automaticamente un registro AX come secondo operando.
L’operando può essere un registro o una locazione di memoria (non una costante!), mentre il tipo
può essere byte o word:
• se l’operando è di tipo byte allora l’istruzione intercorre tra operando e AL e il risultato
viene copiato in AX.
• se l’operando è di 16 bit l’istruzione moltiplica operando e AX e il risultato lo si mette in AX
(per i bit più significativi, MSB) e DX (per i bit meno significativi, LSB)
In Assembler la divisione è implementata con le parole di comando DIV (senza segno) e IDIV
(con segno), le quali sono analoghe a MUL e IMUL per quanto riguarda il formato (un unico
operando esplicito e un operando implicito; l’operando può essere un registro o una locazione di
memoria ma non può essere un immediato).
Operando byte: processore divide AX per l’operando. Quoziente in AL e resto in AH.
Operando word: processore divide DX:AX e l’operando. Quoziente in AX e il resto in DX.
NOTA: l’operando sorgente non può essere una costante.
ISTRUZIONI LOGICHE E DI SCORRIMENTO
Esse sono utilizzate, in particolare, per forzare il valore di uno o più bit all’interno della parola.
Es. forziamo a 1 il quarto bit di AX
OR AX, 1000b
In alcuni casi (laddove si vogliano programmare i dispositivi periferici che vivono attorno al
processore) può essere necessario dover modificare i registri specifici un bit alla volta. Operandi:
AND
OR formato: dest, sorg
XOR
Ogni operazione logica viene effettuata fra dest e sorg e il risultato viene posto in dest.
Istruzione NOT (ha un solo operando) � effettua il complemento bit a bit. Non è equivalente a
NEG: NEG cambia aritmeticamente il segno, NOT inverte i bit logicamente.
SHL e SHR hanno il formato:
SH¶ operando, contatore ¶ = L (sinistra), R (destra)
Il contatore specifica di quante posizioni dev’essere effettuato lo scorrimento. Lo scorrimento
lascia dietro di sé degli zeri. L’ultimo bit uscita viene inserito nel CF.
NOTA: per le proprietà dei numeri binari l’istruzione SHL BX, 1 moltiplica BX per 2.
SAL e SAR hanno lo stesso formato di SHL e SHR, solo che i vuoti creati dallo spostamento sono
riempiti di bit pari al valore del bit più significativo. Ancora una volta l’ultimo bit in uscita viene
inserito nel CF.
Es. operando (CF = --) (0 shift)
ooperand (CF = o) (1 shift)
oooperan (CF = d) (2 shift)
oooopera (CF = n) (3 shift)
ROR e ROL hanno la stessa forma e scopo delle ultime istruzioni viste (SHL, SHR, SAL, SAR) ma
hanno caratteristica di ricorsività. L’ultimo bit in uscita viene copiato in CF e rientra dalla parte
opposta.
CONTROLLO DEL FLUSSO
Le istruzioni di un programma sono normalmente eseguite sequenzialmente, ma può esserci
bisogno di salti e cicli.
Salti incondizionati
Un salto incondizionato è un salto eseguito a prescindere, senza nessuna condizione.
L’istruzione JMP (jump) ha un formato relativamente scontato, ovvero: JMP destinazione
Esistono due tipi di salti: diretti e indiretti. Nel primo caso (salto diretto) all’interno dell’operando
può esserci l’indirizzo stesso dell’istruzione cui saltare (o le informazioni necessarie per
calcolarlo); spesso come operando usiamo un’etichetta. Nel salto indiretto l’operando contiene
l’indicazione di quale sia un puntatore che contiene a sua volta l’indirizzo dell’istruzione cui
saltare: c’è, insomma, un passaggio in più rispetto al salto diretto.
es. diretto �JMP AX (salta all’indirizzo indicato dal registro AX)
indiretto � JMP [AX] (salta all’indirizzo contenuto nella cella di memoria che si trova
all’indirizzo indicato dal registro AX) .
I salti indiretti possono essere utilizzati per implementare costrutti di tipo CASE.
ad es. JMP TAB[BX], al variare di BX saltiamo in punti diversi.
Salti condizionati
Prima verificano se è
verificata una certa
condizione e poi fanno il
salto.
Formato: JXXX label
(dove XXX è un suffisso che specifica la condizione, v. tabella)
L’istruzione JXXX dev’essere sempre preceduta dall’istruzione CMP (compare) � formato:
CMP destinazione, sorgente
Questa compara i due valori (sottraendoli) e sulla base del risultato della sottrazione setta i flag
che saranno necessari per effettuare i confronti.
NOTA: CMP funziona tra
• due registri,
• una locazione di memoria e un registro,
• un registro e un valore immediato (non il viceversa),
• fra una locazione di memoria e un immediato (non il viceversa).
Gli operandi del confronto devono inoltre avere la stessa lunghezza e non è ammesso il confronto
fra due locazioni di memoria.
I flag impostati da CMP vengono testati per effettuare il salto condizionato.
CICLI
LOOP ha un formato del tipo LOOP label
e quindi viene esclusivamente esplicitata l’etichetta (cioè il nome simbolico, la label) dell’istruzione
cui si vuole saltare.
Quando il processore incontra LOOP:
• decrementa CX di una unità,
• controlla e se è diverso da 0
� se sì � salta all’istruzione con etichetta label;
� se no � va avanti.
In questo modo CX può essere utilizzato come contatore, laddove si voglia fare un ciclo che
dev’essere ripetuto un numero noto a priori di volte.
PROCEDURE
Attraverso le procedure è possibile scrivere una volta quelle parti di codice che vengono eseguite
ripetutamente all’interno di un programma.
Come definiamo una procedura? Come isoliamo una procedura nel codice?
Usiamo due direttive: PROC e ENDP. Formato: nome PROC tipo3
corpo della procedura nome ENDP
Chiamata di una procedura
L’istruzione CALL trasferisce il controllo del flusso di esecuzione del programma ad una
procedura specificata. Formato: CALL target
Alla fine della procedura bisogna tornare indietro, al programma chiamante: per questo viene
salvato nello stack l’indirizzo di ritorno.
3 Opzionale: NEAR = all’interno dello stesso segmento di codice, FAR = tra segmenti diversi, per cui bisogna salvare CS e offset di
ritorno
Ritorno da una procedura
L’istruzione RET permette di restituire il controllo alla procedura chiamante, una volta che la
procedura chiama ha terminato l’esecuzione. Si estrae perciò dallo stack l’indirizzo di ritorno e si
ripassa il controllo al chiamante.
Formato: RET.
Soluzione compito del
Punto 1
48 KB di EPROM (32+16) agli indirizzi alti
32 KB EPROM1 FFFFFH
16 KB EPROM2 F7FFFH
8 KB di RAM (indirizzi bassi): 01FFFH
Decodifica completa:
32 KB EPROM1 BA19 BA18 BA17 BA16 BA15
16 KB EPROM2 BA19 BA18 BA17 BA16
8 KB di RAM /BA19 /BA18
Decodifica semplificata:
32 KB EPROM1 BA19 BA15
16 KB EPROM2 BA19 /BA15
8 KB di RAM /BA19
Punto 2
Mapping:
PIC (2 byte) 100H (100
Decodifica semplificata: BA8
Motore (1 byte) 80H
Decodifica semplificata: BA7
Punto 3
Soluzione compito del 14 luglio 2007
agli indirizzi alti:
FFFFFH � F8000H
F7FFFH � F4000H
01FFFH � 00000H
BA19 BA18 BA17 BA16 BA15
BA19 BA18 BA17 BA16 /BA15 BA14
BA18 /BA17 /BA16 /BA15 /BA14 /BA13
BA15 � De Morgan � /BA19 + /BA15
BA15 � De Morgan � /BA19 + BA
� De Morgan � BA19
100H (100H e 101H) � 0001 0000 0000
� 0000 1000 0000
/BA19 + /BA15
/BA19 + BA15
Punto 4
MOTORE EQU 80H
PIC EQU 100H
ValoreT1 DB 2
NewT1 DB 1 (ne basta uno perché tanto T1 è compreso fra 0 e 255)
Il PIC viene utilizzato per generare 3 tipi di interrupt:
• IR0: ogni 0,1 msec su IR0 che serve come base per il conteggio del tempo.
• IR1: sul fronte positivo del segnale PWM_SN (inizio di T1).
• IR2: sul fronte negativo del segnale PWM_SN (fine di T1).
IR0: sarà sufficiente andare ad incrementare il valore di una variabile (T1) in modo da avere un conteggio
di quanto tempo è trascorso tra un fronte positivo e uno negativo del segnale PWM_SN.
IR1: resettare il valore di T1, abilita la ricezione su IR2 e IR0, disabilita quella su IR1.
IR2: la routine bloccherà la ricezione degli interrupt su IR0 e IR2, bloccando in questo modo l’incremento di
T1. Segnalerà quindi al main l’avvenuto campionamento di un nuovo valore di T1.
; Indirizzi
ICW1 EQU 100H
ICW2 EQU 101H
ICW4 EQU 101H
OCW1 EQU 101H
OCW2 EQU 100H
; Parole di comando
RESET EQU 13H ; l’interrupt viene rilevato sul fronte (1BH = a livello)
ADDRESS EQU 1FH ; 5 bit più significativi dell’interrupt type
EN_AEOI EQU 1H ; AEOI disabilitato (3H = abilitato)
MASK_ALL EQU FH ; 8 bit di maschera (0: non mascherato,1: mascherato)
MASK_IR0 EQU 1H ; maschera (0000 0001)
MASK_IR1 EQU 2H ; maschera (0000 0010)
MASK_IR2 EQU 4H ; maschera (0000 0100)
EOI EQU 20H ; da inviare per segnalare la fine della routine
OUT OCW2, EOI ; segnalazione di fine routine
Punto 6
CLI
OUT ICW1, RESET ; FRONTE
OUT ICW2, ADDRESS ; 5 bit più significati dell’interrupt ty
OUT ICW4, EN_AEOI ; abilitazione o meno dell AEOI
OUT OCW1, FDH ; 1111 1101 abilita IR1
MOV [T1],0
MOV [NewT1],0
STI
Loop: CMP [NewT1],1
JNE Loop
MOV AX,[T1]
DIV 10
DEC AL
OUT MOTORE, AL
MOV [NewT1],0
JMP Loop
Punto 7
IR0
int_F8H: INC [T1]
OUT OCW2,EOI
IRET
IR1
Int_F9H: MOV [T1],0
OUT OCW1, FAH ; 1111 1010 abilita IR0 e IR2
OUT OCW2, EOI ; segnalazione di fine routine
IRET ; uscita dalla routine
IR2
Int_FAH: OUT OCW1, FEH ; 1111 1101 abilita IR1
MOV [NewT1],1
OUT OCW2, EOI ; segnalazione di fine routine
IRET ; uscita dalla routine
Compito del 16/06/2008
Punto 1
Si chiedono le espressioni del chip select (complete e semplificate), avendo cura di mostrare la disposizione
nello spazio di indirizzamento dei vari chip (indirizzo iniziale e finale).
32 KB di EPROM mappati all’indirizzo A8000H: da A8000H ad AFFFFH
espressione completa: BA19 /BA18 BA17 /BA16 BA15
64 KB di RAM mappati all’indirizzo 10000H: da 10000H ad 1FFFFH
espressione completa: /BA19 /BA18 /BA17 BA16
Decodifica semplificata:
EPROM BA19
RAM /BA19
Punto 2
Mapping dei dispositivi di I/O.
PIC 8259 mappato a A000H 1010 0… 0 0000 0..0
Dec. completa: BA15 /BA14 BA13 /BA12 /BA11 /BA10 /BA9 /BA8 /BA7 /BA6 /BA5 /BA4 … /BA1
RTC ad F000H 1111 0… 0 0000 0..0
Dec. completa: BA15 BA14 BA13 BA12 /BA11 /BA10 /BA9 /BA8 /BA7 /BA6 /BA5 /BA4 … /BA1
Lettore FP E800H 1110 10..0 0000 0..0
Dec. completa: BA15 BA14 BA13 /BA12 BA11 /BA10 /BA9 /BA8 /BA7 /BA6 /BA5 /BA4 … /BA1 /BA0
8250 usato a polling a F0A0H 1111 0000 1010 0..0
Dec. completa: BA15 BA14 BA13 BA12 /BA11 /BA10 /BA9 /BA8 BA7 /BA6 BA5 /BA4 /BA3
Decodifica semplificata: PIC /BA14
RTC BA12
FP BA11
8250 BA5
Punto 3
RFID
reader
RS232
FP e RTC sono due dispositivi di input (vengono solamente letti), quindi li interfacciamo al bus dati (BD)
con dei componenti 244: il FP gestisce 8 bit, il RTC invece lavora a 16 bit (presumibilmente 8 per le ore e 8
per i minuti) e quindi ha bisogno di du
(CS_RTC#).
L’8250 è il componente che può supportare l’interfaccia RS232, utilizz
gestiscono il pass elettronico (basato su tecnologia RFID).
alcun collegamento fra l’8250 e i piedini IR dell’8259.
L’8259 gestisce gli interrupt (che riceve dal FP) ed è interfacciato come mostrato in figura.
Punto 4
I dispositivi sono divisi in due “famiglie”: i dispositivi di I/O e quelli per le memorie.
DISPOSITIVI DI I/O � PIC, FP e 8250
Per gestire i relativi cicli di clock utilizziamo un multiplexer
• BA6 alto attiva l’8250 (ingressi 10 e 11 del multiple
• BA6 basso + BA14 alto attiva l’FP (ingresso 01 del multiplexer, collegato al segnale Q3
wait);
BA14 BA6
FP e RTC sono due dispositivi di input (vengono solamente letti), quindi li interfacciamo al bus dati (BD)
con dei componenti 244: il FP gestisce 8 bit, il RTC invece lavora a 16 bit (presumibilmente 8 per le ore e 8
per i minuti) e quindi ha bisogno di due 244, i quali vengono chiaramente attivati dallo stesso chip select
L’8250 è il componente che può supportare l’interfaccia RS232, utilizzata per le comunicazioni seriali che
(basato su tecnologia RFID). Si noti che, siccome è gestito a polling, non vi è
alcun collegamento fra l’8250 e i piedini IR dell’8259.
L’8259 gestisce gli interrupt (che riceve dal FP) ed è interfacciato come mostrato in figura.
“famiglie”: i dispositivi di I/O e quelli per le memorie.
PIC, FP e 8250 � segnale IO/M basso (si abilita RDY2)
Per gestire i relativi cicli di clock utilizziamo un multiplexer:
BA6 alto attiva l’8250 (ingressi 10 e 11 del multiplexer, collegati al segnale Q3
alto attiva l’FP (ingresso 01 del multiplexer, collegato al segnale Q3
FP e RTC sono due dispositivi di input (vengono solamente letti), quindi li interfacciamo al bus dati (BD)
con dei componenti 244: il FP gestisce 8 bit, il RTC invece lavora a 16 bit (presumibilmente 8 per le ore e 8
e 244, i quali vengono chiaramente attivati dallo stesso chip select
ata per le comunicazioni seriali che
che, siccome è gestito a polling, non vi è
L’8259 gestisce gli interrupt (che riceve dal FP) ed è interfacciato come mostrato in figura.
“famiglie”: i dispositivi di I/O e quelli per le memorie.
segnale IO/M basso (si abilita RDY2)
xer, collegati al segnale Q3 � 3 cicli di wait);
alto attiva l’FP (ingresso 01 del multiplexer, collegato al segnale Q3 � 3 cicli di
BA6 basso + BA 14 basso attiva il PIC (ingresso 00 del multiplexer, collegato al segnale Q1 � 1 ciclo di
wait).
DISPOSITIVI DI MEMORIA � RAM/EPROM � segnale IO/M alto (si abilita RDY1)
• BA 19 alto attiva la EPROM (segnale Q2 � 2 cicli di wait);
• BA 19 basso attiva la RAM (segnale Q1 � 1 ciclo di wait).
Punto 5
FP_Adress EQU E800H
CorrectIdentity EQU FFH
UncorrectIdentity EQU 00H
NewAccessAllowed DB 1
PUSH AX
PUSH DX
MOV DX, FP_Adress ; Leggiamo dal finger-print
IN AL, DX ;
CMP AL, CorrectIdentity ; Abbiamo rilevato un pass valido?
JNE Epilogue ; Se non l’abbiamo rilevato andiamo all’epilogo
MOV [NewAccessAllowed], 1 ; sennò segnaliamo impostando il flag appropriato
Epilogue: POP DX
POP AX
IRET
Punto 6
RTC_Adress EQU F000H
LSR EQU F0A5H
THR EQU F0A0H
CLI ; Inizializzazione
MOV [NewAccessAllowed], 0
STI
Main: CMP [NewAccessAllowed], 0 ; c’è un nuovo accesso valido?
JE Main ; se non c’è allora aspetta
MOV DX, RTC_Adress ; altrimenti
IN AX, DX ; leggiamo l’ora
CALL write_com1 ; scriviamo
JMP Main
write_com1 PROC FAR
PUSH DX
PUSH AX
MOV DX, LSR
wait_vuoto: IN AL, DX
CMP AL, 20H
JZ wait_vuoto
MOV DX, THR
POP AX
OUT DX, AL
POP DX
IRET
Soluzione compito del 20 luglio 2007
Punto 1
Decodifica completa:
EPROM E8000H � FFFFFH
una da 64 KB F0000H
una da 32 KB E8000H
RAM 00000H � 03FFFH
Decodifica semplificata:
EPROM2 BA19 BA16 �
EPROM1 BA19 /BA16 �
RAM /BA19 �
Punto 2
PIC (2 byte) a 100H
Dec. completa: 0001 0000 0000 /BA11 /BA10 /BA9 BA8 /BA7
Interfaccia CAT � 2 indirizzi x 1 byte,
Dec. completa: 0000 0100 0000 /BA11 /BA10 /BA9
Dec. completa: 0000 0100 0001 /BA11 /BA10 /BA9
LED (1 byte) a 80H
Dec. completa: 0000 1000 0000 /BA11 /BA10 /BA9
Decodifica semplificata:
PIC BA8
CAT1 /BA8 /BA0
CAT2 /BA8 BA0
LED BA7
Punto 3
Soluzione compito del 20 luglio 2007
FFFFFH
F0000H � FFFFFH BA19 BA18 BA17 BA16
E8000H � EFFFFH BA19 BA18 BA17 /BA16 BA15
03FFFH /BA19 /BA18 /BA17 /BA16 /BA15 /BA14
� EPROM2# = /BA19 + /BA16
� EPROM1# = /BA19 + BA16
� RAM# = BA19
/BA11 /BA10 /BA9 BA8 /BA7…. tutti negati …
byte, uno per IR0 e uno per IR1 a 40H e 41H rispettivamente
/BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5…. tutti negati
/BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5…. tutti negati
/BA11 /BA10 /BA9 /BA8 BA7 /BA6 /BA5…. tutti negati
BA19 BA18 BA17 BA16
BA19 BA18 BA17 /BA16 BA15
/BA19 /BA18 /BA17 /BA16 /BA15 /BA14
… /BA1
e 41H rispettivamente
. tutti negati … /BA1 /BA0
. tutti negati … /BA1 BA0
. tutti negati … /BA1 /BA0
Punto 4
Il segnale IN è OVER_TH, che risulta essere pari a 0 quando la tensione è entro i valori nominali e pari a 1
quando supera i valori di soglia.
Col collegamento illustrato sopra accade che:
• il flip-flop che regola INT_REQ0 entrerà in funzione (cioè campionerà l’ “1”) sul fronte positivo di
IN cioè quando OVER_TH passa da 0 a 1 � c’è sovratensione, andiamo in panico e generiamo la
relativa interruzione (/ACK0 è alto perché c’è un nuovo evento ancora non servito) � quando
l’interruzione è stata servita, torniamo alla normalità (OVER_TH = 0) quindi /ACK0 va a basso e
resetta INT_REQ0. Contemporaneamente, il flip-flop sottostante campiona il suo “1” e segnala che
siamo tornati alla normalità; infine, /ACK1 andrà a basso e questo segnalerà che il calcolatore ha
capito il messaggio;
• il flip-flop che regola INT_REQ1 entrerà in funzione (cioè campionerà l’ “1”) sul fronte positivo di
/IN cioè quando OVER_TH passa da 1 a 0 � torniamo alla normalità e generiamo la relativa
interruzione (/ACK1 è alto perché c’è un nuovo evento ancora non servito) � quando essa sarà
stata recepita, /ACK1 andrà a basso e resetterà INT_REQ1. Dopo un po’, alla successiva situazione
di panico (OVER_TH = 1 � /IN = 0), il flip-flop soprastante campiona il suo “1” e segnala che siamo
ufficialmente in panico.
Punto 5
LED EQU 80H
ACK0 EQU 40H
ACK1 EQU 41H
Punto 6
STI
MOV AL, 0
OUT ACK0, AL
OUT ACK1, AL
CLI
Main: JMP Main
Punto 7
INT0: OUT LED, 1
OUT ACK0, 0
IRET
INT1: OUT LED, 0
OUT ACK1, 0
IRET
96 KB di EPROM = 64 KB + 32 KB indirizzi alti
160 KB di RAM = 128 KB + 32 KB indirizzi bassi
Unità principale: 32 KB � bit discriminanti: BA19 BA18 BA17 BA16 BA15
EPROM64 FFFFFH � F0000H
EPROM32 EFFFFH � E8000H
RAM32 27FFFH � 02000H
RAM128 1FFFFH � 00000H
Decodifica completa:
EPROM64 BA19 BA18 BA17 BA16
EPROM32 BA19 BA18 BA17 /BA16 BA15
RAM32 /BA19 /BA18 BA17 /BA16 /BA15
RAM128 /BA19 /BA18 /BA17
Decodifica semplificata:
EPROM64 BA19 BA16
EPROM32 BA19 /BA16
RAM32 /BA19 BA17
RAM128 /BA17 (le due EPROM hanno BA17 quindi non c’è ambiguità)
Il 244 è un driver 3-state che comunica col bus dati di 8 bit (dei quali ne vengono effettivamente
usati soltanto 3). Tale componente è proprio ad 8 bit ma strutturato in 2 gruppi di 4 bit, abilitati da
EN1 ed EN2 (i quali, chiaramente, devono ricevere lo stesso segnale perché gli 8 bit del dato
devono arrivare sul bus tutti insieme). Tale componente è attivo quando si vuole leggere dal bus
dati di I/O (IORDC# = I/O Read Command = 0) e quando espressamente si vuole far agire il
sensore CMPS (CS_CMPS# = 0). Siccome la bussola è a tre bit, A7, A6 … A3 possono essere posti a
massa.
Il 373 è un latch a 8-bit con uscite 3-state: esso campiona sul fronte negativo di CK e mantiene il
valore fino a quando CK non torna ad 1. Per questo il clock viene collegato al NOR fra CS_LED# (è
0 quando vogliamo accendere un led) e IOWRC# (che è 0 quando vogliamo scrivere sul bus dati di
I/O). OE* è sempre basso perché vogliamo che il buffer sia trasparente e mai in configurazione di
alta impedenza.
Passiamo al PIC (8259):
Il clock collegato al PIC è a 100 Hz perché a quella frequenza va campionato l’informazione
d’orientamento fornita dal sensore.
Mappiamo:
• il PIC a 80H; 1000 0000 � BA7 /BA6 /BA5 /BA4
• il CMPS a 60H; 0110 0000 � /BA7 BA6 BA5 /BA4
• i led a 40H. 0100 0000 � /BA7 BA6 /BA5 /BA4
Decodifica semplificata:
• il PIC � BA7
• il CMPS � /BA7 BA5
• i led a 40H � /BA7 /BA5
Per cui, applicando DeMorgan:
CS_PIC# = /BA7
CS_CMPS# = BA7 + /BA5
CS_LED# = BA7 + BA5
Il circuito è il seguente: Q3 riguarda gli stati di wait (3 appunto) dei dispositivi di I/O; Q2 si
riferisce ai 2 stati di wait per le RAM e Q1 al singolo stato di wait
necessitato dalle EPROM. Questi tre segnali vengono generati da
uno shift register (v. figura a destra).
IO/M e /IO/M sono due segnali complementari (IO/M = 1 quando
dobbiamo leggere l’I/O, IO/M = 0 quando sono in uso le
memorie), collegati rispettivamente a AEN2* e AEN1*, sicché gli stati di wait saranno
effettivamente quelli richiesti dalla situazione.
Ricordando le espressioni dei chip select
si nota facilmente che il bit che discrimina l’accesso alla RAM o alla EPROM è BA19 (ed infatti è
quello che compare nello schema).
Uscita CMPS Stringa
000 (N) 00000001
001 (NE) 00000010
011 (E) 00000100
010 (SE) 00001000
110 (S) 00010000
111 (SW) 00100000
101 (W) 01000000
100 (NW) 10000000
ORDINATI
000 (N) 00000001
001 (NE) 00000010
010 (SE) 00001000
011 (E) 00000100
100 (NW) 10000000
101 (W) 01000000
110 (S) 00010000
111 (SW) 00100000
Programmazione del PIC
Si cop-incolla sempre lo
stesso codice, avendo
cura di modificare le
parole di comando e gli
indirizzi del PIC in base a
dove è mappato. Siccome
abbiamo collegato un solo
piedino IR, MASK è da
porre a
1111 1110 = FEH
ADDRESS è da porre a
0001 1111 = 1FH
cosicché abbiamo individuato i 5 bit più significativi.
Definizione della tabella: CMPS_TABLE DB 00000001, 00000010, 00001000, 00000100, 10000000, 01000000, 00010000, 00100000
newDirectionAvailable DB 1
newDirection DB 1
CMPS EQU 60H
LEDS EQU 40H
MOV [newDirectionAvailable], 0 ; inizialmente settato a zero
Main: CMP [newDirectionAvailable], 1 ; c’è una qualche nuova informazione?
JNE Main
MOV [newDirectionAvailable], 0 ; richiesta accettata
IN SI, [newDirection] ; nel registro SI mettiamo la nuova direzione
MOV AX, [CMPS_TABLE+SI] ; peschiamo il led che si deve accendere usando la tabella
OUT AX, LEDS ; accendiamo il LED! Quante lucine! Buon Natale!
JMP Main
int_CMPS: PUSH AX ; salvataggio di contesto
IN AL, CMPS ; leggi il CMPS
MOV [newDirection], AL ; metti il tutto nella [newDirection]
MOV [newDirectionAvailable], 1 ; segnaliamo al MAIN
POP AX ; ripristino del contesto
OUT OCW2, EOI ; end of interrupt
IRET ; usciamo dalla routine
Soluzione compito del 1 aprile 2008
Punto 1
Espressioni dei CS + decodifica completa:
• EPROM A8000H � AFFFFH
• RAM2 30000H � 37FFFH
• RAM1 10000H � 17FFFH
Decodifica semplificata:
• EPROM BA19
• RAM2 /BA19 BA17
• RAM1 /BA19 /BA17
Punto 2
Dispositivi di I/O:
• PIC F00EH 1111 0000 0000 1110
BA15 BA14 BA13 BA12 /BA11 … tutti negati … BA3 BA2
• tornello 1 1000H 0001 0000 0000 0000
/BA15 /BA14 /BA13 BA12 /BA11 … /BA0
• tornello 2 2000H 0010 0000 0000 0000
/BA15 /BA14 BA13 /BA12 /BA11 … /BA0
• tornello 3 3000H 0011 0000 0000 0000
/BA15 /BA14 BA13 BA12 /BA11 … /BA0
Punto 3
Soluzione compito del 1 aprile 2008
Espressioni dei CS + decodifica completa:
AFFFFH BA19 /BA18 BA17 /BA16 BA15
37FFFH /BA19 /BA18 BA17 BA16 /BA15
17FFFH /BA19 /BA18 /BA17 BA16 /BA15
� De Morgan � /BA19
� De Morgan � BA19 + /BA17
� De Morgan � BA19 + BA17
1111 0000 0000 1110
BA15 BA14 BA13 BA12 /BA11 … tutti negati … BA3 BA2 BA1 � sempl. BA15
0001 0000 0000 0000
/BA11 … /BA0 � sempl.
0010 0000 0000 0000
/BA11 … /BA0 � sempl.
0011 0000 0000 0000
/BA11 … /BA0 � sempl.
15
/BA19 /BA18 BA17 BA16 /BA15
BA17 BA16 /BA15
sempl. BA15
sempl. /BA13
sempl. /BA12
sempl. BA13 BA12
Punto 4
5 bit più significativi dell’interrupt type: 10100 � 1010 0 � A0H (in esadecimale)
I tre valori di interrupt type associati alle 3 routine di interrupt presenti nel sistema sono perciò A0H (ski-
pass mattutino), A1H (ski-pass pomeridiano) e A2H (ski-pass giornaliero).
Punto 5
Prendiamo come riferimento il tornello 1.
TORNELLO1 EQU 1000H
MASK_HOUR EQU FCH (1111 1100)
MASK_PASS EQU 03H (0000 0011)
NINE EQU 28H (0 01001 00) in rosso � 9 in binario
TWELWE EQU 30H (0 01100 00) in rosso � 12 in binario
SIXTEEN EQU 40H (0 10000 00) in rosso � 16 in binario
arrival: PUSH AX
PUSH DX
MOV DX, TORNELLO1 ; la porta ha indirizzo >255 perciò bisogna usare
; l’indirizzamento indiretto tramite DX
IN AL, DX ; prendiamo il dato dal tornello
MOV AH, AL ; copiamo il dato in AH
AND AL, MASK_HOUR ; in AL finiscono le ore
AND AH, MASK_PASS ; in AH finiscono i due bit che riguardano il tipo di pass
CMP AL, NINE ; siamo prima delle nove?
JL Epilogue ; se sì non possiamo aprire il tornello
CMP AL, SIXTEEN ; siamo dopo delle sedici (comprese)?
JEG Epilogue ; se sì non possiamo aprire il tornello
CMP AH, 10H ; è un abbonamento giornaliero?
JE ValidPass ; se sì, il tizio può passare
CMP AL, TWELWE ; sono passate le dodici (comprese)?
JEG TwelvePassed ; andiamo a verificare se il tizio ha il permesso
CMP AH, 00H ; è un pass mattutino?
JE ValidPass ; siamo tra le 9 e le 12 quindi il tizio può passare
TwelvePassed: CMP AH, 01H ; è un pass pomeridiano?
JE ValidPass ; siamo tra le 12 (comprese) e le 16 (escluse): valido!
ValidPass: MOV AH, 1 ; segnale alto
MOV AL, 0 ; segnale basso
OUT DX, AH ; spediamo il valore alto (viene memorizzato e
; mantenuto dalla batteria di latch)
CALL WAIT ; aspettiamo 1 ms
OUT DX, AL ; rimandiamo a basso
Epilogue: POP DX
POP AX ; ripristino contesto
IRET
Punto 6
1MHz � periodo di clock = 1μs
1 ms = 1.000 μs = 1.000 colpi di clock
ogni operazione = 10 colpi di clock
totale operazioni di effettuare = 1.000/10 = 100
WAIT: MOV CX, 98 ; dobbiamo “looppare” per 99 istruzioni perché la prima delle 100 è
; questa MOV!! CX è quindi numero di istruzioni + 1
LOOP WAIT ; il programma decrementa CX fino a quando non raggiunge zero:
; fino a quel momento, salta di volta in volta a WAIT
Dovendo disporre di 48 KB di EPROM, avremo bisogno di due blocchi (32+16) mappati agli
indirizzi alti; i 16 KB di RAM vanno invece posti agli indirizzi bassi.
Usando una CPU 8088 abbiamo 1 MB di spazio di indirizzamento a disposizione, per cui si avrà:
32 KB di EPROM FFFFFH � F8000H
16 KB di EPROM F7FFFH � F4000H
…
16 KB di RAM 03FFFH � 00000H
Sottomultipli di 16 KB � XXXX XX XX XXXX XXXX XXXX
BA19 BA18 BA17 BA16 BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 BA5 BA4 BA3 BA2 BA1 BA0
Decodifica completa:
EPROM (32 KB) BA19 BA18 BA17 BA16 BA15 /BA14 -- -- -- -- -- -- -- -- -- -- -- -- -- --
EPROM (16 KB) BA19 BA18 BA17 BA16 /BA15 BA14 -- -- -- -- -- -- -- -- -- -- -- -- -- --
RAM (16 KB) /BA19 /BA18 /BA17 /BA16 /BA15 /BA14 -- -- -- -- -- -- -- -- -- -- -- -- -- --
Decodifica semplificata1:
EPROM (32 KB) BA19 -- -- -- BA15 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
EPROM (16 KB) BA19 -- -- -- /BA15 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
RAM (16 KB) /BA19 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Se vogliamo ottenere i chip select relativi al funzionamento attivo-basso (simbolo #) dobbiamo
usare le formule di De Morgan (da moltiplicazione a somma con negazione degli elementi):
CS-EPROM32# /BA19 + /BA15
CS-EPROM16# /BA19 + BA15
CS-RAM# BA19
Ecco lo schema:
Il 244 è un driver 3-state che comunica col bus dati di 8 bit (come viene specificato nel testo). Tale
componente è proprio ad 8 bit ma strutturato in 2 gruppi di 4 bit, abilitati da EN1 ed EN2 (i quali,
chiaramente, devono ricevere lo stesso segnale perché gli 8 bit del dato devono arrivare sul bus
1 Trucchettino: bisogna sempre fare in modo di avere, per i bit, una successione del tipo
111 110 101 100 …
con gli uni e gli zeri che si alternano alla stregua di un semplice conteggio di passo 1.
tutti insieme). Tale componente è attivo quando si vuole leggere dal bus dati di I/O (IORDC# = I/O
Read Command = 0).
Il 373 è un latch a 8-bit con uscite 3-state: esso campiona sul fronte negativo di CK e mantiene il
valore fino a quando CK non torna ad 1. Per questo il clock viene collegato al NOR fra CS_ABS# (è
0 quando vogliamo attivare l’ABS) e IOWRC# (che è 0 quando vogliamo scrivere sul bus dati di
I/O). OE* è sempre basso perché vogliamo che il buffer sia trasparente e mai in configurazione di
alta impedenza.
Passiamo al PIC (8259):
Il segnale WR* serve per abilitare l’8259 alla ricezione di parole di controllo da parte della CPU,
mentre il piedino RD* serve alla CPU per richiedere al componente lo stato sul bus dati.
I tre componenti sono mappati ai seguenti indirizzi2:
PIC � 100H (come nel testo)3
373 (centralina ABS) � 40H
244 (trasduttore di velocità) � 60H
Le finestre occupate dai dispositivi di I/O sono solitamente molto piccole rispetto a quelle dei
dispositivi di memoria (ad es. 16 byte).
Sottomultipli di 32 byte � XXXX XXXX XXXX XXXX
BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 BA5 BA4 BA3 BA2 BA1 BA0
Decodifica completa:
PIC /BA15 /BA14 /BA13 /BA12 /BA11 /BA10 /BA9 BA8 -- -- -- -- -- -- -- --
2 Lo spazio di indirizzamento per l’I/O è di 64 KB (fino a FFFFH). 3 Il PIC dev’essere mappato nello spazio d’indirizzamento di I/O e occupa 2 byte.
Centralina ABS /BA15 /BA14 /BA13 /BA12 /BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5 /BA4 -- -- -- --
Trasduttore di velocità /BA15 /BA14 /BA13 /BA12 /BA11 /BA10 /BA9 /BA8 /BA7 BA6 BA5 /BA4 -- -- -- --
Decodifica semplificata:
PIC -- -- -- -- -- -- -- BA8 -- -- -- -- -- -- -- --
Centralina ABS -- -- -- -- -- -- /BA9 -- -- -- /BA5 -- -- -- -- --
Trasduttore di velocità -- -- -- -- -- -- /BA9 -- -- -- BA5 -- -- -- -- --
Per la generazione del ready serve il componente 8284, il quale è suddiviso in tre parti funzionali:
Ready, Clock e Reset Generator.
RDY1 e RDY2 sono le uscite di contatori diversi che una volta sincronizzati con il segnale ALE,
danno ritardi diversi con i quali generare il segnale READY. Per capirci, RDY1 può essere il
ritardo della RAM, mentre RDY2 il ritardo della EPROM. Se quindi il piedino RDY1 contiene un
segnale ritardato di n cicli di clock e il pedino RDY2 un segnale ritardato di m cicli di clock, il
componente provoca la generazione di n o m stati di wait, rispettivamente se è attivo AEN1* o
AEN2*. Dunque AEN1* serve a selezionare RDY1 (per indicare al 8284 che si sta usando la RAM)
e AEN2* serve per selezionare RDY2 (per indicare al 8284 che si sta usando la EPROM).
Siccome vogliamo che la rete di generazione del ready non sia troppo complessa, usiamo le
espressioni semplificate per i chip select:
Dividiamo i dispositivi in due gruppi: le memorie (RAM + EPROM) e l’I/O. Per l’I/O la
generazione del ready dev’essere istantanea perciò colleghiamo “1” a RDY1. I segnali AEN1* e
AEN2* dovranno essere l’uno il complementare dell’altro perché la memoria e i dispositivi di I/O
non possono essere attivi contemporaneamente: chiamiamo perciò IO/M il segnale che si riferisce
alle memorie se alto e all’I/O se è basso.
Per quanto riguarda RDY2, usiamo la seguente espressione:
Qi (nell’espressione vediamo Q3 e Q2) è un segnale generato da un particolare shift register dopo i
colpi di clock. BA19 compare in quanto discrimina quale tipo di memoria andremo a usare.
Quindi RDY2 si attiva se:
• sono passati tre colpi di clock e BA19 è basso (si attiva la RAM);
• sono passati due colpi di clock e BA9 è alto (si attiva la EPROM).
Ecco quindi lo schema finale:
;Variabili
lastOmegaValue DB 1 (con DB indichiamo delle variabili)
enableABS DB 1 (1 sta per 1 Byte)
;Mapping I/O
ABS_ON EQU 40H (con EQU indichiamo delle costanti)
OMEGA EQU 60H
;Soglia ABS
SOGLIA_ABS EQU 0FH
CLI4
MOV AL,0 (viene posto AL a 0)
OUT ABS_ON,AL5 (comunichiamo con l’I/O: spegni l’ABS)
MOV [enableABS],0
IN AL, OMEGA (prendiamo omega dall’I/O e lo mettiamo dentro AL)
MOV [lastOmegaValue],AL (spostiamo il valore dal registro alla variabile)
STI (settiamo le interruzioni)
waitABSOnLabel: CMP [enableABS],1 (enableABS è ad 1?)
JE enableABSLabel (se sì salta a enableABSLabel, cioè attiva l’ABS)
JMP waitABSOnLabel (altrimenti looppa!)
enableABSLabel: MOV AL,1 (metti 1 nel registro AL)
OUT ABS_ON,AL (comunichiamo con l’I/O: ABS acceso)
4 Azzera i bit IF del registro dei FLAGS* (disabilitiamo le interruzioni) 5 Usiamo AL perché trasferiamo un byte.
waitABSOffLabel: CMP [enableABS],0 (l’ABS deve spegnersi?)
JNE waitABSOffLabel (se sì, riprendi ad aspettare)
MOV AL,0 (altrimenti…)
OUT ABS_ON,AL (… manda ABS_ON a 0)
JE waitABSOnLabel (e aspetta una nuova richiesta di accensione)
int_ABS: PUSH AX (Mettiamo AX nello stack)
IN AL, OMEGA (Leggiamo il valore del trasduttore)
MOV AH, [lastOmegaValue] (Mettiamo il penultimo valore dentro AH)
MOV [lastOmegaValue],AL (Aggiorniamo lastOmegaValue)
CMP AL, AH (Confronto tra il penultimo e l’ultimo valore)
JGE int_ABSOFF ;accelerazione (se OMEGA >= lastOmegaValue
spegni l’ABS)
SUB AH, AL (fa AL - AH, cioè {ultimo valore} - {penultimo valore},
questo risultato dice di quanto è aumentata la velocità)
CMP AH,SOGLIA_ABS
JL int_ABSOFF ;decelerazione bassa (AL - AH è minore della
soglia? Se sì disabilita l’ABS)
MOV [enableABS], 1 ;ABS ON!!! (altrimenti attivalo!)
JMP int_ABS_end (e procedi con la fine dell’interruzione)
int_ABSOFF: MOV [enableABS],0 (disabilitazione dell’ABS)
int_ABS_end: POP AX (epilogo)
OUT OCW2, EOI (segnale end of interrupt)
IRET
96 KB (= 64 + 32) EPROM (indirizzi alti) � EPROM1 FFFFH � F0000H
EPROM2 EFFFFH � E8000H
160 KB (= 128 + 32) RAM (indirizzi bassi) � RAM2 27FFFH � 20000H
RAM1 1FFFFH � 00000H
Decodifica completa:
96 KB (= 64 + 32) EPROM (indirizzi alti) � EPROM1 BA19 BA18 BA17 BA16
EPROM2 BA19 BA18 BA17 /BA16 BA15
160 KB (= 128 + 32) RAM (indirizzi bassi)� RAM2 /BA19 /BA18 BA17 /BA16 /BA15
RAM1 /BA19 /BA18 /BA17
Decodifica semplificata:
96 KB (= 64 + 32) EPROM (indirizzi alti) � EPROM1 BA19 BA16
EPROM2 BA19 BA17
160 KB (= 128 + 32) RAM (indirizzi bassi)� RAM2 /BA19 BA17
RAM1 /BA17
Leggi di DeMorgan:
96 KB (= 64 + 32) EPROM (indirizzi alti) � CS_EPROM1# /BA19 + /BA16
CS_EPROM2# /BA19 + /BA17
160 KB (= 128 + 32) RAM (indirizzi bassi)� CS_RAM1# BA19 + /BA17
CS_RAM2# BA17
A differenza degli esercizi precedenti, questa volta gli stati di wait vengono differenziati in base
alla grandezza delle memorie (mentre l’I/O non compare). Scegliamo di affidare al RDY1 i chip da
32 KB (quelli da 1 stato di wait � segnale Q1), ovvero la RAM2 e la EPROM2; il segnale AEN1*
dovrà quindi tenere conto del fatto che:
• vogliamo usare la memoria e non l’I/O (segnale IO/M dev’essere basso);
• vogliamo usare o la RAM2 o la EPROM2 (segnali CS_RAM2# e CS_EPROM2#).
Queste condizioni vengono soddisfatte grazie al semplice circuito nel riquadro ROSSO.
Il segnale AEN2* dovrà invece tenere conto del fatto che:
• vogliamo usare la memoria e non l’I/O (segnale IO/M dev’essere basso);
• vogliamo usare o la RAM1 o la EPROM1 (segnali CS_RAM1# e CS_EPROM1#).
Queste condizioni vengono soddisfatte grazie al semplice circuito nel riquadro VERDE.
In tale secondo caso bisogna anche differenziare le memorie, visto che la EPROM è da 64 KB e
richiede 2 stati di wait mentre la RAM ne richiede 3 (essendo una memoria da 128 KB).
Ricordando le espressioni dei CS, è il BA19 a fare la differenza (e infatti esso fa bella mostra
all’interno del circuito).
L’interfacciamento è quello in figura a fianco: in pratica, il
buffer 3-state lascia passare il segnale se e solo se il CS_SET# è
basso e si vuole leggere dal bus (IORDC# = 0).
La gestione è a polling perché SET viene portato direttamente
al BUS dati (se era ad interrupt bisognava mandare tutto al
PIC).
Dobbiamo fare uso del 373 (latch a 8-bit con uscite 3-state), cioè di un dispositivo di
memorizzazione (abbiamo bisogno dei dati relativi a ore e minuti). I 373 campionano sul fronte
negativo e sono collegati al bus dati: il latch “delle ore” ha il piedino CK collegato a IOWRC# e
CS_HOUR#, mentre quello dei minuti a IOWRC# e CS_MINUTE#.
Utilizziamo inoltre due componenti 5447 per trasformare i gruppi di 4 bit uscenti dai 373 in
stringhe di 7 bit corrispondenti ai valori leciti del codice a 7 segmenti. Avremo così 4 cifre
fondamentali (2 per le ore e 2 per i minuti) le quali saranno trattate nella parte software
dell’esercizio.
Rimane da specificare dove mapperemo i vari dispositivi di I/O:
SET a 20H HOUR a 40 H MINUTE a 30 H PIC a 90 H
SET a 20H � 0010 0000 � /BA7 /BA6 BA5 /BA4 � BA5 /BA4
HOUR a 40 H � 0100 0000 � /BA7 BA6 /BA5 /BA4 � /BA5 /BA4
MINUTE a 30 H � 0011 0000 � /BA7 /BA6 BA5 BA4 � BA5 BA4
PIC a 90 H � 1001 0000 � BA7 /BA6 /BA5 BA4 � /BA5 BA4
I collegamenti effettuati sono simili a quelli già visti negli altri esercizi, solo che questa volta
dobbiamo ben esplicitare ciò che arriva ai piedini IR:
• IR0 � il clock dei secondi (a 1 Hz)
• IR1 � il pulsante HOUR
• IR2 � il pulsante MINUTE
Si noti che non dobbiamo collegare HOUR e MINUTE al bus dati, ma direttamente al PIC, perché
le due interruzioni in questione non avvengono a polling.
Setting DB 1
Hour DB 1 (da 0 a 23)
Minute DB 1 (da 0 a 59) � 8 bit sono più che sufficienti
Second DB 1 (da 0 a 59)
SetButton EQU 20H
HourDisplay EQU 40H
MinuteDisplay EQU 30H
MOV [Hour], 0
MOV [Minute], 0
MOV [Second], 0
MOV [Setting], 0
OUT [Minute], MinuteDisplay ; settiamo il display
OUT [Hour], HourDisplay
waitSet: IN AL, SetButton ; leggiamo il pulsante
AND AL, 1
MOV [Setting], AL ; andiamo a mettere il valore del pulsante in [Setting]
JUMP waitSet
NOTA: il PIC è mappato a 90H
; Indirizzi
ICW1 EQU 90H
ICW2 EQU 91H
ICW4 EQU 91H
OCW1 EQU 91H
OCW2 EQU 90H
; Parole di comando
RESET EQU 13H
ADDRESS EQU 1FH
EN_AEOI EQU 1H
MASK EQU F8H
EOI EQU 20H
; Inizializzazione
CLI
OUT ICW1, RESET
OUT ICW2, ADDRESS
OUT ICW4, EN_AEOI
OUT OCW1, MASK
STI
I cinque bit più significativi della parola ICW2 sono 1FH, cioè 11111. Per cui gli interrupt types sono:
11111 000 � F8H � IR0
11111 001 � F9H � IR1
11111 010 � FAH � IR2
11111 011 � FBH � IR3
…
secsGoesOn si riferisce al piedino IR0 � F8H
setHour si riferisce al piedino IR1 � F9H
setMinute si riferisce al piedino IR2 � FAH
*La seguente routine aggiorna i secondi appoggiandosi al clock di 1 Hz*
secsGoesOn: CMP [Setting],1 ; è premuto il pulsante set?
JE exit
INC [Second]
CMP [Second], 60
JNE exit
CALL refresh
exit: OUT OCW2, EOI
IRET
*La seguente routine incrementa le ore una volta che si preme il tasto HOUR e SET è contemporaneamente premuto*
setHour: CMP [Setting], 1 ; è premuto il pulsante set?
JNE exit
MOV [Second], 0
INC [Hour]
CALL refresh
exit: OUT OCW2, EOI
IRET
*La seguente routine incrementa le ore una volta che si preme il tasto HOUR e SET è contemporaneamente premuto*
setMinutes: CMP [Setting], 1 ; è premuto il pulsante set?
JNE exit
MOV [Second], 0
INC [Minute]
CALL refresh
exit: OUT OCW2, EOI
IRET
refresh: PUSH AX ; Prologo!
PUSH BX ; Eseguiamo il push anche su BX (ci servirà)
CMP [Second], 60 ; I secondi da segnare sarebbero 60?
JNE minRefresh ; Se non è vero vai a minRefresh
MOV [Second], 0 ; Se invece è vero bisogna azzerare i secondi
INC [Minute] ; e aumentare i minuti di uno
minRefresh: CMP [Minute], 60 ; I minuti da segnare sarebbero 60?
JNE hourRefresh ; Se non è vero vai a hourRefresh
MOV [Minute], 0 ; Se invece è vero bisogna azzerare i minuti
INC [Hour] ; e aumentare le ore di uno
hourRefresh: CMP [Hour], 24 ; Le ore da segnare sarebbero 24?
JNE displayRefresh ; se non è vero, siamo pronti per aggiornare il display
MOV [Hour], 0 ; altrimenti metti le ore a zero e aggiorna il display
displayRefresh: MOV BX, 10 ; mettiamo 10 in BX, ci serve per trovare le decine
MOV AX, [Minute]
DIV BX ; si esegue la divisione BX / AX, il quoziente viene messo
; in AL e il resto in AH
SHL AL, 4 ; spostiamo di 4 posizioni AL, così occuperà i posti più
significativi, i quali si riferiscono alle decine
OR AL, AH ; saldiamo in un unico byte i dati su decine e unità dei minuti
; i 4 bit più significativi sono le decine (posizioni 7...4), quelli
; meno significativi le unità (posizioni 0…3)
OUT MinuteDisplay, AL ; accendiamo il display dei minuti
MOV AX, [Hour]
DIV BX ; si esegue la divisione BX / AX, il quoziente viene messo
; in AL e il resto in AH
SHL AL, 4 ; spostiamo di 4 posizioni AL, così occuperà i posti più
significativi, i quali si riferiscono alle decine
OR AL, AH ; saldiamo in un unico byte i dati su decine e unità delle ore
; i 4 bit più significativi sono le decine (posizioni 7...4), quelli
; meno significativi le unità (posizioni 0…3)
OUT HourDisplay, AL ; accendiamo il display delle ore
POP BX
POP AX
Soluzione del compito del 7/12/2007
Punto 1
Mapping:
EPROM F8000H � FFFFFH
RAM2 48000H � 4FFFFH
RAM1 00000H � 07FFFH
Decodifica semplificata:
EPROM BA19
RAM2 /BA19 BA18
RAM1 /BA19 /BA18
Punto 2
PIC FFFEH � 1111 1111 1111 1110
Decodifica completa: BA15 BA14 … tutti veri… BA2
Interfaccia COM1 2000H � 0010 0000 0000 0000
Decodifica completa: /BA15 /BA14
Interfaccia COM2 2008H � 0010 0000 0000 1000
Decodifica completa: /BA15 /BA14 BA13 /BA12 … tutti negati… /BA4 BA3
Decodifica semplificata:
PIC BA15
COM1 /BA15 /BA3
COM2 /BA15 BA3
Punto 3
Le due porte seriali sono gestite ad interrupt quindi dobbiamo collegarle direttamente all’8259.
Soluzione del compito del 7/12/2007
FFFFFH Decodifica completa: BA19 BA18 BA17 BA
4FFFFH Decodifica completa: /BA19 BA18 /BA17 /BA
07FFFH Decodifica completa: /BA19 /BA18 /BA17
1111 1111 1111 1110
BA15 BA14 … tutti veri… BA2
0010 0000 0000 0000
/BA15 /BA14 BA13 /BA12 … tutti negati… /BA3
0010 0000 0000 1000
/BA15 /BA14 BA13 /BA12 … tutti negati… /BA4 BA3
gestite ad interrupt quindi dobbiamo collegarle direttamente all’8259.
: BA19 BA18 BA17 BA16 BA15
: /BA19 BA18 /BA17 /BA16 BA15
: /BA19 /BA18 /BA17 /BA16
/BA15 /BA14 BA13 /BA12 … tutti negati… /BA4 BA3
gestite ad interrupt quindi dobbiamo collegarle direttamente all’8259.
Punto 4
Punto 5
TXBUFFER DB 1024
EndTransmission DB 1
BaudRate DB 1
BufferIndex DB 1
THR EQU 2000H
CLI
MOV [EndTransmission], 0
MOV [BaudRate], 0
MOV [BufferIndex], 0
STI
Main: CMP [EndTransmission], 1
JNE Main
MOV [EndTransmission], 0
INC [BaudRate]
CMP [BaudRate], 3
JNE SetBaud
MOV [BaudRate], 0
SetBaud: MOV AL, [BaudRate]
CALL SET_BAUD
MOV DX, THR
MOV [BufferIndex], 0
MOV AH, [TXBUFFER+BufferIndex]
OUT DX, AH
JMP Main
Punto 6
OVER_4800 DB 1
PAR_4800 DB 1
OVER_9600 DB 1
PAR_9600 DB 1
[EndTransmission], 0
], 0
[EndTransmission], 1
[EndTransmission], 0
AL, [BaudRate]
0
[TXBUFFER+BufferIndex]
OVER_19200 DB 1
PAR_19200 DB 1
LSR EQU 200DH
PARITY EQU 02H
OVERRUN EQU 04H
BAUD_4800 EQU 00H
BAUD_9600 EQU 01H
BAUD_19200 EQU 02H
BaudRate DB 1
PUSH AX ; Prologo
PUSH DX
MOV DX, LSR ; Carichiamo in AL il registro LSR di COM2
IN AL, DX
MOV AH, AL ; Copiamo in AH (AL si “contaminerà” con l’AND)
AND AL, PARITY ; cerchiamo di capire se c’è stato un errore di parità
CMP AL, 0 ; c’è stato?
JNE ParityError ; se sì (il CMP dà risultato 1) allora salta
MOV AL, AH ; facciamo il refresh di AL
AND AL, OVERRUN ; cerchiamo di capire se c’è stato un errore di overrun
CMP AL, 0 ; c’è stato?
JNE OverrunError ; se sì (il CMP dà risultato 1) allora salta
ParityError: MOV AL, [BaudRate] ; cerchiamo di capire in che BaudRate siamo
CMP AL, BAUD_4800 ; la baud è 4800?
JE Baud4800P ;
CMP AL, BAUD_9600 ; la baud è 9600?
JE Baud9600P
INC [PAR_19200] ; allora la baud è per forza 19200: incrementiamo
JMP Epilogue
Baud4800P: INC [PAR_4800] ; incrementiamo
JMP Epilogue
Baud9600P: INC [PAR_9600] ; incrementiamo
JMP Epilogue
OverrunError: MOV AL, [BaudRate] ; cerchiamo di capire in che BaudRate siamo
CMP AL, BAUD_4800 ; la baud è 4800?
JE Baud4800O
CMP AL, BAUD_9600 ; la baud è 9600?
JE Baud9600O
INC [OVER_19200] ; allora la baud è per forza 19200
JMP Epilogue
Baud4800O: INC [OVER _4800] ; incrementiamo
JMP Epilogue
Baud9600O: INC [OVER _9600] ; incrementiamo
Epilogue: POP DX
POP AX
IRET
Punto 7
Percentuale
di errori di
overrun
Baud/rate KB trasmessi
al secondo
Tempo per
trasmettere un
KB
Byte errati ogni KB Errori compiuti nei
20 giri
0,010% 4.800 0,586 1,707 0,102 2,048
0,015% 9.600 1,172 0,853 0,154 3,072
0,100% 19.200 2,344 0,427 1,024 20,48
Tempo totale di un "giro" 2,987 secondi arrotondiamo a 3
Numero di giri 20,089 giri arrotondiamo a 20
Totale errori
Quindi, per garantire il corretto funzionamento del sistema nell’arco di 1 minuto, è sufficiente 1 byte per le
tre variabili OVER_{4800|9600|19200}.
160 KB EPROM (128+32 KB) agli indirizzi alti FFFFFH � D8000H
divisa in EPROM1 (128 KB) FFFFFH � E0000H
ed EPROM2 (32 KB) DFFFFH � D8000H
256 KB RAM agli indirizzi bassi 3FFFFH � 00000H
Decodifica completa:
EPROM1 BA19 BA18 BA17
EPROM2 BA19 BA18 /BA17 BA16 BA15
RAM /BA19 /BA18
Decodifica semplificata:
EPROM1 BA19 BA17
EPROM2 BA19 /BA17
RAM /BA19
PIC a 100H � 0001 0000 0000 � /BA9 BA8
Porta seriale SERIAL_IN a 03F8H � 0011 1111 1000 � BA9 BA8
Porta seriale SERIAL_OUT a 02F8H � 0010 1111 1000 � BA9 /BA8
Possiamo quindi applicare De Morgan per scrivere:
CS_PIC# = BA9
CS_SERIAL_IN# = /BA9 + /BA8
CS_SERIAL_OUT# = BA8
Abbiamo 4 diversi dispositivi da controllare, quindi sarà necessario
introdurre un componente (ad es. un multiplexer) che sia in grado di
discriminare ognuno di essi. Come sempre Qi è un segnale che si attiva
dopo i colpi di clock.
IO/M discrimina le situazioni in cui facciamo uso delle memorie o dell’I/O: se IO/M è alto allora
usiamo l’I/O (viene negato e dato a AEN1*), altrimenti le memorie. Nel primo caso ci toccherà
aspettare 4 stati di wait, nel secondo…
• 3 stati di wait se si attiva la RAM: ricordando che le espressioni dei chip select sono
EPROM1 BA19 BA17
EPROM2 BA19 /BA17
RAM /BA19
la RAM si attiverà quando BA19 è basso, ed infatti le prime due posizioni (00 e 01) del MUX
sono settate su Q3;
• 2 stati di wait se si attiva la EPROM1 (BA19 BA17 attivi, posizione 11 del MUX);
• 1 stato di wait se si attiva la EPROM2 (BA19 alto BA17 basso, posizione 10 del MUX).
Le porte seriali vengono interfacciate al sistema grazie al componente 8250.
Fra i segnali da definire:
• i chip select sono già stati ricavati;
• WR* e RD* sono comuni all’8088;
• D[0..7] e A[0..2] vanno connessi al bus dati e al bus degli indirizzi;
• INT finisce all’8259, in uno dei suoi piedini IR (corrispondenti alle
varie interrupt request).
Il PIC, invece, va configurato come al solito. Il risultato finale è quindi il seguente:
SerialInAddress EQU 03F8H
SerialOutAddress EQU 02F8H
PicAddress EQU 100H
ChecksumResult DB 1
NewMessage DB 1
Buffer1 DB 1024
Buffer2 DB 1024
CurrentRXBuffer DB 2
CheckBufferStartAddress DB 2
CurrentRXBufferIndex DB 2
CLI
MOV [CurrentRXBuffer], Buffer1 ; inizializzazione dei vari parametri
MOV [CurrentRXBufferIndex], 0
MOV [NewMessage], 0
STI
wait: CMP [NewMessage],1
JNE wait
MOV [NewMessage],0 ; non è arrivato alcun messaggio quindi resettiamo
MOV [ChecksumResult], 0
CALL CHECKSUM ; controlliamo la correttezza del pacchetto
CMP [ChecksumResult], 1
JE wait ; se non viene bene il Checksum, rifacciamo wait,
il ché vuol dire ritrasmettere
MOV SI, 0 ; mettiamo il registro indice a zero
send: MOV AL, [CheckBufferStartAddress + SI] ; passiamo il byte sul registro
CALL write_com1 ; scriviamo il byte su com1
INC SI ; incrementiamo il contatore
CMP SI, 1024 ; hai spedito tutta la parola?
JE wait ; se sì torna in wait e fai il Checksum
JMP send ; sennò manda un altro byte
PUSH AX ; salvataggio del contesto
PUSH DX
PUSH SI ; dobbiamo salvare anche SI perché potremmo star scrivendo
sulla porta d’uscita
MOV DX, RBR ; lettura del byte ricevuto
IN AL, DX
MOV SI, [CurrentRXBufferIndex] ; indice corrente (0..1023)
MOV [CurrentRXBufferIndex + SI], AL ; copia in memoria
INC [CurrentRXBufferIndex] ; aggiornamento indice corrente
CMP [CurrentRXBufferIndex], 1024 ; fine buffer?
JNE routine_end ; se non hai finito termina (hai scritto 1 byte)
MOV [CurrentRXBufferIndex],0 ; fine buffer
MOV [checkBufferStartAddress], currentRxBuffer ; indirizzo del buffer da controllare
CMP [CurrentRXBufferIndex], Buffer1 ; scambio i buffer:
JE swap_1 ; se il buffer corrente è l’1 allora scambiali
MOV [CurrentRXBufferIndex], Buffer1 ; altrimenti seta l’1 come buffer corrente
JMP swap_0
swap_1: MOV [CurrentRXBufferIndex], Buffer2
swap_0: MOV [NewMessage],1 ; ricevuto nuovo messaggio! Abbiamo finito
di ricevere
routine_end: POP SI ; ripristino del contesto
POP DX
POP AX
OUT OCW2, EOI ; segnalazione di fine routine
IRET ; ritorno al programma principale
Soluzione compito dell’11/01/2008
Punto 1
Chip select per le memorie:
EPROM F8000H � FFFFFH
RAM2 30000H � 37FFFH
RAM 00000H � 07FFFH
Decodifica semplificata:
EPROM BA19
RAM2 /BA19 BA17
RAM /BA19 /BA17
Punto 2
PIC: FFFEH 1111 1111 1111 1110
Decodifica completa: BA15 … tutti veri …
Porta seriale: A000H 1010 0…0
Decodifica completa: BA15 /BA14 BA13 /B
Interfaccia centralina: FFD0H 1111 1111 1101 0000
Decodifica completa: BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 /BA5 BA4 /BA3
Decodifica semplificata:
PIC BA14 BA1
Porta seriale /BA14
Interfaccia centrale BA14 /BA
Punti 3 e 4
Soluzione compito dell’11/01/2008
FFFFFH Cod. completa: BA19 BA18 BA17 BA16 BA15
37FFFH Cod. completa: /BA19 /BA18 BA17 BA16 BA15
07FFFH Cod. completa: /BA19 /BA18 /BA17 /BA16
1111 1111 1111 1110
tutti veri … BA1
1010 0…0 0000 0000
Decodifica completa: BA15 /BA14 BA13 /BA12 /BA11 … tutti negati … /BA3
1111 1111 1101 0000
BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 /BA5 BA4 /BA3
BA14 BA1
/BA14
BA14 /BA1
Cod. completa: BA19 BA18 BA17 BA16 BA15
BA18 BA17 BA16 BA15
BA16 /BA15
/BA3
BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 /BA5 BA4 /BA3 /BA2 /BA1 /BA0
Punto 5
NewParameter DB 1
Data DB 1
DataCode DB 1
LSR EQU A006H
THR EQU A000H
CLI
MOV [NewParameter], 0
STI
Main: CMP [NewParameter], 1
JNE Main
MOV AL, [Data]
MOV AH, [DataCode]
PUSH DX
PUSH AX
MOV DX, LSR
wait_vuoto: IN AL, DX
CMP AL, 20H
JZ wait_vuoto
MOV DX, THR
POP AX
OUT DX, AL ; in AL abbiamo messo il dato
OUT DX, AH ; in AH il codice
MOV [NewParameter], 0
POP DX
JMP Main
Punto 6
S_Luce EQU 08H
S_Umidità EQU 10H
S_Temperatura EQU 11H
S_ForzaVento EQU 20H
BitLuce EQU 01H
BitUmidità EQU 02H
BitTemperatura EQU 04H
BitForzaVento EQU 08H
NewParameter DB 1
Data DB 1
DataCode DB 1
ControlRegister EQU FFD0H
StatusRegister EQU FFD1H
DataRegister EQU FFD2H
Serial EQU A000H
PUSH AX
PUSH DX
MOV DX, ControlRegister ; Riceviamo dal registro di controllo
IN AH, DX
MOV AL, AH ; copiamo in AL (così poi possiamo fare le operazioni
; che ci pare!)
AND AL, BitLuce ; facciamo l’and con il bit di indice 0
CMP AL, 1 ; viene uno?
MOV [DataCode], S_Luce ; copiamo in [DataCode] il codice relativo al parametro
JE Luce ; allora l’informazione riguarda la luce
MOV AL, AH ; riaggiorniamo AL (il “vecchio“ AL era stato modificato
; dall’AND)
AND AL, BitUmidità ; facciamo l’and con il bit di indice 1
CMP AL, 1 ; viene uno?
MOV [DataCode], S_Umidità ; copiamo in [DataCode] il codice relativo al parametro
JE Umidità ; allora l’informazione riguarda l’umidità
MOV AL, AH ; riaggiorniamo AL
AND AL, BitTemperatura ; facciamo l’and con il bit di indice 2
CMP AL, 1 ; viene uno?
MOV [DataCode], S_Temperatura ; copiamo in [DataCode] il codice relativo al parametro
JE Temperatura ; allora l’informazione riguarda la temperatura
MOV AL, AH ; riaggiorniamo AL
AND AL, BitForzaVento ; facciamo l’and con il bit di indice 3
CMP AL, 1 ; viene uno?
MOV [DataCode], S_ForzaVento ; copiamo in [DataCode] il codice relativo al parametro
JE ForzaVento
Luce: MOV DX, StatusRegister ; scriviamo sul registro di stato
OUT DX, S_Luce
MOV DX, DataRegister ; poi preleviamo il dato
IN [Data], DX
JMP Epilogue
Umidità: MOV DX, StatusRegister ; scriviamo sul registro di stato
OUT DX, S_Umidità
MOV DX, DataRegister ; poi preleviamo il dato
IN [Data], DX
JMP Epilogue
Temperatura: MOV DX, StatusRegister ; scriviamo sul registro di stato
OUT DX, S_Temperatura
MOV DX, DataRegister ; poi preleviamo il dato
IN [Data], DX
JMP Epilogue
ForzaVento: MOV DX, StatusRegister ; scriviamo sul registro di stato
OUT DX, S_ForzaVento
MOV DX, DataRegister ; poi preleviamo il dato
IN [Data], DX
JMP Epilogue
Epilogue: MOV [NewParameter], 1 ; “c’è posta per te!”
PUSH DX
PUSH AX
IRET
Soluzione compito del 14/07/2008
Punto 1
Espressioni dei CS: abbiamo
• una EPROM 18000H � 19FFFH (32 KB)
• una RAM 50000H � 5FFFFH (64 KB)
Decodifica semplificata: EPROM
RAM
Punto 2
Dispositivi di I/O:
• PIC A000H 1010 0…0
• RTC 4000H 0100 0…0
• elettrovalvola 8000H 1000 0…0
Decodifica semplificata: PIC
RTC
elettrovalvola
Punto 3
I due pulsanti (rosso e verde) sono gestiti ad interrupt (e infatti vengono collegati all’8259).
Il real time clock (RTC) gestisce le ore e
l’elettrovalvola viene gestita in output ed è perciò interfacciata con il componente 373.
Soluzione compito del 14/07/2008
19FFFH (32 KB) /BA19 /BA18 /BA17 BA16 BA15
5FFFFH (64 KB) /BA19 BA18 /BA17 BA16
EPROM /BA18 � Logica negata � BA18
RAM BA18 � Logica negata � /BA18
1010 0…0 BA15 /BA14 BA13 /BA12 … tutti negati … /BA1
0100 0…0 /BA15 BA14 /BA13 /BA12 … tutti negati … /BA
1000 0…0 BA15 /BA14 /BA13 /BA12 … tutti neg
PIC BA13
RTC /BA15
elettrovalvola /BA13
I due pulsanti (rosso e verde) sono gestiti ad interrupt (e infatti vengono collegati all’8259).
(RTC) gestisce le ore e i minuti (due dati da 8 bit) ed è interfacciato al bus tramite il 244;
l’elettrovalvola viene gestita in output ed è perciò interfacciata con il componente 373.
/BA19 /BA18 /BA17 BA16 BA15
BA18
/BA18
… tutti negati … /BA1
… tutti negati … /BA1
… tutti negati … /BA0
I due pulsanti (rosso e verde) sono gestiti ad interrupt (e infatti vengono collegati all’8259).
(due dati da 8 bit) ed è interfacciato al bus tramite il 244;
l’elettrovalvola viene gestita in output ed è perciò interfacciata con il componente 373.
Punto 4
Punto 5
StartIrrigationTime DB 2
StopIrrigationTime DB 2
IncumbentIrrigation DB 1
SystemInitialized DB 1
RealTimeClock EQU 4000H
Valvola EQU 8000H
GreenButtonPressed: PUSH AX
PUSH DX
CMP [IncumbentIrrigation], 1
JE Epilogue
MOV DX,
IN AX, DX
MOV [StartIrrigationTime], AX
MOV [IncumbentIrrigation],
MOV [SystemInitialized], 0
MOV DX, Valvola
OUT DX, 1
Epilogue: POP DX
POP AX
IRET
RedButtonPressed: PUSH AX
PUSH DX
CMP [IncumbentIrrigation], 0
JE Epilogue
MOV DX, RealTimeClock
IN AX, DX
MOV [StopIrrigationTime], AX
MOV [IncumbentIrrigation], 0
2
2
1
1
4000H
8000H
AX
DX
[IncumbentIrrigation], 1 ; per caso sta avvenendo l’irrigazione?
Epilogue ; se sì vai all’epilogo
DX, RealTimeClock ; sennò leggi dall’orologio
AX, DX
[StartIrrigationTime], AX ; e segna l’ora d’accensione
[IncumbentIrrigation], 1 ; e segnala che sta avvenendo l’irrigazione
[SystemInitialized], 0 ; il sistema non è a
; inizializzato
DX, Valvola ; apri la valvola
DX, 1
DX ; ripristino contesto
AX
AX
DX
[IncumbentIrrigation], 0 ; per caso l’irrigazione è
Epilogue ; se sì non fare nulla (vai all’epilogo)
DX, RealTimeClock ; sennò leggi dall’orologio
AX, DX
[StopIrrigationTime], AX ; e segna l’ora di spegnimento
[IncumbentIrrigation], 0 ; e segnala che l’irrigazione è ferma
; per caso sta avvenendo l’irrigazione?
; se sì vai all’epilogo
; sennò leggi dall’orologio
d’accensione
; e segnala che sta avvenendo l’irrigazione
; il sistema non è ancora (o non è più)
; ripristino contesto
; per caso l’irrigazione è ferma?
; se sì non fare nulla (vai all’epilogo)
; sennò leggi dall’orologio
; e segna l’ora di spegnimento
l’irrigazione è ferma
MOV [SystemInitialized], 1 ; il sistema a questo punto è inizializzato
MOV DX, Valvola ; chiudi la valvola
OUT DX, 0
Epilogue: POP DX ; ripristino del contesto
POP AX
IRET
Punto 6
CLI
MOV [IncumbentIrrigation], 0 ; inizializzazione
MOV [SystemInitialized], 0
STI
Main: CMP [SystemInitialized], 0 ; il sistema è già inizializzato?
JE Main ; se non lo è, looppa!
MOV DX, RealTimeClock ; altrimenti leggi l’ora
IN AX, DX
CMP AX, [StartIrrigationTime] ; se siamo prima del momento d’inizio
; dell’irrigazione
JL TimeNotValid
CMP AX, [StopIrrigationTime] ; o se siamo dopo il momento di fine
; dell’irrigazione
JG TimeNotValid ; allora vai alla relativa parte di codice
CMP [IncumbentIrrigation], 1 ; altrimenti: stiamo già irrigando?
JE Main ; se sì torna al main
OUT DX, 1 ; altrimenti apri la valvola
MOV [IncumbentIrrigation], 1 ; segnala che sta avvenendo l’irrigazione
JMP Main ; e rieffettua il controllo
TimeNotValid: CMP [IncumbentIrrigation], 0 ; l’irrigazione era già spenta?
JE Main ; se sì torna al main
OUT DX, 0 ; sennò chiudi la valvola
MOV [IncumbentIrrigation], 0 ; e segna che non si sta più irrigando
JMP Main ; poi torna al main