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.
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.
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:
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.
Duty-cycle al 10%
Duty-cycle al 50%
Duty-cycle al 90%
Duty-cycle al 10% e al 90% a confronto
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.
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. Benmché 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
Schema elettrico
I componenti utilizzati
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.
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.
Metto a disposizione i file di progetto, come di consueto. Il firmware di esempio è tale da:
Il file, come detto, è scritto e compilato per SDCC; lo si può scaricare a questo link.
Questo articolo ed il software rilasciato rientrano nell'ambito della licenza CREATIVE COMMONS BY-NC-ND Italia 3.0, secondo quanto indicato nelle note legali qui riportate.
Sintesi delle note legali (italiano) | |
Note legali (italiano) | |
Legal Code (international) | |
Commons deed (international) |