vuoi
o PayPal
tutte le volte che vuoi
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