Mini esempio applicativo

Traduzione per PIC 16F84 dagli appunti per Z80.

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 +5V (o 1 logico), si porta a zero quando arriva la chiamata. 


Il funzionamento è il seguente: appena arriva un impulso 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, a questo punto il circuito si azzera liberando la linea e rimettendosi in attesa. 

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 può realizzare 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 hardware. 


Per questo esempio supponiamo di usare un PIC16F84, settando la porta A come ingresso e la porta B come uscita.
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 settare le porte ed impostare in uscita i corretti valori di riposo (nel nostro caso 0).
          CLRF   PORTB
          BSF    STATUS,RP0
          CLRF   TRISB
          BSF    STATUS,RP0
L'istruzione CLRF PORTB azzera i latch di uscita della porta ancora prima che questa venga settata come uscita, in questo modo appena viene configurata le uscite assumono già il valore logico 0.
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   BTFSC  PORTA,0
          GOTO   ATTESA1
Con l'istruzione BTFSC PORTA,0 leggiamo la porta controllando il valore del bit 0. Se questo bit vale 0 viene saltata la GOTO e il programma prosegue, se invece non lo è si risalta ad ATTESA1 e ricominciamo la lettura. Un PIC a 4MHz impiega 3 microsecondi ad eseguire queste due istruzioni, e quindi effettuerà circa 333 mila 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". 
          MOVLW  58
          MOVWF  DH
          MOVLW  152
          MOVWF  DL
          CALL   DELAY
Qui chiamiamo semplicemente una subroutine di ritardo di cui ci occuperemo dopo, quello che importa è sapere che causerà un ritardo di circa N millisecondi, dove N è un valore a 16 bit compreso tra 1 e 65535 scritto nei registri DH e DL. In particolare DH contiene la "parte alta" del nostro valore, e DL quella bassa. Impostiamo quindi 58*256 + 152 = 15000.
Adesso dobbiamo prendere la linea, e questo si fa portando a 1 il bit RB1 della porta di uscita:
          BSF    PORTB,1
 
Ora (mantenendo fisso a 1 il pin RB1 per tenere impegnata la linea) si deve commutare ciclicamente a 1 e a 0 il pin RB0 per inviare sul filo F una frequenza, e il tutto deve continuare per 10 secondi. Inoltre la commutazione del pin RB0 deve essere rallentata con appositi cicli di ritardo, altrimenti verrebbe generata una frequenza ultrasonica non udibile di oltre 100kHz.
          BSF    PORTB,0
          MOVLW  200
          MOVWF  CL
RIT1      DECFSZ CL,F
          GOTO   RIT1
          BSF    PORTB,0
          MOVWF  CL
RIT2      DECFSZ CL,F
          GOTO   RIT2
Si setta il pin RB0 a 1. Poi si carica il registro CL con il valore 200 e si esegue un'istruzione DECFSZ. Questa istruzione decrementa il valore di CL e se è arrivato a zero salta il GOTO. Se CL non è arrivato a 0 viene eseguita l'istruzione GOTO RIT1, per cui si effettua un nuovo decremento e così via. In pratica vengono eseguiti 200 decrementi, dopo di che il programma prosegue. Si azzera il pin RB0 e si attende un'altra pausa di 200 iterazioni.
Considerando che con un clock di 4 Mhz l'esecuzione di una DECFSZ e della successiva GOTO dura 3 microsecondi, l'esecuzione del ciclo completo di 200 iterazioni dura 599 microsecondi. Se commutiamo il pin di uscita RB0 ogni 599 microsecondi otteniamo una frequenza di circa 835Hz.

Noi dobbiamo emettere questa frequenza per 10 secondi, e visto che in 10 secondi ci sono circa 8350 periodi dobbiamo inserire le istruzioni viste sopra in un ciclo di 8350 iterazioni: 
          MOVLW  32
          MOVWF  DH
          MOVLW  158
          MOVWF  DL

TONO      BSF    PORTB,0
          MOVLW  200
          MOVWF  CL
RIT1      DECFSZ CL,F
          GOTO   RIT1
          BSF    PORTB,0
          MOVWF  CL
RIT2      DECFSZ CL,F
          GOTO   RIT2

          DECF   DL,F
          COMF   DL,W
          BTFSC  STATUS,Z
          DECF   DH,F
          MOVF   DH,W
          IORWF  DL,W
          BTFSS  STATUS,Z
          GOTO   TONO
Si usa la coppia di registri DH e DL come contatore a 16 bit, lo si inizializza a 8350 (32*256+158=8350) e, dopo un periodo della frequenza, lo si decrementa controllando 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 DH o DL è a 1 si salta a TONO per emettere un altro periodo.
A questo punto liberiamo la linea riportando a 0 RB1 e torniamo all'inizio del programma: 
          BCF    PORTB,1
          GOTO   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. Per comodità creiamo un'ulteriore subroutine per il ritardo di 1 millisecondo da richiamare 15000 volte (chiamata MS1).
DELAY     CALL   MS1
          DECF   DL,F
          COMF   DL,W
          BTFSC  STATUS,Z
          DECF   DH,F
          MOVF   DH,W
          IORWF  DL,W
          BTFSS  STATUS,Z
          GOTO   DELAY
          RETURN

MS1       MOVLW  142
          MOVWF  CL
MSR1      GOTO   $+1
          GOTO   $+1
          DECFSZ CL,F
          GOTO   MSR1
          NOP
          RETURN
Qui si vede bene come ogni subroutine chiamata con CALL deve terminare con l'istruzione RETURN, che fa riprendere l'esecuzione dall'istruzione successiva alla CALL.
La coppia di registri DH:DL viene usata nello stesso modo del ciclo di emissione della frequenza.
La subroutine MS1 dura esattamente 1 millisecondo (compresa la CALL che la chiama). Le due istruzioni GOTO $+1 servono per far trascorrere 2 microsecondi (a 4MHz) e occupano una sola locazione di memoria programma.

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,135001 secondi.

Il programma completo


          PROCESSOR           16F84
          RADIX               DEC
          INCLUDE             "P16F84.INC"
          __CONFIG            11111111110001B
          ORG    12           ;INDIRIZZO INIZIO RAM DATI
CL        RES    1
DL        RES    1
DH        RES    1
          ORG    0            ;INDIRIZZO INIZIALE PROGRAMMA
;------------------------------------------------------------
          CLRF   PORTB
          BSF    STATUS,RP0
          CLRF   TRISB
          BSF    STATUS,RP0

ATTESA1   BTFSC  PORTA,0      ;LETTURA FILO R
          GOTO   ATTESA1

          MOVLW  58           ;IMPOSTA RITARDO 15 SECONDI
          MOVWF  DH
          MOVLW  152
          MOVWF  DL
          CALL   DELAY

          BSF    PORTB,1      ;IMPEGNA LA LINEA

          MOVLW  32           ;IMPOSTA 8350 PERIODI
          MOVWF  DH
          MOVLW  158
          MOVWF  DL

TONO      BSF    PORTB,0      ;ALZA PIN RB0
          MOVLW  200
          MOVWF  CL
RIT1      DECFSZ CL,F
          GOTO   RIT1         ;ATTESA 599 MICROSEC.
          BSF    PORTB,0      ;ABBASSA PIN RB0
          MOVWF  CL
RIT2      DECFSZ CL,F
          GOTO   RIT2         ;ATTESA 599 MICROSEC.

          DECF   DL,F         ;DECREMENTA CONTEGGIO PERIODI
          COMF   DL,W
          BTFSC  STATUS,Z
          DECF   DH,F
          MOVF   DH,W
          IORWF  DL,W
          BTFSS  STATUS,Z
          GOTO   TONO         ;SE NON SONO FINITI TORNA A TONO

          BCF    PORTB,1      ;LIBERA LA LINEA
          GOTO   ATTESA1      ;TORNA AD ATTESA CHIAMATA
;------------------------------------------------------------
DELAY     CALL   MS1          ;CHIAMA RITARDO 1 MILLISEC.
          DECF   DL,F         ;DECREMENTA CONTEGGIO MILLISEC
          COMF   DL,W
          BTFSC  STATUS,Z
          DECF   DH,F
          MOVF   DH,W
          IORWF  DL,W
          BTFSS  STATUS,Z
          GOTO   DELAY        ;SE NON SONO FINITI TORNA A DELAY
          RETURN
;------------------------------------------------------------
MS1       MOVLW  142
          MOVWF  CL
MSR1      GOTO   $+1
          GOTO   $+1
          DECFSZ CL,F
          GOTO   MSR1
          NOP
          RETURN
;------------------------------------------------------------
          END

Questo è un programma piuttosto semplice, usa solo 3 byte di memoria per contenere i valori di lavoro, non usa gli interrupt, 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 degli impulsi 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 relativamente semplici.

Inoltre quelle che in hardware sarebbero le eventuali regolazioni dei trimmer dei temporizzatori, qui diventano semplici regolazioni del valore di un registro, per esempio si potrebbe calibrare il valore scritto nei registri DH:DL per ottenere 15 secondi con maggior precisione, o altri valori che fossero necessari.



Pagina e disegni realizzati da Claudio Fin