Salta ai contenuti

UDA 5 — Interrupt e periferiche avanzate

9 ore (3 di teoria + 6 di laboratorio) · Settimane 9-11

Al termine di questa unità:

  • distingui polling e interrupt e scegli la strategia giusta;
  • conosci la catena di un interrupt: richiesta → IM2 → tabella dei vettori → servizio → RETI;
  • configuri un device vettorato del G80 (vettore < 0x80, poi 0x83) e scrivi il servizio;
  • usi il timer CTC (0x70/0x71) e il counter/nastro (0xE0/0xE1);
  • piloti motore, forno, beeper e leggi potenziometro e chart recorder;
  • hai completato l’Esercizio 6 e sostieni la prova pratica n. 3.

Piano delle tre settimane — Sett. 9: polling vs interrupt, IM2, esempio 08 + Esercizio 6. Sett. 10: timer e counter, esempi 09–10. Sett. 11: motore/forno/chart/potenziometro/beeper, sfide e prova pratica n. 3.


Finora abbiamo chiesto noi ai device se avevano novità (il “polling”: in a,(0xC0) in ciclo). È semplice, ma ha due costi: spreco di tempo e ritardo nella risposta.

PollingInterrupt
Chi decideil programma, con un cicloil device, quando serve
Costoalto (ciclo continuo di lettura)quasi zero finché non arriva l’evento
Reattivitàdipende da quanto spesso si leggeimmediata
Complessitàbassamedia (servizio, stack, flag)
Uso tipicotastiere, semplici ingressitimer, eventi periodici, “allarmi”

Con gli interrupt il programma principale può fare altro (contare, aggiornare il display…) e il device lo “interrompe” solo quando c’è davvero qualcosa da fare.


La sequenza completa:

  1. il device alza la richiesta (nel G80: INTAK);
  2. se gli interrupt sono abilitati (EI, cioè IFF1 = 1), la CPU finisce l’istruzione in corso, salva il PC sullo stack e salta al servizio;
  3. il servizio lavora, poi esegue EI e RETI;
  4. RETI ripristina il PC: il programma principale riprende esattamente da dove era.

Nel G80 (e nello Z80) l’hardware dei device usa la modalità IM2 — “vettorata”:

.org 0x0010 ; la tabella dei vettori (indirizzo PARI)
VTab: .dw servizio ; una parola: l'indirizzo del servizio
ld a, 0
ld i, a ; I = byte ALTO della tabella
im 2 ; modalità 2
ei ; abilita gli interrupt

Quando arriva la richiesta, la CPU forma l’indirizzo con I come byte alto e il vettore fornito dal device come byte basso(con il bit 0 a 0): in pratica “nella pagina puntata da I, all’indirizzo del vettore, c’è la parola con l’indirizzo del servizio”.

Altre cose utili:

  • DI disabilita gli interrupt (le richieste restano in attesa);
  • EI li riabilita — e ricordati che EI fa effetto dopo l’istruzione successiva (una piccola rete di sicurezza del processore);
  • NMI (interrupt non mascherabile) è una cosa a parte: non si può disabilitare con DI e salta a un indirizzo fisso (0x0066).

Ogni device vettorato ha due porte: una per la costante (quanto contare) e una di controllo, che serve a due cose:

PassoCosa si scrivePerché
1il byte basso del vettore (valore < 0x80)il device lo memorizza
20x83bit 0 = run, bit 1 = abilita IRQ

Perché il vettore deve essere < 0x80? Perché la porta di controllo distingue le due cose dal bit 7: se è 0 il byte è un vettore, se è 1 (come 0x83 = 1000 0011) è un comando. Se sbagli l’ordine non funziona nulla — è l’errore classico.

DeviceCostanteControlloVettore tipico
timer — CTC0x700x710x10 (come nell’esempio 08)
keypad—0x810x20
counter/nastro0xE00xE10x70 (come nell’esempio 09)

La tabella dei vettori va in una pagina ad indirizzo pari (spesso 0x0010, 0x0070…) e contiene parole .dw: I fornisce il byte alto, il device quello basso.


Il servizio (o ISR, interrupt service routine) è una subroutine con obblighi speciali:

servizio:
push af ; 1) salva TUTTO quello che usi
push hl
push bc
; ... fai il lavoro (breve!) ...
pop bc ; 2) ripristina (ordine inverso!)
pop hl
pop af
ei ; 3) riabilita gli interrupt
reti ; 4) ritorno "speciale": ripristina anche IFF1

Regole d’oro:

  • breve: il main resta fermo finché il servizio gira;
  • salva i registri che usi: il main non deve accorgersi di nulla (flags compresi: push af);
  • EI + RETI alla fine: senza EI, dopo il primo interrupt non ne arrivano più;
  • non chiamare subroutine lunghe o che usano gli stessi registri senza salvarli;
  • se il servizio e il main si scambiano dati, usa variabili in memoria (RAM), non i registri.

Il timer conta i cicli della CPU (T-state) e genera un interrupt periodico:

periodo (in T-state) = COSTANTE × PRESCALER
  • la costante si scrive su 0x70 (es. 0x08 = 8);
  • il prescaler (16, 64 o 256) si sceglie dalla tendina CTC nella UI: è il fattore di scala del contatore (default 256);
  • ogni volta che il conteggio raggiunge il periodo, il device chiede l’interrupt.

Esempio di calcolo: costante 0x20 (32) e prescaler 256 → periodo = 32 × 256 = 8192 T-state. Se vuoi un ritmo più veloce: costante più piccola o prescaler più basso.

Il timer è perfetto per: lampeggiatori regolari, “orologi”, campionamenti periodici. (Per tempi “da orologio” in millisecondi reali il G80 usa invece il nastro: 500 ms fissi.)

Il nastro trasportatore avanza di un passo ogni 500 ms (tempo reale, come nel simulatore originale). Il counter conta i passi e, a zero, ricarica la costante e chiede l’interrupt:

costante = quanti passi del nastro tra un interrupt e il successivo

Con costante 4: un interrupt ogni 2 secondi (= 4 × 500 ms) — come nell’esempio 09.

Uso tipico: contare pezzi su un nastro, gestire lotti, sincronizzare il motore.

Al motore si invia un impulso: la transizione 0 → 1 fa fare un passo.

ld a, 1
out (MOTOR), a ; fronte 0->1: un passo
xor a
out (MOTOR), a ; torna a 0 (pronto per il prossimo passo)

Nell’esempio 09 il servizio del counter fa avanzare il motore di un passo a ogni interrupt.

  • 0xF1 in OUT: bit 0 = riscaldatore ON/OFF;
  • 0xF1 in IN: bit 7 = “dato pronto” (una nuova conversione ogni 500 ms);
  • 0xF0 in IN: la temperatura misurata.

Schema dell’esempio 10 (polling): imposta il riscaldatore, aspetta il bit 7, leggi la temperatura, traccia il valore sul chart.

PerifericaPortaUso
Potenziometro0x40 INvalore 0–255 da uno slider (setpoint, velocità…)
Chart recorder0x30 OUTogni scrittura = un campione tracciato (canale 0)
Marker0x31–0x33 OUTmarcatori rosso/verde/blu sul grafico
Beeper0x60 OUTun beep (un impulso sonoro)

Combinazione tipica: potenziometro → soglia → riscaldatore (forno); temperatura → chart (grafico); evento → beep.


Attività A — Esempio 08 e Esercizio 6 (timer + IM2)

Sezione intitolata “Attività A — Esempio 08 e Esercizio 6 (timer + IM2)”

Apri 08-timer-interrupt.asm e individua le tre parti:

  1. la tabella dei vettori (.org 0x0010, .dw servizio);
  2. il setup: costante, vettore, 0x83, I, im 2, ei;
  3. il servizio: push, OTIR verso l’LCD, pop, ei, reti.

Poi fai l’Esercizio 6 (esercizi/06-timer.asm): il servizio è vuoto, completalo. Verifica: il messaggio compare ripetutamente e il conteggio LED prosegue (il main è vivo).

Da osservare con Passo: il salto al servizio avviene tra due istruzioni del main e lo stack cresce di 2 byte (indirizzo di ritorno). Alla fine SP torna al valore iniziale.

Attività B — Esempi 09 e 10 (counter, motore, forno, chart)

Sezione intitolata “Attività B — Esempi 09 e 10 (counter, motore, forno, chart)”
  1. 09-motore-counter.asm: costante 4 → un interrupt ogni 4 passi del nastro (2 s); a ogni interrupt il motore fa un passo. Prova a cambiare la costante: cosa cambia?
  2. 10-kiln-chart.asm: alza l’interruttore 0 (riscaldatore ON) e osserva: la temperatura sale, il chart disegna la curva; spegnilo e guarda la discesa. Domanda: perché il programma aspetta il bit 7 prima di leggere la temperatura?

C1 — Lampeggiatore “preciso”: un LED che lampeggia a interrupt (niente cicli di ritardo!).

Mostra una soluzione (C1)
TIM = 0x70
TIM_C = 0x71
.seg CODE (abs)
.org 0x0010
VTab: .dw servizio
.org 0x0020
stato: .db 0
.org 0x0100
inizio:
ld sp, 0xFFFF
ld a, 0x20 ; costante del timer (prova a cambiarla)
out (TIM), a
ld a, VTab
out (TIM_C), a
ld a, 0x83
out (TIM_C), a
ld a, 0
ld i, a
im 2
ei
ciclo:
nop
jp ciclo
servizio:
push af
push hl
ld hl, stato
ld a, (hl)
xor 0x01 ; inverte il bit 0
ld (hl), a
out (0x20), a
pop hl
pop af
ei
reti

Lo stato del LED sta in memoria (stato): il servizio non può “ricordarsi” nulla nei registri… perché il main li usa!

C2 — Pianola: premi un tasto del keypad, il numero compare sull’LCD e il beeper suona.

C3 — Linea di trasporto: il counter fa avanzare il motore e conta i pezzi sui LED (mostra il totale).


In coda a questa unità c’è la prova pratica sugli interrupt (1 ora al simulatore): un servizio IM2 che interagisce con una periferica mentre il programma principale continua a lavorare. La trovi nella pagina “UDA 5 — Esempio di prova pratica n. 3” (due tracce + griglia di correzione).


  1. Perché con il polling si “spreca” tempo di CPU? In che caso è comunque la scelta giusta?
  2. Descrivi la catena: richiesta del device → … → RETI.
  3. A cosa servono I, la tabella dei vettori e il vettore < 0x80?
  4. Perché nel servizio si esegue EI prima di RETI? E perché si salva AF?
  5. Timer e counter: quale dei due misura il tempo reale (500 ms) e quale conta i cicli della CPU? Come si calcola il periodo del timer?

📎 I file .asm di esempi ed esercizi si scaricano dalla pagina Materiali.

  • Compito: completa l’Esercizio 6 e le tre sfide (almeno C1 e C2); prepara la prova pratica n. 3 sul foglio.
  • Pre-lettura: manuale, sezione Interrupt (IM2) e tabella delle porte.
  • Materiali: esempi 08–10; esercizio 06-timer.asm (+ soluzione).
  • Nota per il docente: i percorsi interrupt di questa unità sono coperti da test automatici end-to-end (tests/interrupt-e2e.test.ts): timer CTC → IM2 → servizio, counter/nastro → motore, keypad → servizio con main attivo.
  • Prossima unità: progetto finale (UDA 6).