4 Esercitazioni Assembly Sistemi elettronici - No codice
Anteprima
ESTRATTO DOCUMENTO
Esercitazione 3 sistemi elettronici: Timer
All’inizio viene usato il TMR0 per il debouncing.
Come prima cosa si va a fissare il clock interno, il prescaler al massimo per vedere quant’è il mio
range, per vedere se quel timer è in grado di supportare la memorizzazione.
Comincio a fare i calcoli: come sorgente di clock ho il Fosc/4, quindi la frequenza di tick sarà
(Fosc/4)/Prescaler. Calcolo la mia massima frequenza e il periodo:
8
2 è il modulo del timer, quindi se lo moltiplico al periodo trovo il periodo massimo che questo
timer riesce a coprire, l’intervallo di tempo massimo che posso raggiungere. Per il debouncing mi
veniva richiesto 10 ms, quindi calcolo quanti ticks sono necessari solo con una proporzione,
facendo così partire la temporizzazione non da zero ma dal valore che manca a raggiungere 256. In
questo caso, con 39 ticks ho 10 ms, quindi faccio in modo che il mio timer vada da 256-39 a 255.
Quando parlo di timer ho due registri, uno per la configurazione del timer, uno per il valore del
conteggio, quindi le impostazioni sopra (prescaler a 256, Fosc/4 ecc.) le vado a mettere
nell’OPTION REGISTER, mentre il valore trovato (256-39) lo vado a scrivere proprio in TMR0.
Tuttavia, ogni volta che si verifica un overflow, il TMR viene riportato a 0 dalla CPU; devo essere io
allora a caricare il valore voluto, per fare in modo di avere ogni volta i miei 10 ms.
Viene anche usato il TMR1: mentre il TMR0 è presente in entrambi i PIC, il TMR1 è vincolato al
secondo PIC, quello usato in questa esercitazione. Il TMR1 ha un modulo maggiore e perché aveva
più sorgenti di clock da poter selezionare: per questo motivo lo andiamo ad utilizzare per il blink
del led. Se viene richiesto un blink di un secondo, significa che all’interno di quel periodo il led
deve accendersi e spegnersi, quindi 500 ms è acceso, 500 ms è spento. La mia temporizzazione qui
è di 1 s, col TMR0 arrivo al massimo a 65 ms, quindi so già che non ce la faccio, perciò mi sposto
col TMR1, che avendo modulo maggiore mi permette di raggiungere temporizzazioni più ampie.
Inoltre, questo blinking doveva avvenire anche durante lo sleep: mi appoggio al TMR1 perché mi
permette di lavorare anche in modalità asincrona, completamente svincolato dal clock interno.
Perciò il t è 500 ms, di conseguenza il duty cycle è 50%. Continuando così è come se stessi
ON
generando una PWM costruita a mano con un timer e sfruttando l’overflow del timer, mi imposto
il timer in modo che vada in overflow ogni 500 ms, nel momento in cui entra nell’ISR accendo il
led, poi lo spengo e così via,avendo in totale un blinking di 1 s.
Prendiamo come sorgente di clock quella esterna a 32 kHz così ci svincoliamo dalla modalità
sincrona e possiamo lavorare anche in sleep.
Il modulo è 16 bit, l’overflow arriva dopo 65536 colpi di ticks. Calcolo poi il periodo massimo,
mettendo stavolta il prescaler a 1, poiché quello 8 non serve. Il periodo che mi interessa è 500 ms,
così al termine di ognuno posso settare il LEDon e il LEDoff.
Per ultima cosa voglio generare la PWM.
Qui l’ottica è un po’ diversa: mi viene chiesto di generare delle note. Di base l’onda sonora può
essere modellata come una sinusoide. Il parametro caratteristico è pitch, l’altezza della nota, che è
data dalla frequenza; ogni nota è caratterizzata dal proprio pitch, quindi dalla propria frequenza.
Se ho un’informazione su T e un’informazione su t io posso approssimare la sinusoide all’interno
ON
del mio circuito che è limitato come una forma d’onda quadra (una PWM appunto) e da qui
generare le varie note. T e t sono le uniche informazioni di cui ho bisogno per generare la mia
ON
nota.
Qui ho come informazione il periodo di Do, che vale 1912 us. A differenza dei timer precedenti,
non andiamo a caricare il valore massimo meno il numero di tick trovati, questo perché il TMR2
funziona in maniera diversa, si appoggia ad un modulo comparatore per cui il valore che vado a
trovare non lo vado a scrivere in TMR2 ma lo vado a scrivere in PR2, ovvero il valore comparatore,
una volta raggiunto il quale il timer si azzera. Il secondo modulo comparatore mi fa un confronto
tra il valore di TMR2 e il valore scritto nel registro CCPR1H (CCP interagisce con TMR1 e TMR2, per
il TMR2 che ha 8 bit non ho bisogno di parte alta e bassa, per il TMR1 che ha modulo 16 mi
servono parte alta e bassa per memorizzare la variabile). Nel momento in cui c’è il matching,
abbiamo individuato il t . I due comparatori ci servono perché uno determina il periodo della
ON
PWM e l’altro determina il duty cycle della stessa. Perciò la costante di tempo individuata prima
(119 tick) ci serve per individuare il periodo però non andrà scritta in TMR2, ma in PR2.
Se il t mi viene dato dal confronto tra TMR2 e CCPx e il duty cycle lo voglio al 50%, significa che
ON
dentro CCPx io ci vado a scrivere PR2/2.
Per memorizzare PR2/2 in binario, prendo il codice di PR2 e faccio lo shift a destra, causando una
n
divisione per 2. Regola: se shifto di n posizioni, ho una divisione di 2 .
Con tutto questo abbiamo generato la PWM. Come viene riprodotta? Nella scheda c’è un
piezoelettrico, un trasduttore, che mi permette di riprodurre quest’onda sonora: è un materiale
che produce una deformazione meccanica quando si applica una tensione ai suoi capi. Un modo
per migliorare il segnale sarebbe l’utilizzo della lookup table, ma qui usiamo solo un buzzer, il
piezoelettrico, non perfetto come qualità poiché non dedicato alla musica.
Ora andiamo sul codice in MPLAB:
La define associa una sequenza di caratteri a un nome, in modo tale che all’interno del codice, ogni
volta che in fase di compilazione il processore va a fare la scansione del codice sorgente, va a
sostituire a questa sequenza di caratteri il nome indicato dalla define. Posso dire per esempio
define time 10, o per esempio un valore, oppure define loop, un’istruzione più o meno lunga. In
questo caso nel codice è presente
#define SHOW_SLEEP: qui utilizzo la define senza operandi, non c’è la sequenza di caratteri qui;
quando li utilizzo in questo modo è utilizzabile con le direttive ifdef e ifndef: mi dichiarano l’inizio e
la fine di questa sorta di macro. Se avessi scritto ifndef, ovvero se non è definita quella label, allora
eseguimi queste righe di codice, altrimenti no. Quindi:
- Utilizzata con gli operandi, in fase di compilazione il preprocessore sostituisce alla label la
sequenza di caratteri che la accompagna.
- Quando non c’è una label invece viene utilizzata come label per abilitare o meno una
porzione di codice
Dal codice in MPLAB: #define <nome-macro> <sequenza-caratteri>. La direttiva #define è usata
per associare una sequenza di caratteri a un identificatore. Il preprocessore, nel fare la scansione
del codice sorgente, sostituisce a ogni occorrenza dell’identificatore la stringa di caratteri che vi è
stata associata con la direttiva #define. La definizione di un nome senza la sua relativa stringa è da
considerarsi utilizzabile con le direttive #ifdef e #ifndef.
Abbiamo poi la direttiva list e l’include, che noi includiamo sempre mentre il libro no.
Poi il configuration word: stavolta c’è LVP_OFF, mi abilita un pin per la scrittura su seriale e quindi
la programmazione tramite programmatore fisico. Nei PIC ci sono pin che hanno più funzioni:
questo è il caso di RB3, che ha ingresso analogico AN9, il PGM legato all’LVP. Siccome voglio
utilizzare RB3 per il pulsante, poiché la mia scheda è collegata al pulsante, quindi disattivo LVP, in
modo da poter utilizzare il pin RB3 come I/O generico. Negli esempi precedenti lavoravamo con
RB0, quindi questo LVP_OFF non l’avevamo visto.
Con la direttiva EQU andiamo ad associare le costanti delle label che poi andremo ad utilizzare nel
codice. Tutta questa parte riguarda i calcoli che abbiamo fatto prima:
- Il TMR0 era usato per il debouncing di 10ms, e prima avevamo trovato il numero di 39 tick
per avere quel tempo, allora vado a definire la costante che sarà quello che andrò a
scrivere su TMR0 per far partire il conteggio: (.256 - .39).
- Il TMR1 era usato per il blinking dei led, avevamo calcolato il numero dei tick per avere
8
500ms, ovvero 16384, e lo andiamo a sottrarre non a 256 ma a 2 =65536.
- Il TMR2 era usato per ottenere le frequenze delle note desiderate. Qui non si tratta di
sottrazioni, ma di trovare un valore che vado a scrivere su PR2 per far avvenire il matching
tra il timer che incrementa e il valore scritto in PR2.
Ora con UDATA_SHR mi riservo in RAM una porzione di codice da allocare in RAM e condivisa a
tutti i banchi; lo faccio per il salvataggio di contesto: se sto utilizzando portB e TMR0, mi aspetto
che ci siano delle routine di interrupt, quindi per gestirli ho bisogno di salvataggi e ripristino del
contesto, quindi un byte per salvare il registro accumulatore, uno per lo status, uno per pclath (per
l’interrupt); poi siccome sto lavorando con pressione e rilascio di bottoni, riserviamo un byte per
salvare il valore dello stato precedente della porta B (portBprev). Visto che è tutto gestito tramite
interrupt, mi riservo un byte per far dormire la CPU fin quando non viene chiamato un interrupt
(canSleep); infine una variabile temp per fare una serie di calcoli utili nel codice.
Infine, come al solito mettiamo il vettore di reset RST_VECTOR, prima di cominciare
definitivamente il codice. Poi viene fatto il goto per spostarmi nella pagina di programma per far in
modo che non si blocchi a 0x0004 (fatto con codice rilocabile).
❖ Con lo start comincia il programma. La prima cosa è andate ad inizializzare l’hardware, e lo
faccio chiamando con la call della subroutine INIT_HW
➢ In INIT_HW dobbiamo impostare timer e porte GPIO, perciò le prime impostazioni che
vado a fare sono queste, poi ci sarà anche la PWM.
Per il TMR0 abbiamo visto l’option register, T1CON e T2CON per il timer 1 e 2.
Nel TMR0 volevo un clock interno, un prescaler a 256: imposto l’option register (controlla
171/688) con tutti 0 e gli ultimi tre settati a 1. Il valore del PSA viene impostato a 0 perché
in questo modo assegno il prescaler non al watchdog timer ma al TMR0. Quanto vale il bit
T0SE non ci interessa perché utilizziamo la sorgente di clock interna, ci interessa quanto
che T0CS sia messo a 0 per impostare la sorgente di clock interna. Metto perciò il valore
voluto dentro w e quindi dentro OPTION_REG, così ho configurato il TMR0.
A questo punto vado sul registro INTCON (controlla 33/338), il registro di configurazione
degli interrupt. Non voglio che si verifichi un interrupt adesso, non ho finito a configurare le
mie periferiche, quindi faccio un clear di INTCON
Quindi faccio la configurazione dei GPIO (controlla 41/388): scelgo A, B, C, E come input
digitali per tutti i pin tranne RC2 come output per il buzzer, la portaD connessa ai led la
voglio con i pin 0…3 che siano settati come output, i pin 4…7 come input. La direzione I/O
mode è data dal registro TRISx, quindi per le porte A, B, E metto tutti input, ovvero tutti 1
in TRISx. Per il C, io voglio RC2 come output per il buzzer, il terzo bit lo metto a 0, tutti gli
altri li posso lasciare a 1. Per quanto riguarda TRISD, voglio 0…3 settati come output, 4…7
settati come input, quindi avrò 0xF0, che vado a scrivere dentro TRISD.
Di default ho che tutti i pin connessi all’ADC, che in questo caso sono RB0…RB3, che volevo
utilizzare come input, di default sono settati come input analogici, quindi mi impediscono
l’utilizzo come input digitali. Per poterli utilizzare come I/O digitali, vado a fare un clear di
tutto il registro che automaticamente mi disabilita l’utilizzo di tutti quei I/O come input
analogici, perché erano proprio quelli che volevo utilizzare per il bottone.
Su PORTB ho detto qual è la direzione, ho configurato TRISB per avere la direzione dei pin
sul banco PORTB (RB0…RB3 come input). In particolare voglio abilitare l’interrupt on
change su questi bit. Nel PIC16F84 si faceva subito, poiché avevo l’interrupt enable e
l’interrupt flag dedicato; in questi PIC16F887 il cambiamento di stato della portaB lo posso
avere su tutti i pin della portaB, in particolare in questo caso lo voglio limitare ai primi 4.
Qui ho il registro IOC che mi va a specificare su quale bit io voglio il mio portB on-change: io
lo volevo sui primi 4 più bassi quindi vado a scrivere 0x0F su IOCB.
Nel TMR1 imposto il registro di controllo che stavolta è T1CON (controlla 183/688) e andrò
a settare i bit: setto il quarzo esterno così posso lavorare in modalità asincrona, voglio il
prescaler a 1 e il timer attivo, ovvero B’00001111’.
Nel TMR2 imposto due registri di controllo, T2CON (controlla 197/688) e CCP1CON
(controlla 205/688) in modo tale da avere sorgente di clock interna, prescaler a 16, timer
inizialmente off perché lo andrò ad attivare quando avrò finito di configurare il modulo CCP
per la PWM. Su T2CON vado a scrivere B’00000011’ in modo da impostare il prescaler a 16.
Per CCP1CON imposto i bit a B’00001100’ in modo tale da lavorare in PWM mode. Le
quattro PWM mode (sul data sheet ho 11xx dipendono dallo stato dei pin da cui prelevo il
segnale della PWM.
Ora posso uscire dalla subroutine INIT_HW con il return. In questo modo ancora non ho
attivato il GIE perciò non servo alcuna richiesta di interrupt.
Viene fatto un clear di PORTD solo per scelta di progetto, per partire con tutti i led spenti. Poi
vado ad inizializzare lo stato precedente della PORTB, ovvero portBprev; mi serve infatti per
salvarmi lo stato corrente, che nell’azione successiva diventa lo stato precedente, della porta.
Ovvio che all’inizio devo inizializzarla, perciò suppongo che i pulsanti siano non premuti: non
premuti corrisponde al livello logico alto, 1. Con l’inizializzazione imposto quindi che i pulsanti
non sono premuti, è lo stato 0.
Ora vado ad abilitare l’interrupt on-change di PORTB: devo mettere a 1 l’RBIE, quindi prima
faccio il clear del flag. Poi vado a caricare il TMR1 con il valore che avevo impostato, i 500ms, ci
faccio una routine a parte perché è possibile che la debba richiamare più volte: con la
reload_timer1 con la call per ricaricare il contatore timer 1 per ricominciare il conteggio.
➢ Perciò la reload_timer1 ricarica il contatore per ricominciare il conteggio. Lavorando in
modalità asincrona devo arrestare prima il timer e poi caricare il valore della costante.
Perciò prima faccio un clear di TMR1ON per arrestare il timer, poi vado a scrivere la mia
costante tmr_500ms in due parti, poiché la nostra costante era (65536-16384) e ho
bisogno di tutti i 16 bit. Scriviamo la parte bassa di questo valore in TMR1L, la parte alta in
TMR1H. Perciò i passaggi sono i seguenti:
- Se voglio aggiornare lo stato del contatore o inizializzarlo se è la prima volta che ci entro, lo
vado a scrivere la costante di tempo in TMRx.
- Se il modulo è 16, lo vado a scrivere in TMRxL e TMRxH.
- Se sono in modalità asincrona, prima di fare la scrittura vado ad arrestare il timer.
Poi vado ad azzerare il flag dell’interrupt e riattivo il timer.
Era una call, quindi ritorno sul main col return.
Ritorno sul main. A questo punto vado ad abilitare l’interrupt su TMR1: non è sufficiente
abilitare il GIE ma devo abilitare anche il PIE1 del registro TMR1IE. Infatti INTCON gestisce il
GIE, il PIEI e alcuni dei dispositivi primari; TMR1 non sta qui, quindi per abilitarlo devo mettere
a 1 TMRIE su PIE1, però automaticamente devo mettere a 1 anche PIEI che gestisce le
periferiche e GIE. A questo punto ho abilitato l’interrupt.
Dopo di ciò ho la mia variabile di appoggio, canSleep, impostata in maniera tale da poter
andare subito in sleep. Imposto il bit 0 di canSleep che è il mio flag a 1, così posso andare
subito in sleep.
❖ Col main_loop non faccio nient’altro che controllare se posso o no andare in sleep, poiché nel
verificarsi degli eventi la can_sleep verrà modificata.
➢ waitSleep: Prima di farlo però faccio un clear del GIE, perché quando sto per andare in
sleep si considera questa sezione di codice come critica: nel momento in cui si sta per
andare in sleep si vuole evitare che si verifichi un interrupt, perciò per gestire questa
criticità si fa un clear di GIE, lasciando attiva la possibilità di potermi svegliare dallo sleep
(non azzerare i periferical, i timer, ecc.). Con la solita istruzione di bit testing vado a
controllare se posso andare in sleep: se il bit 0 è 0, non posso andare in sleep e continuo
con le mie istruzioni riabilitando il GIE e rientrando costantemente nel waitsleep; se invece
è impostato a 1 (quello che accade a noi) vado immediatamente in goSleep. Perché viene
settato a 1 il GIE? Di solito quello che si fa è metterlo a 0 appena sto per andare in sleep,
ma appena ho skippato il controllo, riabilitare il GIE.
Il bit 0 di canSleep l’ho messo a 1, quindi sono sicuro che lo skip if clear non mi va a buon
fine ma vado direttamente in sleep; se vado direttamente in sleep ho una goto che mi
manda nella subroutine di gosleep
➢ goSleep: Qui è presente la macro (la define) SHOW_SLEEP che spegne il led 4 prima dello
sleep e poi mi chiama l’opcode sleep e quindi mi manda la CPU in sleep. Fintanto che
nessuno mi sveglia io mi fermo qua.
Tutto quello che devo fare lo faccio in seguito al verificarsi di un interrupt, quindi lo gestirò
tutto dentro l’ISR, e se lo gestisco lì e non dentro il main loop, nel main loop me ne posso
stare a dormire fintanto non c’è un interrupt che mi sveglia.
Supponiamo che il TMR1 è andato in overflow o qualcuno abbia premuto un bottone,
quando si verifica a livello hardware si genera un interrupt e l’interrupt flag viene messo a
1. Però io avevo fatto un clear del GIE, quindi significa che mi sveglio dallo sleep, però non
gestisco la richiesta di interrupt. Poiché non voglio perdere l’interrupt che si è verificato, lo
voglio gestire, appena esco dallo sleep la prima istruzione che seguo è quella di riabilitare il
GIE in modo tale che la richiesta di interrupt che è rimasta pendente si trova il GIE a 1 e
passa. Quindi faccio tutto in modo tale che a livello hardware l’interrupt si verifica, a livello
software la CPU mette l’interrupt flag a 1 e mi sveglio dallo sleep; non gestisco ancora
l’interrupt ma appena esco dallo sleep rimetto il GIE a 1 e quindi posso gestire la richiesta
di interrupt. Da qui salto direttamente all’ISR, la locazione 0x0004.
❖ IRQ INTERRUPT: si trova alla locazione 0x0004. Come si gestisce l’ISR? La prima cosa che vado
a fare è il salvataggio di contesto tramite i byte che mi ero salvato perché prima di entrare
nell’ISR (ci sono entrato inaspettatamente, interrompendo il normale flusso del programma)
avevo lavorato sui registri w ecc. potrei lavorarci all’interno dell’ISR quindi mi copio il valore
attuale di w, STATUS e PCLATH in modo tale che posso modificarli all’interno dell’ISR ma posso
ripristinarli all’uscita dell’ISR in modo tale che nel main program è come se su quei registri non
fosse successo niente. Dopodiché mi trovo nella condizione di multiple sorgenti di interrupt
(abbiamo visto codici per multiple o per singole), perciò ho una serie di subroutine in ognuna
delle quali la prima cosa che faccio è il test dell’interrupt flag, cioè vado a vedere se l’interrupt
flag corrispondente a quella periferica è a 1. Se si entro in quella subroutine, se no salto alla
subroutine successiva. Chi può aver generato un interrupt è TMR0, button, e TMR1; in
particolare questi ultimi due sono quelli che mi possono svegliare dallo sleep perché TMR0 non
mi può svegliare dallo sleep perché dipende dal clock di sistema, il bottone ho l’interrupt alla
pressione, e mi può svegliare perché non dipende dal clock di sistema, TMR1 ho l’interrupt
all’overflow e mi può svegliare perché asincrono esterno.
➢ test_timer0: Se per esempio sono entrato perché svegliato dallo sleep, sicuramente questo
test non andrà a buon fine, quando andrò a controllare l’interrupt flag del TMR0,
sicuramente il valore sarà 0 perciò entro in test_button e passo a controllare se l’interrupt
si è verificato da un’altra periferica.
--------------------------------------- non ha completato questa parte
➢ test_button: se il test_timer0 è 0 allora entro in test_button. Con le skip if set non vado ai
controlli successivi ma testo gli eventi port-change di PORTB (RBIF+RBIE), per sicurezza
controllo anche l’interrupt enable, e vado a gestire l’interrupt generato dalla pressione del
bottone. Appena entrato, rimetto subito l’interrupt flag a 0, in maniera tale che una volta
gestito viene rimesso a 1 solo dalla CPU e non rimane attivo per colpa mia. Dopodiché c’è
la stessa porzione di codice vista l’altra volta, una serie di XOR e AND che mi permetteva di
vedere quali pulsanti erano cambiati e poi in particolare con lo XOR vado a vedere il
cambiamento di stato di W con portBprev. Con questo XOR avrò un byte con degli 1 che mi
dicono quali bit hanno cambiato di stato; sto utilizzando i bit 0, 1, 2, 3, quindi avrò bisogno
di 00001111 per fare la maschera e limitare il controllo dei pulsanti 1…4 connessi ai pin
0…3. Con STATUS vado a vedere se Z=0, allora il risultato non è nullo, se il pulsante non è
nullo significa che il pulsante è effettivamente cambiato di stato e quindi skippo il
button_end. Se Z=1 il pulsante non ha cambiato di stato e vado in button_end.
▪ button_end: si va a scrivere il contenuto di portB in portBprev per le volte successive in
cui rientrerò nella routine e dovrò fare le operazioni aritmetiche. Poi va in irq_end
perché ho gestito l’interrupt che si è generato e posso uscire, e ripristina il contesto in
base ai valori temp precedentemente salvati. Con RETFIE esco dalla routine di
interrupt.
Se lo skip va a buon fine, il pulsante ha cambiato di stato, mi vado a salvare una variabile
temporanea che contiene solo i bit corrispondenti ai pulsanti che sono cambiati, quindi
contiene il risultato dell’ANDLW. Se il pulsante ha cambiato di stato, allora devo iniziare il
conteggio di debouncing per cui andrò a fare un clear dell’interrupt on-change su portB,
perché per la durata del debouncing voglio evitare che si verifichi un altro interrupt, quindi
vado a fare un clear di RBIE, vado a caricare la mia costante di tempo tmr_10ms in TMR0,
nel momento in cui scrivo su TMR0 il conteggio parte, vado ad azzerare l’interrupt flag col
solito discorso e abilito l’interrupt di TMR0, in questo modo sto praticamente inizializzando
il debouncing. Fino alla fine del debouncing non solo disabilito l’interrupt on-change su
PORTB, ma evito anche che la CPU possa andare in sleep. A questo punto voglio testare
quale pulsante è stato premuto o rilasciato; per fare questo utilizzo la variabile temp che
avevo messo da parte prima: questa contiene il risultato dell’ANDLW, ovvero i bit a 1 in
corrispondenza dei pulsanti che sono stati cambiati; quindi se lo stato precedente del
pulsante era a 1, significa che il pulsante è stato premuto, altrimenti rilasciato. Di fatto
temp = temp AND portBprev. In che modo? Supponiamo che RB0=1, non ho premuto il
pulsante. Inoltre il bit 0 di temp sia 1, significa che il bit 0 è cambiato. Quando vado a fare
l’AND, il risultato è 1: avevo un pulsante non premuto, ma il temp mi dice che il pulsante è
cambiato, quindi sicuramente il pulsante sarà passato da non premuto a premuto. Quest’1
non è il livello logico alto o basso, è solo un flag per indicare quali pulsanti sono stati
premuti o non premuti. Faccio questo per capire chi è stato premuto perché a seconda di
quale pulsante è stato premuto devo generare il Do, il Sol, il Fa, ecc. infatti ad ogni pin ho
associato la riproduzione di una nota. Faccio ancora il test su Z, infatti il bit test è un test su
bit, non su byte, quindi mi devo sempre appoggiare al risultato dell’operazione logica che è
memorizzata in Z. Se il risultato è 0, Z=1, il pulsante è stato rilasciato, altrimenti entro in
button_pressed.
▪ button_pressed: quindi quando il risultato di Z è zero, devo testare quale bottone è
stato premuto, perché per ognuno di questi dovrò riprodurre una determinata nota.
Vado a fare il solito bit test e lo vado a fare sul bit 0, 1, 2, 3 di temp, perché so che temp
contiene degli 1 in corrispondenza dei bit che sono cambiati di stato e che sono stati
premuti, per cui se va a buon fine questo controllo significa che è stato premuto il
bottone 1, in caso negativo si va al successivo, si controlla e così via.
• Quando un controllo va a buon fine sposto la costante che avevo calcolato per
riprodurre quella nota (il numero di tick che uso per scrivere nel PRx e settare il
period). Vado quindi alla routine play note, che è la routine che mi riproduce la
nota, ovvero mi riproduce la PWM, andando a copiare il valore di w su PR2, per
ottenere un’onda quadra al 50% vado a scrivere PR2/2 su CCPR1L, che si va con lo
shifting, attivo il TMR2 e aspetto per ogni matching di avere la generazione della
mia onda quadra.
Esercitazione 4 sistemi elettronici: dimming
Stavolta si vuole fare un dimming di un LED tramite uscita PWM (con il LED collegato
esternamente, PWM regolata da potenziometro connesso ad ADC). Il dimming corrisponde ad una
variazione di luminosità del LED, è sempre però un’accensione e uno spegnimento temporizzato
però con una frequenza fissa, alta, in modo da non essere percepita dall’occhio umano, ed essere
percepita come una sorta di variazione di luminosità. Di conseguenza il periodo è fisso, ma il duty
cycle è variabile (che è la variazione dell’intensità); di conseguenza posso fare questo dimming con
una PWM che ha un T fisso e un t variabile che determina intensità più alta o più bassa.
ON
Come faccio ad avere la t variabile, non solo per l’intensità luminosa, ma anche sonora? Devo di
ON
fatto refreshare il valore dell’output compare ogni volta, poiché PR2 mi dà il periodo, CCPR mi dà il
t . Avrò quindi una frequenza fissa, che sarà quella che vado a scrivere in PR2, però avrò un CCPR
ON
variabile. Come faccio ad aggiornarlo per percepire la variazione di luminosità? Viene utilizzato il
potenziometro, che di fatto è un partitore di tensione resistivo, che fornisce di fatto una tensione
variabile; essendo collegato all’input dell’ADC, i valori di tensione li andrà a convertire e scrivere
nel CCPR1H, quindi ho un t che è sempre diverso poiché settato automaticamente tramite la
ON
manipolazione del potenziometro. Quindi come modifico il potenziometro vedo in real time una
variazione della luminosità del led (o del buzzer). Connessa alla mia scheda ho un sensore di
temperatura, un sensore di luce e un potenziometro, utile per dare valori diversi alla PWM e
connesso all’input dell’ADC. Se andiamo sulla PWM, CCP1 sta su RC2, che è connesso alla label
UserRC2, ovvero un buzzer con un jumper che mi permette di abilitare o disabilitare il buzzer,
poichè se vado a vedere nello schema degli external I/O, anche qui ho UserRC2 (pin 13…16);
questo significa che l’output della PWM mi va sul pin RC2, a RC2 ho connesso di default il buzzer
però in realtà se io rimuovo il jumper allora UserRC2 corrisponde agli external I/O. Quindi posso
decidere se pilotare il buzzer con il jumper inserito oppure pilotare il mio external I/O con il
jumper tolto. Quindi la variazione dell’intensità è sia luminosa che sonora perché dipende dal fatto
che sto ragionando col potenziometro che mi fa variare il duty cycle della PWM, la quale ha
un’uscita su RC2, ma in realtà ho due alternative: se inserisco il jumper ho una variazione
dell’intensità sonora, se vado a variare l’external I/O allora ho una variazione dell’intensità
luminosa.
Con questo faccio il dimming o del buzzer o del led esterno; ma se volessi far dimmare un led di
quelli che ho già, siccome non sono connessi a RC2 quindi non posso sfruttare l’output della PWM;
quello che posso fare è fare quello che avevo fatto con il blinking: creare una PWM a mano,
lavorando con un timer e due variabili (t , t ). Qui a differenza di prima non posso fissare i
ON OFF
millisecondi perché voglio simulare la variazione di luminosità; quindi costruirò lo stesso loop
dell’altra volta, ma ogni volta che vado a memorizzare il mio valore del timer, uno lo vado a
incrementare uno lo vado a decrementare, andando sempre a controllare che non vada sotto a 0 e
sopra a 255.
Di fatto quando premo il bottone attivo la conversione della PWM, nonostante di base il micro è in
sleep; attivandolo dallo sleep, attivo anche la conversione del modulo ADC, l’uscita è connessa al
discorso del potenziometro, col potenziometro vario la tensione.
Ora andiamo a vedere il codice:
- All’inizio è presente la #define SHOW_SLEEP, utilizzata per abilitare o disabilitare porzioni di
codice, spesso lo utilizziamo per il debug, ovvero che abilitiamo una define associata ad una label e
con quella label attivo una porzione di codice che mi serve per accendere o spegnere un led,
I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher brandontesla di informazioni apprese con la frequenza delle lezioni di Sistemi elettronici e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Politecnico delle Marche - Univpm o del prof Falaschetti Laura.
Acquista con carta o conto PayPal
Scarica il file tutte le volte che vuoi
Paga con un conto PayPal per usufruire della garanzia Soddisfatto o rimborsato