Sei sulla pagina 1di 70

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.

(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 ABS 1 , un pulsante, un componente 244, un componente 373);

OUT

Display

AX

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 byte 2 )

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 ruota 1 , il valore di un display 3 );

un buffer o un vettore che dev’essere riempito (ad es. in una comunicazione seriale 4 );

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)

0 a 59 e con 8 bit riusciamo a fare tutto). 2. Il PIC (8259) Il

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

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

)

che devono essere gestiti ad interrupt.

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:

IMR (Interrupt Mask Register): permette di mascherare le richieste di interruzione provenienti da uno o più dispositivi (0 = non mascherato, 1 = mascherato);

IRR (Interrupt Request Register) tiene traccia dei dispositivi che hanno fatto una richiesta e che non sono riusciti ancora ad averne riscontro;

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:

o

o

bit corrispondente in IRR e settato quello in ISR). SEQUENZA DI INTERRUZIONE: o o o 1.

o

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:

rileviamo a fronte l’interrupt viene generato a seguito di una transizione di un ingresso

se

da

0 a 1. Viene quindi generato un interrupt in corrispondenza di ogni fronte positivo di un

segnale d’ingresso IRi;

rileviamo a livello l’interrupt viene riconosciuto se viene generato un livello logico alto

se

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.

o

o

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

Es: all’inizializzazione del PIC passiamo la stringa

IR0 Es: all’inizializzazione del PIC passiamo la stringa 7 a cui è connesso il dispositivo. EQU

7

a cui è connesso il dispositivo.

EQU

1FH = 0001 1111

ADDRESS

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.

ICW3 viene usata solo quando l’8259 è connesso in cascata.

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

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.

un canale (ovvero su uno dei piedini IR0

7

; 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

 

EOI

EQU

20H

mascheriamo tutti gli altri 1111 1000 = F8H ; 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)

AL, DX

; legge lo stato della stampante

pr_n_ready: IN

TEST AL, sERR

JZ

TEST AL, sBUSY

JZ

pr_error

pr_n_ready

; errore

; 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:

un’operazione di I/O seguendo lo schema ad interrupt: NOTA: l’ interrupt handler trasferisce un solo dato
un’operazione di I/O seguendo lo schema ad interrupt: NOTA: l’ interrupt handler trasferisce un solo dato

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.

di una procedura di servizio dell’interruzione. Problemi importanti: ∑ Come fa il processore a capire chi

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;

8 (256) possibili tipi di

interruzione (0… 255). Ogni elemento della tabella occupa 4 byte, per cui la tabella avrà

della tabella occupa 4 byte, per cui la tabella avrà un interrupt type è un numero

un interrupt type è un numero naturale a 8 bit, quindi esistono

2

dimensione pari a 1 KB (M[0…3FFH]).

SCHEMA DEL PROTOCOLLO DI INTERRUZIONE

1.

2.

3.

4.

5.

6.

7.

8.

Un dispositivo richiede un servizio attraverso una richiesta di interruzione (mandata al PIC

e non al BUS dati come avviene nel caso polling);

il PIC decide se quella richiesta ha da essere servita o meno, e in caso positivo inoltra quella (sola) richiesta al processore;

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;

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);

il PIC risponde passando al microprocessore il tipo di

interruzione che dev’essere servita;

il processore accede alla IVT e legge l’indirizzo della procedura d i servizio che dev’essere

attivata;

parte la procedura di servizio riguardante il dispositivo che ha fatto la richiesta;

terminata la procedura, il processore ritorna al punto in cui era stato interrotto.

il processore ritorna al punto in cui era stato interrotto. LO STESSO SCHEMA DAL PUNTO DI

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. processore invia un impulso su INTA per segnalare che la richiesta è stata rilevata;

4. processore invia un secondo impulso di INTA per chiedere di leggere sul data bus il

il

il

codice del dispositivo;

5. processore legge dal data bus il codice n del device che ha richiesto l’interrupt (1 byte);

6. 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;

il

il

9. procedura va in esecuzione;

10. termine della procedura il processore ripristina dallo stack il registro dei flag e l’indirizzo

la

al

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

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.

(cicli di bus) e quella utilizzata dall’unità di I/O. Negli esercizi vi sono due principali modi

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

riportato nel riquadro si occupa di leggere lo stato degli interruttori tramite il comando IN. Altri

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.

logico alto) e quando CK = 1 l’uscita riproduce l’ingresso. I latch hanno il pregio di

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

registri interni (analogamente a quanto già visto per i chip di memoria). Per accedere ai registri interni è necessario scrivere una certa configurazione binaria (control word 7 ) 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

n-1], v. figura in alto pagina precedente) consentono la selezione fra i suoi differenti

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

(“buffer”) utilizzati nei trasferimenti dei dati da CPU ad unità di I/O e viceversa. Per inviare

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)

vs. polling: qualche esempio software Esempio 1 (polling) Esempio 2 (polling) Il problema che esaminiamo consiste

Esempio 2 (polling)

Il problema che esaminiamo consiste nel:

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.

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:

CALL

CALCOLA

si resetta l’indice SI per il prossimo giro ; 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

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

 

INC

[Secondi]

detta “epilogo” (il termine della procedura) ; facciamo andare avanti i secondi (abbiamo avuto

;

CMP

[Secondi], 60

un interrupt dal segnale di onda con periodo 1 s ; abbiamo fatto un minuto?

;

JNE

End

; se no, vai a End, altrimenti…

MOV [Secondi], 0

INC

[Minuti]

[Minuti], 60

; bisogna resettare i secondi e ; aumentare i minuti di uno!

CMP

; abbiamo fatto un’ora?

JNE

End

; se no, vai a End, altrimenti…

MOV [Minuti], 0

INC

[Ore]

[Ore], 24

End

[Ore], 0

AL, [Secondi]

; bisogna resettare i minuti e ; aumentare le ore di uno!

CMP

; abbiamo fatto un giorno?

JNE

; se no, vai a End, altrimenti…

MOV

; resetta le ore!

End:

MOV

; dobbiamo aggiornare il display: … secondi….

OUT

MOV AL, [Minuti]

OUT

MOV AL, [ore]

OUT

CLOCK_S, AL

CLOCK_M, AL

CLOCK_H, AL

; ; dobbiamo aggiornare il display: … minuti…. ; ; dobbiamo aggiornare il display: … ore!

POP AX

; ripristino del contesto (faccio il pop di tutto ciò

 

;

che avevamo push-ato nel prologo): questa parte

IRET

;

viene detta “epilogo” ; con questo comando usciamo dalla routine di

Programmazione ad interrupt per l’interfaccia

Main:

CLI

MOV AL, IN_ControlWord

OUT IN_Control, AL MOV AL, OUT_ControlWord OUT OUT_Control, AL MOV [1k_done], 0

MOV [Index], 0 STI

; interrupt

; Disabilitazione interrupt (all’inizializzazione

; non devono disturbare!)

; Programmazione interfacce tramite parole di

; comando

; spediamo la prima parola

; spediamo la seconda parola ; Inizializzazione variabili: la variabile

; [1k_done] indica al main l’avvenuta

; ricezione di 1 KB ; indice del vettore a 0 ; 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?

Utile:

JNE Utile MOV [1k_done], 0 CALL CALCOLA …

; Se no, torna al lavoro utile… ; altrimenti resetta la variabile 1k_done ; e chiama la procedura “CALCOLA” ; 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]

IN

MOV BUF_IN[SI], AL INC [Index]

AL, IN_BufferIN

; Mettiamo l’indice corrente nel registro SI ; Lettura del nuovo dato ; Scrittura in BUF_IN (memoria) del dato ricevuto ; 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

della sua controparte polling. 8. Comunicazioni seriali Esistono due modalità diverse di trasferimento dei dati tra

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:

ed interpretare correttamente i dati ricevuti, deve sapere: ∑ quando campionare la linea per identificare il

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 clock 8 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 seriale 9

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

4. parity bit : fornisce una indicazione sul verificarsi o 8 ATTENZIONE - Non è che

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.

bit : fanno capire al ricevitore che la trama è finita. Dispositivo 8250 e protocollo RS-232

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.

il segnale INT per richiamare l’attenzione del processore. Abbiamo però anche dei registri nonché dei segnali

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

di

o interrupt identification register (IIR): identifica di la richiesta interruzione; ∑ registri di CONTROLLO o line

la richiesta

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à);

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);

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).

o

è stata calcolata sulla frequenza F = 1,8432 MHz). o o Infine, l’8250 ha un insieme
è stata calcolata sulla frequenza F = 1,8432 MHz). o o Infine, l’8250 ha un insieme

o

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
PUSH
AX
; prologo
PUSH DX
; DLAB = 1 per accedere a DLM e DLL
MOV
DX, LCR
MOV
AL, 80H
; mettiamo 80H = 1000 0000
; 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

write_com1

PROC FAR

; la procedura legge un carattere da COM1

; la procedura scrive su COM1 il carattere passato

; e lo restituisce in AL

; in AL

 

PUSH DX

 

PUSH DX

 

PUSH AX

attesa che il buffer di ricezione sia pieno: il bit 0 ; del registro LSR fornisce lo stato del buffer di

;

; attesa che il buffer di trasmissione sia vuoto:

;

; ricezione ( 1 = pieno, 0 = vuoto )

ricezione ( 1 = pieno, 0 = vuoto )

; il bit 5 del registro LSR fornisce lo stato del buffer

;

di trasmissione (1 = vuoto, 0 = pieno)

wait_pieno:

MOV

IN

DX, LSR

AL, DX

 

MOV

DX, LSR AL, DX

DX, LSR

AL, DX

CMP AL, 01H JZ wait_pieno

( 0000 0001)

wait_vuoto:

IN

CMP AL, 20H JZ wait_vuoto

( 0010 0000)

;

lettura del carattere ricevuto

 
 

MOV

DX, RBR

;

scrittura del carattere da trasmettere

IN

AL, DX

 

MOV

DX, THR

POP

DX

POP

AX

RET read_com1 ENDP

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’operando 1 è 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);

ES.

un campo opzionale. 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:

modificare i registri specifici un bit alla volta. Operandi: AND OR formato: dest, sorg XOR Ogni

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

variare di BX saltiamo in punti diversi. Salti condizionati Prima verificano se è verificata una certa

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 corpo della procedura

tipo 3

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.

Soluz ione compito del 14 luglio 2007

Punto 1

48 KB di EPROM (32+16) agli indiriz zi alti:

 

32

KB EPROM1

FFFFFH F8000H

16

KB EPROM2

F7FFFH F4000H

8

KB di RAM (indirizzi bassi):

01FFFH 00000H

Decodifica completa:

32

KB

EPROM1

BA19 B A18 BA17 BA16 BA15

16

KB

EPROM2

BA19 B A18 BA17 BA16 /BA15 BA14

8 KB di RAM

Decodifica semplificata:

/BA19 / BA18 /BA17 /BA16 /BA15 /BA14 /BA13

32

KB

EPROM1

BA19 B A15

De Morgan

/BA19 + /BA15

16

KB

EPROM2

BA19 /B A15

De Morgan

/BA19 + BA15

8

KB di RAM

/BA19

De Morgan

BA19

Punto 2

Mapping:

PIC (2 byte)

100H (1 00H e 101H)

0001 0000 0000

Decodifica semplificata: BA8

Motore (1 byte)

80H

Decodifica semplificata: BA7

Punto 3

0000 1000 0000

0001 0000 0000 Decodifica semplificata: BA8 Motore (1 byte) 80H Decodifica semplificata: BA7 Punto 3 0000

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

Dec. completa:

F0A0H

1111 0000 1010 0 0

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

RS232 RFID reader
RS232
RFID
reader

FP e RTC sono due dispositivi di in put (vengono solamente letti), quindi li inte rfacciamo al bus dati (BD) con dei componenti 244: il FP gestisc e 8 bit, il RTC invece lavora a 16 bit (presu mibilmente 8 per le ore e 8 per i minuti) e quindi ha bisogno di due 244, i quali vengono chiaramente attiv ati dallo stesso chip select (CS_RTC#). L’8250 è il componente che può sup portare l’interfaccia RS232, utilizzata per le comunicazioni seriali che gestiscono il pass elettronico (basato su tecnologia RFID). Si noti che, siccome è gestito a polling, non vi è alcun collegamento fra l’8250 e i pied ini IR dell’8259. L’8259 gestisce gli interrupt (che rice ve dal FP) ed è interfacciato come mostrato i n figura.

Punto 4

BA6 BA14
BA6
BA14

I dispositivi sono divisi in due “fami glie”: i dispositivi di I/O e quelli per le memo rie.

DISPOSITIVI DI I/O

Per gestire i relativi cicli di clock utili zziamo un multiplexer:

PIC, FP e 8250

segnale IO/M basso (si abilita RDY2)

BA6 alto attiva l’8250 (ingress i 10 e 11 del multiplexer, collegati al segnale Q3 3 cicli di wait);

BA6 basso + BA14 alto attiva l’FP (ingresso 01 del multiplexer, collegato al segnale Q3 3 cicli di wait);

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

Soluz ione compito del 20 luglio 2007

Punto 1

Decodifica completa:

EPROM

E8000H FFF FFH

RAM

una da 64 KB una da 32 KB

F0000H FFFFFH E8000H EFFFFH

00000H 03F FFH

Decodifica semplificata:

BA19 BA18 BA17 BA16 BA19 BA18 BA17 /BA1 6 BA15 /BA19 /BA18 /BA17 /B A16 /BA15 /BA14

EPROM2

BA19 BA16

EPROM2# = /BA19 + /BA16

EPROM1

BA19 /BA16

EPROM1# = /BA19 + BA16

RAM

/BA19

RAM# = BA19

Punto 2

PIC (2 byte) a 100H Dec. completa: 0001 0000 0000

/ BA11 /BA10 /BA9 BA8 /BA7…. tutti negati

… /BA1

Interfaccia CAT 2 indirizzi x 1 byt e, uno per IR0 e uno per IR1 a 40H e 41H ris pettivamente

Dec. completa: 0000 0100 0000 Dec. completa: 0000 0100 0001

/ BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5…. t utti negati … /BA1 /BA0 / BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5…. t utti negati … /BA1 BA0

LED (1 byte) a 80H Dec. completa: 0000 1000 0000

/ BA11 /BA10 /BA9 /BA8 BA7 /BA6 /BA5…. t utti negati … /BA1 /BA0

Decodifica semplificata:

PIC

BA8

CAT1

/BA8 /BA0

CAT2

/BA8 BA0

LED

BA7

Punto 3

negati … /BA1 /BA0 Decodifica semplificata: PIC BA8 CAT1 /BA8 /BA0 CAT2 /BA8 BA0 LED BA7

Punto 4

Punto 4 Il segnale IN è OVER_TH, che risulta essere pari a 0 quando la tensione

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
96 KB di EPROM = 64 KB + 32 KB indirizzi alti 160 KB di

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
Il 244 è un driver 3-state che comunica col bus dati di 8 bit (dei

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):

configurazione di alta impedenza. Passiamo al PIC ( 8259 ): Il clock collegato al PIC è

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; ∑ il CMPS a 60H; ∑ i led a

Mappiamo:

il PIC a 80H;

il CMPS a 60H;

i led a 40H.

1000 0000 BA7 /BA6 /BA5 /BA4

0110 0000 /BA7 BA6 BA5 /BA4

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

CS_PIC# = /BA7 CS_CMPS# = BA7 + /BA5 CS_LED# = BA7 + BA5 Il circuito è
CS_PIC# = /BA7 CS_CMPS# = BA7 + /BA5 CS_LED# = BA7 + BA5 Il circuito è

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

dalla situazione. Ricordando le espressioni dei chip select si nota facilmente che il bit che discrimina
dalla situazione. Ricordando le espressioni dei chip select si nota facilmente che il bit che discrimina

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)

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

  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

110 (S) 00010000 111 (SW) 00100000 Programmazione del PIC Si cop-incolla sempre lo stesso
110 (S) 00010000 111 (SW) 00100000 Programmazione del PIC Si cop-incolla sempre lo stesso
110 (S) 00010000 111 (SW) 00100000 Programmazione del PIC Si cop-incolla sempre lo stesso

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

il LED! Quante lucine! Buon Natale! JMP Main int_CMPS: PUSH AX ; salvataggio di contesto IN

int_CMPS:

PUSH