Che materia stai cercando?

4 Esercitazioni Assembly Sistemi elettronici - No codice Appunti scolastici Premium

Spiegazione molto dettagliata dei 4 esercizi in linguaggio Assembly proposti dalla prof.ssa Falaschetti. All'interno non è presente il codice per ragioni di spazio. I 4 esercizi hanno come argomento:
- Shift dei led con polling
- Shift dei led con interrupt
- Timer
- Dimming

Esame di Sistemi elettronici docente Prof. L. Falaschetti

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,


PAGINE

20

PESO

1,000.51 KB

PUBBLICATO

+1 anno fa


DETTAGLI
Corso di laurea: Corso di laurea in ingegneria elettronica
SSD:
A.A.: 2017-2018

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

Recensioni
Ti è piaciuto questo appunto? Valutalo!

Altri appunti di Sistemi elettronici

Tesina sistemi elettronici, linguaggio Assembly - con codice
Appunto
Appunti Sistemi Elettronici
Appunto
Rielaborazione - Sistemi Elettronici
Appunto
Controlli automatici 3
Esercitazione