Esempio applicativo

Supponiamo di avere il circuito riportato nella figura qui sotto. E' un semplice risponditore telefonico che quando viene chiamato invia un tono in linea per un certo tempo. Sulla sinistra c'è l'interfaccia di linea, non ci interessa come è fatta, quello che è importante è conoscere il significato dei tre fili che escono da essa. L è il filo di impegno, una tensione positiva su questo filo fa "prendere la linea". F è il filo su cui va inviata la frequenza da mandare in linea. R è il filo che segnala l'arrivo di una chiamata. E'normalmente a +V (o 1 logico), si porta a zero quando arriva la corrente di chiamata. 


Il funzionamento è il seguente: appena arriva un impulso di corrente di chiamata il filo R si porta a zero, il flip flop di tipo set-reset si attiva e la sua uscita Q si porta a 1. La rete RC del TIMER1 fa si che per 15 secondi il circuito non risponda. Trascorso questo tempo l'uscita del TIMER1 si porta a 1 comandando l'impegno sul filo L, e abilitando la porta AND ad inviare in linea la frequenza prodotta dall'oscillatore. Contemporanemante questo livello 1 comanda il TIMER2, che dopo 10 secondi invia un 1 (negato da una porta NOT) verso il pin reset del flip flop e il circuito si azzera liberando la linea. 

La logica di funzionamento incorporata nel circuito di controllo composto dal flip flop, dai timer ecc... si può quindi riassumere nei seguenti passi: 

  1. attendere una chiamata (filo R a 0) 
  2. attendere 15 secondi 
  3. prendere la linea (risposta) 
  4. inviare un tono per 10 secondi 
  5. liberare la linea e tornare al punto 1 
Un programma per microprocessore che esegua questi passi realizza un processo di controllo del tutto equivalente al circuito hardware mostrato sopra. Una differenza sostanziale è che il funzionamento può essere modificato o migliorato senza intervenire su alcun componente. 
Per questo esempio supponiamo di usare il modulo a microprocessore presentato nella pagina collegamento I/O 1 che dispone di 8 ingressi e 8 uscite a cui si può accedere leggendo o scrivendo all'indirizzo 0. 

Bisogna innanzitutto stabilire come si vogliono usare i singoli bit di I/O delle porte, in sostanza si deve decidere su quale ingresso collegare il filo R e su quali uscite i fili L e F, scegliamoli ad esempio nel seguente modo: 

Il programma deve eseguire esattamente i passi visti prima, prima di tutto però deve impostare sulla porta di uscita i corretti valori di riposo (nel nostro caso 0) dato che all'accensione le uscite assumono valori casuali. Inoltre, se questo è l'unico modulo software presente, all'inizio deve anche impostare lo stack pointer come spiegato nella pagina elementi software
            LD SP,65535
            XOR A
            OUT (0),A
L'istruzione XOR A serve ad azzerare il registro A. E' del tutto equivalente a LD A,0 però è più veloce da eseguire e occupa meno memoria. In un caso come questo usare l'uno o l'altro dei sistemi è irrilevante, ma è bene sapere che ci sono entrambi. 
A questo punto dobbiamo leggere il filo R per vedere se è in arrivo una chiamata. Se R è a 1 continuiamo a leggerlo finchè lo troviamo a zero, questo ciclo di letture realizza il passo "attendere una chiamata": 
ATTESA1     IN A,(0)
            BIT 0,A
            JR NZ,ATTESA1
Con l'istruzione IN leggiamo la porta trasferendo i valori letti in A. Con BIT testiamo il bit 0 di A per vedere se è a zero. Se non lo è, saltiamo ad ATTESA1 e ricominciamo la lettura. Uno Z80 con clock a 4 Mhz impiega 7,75 microsecondi per eseguire queste tre istruzioni, e quindi effettuerà circa 129000 letture al secondo. 
Se invece il filo R è a zero, cioè è in arrivo un impulso di chiamata, si esce dal ciclo (non si salta più a ATTESA1) e si prosegue con il passo 2: "attendi 15 secondi". 
            LD BC,15000
            CALL DELAY
Qui impostiamo 15000 nel registro BC e chiamiamo una subroutine DELAY di cui ci occuperemo dopo, quello che importa è sapere che causerà un ritardo di BC millisecondi. 
Adesso dobbiamo prendere la linea, e questo si fa portando a 1 il bit D1 della porta di uscita: 
            LD A,00000010B
            OUT (0),A
Il valore caricato in A qui è scritto in binario perchè così risulta subito evidente la corrispondenza tra i bit della porta e il valore scritto. Scrivere LD A,2 oppure LD a,02H era assolutamente identico in quanto 00000010B = 2 = 02H. 
Ora (mantenendo fisso a 1 il bit D1 per tenere impegnata la linea) si deve commutare ciclicamente a 1 e a 0 il bit D0 per inviare sul filo F una frequenza, e il tutto deve continuare per 10 secondi. Inoltre la commutazione del bit D0 deve essere rallentata con appositi cicli di ritardo, altrimenti verrebbe generata una frequenza ultrasonica sull'ordine degli 80 Khz. 
            LD A,00000011B
            OUT (0),A
            LD B,200
RIT1        DJNZ RIT1
            RES 0,A
            OUT (0),A
            LD B,200
RIT2        DJNZ RIT2
Si predispone il bit D0 di A a 1 e lo si scrive sulla porta. Poi si carica il registro B con il valore 200 e si esegue un'istruzione DJNZ. Questa istruzione decrementa il valore di B e se non è arrivato a zero salta all'etichetta specificata. Nel nostro caso salta a se stessa e in pratica viene eseguita 200 volte, dopo di che il programma prosegue. Si resetta il bit D0 di A, lo si scrive sulla porta di uscita e si attende un'altra pausa di 200 iterazioni dell'istruzione DJNZ. 
Considerando che con un clock di 4 Mhz l'esecuzione di una DJNZ dura 3,25 microsecondi, l'esecuzione del ciclo completo di 200 iterazioni dura 650 microsecondi. Se commutiamo il bit di uscita D0 ogni 650 microsecondi otteniamo una frequenza di circa 770 Hz. 

Noi dobbiamo emettere questa frequenza per 10 secondi, e visto che in 10 secondi ci sono circa 7700 periodi dobbiamo inserire le istruzioni viste sopra in un ciclo di 7700 iterazioni: 
            LD DE,7700

TONO        LD A,00000011B
            OUT (0),A
            LD B,200
RIT1        DJNZ RIT1
            RES 0,A
            OUT (0),A
            LD B,200
RIT2        DJNZ RIT2

            DEC DE
            LD A,D
            OR E
            JR NZ,TONO
Si usa il registro DE come contatore, lo si inizializza a 7700 e, dopo un periodo della frequenza, lo si decrementa con DEC DE e si controlla se entrambi i byte che lo compongono sono a zero. Questo controllo si effettua con una operazione di OR, se anche uno solo dei bit di DE è a 1 si salta a TONO per emettere un altro periodo. 

Con i valori riportati in questo programma e un clock di 4 Mhz in realtà viene generata una frequenza di 759,44 Hz per 10,139 secondi, e questo perchè anche le altre istruzioni portano via del tempo. 

A questo punto liberiamo la linea riportando a 0 D1 e torniamo all'inizio del programma: 

            XOR A
            OUT (0),A
            JP ATTESA1
Rimane ancora una cosa da scrivere, la subroutine DELAY per il ritardo dei 15 secondi. E'evidente che sfrutterà lo stesso principio dell'esecuzione ciclica di qualche istruzione per far trascorrere del tempo. Abbiamo detto che accetta in BC il numero di millisecondi, noi per semplicità creiamo un'ulteriore subroutine per il ritardo di un millisecondo, da richiamare BC volte: 
DELAY       PUSH BC
            CALL MS1
            POP BC
            DEC BC
            LD A,B
            OR C
            JR NZ,DELAY
            RET

MS1         LD B,187
MSR1        NOP
            NOP
            DJNZ MSR1
            NOP
            RET
Qui prima di chiamare la subroutine MS1 si usa l'istruzione PUSH per salvare BC nello stack. Questo è necessario perchè MS1 al suo interno modifica il valore del registro B. Al termine il valore di BC viene recuperato dallo stack con un POP e si procede al decremento del conteggio dei millisecondi. Ogni subroutine chiamata con CALL deve terminare con RET. 

Le istruzioni NOP non producono alcun effetto se non un po' di ritardo, 1 microsecondo ciascuna (con clock a 4 Mhz). Calcolando la somma complessiva di tutte le istruzioni eseguite si può determinare che la subroutine delay dura esattamente 15,0075 secondi.

Il programma completo:

            .ORG 0            ;INDIRIZZO INIZIALE
;------------------------------------------------------------
            LD SP,65535       ;INIZIALIZZA LO STACK POINTER
            XOR A
            OUT (0),A         ;INIZIALIZZA LE USCITE

ATTESA1     IN A,(0)          ;LETTURA FILO R
            BIT 0,A
            JR NZ,ATTESA1

            LD BC,15000       ;IMPOSTA RITARDO DI 15 SECONDI
            CALL DELAY

            LD A,00000010B    
            OUT (0),A         ;IMPEGNA LA LINEA

            LD DE,7700        ;IMPOSTA 7700 PERIODI
TONO          LD A,00000011B    
              OUT (0),A       ;ALZA BIT D0
              LD B,200
RIT1          DJNZ RIT1       ;ATTESA 650 MICROSEC.
              RES 0,A
              OUT (0),A       ;RIABBASSA BIT D0
              LD B,200
RIT2          DJNZ RIT2       ;ATTESA 650 MICROSEC.
              DEC DE          ;DECREMENTA CONTEGGIO PERIODI
              LD A,D
              OR E
            JR NZ,TONO        ;SE NON SONO FINITI TORNA A TONO

            XOR A
            OUT (0),A         ;LIBERA LA LINEA
            JP ATTESA1        ;TORNA AD ATTESA CHIAMATA

;------------------------------------------------------------
DELAY         PUSH BC         ;SALVA BC
              CALL MS1        ;CHIAMA RITARDO 1 MILLISEC.
              POP BC          ;RECUPERA BC
              DEC BC          ;DECREMENTA CONTEGGIO MILLISEC.
              LD A,B
              OR C
            JR NZ,DELAY       ;SE NON SONO FINITI TORNA A DELAY
            RET
;------------------------------------------------------------
MS1         LD B,190
MSR1          NOP
              NOP
            DJNZ MSR1
            NOP
            RET
;------------------------------------------------------------
            .END
NOTA: alcune istruzioni sono scritte leggermente spostate a destra per evidenziare che sono all'interno di un ciclo.

Questo è un programma piuttosto semplice, non usa aree di memoria per contenere dati di lavoro, non usa gli interrupt, effettua tutto il lavoro con i soli registri della CPU, però mostra come si può realizzare via software un processo dal funzionamento del tutto identico a quello di una rete hardware.

Il funzionamento potrebbe essere reso più affidabile per esempio controllando l'effettiva temporizzazione della corrente di chiamata, in modo da distinguere una chiamata vera da un impulso spurio o da un disturbo.

O ancora al posto di una frequenza fissa potrebbero essere emessi dei treni di impulsi, magari a frequenze diverse. Tutte queste modifiche sarebbero molto pesanti da realizzare in hardware, al contrario via software sono molto semplici.

Inoltre quelle che in hardware sarebbero le eventuali regolazioni dei trimmer dei temporizzatori, qui diventano semplici regolazioni del valore di un registro.



Pagina e disegni realizzati da Claudio Fin