Professional Documents
Culture Documents
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
Si noti che quando nel programma principale si dovranno richiamare le costanti (definite con
EQU) o le variabili (definite con DB, DW, etc…):
2. Il PIC (8259)
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
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.
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 2 8 (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.
6. Interfacce 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.
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).
Esempio 1 (polling)
Esempio 2 (polling)
BUF_IN DB 1024
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
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)
NOTA: spesso il main viene racchiuso da CLI - STI perché contiene l’inizializzazione del programma, che non
dev’essere interrotta per nessun motivo.
…
…
…
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
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);
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.
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
; 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”
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 ) ; il bit 5 del registro LSR fornisce lo stato del buffer
; di trasmissione (1 = vuoto, 0 = pieno)
MOV DX, LSR MOV DX, LSR
wait_pieno: IN AL, DX
CMP AL, 01H ( 0000 0001) wait_vuoto: IN AL, DX
JZ wait_pieno CMP AL, 20H ( 0010 0000)
JZ wait_vuoto
; lettura del carattere ricevuto
MOV DX, RBR ; scrittura del carattere da trasmettere
IN AL, DX MOV DX, THR
POP DX POP AX
RET OUT DX, AL
read_com1 ENDP POP DX
RET
write_com1 ENDP
TRASFERIMENTO DATI
MODI DI INDIRIZZAMENTO
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).
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.
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
Operano su numeri interi binari senza (o con segno) a 8 bit o a 16 bit. Il processore 8086 supporta
anche il formato BCD.
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).
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.
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.
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.
Salti incondizionati
Un salto incondizionato è un salto eseguito a prescindere, senza nessuna condizione.
Salti condizionati
Prima verificano se è
verificata una certa
condizione e poi fanno il
salto.
Formato:
JXXX label
CICLI
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?
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 14 luglio 2007
Punto 1
Decodifica completa:
32 KB EPROM1 BA19 BA18 BA17 BA16 BA15
16 KB EPROM2 BA19 BA18 BA17 BA16 /BA15 BA14
8 KB di RAM /BA19 /BA18
BA18 /BA17 /BA16 /BA15 /BA14 /BA13
Decodifica semplificata:
32 KB EPROM1 BA19 BA15 De Morgan /BA19 + /BA15
16 KB EPROM2 BA19 /BA15
BA15 De Morgan /BA19 + BA15
BA
8 KB di RAM /BA19 De Morgan BA19
Punto 2
Mapping:
PIC (2 byte) 100H (100H
(100 e 101H) 0001 0000 0000
Decodifica semplificata: BA8
Motore (1 byte) 80H 0000 1000 0000
Decodifica semplificata: BA7
Punto 3
Punto 4
ValoreT1 DB 2
; Indirizzi
; 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).
Decodifica semplificata:
EPROM BA19
RAM /BA19
Punto 2
Mapping dei dispositivi di I/O.
Punto 3
RS232
RFID
reader
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 duedue 244, i quali vengono chiaramente attivati dallo stesso chip select
(CS_RTC#).
L’8250 è il componente che può supportare l’interfaccia RS232, utilizzata
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 piedini IR dell’8259.
L’8259 gestisce gli interrupt (che riceve dal FP) ed è interfacciato come mostrato in figura.
Punto 4
BA6 BA14
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 segnale IO/M basso (si abilita RDY2)
Per gestire i relativi cicli di clock utilizziamo un multiplexer:
multiplexer
• BA6 alto attiva l’8250 (ingressi 10 e 11 del multiplexer,
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).
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
Decodifica completa:
EPROM E8000H FFFFFH
una da 64 KB F0000H FFFFFH BA19 BA18 BA17 BA16
una da 32 KB E8000H EFFFFH BA19 BA18 BA17 /BA16 BA15
RAM 00000H 03FFFH /BA19 /BA18 /BA17 /BA16 /BA15 /BA14
Decodifica semplificata:
EPROM2 BA19 BA16 EPROM2# = /BA19 + /BA16
EPROM1 BA19 /BA16 EPROM1# = /BA19 + BA16
RAM /BA19 RAM# = BA19
Punto 2
Interfaccia CAT 2 indirizzi x 1 byte, uno per IR0 e uno per IR1 a 40H e 41H rispettivamente
Dec. completa: 0000 0100 0000 /BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5….. tutti negati … /BA1 /BA0
Dec. completa: 0000 0100 0001 /BA11 /BA10 /BA9 /BA8 /BA7 BA6 /BA5….. tutti negati … /BA1 BA0
Decodifica semplificata:
PIC BA8
CAT1 /BA8 /BA0
CAT2 /BA8 BA0
LED BA7
Punto 3
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
Punto 6
STI
MOV AL, 0
OUT ACK0, AL
OUT ACK1, AL
CLI
Main: JMP Main
Punto 7
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.
Decodifica semplificata:
• il PIC BA7
• il CMPS /BA7 BA5
• i led a 40H /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
newDirectionAvailable DB 1
newDirection DB 1
Decodifica semplificata:
• EPROM BA19 De Morgan /BA19
• RAM2 /BA19 BA17 De Morgan BA19 + /BA17
• RAM1 /BA19 /BA17 De Morgan BA19 + BA17
Punto 2
Dispositivi di I/O:
• PIC F00EH 1111 0000 0000 1110
BA15 BA14 BA13 BA12 /BA11 … tutti negati … BA3 BA2 BA1 sempl. BA15
• tornello 1 1000H 0001 0000 0000 0000
/BA15 /BA14 /BA13 BA12 /BA11 … /BA0 sempl. /BA13
• tornello 2 2000H 0010 0000 0000 0000
/BA15 /BA14 BA13 /BA12 /BA11 … /BA0 sempl. /BA12
• tornello 3 3000H 0011 0000 0000 0000
/BA15 /BA14 BA13 BA12 /BA11 … /BA0 sempl. BA13 BA12
Punto 3
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.
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
Usando una CPU 8088 abbiamo 1 MB di spazio di indirizzamento a disposizione, per cui si avrà:
BA19 BA18 BA17 BA16 BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 BA5 BA4 BA3 BA2 BA1 BA0
Decodifica completa:
Decodifica semplificata1:
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.
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.
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 -- -- -- -- -- -- -- --
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:
;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)
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).
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.
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)
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
; 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
…
*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
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 BA13 /BA12 … tutti negati… /BA3
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.
Punto 4
Punto 5
TXBUFFER DB 1024
EndTransmission DB 1
BaudRate DB 1
BufferIndex DB 1
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
OVER_19200 DB 1
PAR_19200 DB 1
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
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
CS_PIC# = BA9
CS_SERIAL_IN# = /BA9 + /BA8
CS_SERIAL_OUT# = BA8
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
Punto 2
PIC: FFFEH 1111 1111 1111 1110
Decodifica completa: BA15 … tutti veri … BA1
Porta seriale: A000H 1010 0…0 0000 0000
Decodifica completa: BA15 /BA14 BA13 /BA12
/B /BA11 … tutti negati … /BA3
Interfaccia centralina: FFD0H 1111 1111 1101 0000
Decodifica completa: BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 BA7 BA6 /BA5 BA4 /BA3 /BA2 /BA1 /BA0
Decodifica semplificata:
PIC BA14 BA1
Porta seriale /BA14
Interfaccia centrale BA14 /BA1
/BA
Punti 3 e 4
Punto 5
NewParameter DB 1
Data DB 1
DataCode DB 1
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
NewParameter DB 1
Data DB 1
DataCode DB 1
Punto 2
Dispositivi di I/O:
• PIC A000H 1010 0…0 BA15 /BA14 BA13 /BA12 … tutti negati … /BA1
• RTC 4000H 0100 0…0 /BA15 BA14 /BA13 /BA12 … tutti negati … /BA1
/BA
• elettrovalvola 8000H 1000 0…0 BA15 /BA14 /BA13 /BA12 … tutti negati
neg … /BA0
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 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.
Punto 4
Punto 5
StartIrrigationTime DB 2
StopIrrigationTime DB 2
IncumbentIrrigation DB 1
SystemInitialized DB 1
GreenButtonPressed: PUSH AX
PUSH DX
CMP [IncumbentIrrigation], 1 ; per caso sta avvenendo l’irrigazione?
JE Epilogue ; se sì vai all’epilogo
MOV DX, RealTimeClock ; sennò leggi dall’orologio
IN AX, DX
MOV [StartIrrigationTime], AX ; e segna l’ora d’accensione
MOV [IncumbentIrrigation], 1 ; e segnala che sta avvenendo l’irrigazione
MOV [SystemInitialized], 0 ; il sistema non è ancora
a (o non è più)
; inizializzato
MOV DX, Valvola ; apri la valvola
OUT DX, 1
Epilogue: POP DX ; ripristino contesto
POP AX
IRET
RedButtonPressed: PUSH AX
PUSH DX
CMP [IncumbentIrrigation], 0 ; per caso l’irrigazione è ferma?
JE Epilogue ; se sì non fare nulla (vai all’epilogo)
MOV DX, RealTimeClock ; sennò leggi dall’orologio
IN AX, DX
MOV [StopIrrigationTime], AX ; e segna l’ora di spegnimento
MOV [IncumbentIrrigation], 0 ; e segnala che 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