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 !

Elettrotecnica e non solo (admin)
Un gatto tra gli elettroni (IsidoroKZ)
Esperienza e simulazioni (g.schgor)
Moleskine di un idraulico (RenzoDF)
Il Blog di ElectroYou (webmaster)
Idee microcontrollate (TardoFreak)
PICcoli grandi PICMicro (Paolino)
Il blog elettrico di carloc (carloc)
DirtEYblooog (dirtydeeds)
Di tutto... un po' (jordan20)
AK47 (lillo)
Esperienze elettroniche (marco438)
Telecomunicazioni musicali (clavicordo)
Automazione ed Elettronica (gustavo)
Direttive per la sicurezza (ErnestoCappelletti)
EYnfo dall'Alaska (mir)
Apriamo il quadro! (attilio)
H7-25 (asdf)
Passione Elettrica (massimob)
Elettroni a spasso (guidob)
Bloguerra (guerra)