Indice |
Abstract
Spesso molti utenti di questo sito richiedono di poter disporre di molti canali PWM, ciascuno configurabile a piacere in termini di duty-cycle. Ora, diversi microcontrollori (compresi i PICMicro, ovviamente) dispongono di molteplici canali PWM ma per rimanere nella famiglia a 8-bit al più si arriva a 6 canali, tipicamente impiegati per applicazioni di elettronica di potenza, come inveter, chopper oppure SMPS. Volendo aumentare il numero di canali PWM è necessario mettersi di impegno e cercare di implementare una tecnica per sviluppare PWM soltanto con righe di codice: via software, insomma.
Cos'è il PWM
Non mi voglio dilungare troppo su cosa sia il PWM, tecnica di modulazione di ampiezza di un'onda quadra, impiegata per diversi scopi. In questo articolo ho già trattato l'argomento in relazione al PWM generato dai moduli hardware contenuti all'interno di un PICMicro. Rimando quindi alla lettura di quell'articolo per approfondire le tematiche relative.
Ora l'intento è quello di realizzare 8 canali PWM indipendenti, ciascuno con il proprio valore di duty-cycle impostabile da 0% al 100%; l'unica "pecca" di questo sistema è che, sfruttando interrupt e timer come risorse comuni a tutti i canali PWM, il risultato è quello di avere tutti i canali in fase tra di loro e tutti con la medesima frequenza. In effetti esiste la possibilità di impostare, entro certi limiti, frequenze differenti di Fpwm, ma con il microcontrollore scelto per questo progetto/tutorial l'impresa diventa ardua, in quanto si arriva ben presto ad esaurire le risorse disponibili sul PIC.
Come realizzare un firmware-PWM
Una possibile tecnica è quella di utilizzare timer ed interrupt; l'uso dei timer in polling è infatti molto limitante ed è sconsigliato quando si vogliono avere "molti" PWM firmware. L'esempio proposto, scritto e compilato con SDCC (benché sia facilmente portabile su altri compilatori), si basa su di un PIC16F819, secondo lo schema di figura.
La tecnica non è complicata ed è descritta passo-passo nel seguito:
- La prima cosa da fare è configurare un timer, con una base dei tempi a scelta che determinerà la periodicità con la quale verrà eseguita la funzione PWM. Per semplicità, TMR0 sarà il timer scelto per svolgere questa funzione.
- Un secondo timer va configurato e mandato in esecuzione; questo secondo timer, ad esempio TMR2, non viene impostato per generare un interrupt su overflow, ma soltanto per funzionare come "contatore libero" e sarà quello che determinerà la frequenza (Fpwm) dell'onda quadra che viene modulata.
- Ogni qualvolta si scatena un interrupt su overflow di TMR0, viene eseguita la funzione PWM(), la quale, per ciascun canale, confronta il valore del duty-cycle che si vuole ottenere con il valore di TMR2, e pone l'uscita PWM a valore alto o basso di conseguenza, secondo la regola:
Se duty < TMR2 -> OutPWM = 0
Se duty >= TMR2 -> OutPWM = 1
Una regola importante da seguire è quella per la quale i due timer devono avere necessariamente tempi di aggiornamento differenti, onde evitare che, essendo sincroni, il PWM sostanzialmente non sia possibile eseguirlo.
In fase di inizializzazione, ecco le impostazioni sui timer:
// TMR0 setup OPTION_REG = 0x80; // TMR2 setup T2CON = 0x07; TMR2ON = 1; // TMR0 and General interrupt enable TMR0IE = 1; PEIE = 1; GIE = 1;
Per la gestione di TMR0 si sfrutta l'interrupt:
// Interrupt Service Routine void Intr(void) __interrupt 0 { // Timer 0 overflow if (TMR0IF) // Interrupt each 255us { PWM(); // Call PWM() function TMR0IF = 0; // Clear TMR0 interrupt bit } }
La funzione PWM risulta pertanto realizzata in questo modo:
// Software PWM made with the comparison between a duty value and Timer2 value void PWM (void) { if (uchStartPWM & 1 == 1) { if (uchDuty[0] >= TMR2) { RB0 = 1; } else { RB0 = 0; } } if (uchStartPWM & 2 == 0x02) { if (uchDuty[1] >= TMR2) { RB1 = 1; } else { RB1 = 0; } } if (uchStartPWM & 4 == 0x04) { if (uchDuty[2] >= TMR2) { RB2 = 1; } else { RB2 = 0; } } if (uchStartPWM & 0x08 == 8) { if (uchDuty[3] >= TMR2) { RB3 = 1; } else { RB3 = 0; } } if (uchStartPWM & 0x10== 0x10) { if (uchDuty[4] >= TMR2) { RB4 = 1; } else { RB4 = 0; } } if (uchStartPWM & 0x20 == 0x20) { if (uchDuty[5] >= TMR2) { RB5 = 1; } else { RB5 = 0; } } if (uchStartPWM & 0x40 == 0x40) { if (uchDuty[6] >= TMR2) { RB6 = 1; } else { RB6 = 0; } } if (uchStartPWM & 0x80 == 0x80) { if (uchDuty[7] >= TMR2) { RB7 = 1; } else { RB7 = 0; } } }
Con queste semplici considerazioni, nelle figure che seguono è mostrato il risultato un PWM con duty-cycle al 10%, al 50% e al 90%, con Fpwm a valore di circa 500Hz.
Cycle slip
Se l'implementazione del PWM è tale da richiedere repentine variazioni di duty-cycle, variazioni determinate da calcoli e/o condizioni del programma, si può notare un leggero sfarfallio nella frequenza Fpwm. Il fenomeno va ricercato nel fatto che il microcontrollore deve eseguire un certo numero di istruzioni legate alla gestione degli interrupt ed al ciclo logico del programma. Il ciclo logico potrebbe essere a durata variabile, in funzione degli eventi che intercorrono, allungando o accorciando l'esecuzione di talune routine con conseguente effetto di cycle slip.
The LED CHASER
Mettendo insieme un po' di queste considerazioni, ho voluto cimentarmi nella realizzazione di un circuito a LED in grado di generare effetti luminosi: The Led Chaser. Benché di questo tipo di progetti ne è piena la rete, ho voluto mostrare come sia possibile, con poche risorse disponibili, realizzare qualche effetto luminoso. I componenti utilizzati sono veramente poco più che una manciata: il PIC16F819, 8 LED e 8 resistori, un regolatore di tensione 78L05, un condensatore da 100nF
Una volta montato il tutto, la resa luminosa è di un certo effetto. All'inizio del filmato si nota come l'accensione e lo spegnimento dei LED avvenga con una leggera sfumatura grazie all'intervento del PWM firmware implementato nel codice.
Qualche raccomandazione
I valori impostati nei timer sono stati determinati e testati avendo cura di verificare con la strumentazione che tutto funzionasse a dovere. La scelta di un microcontrollore diverso e/o i valori dei timer che determinano le basi dei tempi, porta inevitabilmente a rimettere tutto in discussione, non tanto dal punto di vista teorico quanto da quello pratico. Insomma, la tecnica va affinata sul campo.
Download
Metto a disposizione i file di progetto, come di consueto. Il firmware di esempio è tale da:
- impostare il PIC16F819 affinché possa lavorare con l'oscillatore interno a 8MHz;
- impostare il duty-cycle su diversi valori (dal 10% circa all'80% circa) su ciascuno degli 8 LED
- avviare il PWM software.
Il file, come detto, è scritto e compilato per SDCC; lo si può scaricare a questo link.
Licenza
Questo articolo ed il software rilasciato rientrano nell'ambito della licenza CREATIVE COMMONS BY-NC-ND 3.0, secondo quanto indicato nelle note legali qui riportate.
Sintesi delle note legali (italiano)
Biblografia
- Datasheet PIC16F819: http://ww1.microchip.com/downloads/en/devicedoc/39598e.pdf
- Ambiente di sviluppo MPLAB: http://www.microchip.com/mplab
- Compilatore SDCC: http://sdcc.sourceforge.net
- GPUTILS: http://gputils.sourceforge.net
- Sito web PicExperience
- Collana "LO HAI MAI REALIZZATO CON UN PIC?":
- Il contamarce
- Una sorpresa musicale per Babbo Natale
- Una tecnica antirimbalzo
- Il dado elettronico
- Un approccio ai timer dei PICMicro
- I PIC e i segnali analogici: la conversione A/D
- Generare segnali PWM con i PICMicro
- La lampada SIBILLA!
- Una tecnica di misura della tensione di batteria
- Il PIC tiene la data e l'ora
- Una prima occhiata a SDCC
- PIC Watch un semplice OROLOGIO SVEGLIA
- I PICMicro e i display LCD alfanumerici (parte 1)
- I PICMicro e i display LCD alfanumerici (parte 2)