Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

5
voti

Due compiti "contemporanei"

Indice

Quale interrupt e quale schema ?

Quale interrupt? Iniziamo proprio con questa domanda, intendendo quale SEGNALE ELETTRICO lo dovra' generare; per semplificare le cose faremo lampeggiare un LED (chi non ci ha già provato ?) ed il tempo tra ON e OFF lo stabiliremo con l'ausilio del primo temporizzatore interno del PIC, ovvero TMR0: sarà questo che genererà l'INTERRUPT.
Il resto dello schema: alimentazione collegata come si deve, quarzetto (segnatevi la F0, diciamo 4 Mhz?) collegato ai suoi pin canonici, una resistenza 1k 0.25W tra +5V e MCLR, un condensatore 100nF in parallelo ai pin di alimentazione e uno tra MCLR e 0V, un pulsante normalmente aperto in parallelo a quest'ultimo condensatore, una resistenza 1k 0.25W collegata ad RA3 e sul terminale opposto colleghiamo l'anodo del LED; il catodo a 0V.
Facciamo riferimento al datasheet del PIC16F877(A) da cui ho "pescato" tutte le immagini cui farò riferimento: a pagina 55 (per il lettore dei files.pdf; mentre il foglio e' numerato 53) si vede come "viaggiano" i segnali quindi, sulla base di questa "cartina", dobbiamo impostare gli "svincoli" per usare CLK0 ed il PRESCALER.
Ignoreremo lo WatchDogTimer e il pin RA4/T0CKI.

Teniamo quindi presenti questi "punti salienti": T0SE=0, T0CS=0, PSA=0.

Calcolo della temporizzazione

La F0 del nostro Xtal viene già divisa x 4, quindi il periodo del segnale che si presenta sul primo "MUX" sara' di un µsec.; il nostro LED non dovrà lampeggiare molto velocemente altrimenti, per il nostro occhio, sara' semplicemente "mezzo acceso" o giù di lì, quindi usiamo il prescaler per dividere ulteriormente la frequenza e allungare il tempo, andiamo a pag 56/54 del datasheet (quella dopo) e cerchiamo nella tabellina il fattore di divisione che più ci piace; qual è?
A me piace 256, che porta il periodo a 256 µsec, ed e' ancora una freccia, per cui impostiamo i bit PS0, PS1 e PS2 tutti e tre a "1".
Abbiamo detto che e' ancora troppo veloce (sarebbe un quarto di millisecondo ON e un quarto OFF...), allora interveniamo sul TMR0-Reg che, se volete accettare il mio punto di vista, in questa configurazione si comporta anch'esso come prescaler, aggiungendo un altro fattore di divisione del tempo, quindi gli scriviamo dentro... 0, si proprio ZERO.

E perche' ?
Perche' in un contatore a otto bit, se parto da zero e incremento fino a tornare a zero mi trovo come se avessi contato da 256 a zero (o viceversa), quindi introduco un'ulteriore divisione per 256; risultato: ottengo un clock di 65,536 msec:

NON CI SIAMO: E' ANCORA TROPPO VELOCE.

Ma allora? Dovremo implementare, a software, un ulteriore contatore che da 7 vada a zero decrementandosi di uno ad ogni "Set Flag bit TMR0IF on Overflow" della figura vista prima; questo moltiplichera' i 65,536 msec facendoli diventare 0,458752 secondi.

Riassumendo, dobbiamo tenere presente:
T0SE=0
T0CS=0
PSA=0
TMR0=0
C_TIME=7

I primi quattro sono riferimenti a segnali interni PIC e dobbiamo rispettare la sintassi dei nomi, l'ultimo e' "roba nostra" (non fraintendiamo...) e possiamo anche cambiargli nome.

Piccola divagazione

Per fare le cose piu' complicate (per chi ci guarda, non per noi) a tutti i pin "RB" colleghiamo una resistenza da 1k e un LED verso 0V; scriveremo SUBITO un programmino di "infinite-loop" che fa accendere gli otto LED in sequenza di conteggio, da 0 a 256, e poi, "sui di lui", innesteremo l'interrupt per il pilotaggio del lampeggiante.

Questo dimostrera' a tutti la realizzazione del "multitasking".


Ancora peggio

Se vogliamo, possiamo anche "sottomettere" le due funzioni ad altrettanti interruttori "vulgaris": potrebbe essere un gadget simpatico, ma facciamo una cosa alla volta; il programma di accensione-conteggio sugli otto bit/LED potrebbe essere fatto, più o meno, così:


        LIST
        PROCESSOR	16F877
        #INCLUDE       P16F877.INC
        _CONFIG	_CP_OFF & _DEBUG_OFF & _WRT_ENABLE_ON & _LVP_OFF & _WDT_OFF & _HS_OSC & _BODEN_OFF & _PWRTE_OFF & _CPD_OFF
;
gpr0    udata
;
CONT1   RES   1     ; primo contatore
CONT2   RES   1     ; secondo contatore
;
;           (1)
;
PROG1   code
        GOTO       INIT
        NOP
        NOP
        NOP
        NOP
;
;           (2)
;
        RETFIE
;
INIT
        BANKSEL   ADCON1
        MOVLW     .6        ; SELEZIONA PORT-A
        MOVWF     ADCON1    ; PER DIGITALE
        BANKSEL   PORTB     ; PREAZZERAMENTO
        CLRF      PORTB     ; DEI PIN
        CLRF      PORTA     ; DI OUTPUT
        BANKSEL   TRISB     ; IMPOSTA IN OUTPUT
        CLRF      TRISB     ; TUTTO IL PORT-B
        CLRF      TRISA,3   ; E PORT-A/BIT-3
        BANKSEL   PORTB     ; TORNA A PUNTARE IL BANCO 0
;
;                  (3)
;
        CLRF      CONT1     ; AZZERAMENTO INIZIALE
        CLRF      CONT2     ; DEI DUE CONTATORI
;
MAIN
        MOVF      PORTB,W   ; PRELEVA CONTENUTO DISPLAY
        ADDLW     .1        ; NE INCREMENTA IL VALORE
        MOVWF     PORTB     ; LO RIEAPONE SUL DISPLAY
MAIN2
        DECFSZ    CONT1,F   ; PRIMO LOOP DI ATTESA
        GOTO      MAIN2
;
        DECFSZ    CONT2,F   ; SECONDO LOOP DI ATTESA
        GOTO      MAIN2     ; DUE LOOPS UNO DENTRO L'ALTRO
;                            PER FAR PASSARE ABBASTANZA TEMPO
        GOTO      MAIN      ; PASSA AL SUCCESSIVO INCREMENTO
        END

E con questo, la questione della riga di LED che fa i fatti suoi, l'abbiamo liquidata.
Proviamo a farlo girare in simulazione sul PC: vedremo il nostro contatore binario a 8 bit che "frulla" fino a riempirsi, azzerarsi, ricominciare, ... FERMATELOOOO ! ! !
Un appunto "specifico": la riga PROG1, che contiene "code", è necessaria per il compilatore Assembler del kit GPUTILS e del Liker-Loader (sempre parte di GPUTILS), ovvero GPASM e GPLINK;
io preferisco NON usare "ORG" per impostare indirizzi assoluti di programma ed "EQU" per indirizzi assoluti delle variabili da usare: lascio che se ne occupino loro, corro meno rischi di dimenticare qualche variabile e, soprattutto, di assegnare lo stesso indirizzo (EQU) a due o più variabili; mi era successo e vi assicuro che si tratta di un errore MOLTO difficile da localizzare.

Piccolo punto da chiarire: abitualmente io NON uso MPLAB (che apprezzo MOLTO GRANDEMENTE) perché non potrebbe girare sotto BGOS su un portatile con 256 MRam a 800MHz: su tale macchinetta di un paio di lustri fa uso Piklab sotto Linux-Ubuntu... ma forse ve l'avevo gia' detto.

Altro "punto-chiave": le prime tre righe subito dopo la label "INIT" sono INDISPENSABILI se, per fare questi esperimenti, usate un PIC come l'877 oppure l'873 che hanno anche il convertitore A/D; se non le inserite, il PORT-A funzione in "modo-analogico" e il vostro LED singolo NON SI ACCENDERA' MAI.

Se, invece, volete fare l' ESPERIMENTO su un 16F84 vulgaris (più ce ne fossero di cosini così...) e usate il file P16F84.INC, nell'intestazione, evitate accuratamente di scrivercele: il compilatore vi insulterebbe.

Cosa abbiamo ottenuto, fin'ora ?

Nulla, un gadget inutile (come tutti i gadgets) che, pero', FA' QUALCOSA (luci) ovvero "impegna" la CPU e l'I/O del PIC.

Adesso viene il bello...

Ora cominciamo sul serio

Ovvietà

Riprendiamo il discorso iniziale sul TMR0 e diciamo subito che alcuni delle label che vedremo tra poco sono, in effetti, i nomi dei "bit significativi" che avevamo citato all'inizio e che sono elencati, e DEFINITI, per bene nel file P16F877.INC, e che possiamo rintracciare nel datasheet, quindi non diventiamo pazzi, quando diremo, per esempio:

    BSF    OPTION_REG,PSA

sapremo di dover andare a puntare il "BANKo 1", indirizzo 0x0080, bit 3; poi, ovviamente, attenti a quale banco si punta...

Come dicevo nel primo articolo: magari sono cose ovvie, da dare per scontate, ma poi se si dimenticano ....


Preparazioni iniziali

Al posto della riga indicata con (3) inseriamo:

;------------
;
;	INIT HARDWARE DI TMR0
;
       BANKSEL   OPTION_REG
       BCF       OPTION_REG,T0CS   ; SELEZIONA INPUT DA OSC. INT.
       BCF       OPTION_REG,PSA    ; SELEZIONA PRESCALER X TMR0
       BCF       OPTION_REG,PS2    ; \
       BCF       OPTION_REG,PS1    ;  > IMPOSTA PRESCALER IN DIVISIONE X 256
       BCF       OPTION_REG,PS0    ; /
       BANKSEL   TMR0
       CLRF      TMR0              ; AZZERA CONTATORE TIMER CPU
       MOVLW     X_TIME            ; INIZIALIZZA IL
       MOVWF     C_TIME            ; CONTATORE SOFTWARE
       BCF       INTCON,T0IF       ; AZZERA CONDIZIONE DI INTERRUPT
       BSF       INTCON,T0IE       ; ABILITA INTERRUPT DI TMR0
;------------
       BSF       INTCON, GIE       ; ABILITA TUTTI GLI INTERRUPT

Ad ogni interrupt

Al posto della riga indicata con (2), invece, inseriamo:

       MOVWF     W_TEMP            ; SALVA W-REG
       SWAPF     STATUS,W          ; SWAPPA LO STATUS IN W
       CLRF      STATUS            ; E AZZERA I BIT DI BANCO
       MOVWF     STATUS_TEMP       ; SALVA LO STATUS
       MOVF      PCLATH,W          ; SALVA IL 
       MOVWF     PCLATH_TEMP       ; PCLATH
       CLRF      PCLATH            ; E LO AZZERA: PAGINA 0
;
;        (ISR)
;
NO_ZERO
       MOVF      PCLATH_TEMP,W     ; RIPRISTINA PCLATH
       MOVWF     PCLATH            ; COM'ERA PRIMA
       SWAPF     STATUS_TEMP,W     ; RIPRISTINA ANCHE STATUS
       MOVWF     STATUS
       SWAPF     W_TEMP,F          ; RIPRISTINA
       SWAPF     W_TEMP,W          ; W-REGISTER

Questi due gruppi di istruzioni, separati dalla riga "(ISR)" e dalla label "NO_ZERO", costituiscono il salvataggio ed il ripristino di tutte le condizioni "di contorno" del sistema: il primo gruppo "mette al sicuro il cucchiaio della minestra", il secondo "riposiziona il cucchiaio in rotta verso la bocca".

NOTA BENE: queste due sequenze di istruzioni sono state copiate dal datasheet del PIC16F877 di Microchip.

Senza questi due gruppi di istruzioni, il sistema non potrebbe funzionare correttamente perché se PRIMA non salvassimo il registro W, ad esempio, e POI non lo ripristinassimo, potrebbe avvenire che il suo valore "corrente", durante il prelievo e il riposizionamento da/verso il display, potrebbe essere sostituito dal valore 7 di X_TIME, come si potrà vedere più avanti; oltre a ciò ci sono altri registri da preservare, come lo STATUS e il PCLATH: sappiamo quanto siano importanti !
Una domanda/risposta:
a quali tempi si riferiscono quel PRIMA e quel POI ?
PRIMA si riferisce all'istante successivo rispetto a quello in cui il segnale del TMR0 ha generato l'INTERRUPT POI, invece, si riferisce al momento in cui, terminato il compito assegnato al programma associato all'INTERRUPT, si torna a dare il controllo al programma che stava "girando" in precedenza.

Un' altra domanda rivolta, principalmente, ai principianti veri del PIC:
Il PIC, quando viene alimentato, inizia ad eseguire le istruzioni che, nel nostro listato, iniziano sotto la riga che contiene "PROG1 code"; quelle quattro istruzioni "NOP" servono a qualcosa ? Si, se non voglio usare "ORG", si, anzi: sono indispensabili!

Quando viene generato un INTERRUPT, il contenuto del registro della CPU che contiene l'indirizzo della PROSSIMA istruzione, viene copiato nello STACK (non spiego nulla, ora, caso mai ne farò argomento di un articolo) e al suo posto viene scritto "4"; se date il numero ZERO alla prima istruzione (il GOTO...) e andate avanti a contare, vedrete che il quarto NOP si trova al posto "4": quella è la prima istruzione che verrà eseguita tutte le volte che si verifica un INTERRUPT.
Vi dico subito che UN NOP potevo risparmiarmelo, ma i computer di trent'anni fa ESIGEVANO un NOP come prima istruzione di un programma: mi è rimasta l'abitudine...
Per terminare il discorso dedicato a chi inizia:
quando ho finito di eseguire tutte le istruzioni dedicate all'INTERRUPT, devo eseguire l'istruzione RETFIE che, andando a riprendere l'indirizzo salvato precedentemente nello STACK, lo rimette al suo posto e tutto torna a funzionare come se l'INTERRUPT non fosse si mai verificato (sempre a patto di aver salvato e ripristinato tutto, altrimenti sono dolori).


Ma non è ancora tutto, al posto della riga indicata da (ISR) dovremo mettere... vedremo !


E per ultimo, ma solo per adesso, come il conto alla rovescia..., inseriamo al posto della riga indicata da (1) le seguenti:

;
PCLATH_TEMP    RES   1   ; SALVATAGGIO DI PCLATH
STATUS_TEMP    RES   1   ; SALVATAGGIO SI STATUS
;
W_TEMP         RES   1   ; SALVATAGGIO DEL W_REGISTER
;
C_TIME         RES   1   ; CONTATORE DEL "PRESCALER" SOFTWARE
X_TIME         EQU   .7  ; VALORE DI INIZIALIZZAZIONE DI C_TIME

Raccogliamo i cocci

Credo sia proprio il caso: siamo partiti con un programmino molto lineare, che gira su se stesso (come quasi tutti), senza nessuna interferenza dall'esterno; poi abbiamo aggiunto delle cose strane che NON SONO LEGATE IN ALCUN MODO AL PROGRAMMA INIZIALE.

Se date un'occhiata al listato completo, dopo i dovuti copia-incolla e le eventuali correzioni causate dai soliti "pulivo il mouse, mi e' partito un click", possiamo riprovare a farlo girare in simulazione ma non e' cambiato praticamente nulla: la sequenza dei LED su PORTB e' sempre la stessa e il LED su PORTA e' sempre spento.

E allora?


Secondo "task"

Abbiamo bisogno, adesso, di realizzare un nuovo "flusso di istruzioni" che realizzi quel benedetto lampeggio, dovremo inserirlo al posto della riga (ISR), e le cose da fare per raggiungere lo scopo sono due:


Lo scandire del tempo

Dobbiamo inserire un'ulteriore sequenza di istruzioni che realizzi questo compito:

decremento del contatore C_TIME di un'unità ad ogni INTERRUPT di TMR0 e, al raggiungimento dello zero, riposizionamento al valore iniziale e, IMPORTANTISSIMO, deve resettare la condizione di interrupt rimettendo a zero il bit che lo ha generato.

Oltre a questo deve passare il controllo ad un'altra sequenza, quella che provvede al lampeggio:

          DECFSZ     C_TIME,F    ; DECREMENTO DEL CONTATORE, SCADUTO ?
          GOTO       NO_ZERO     ; NO, VA VIA
;
;       (LED)
;
          MOVLW      X_TIME      ; REINIZIALIZZA
          MOVWF      C_TIME      ; IL CONTATORE
NO_ZERO
;
          BANKSEL    INTCON      ; RESETTA
          BCF        INTCON,T0IF ; L'INTERRUPT
;


ON - OFF

Per quanto riguarda il lampeggio, invece, io lo gestirei in questo modo:


e' acceso ?

Si, lo spengo e vado a fare dell'altro

no, lo accendo e vado a fare dell'altro


di conseguenza scriverei queste istruzioni:

;
          BTFSC      PORTA,3     ; IL LED E' ACCESO ?
          GOTO       ACCESO      ; SI, VAI A SPEGNERLO
          BSF        PORTA,3     ; NO, ACCENDILO
          GOTO       ZZERO
ACCESO
          BCF        PORTA,3     ; SPEGNILO
ZZERO
;


Ovviamente, se questa sequenza fosse eseguita a "velocita' di PIC" saremmo punto e a capo: LED a mezza luce e basta.

Pero' il PIC "passa di qui'" solo quando glielo dicono il TMR0 e la scadenza del contatore C_TIME, quindi ogni poco meno di mezzo secondo; di conseguenza si DEVE sicuramente percepire il lampeggio.

Proviamo però a inquadrare questa breve sequenza di istruzioni in un'altra posizione... proviamo anche a scriverci una label fra il primo ";" e l'istruzione BTFSC...
Non potrebbe trasformarsi in un programma "tutto per conto suo" ? Teniamo presente questo "dubbio"...

Tiriamo le somme

Adesso abbiamo il tutto che gira come si deve e mentre vediamo tutto questo sfavillio possiamo raggranellare un po' di pensierini:

questo software, se lo analizziamo un po' piu' che superficialmente, in effetti svolge DUE compiti ben distinti:

1) fa girare il contatore luminoso ad 8 bit

2) fa lampeggiare il LED singolo

Guardando bene le sequenze di istruzioni possiamo affermare che SONO COMPLETAMENTE INDIPENDENTI tra di loro, l'unica cosa che rende possibile l' APPARENZA DELLE CONTEMPORANEITA' e' il clock del TMRO che, a tempo debito, toglie il controllo alla "striscia" per passarlo al "singolo", se non e' "multitasking" questo...

Ma c'e' un altra considerazione da fare:

partendo dalla questione che siamo riusciti a rendere indipendenti due compiti diversi, e ciascuno con le sue tempistiche, potremmo anche arguire che il numero di compiti "indipendenti" puo' essere maggiore... e' vero.

Potremmo anche pensare che i dati prodotti in output da un compito possano essere presi in input da un altro... altrettanto vero !!!


Tutto sta nelle necessita' che sono alla base del progetto e nello scopo da raggiungere, ad esempio, prendiamo in esame un PWM completamente software:


1) mi serve un tempo-base abbastanza breve da essere un sottomultiplo sufficientemente piccolo dell'onda quadra "finale" da consentirmi di avere abbastanza "scalini" di regolazione della velocita'.

2) mi serve sapere (da un operatore Umano esterno) quanti gradini di velocita' devo impostare, di volta in volta, secondo le sue necessita'

3) visto che si tratta di un motore (spero...) devo conoscere in che direzione devo farlo girare.


Il punto 1) me lo gestisce TMR), lo abbiamo gia' visto, basta impostare PRESCALER, TMR0 ed eventuale contatore softwareed il gioco è fatto
il punto 2) lo posso dare in consegna all' A/D converter, anche lui puo' generare interrupt, ci sarà un po' da studiare circa il "come si fa", ma poi...
il punto 3), sempre ad interrupt, puo' essere gestito da due dei quattro bit di PORT-B che possono generare un unico interrupt, e poi, nella routine associata vado a vedere...


Rimane qualche domanda:


Ma se quando viene generato l'INTERRUPT, il controllo della CPU inizia ad eseguire l'istruzione all'indirizzo "4", sempre quello, solo quello, come si fa a discriminare tra i vari eventi che possono averlo generato ?

Risposta: devo andare a controllare i bit-flag di TUTTI i dispositivi che ho autorizzato a generare INTERRUPT: quello = 1 mi segnala affermativamente il fatto e DEVO RIPORTARLO = ZERO prima del "RETFIE"


Possono, due o più eventi generare INTERRUPT molto vicini tra loro nel tempo ? talmente vicini che...

Risposta: si, assolutamente si; quando si scrive una routine di gestione dell'INTERRUPT bisogna prevedere DA SUBITO la sequenza COMPLETA dei controlli "di cui alla domanda precedente" rispettando l'ordine delle priorità, prima il più importante e poi gli altri a scalare.


Bene, alla CPU resta tempo, (con il quarzo a 10 MHz c'e', ve lo assicuro ! )per accendere/spegnere un po' di indicatori di stato e sentire un po' interruttori se vengono azionati dall'operatore, questa serie di compiti può essere svolta FUORI dalla gestione degli INTERRUPT, come è stato fatto per la striscia degli otto LED...

Sto maturando l'intenzione di "sciorinarvi" per intero tutto il software di gestione del mio plastico, compresi i ragionamenti che hanno portato alla stesura di certi test logici... Ma vedremo, per intanto...

Meditate, gente, meditate; e se avete quesiti...

................................................................... io son qui !

3

Commenti e note

Inserisci un commento

di ,

Mi piace il tuo articolo, come mi piace il tuo stile di composizione dello stesso, fluido, originale, è evidente la tua propensione all'insegnamento o all'addestramento su questa materia.

Rispondi

di ,

Complimenti.Avevo intuìto che sotto sotto c'era un po' di pensiero "laterale".Un saluto.

Rispondi

di ,

Buon articolo. Finalmente un pezzo in assembler PIC.

Rispondi

Inserisci un commento

Per inserire commenti è necessario iscriversi ad ElectroYou. Se sei già iscritto, effettua il login.