Questo articolo descrive l’implementazione di un generatore PWM a 16 bit utilizzando la periferica Programmable Counter Array in modalità "High-Speed Output" del microcontrollore C8051F000 della Silicon Labs.
In un mercato dei microcontrollori in piena evoluzione sono ancora tanti i progettisti che per applicazioni di fascia bassa si affidano ad un microcontrollore ad 8 bit. L’8051 è sicuramente uno dei capostipiti di questa tipologia di microcontrollori e ancora oggi è molto utilizzato. Una delle classiche applicazioni di fascia bassa è il controllo motore utilizzando segnali PWM. Vediamo come implementare un PWM a 16 bit utilizzando un microcontrollore 8051 della Silicon Labs, il C8051F000. Le forme d’onda PWM (Pulse-Width Modulated) sono molto utilizzate nelle applicazioni di controllo e nelle retroazioni ad anello chiuso. Nel microcontrollore C8051F000 esiste una periferica interna denominata PCA (Programmable Counter Array), visibile anche in Figura 1, che consente di generare segnali PWM a 8 bit. Esistono però dei casi in cui questa risoluzione non è accettabile e serve un’accuratezza maggiore, per esempio generando un segnale a 16 bit.
Utilizzando la PCA in modalità "High- Speed Output" e con una minima aggiunta di software si può soddisfare questa esigenza. Nelle forme d’onda PWM utilizzate come feedback di sistemi di controllo, solitamente la frequenza non è un fattore importante, a patto che questa sia sufficientemente elevata rispetto alla frequenza di risposta del sistema controllato. Il "contenuto informativo" del segnale viene piuttosto codificato nel duty cycle, ossia il rapporto tra il tempo in cui il segnale rimane alto rispetto al periodo del segnale PWM (Figura 2).
Un’implementazione di questo tipo prevede che l’ingresso di un blocco che genera il segnale PWM sia un numero, solitamente intero, proporzionale al duty cycle desiderato in uscita. Per realizzare un segnale PWM in un progetto basato su 8051 esistono diversi metodi: loop software, timer gestiti a polling o a interrupt, etc. In questo caso utilizziamo il PCA, questo essenzialmente significa ridurre la richiesta di banda per la CPU rispetto ad una qualsiasi implementazione a polling, ed eliminare gli sfasamenti temporali causati dalla latenza (variabile) nella gestione degli interrupt. La periferica PCA è composta da un contatore/timer a 16 bit e 5 moduli di capture/compare, come mostrato in Figura 3.
Il contatore ha un registro a 16 bit (PCA0H:PCA0L), un registro di modo (PCA0MD) che seleziona la base dei tempi e un registro di controllo (PCA0CN), che comanda il conteggio e gestisce i flag dei moduli di capture/compare. Ogni modulo di capture/compare ha un registro di configurazione (PCA0CPMx) che ne seleziona la modalità (Edge-triggered Capture, Software Timer, High-Speed Output, or PWM) e un registro a 16 bit di capture/compare (PCA0CPHn:PCA0CPLn). Poiché tutti i moduli condividono la stessa base dei tempi, possono lavorare insieme permettendo di ottenere forme d’onda agganciate in fase per il controllo di motori, per esempio. In alternativa possono sempre lavorare in maniera indipendente, grazie al registro di capture/compare dedicato, facendo attenzione al fatto che un eventuale comando di reset del contatore interviene su tutti i moduli, essendo comune la base dei tempi.
La realizzazione del PWM a 16 bit configura la PCA in modo che i moduli lavorino in maniera indipendente; le routine software per i moduli utilizzati intervengono solo sul registro di configurazione e sul registro capture/compare del relativo modulo. Il registro di modo del PCA (PCA0MD) è configurato una sola volta e poi non toccato più, mentre il registro del contatore/timer lavora in modalità free-running. La base dei tempi per il PCA può essere ottenuta da quattro sorgenti: SYSCLK/12, SYSCLK/4, Timer0 overflow, oppure un fronte di discesa di un pin esterno ECI. Il diagramma a blocchi del contatore/timer del PCA è mostrato in Figura 4.
La selezione di una sorgente per la base dei tempi del PCA determina la frequenza del segnale PWM. Come detto in precedenza la frequenza non è particolarmente importante poiché non è nella frequenza che risiede il contenuto informativo, però deve essere sufficientemente elevata a seconda del tipo di controllo. La PCA può in tal senso essere cloccata fino alla frequenza del SYSCLK, ottenibile impostando la sorgente su "Timer0 overflows" e mettendo timer0 in modalità auto-reload al valore "0xFF". Per l’esempio che segue configuriamo il PCA per utilizzare la sorgente SYSCLK/4.
PWM A 8 BIT CON IL PCA
Partiamo innanzitutto descrivendo come generare un segnale PWM con una precisione di 8 bit, utilizzando il PCA in PWM mode (Figura 5).
Il periodo della forma d’onda su CEXn è uguale a 256 colpi di clock del PCA, il tempo in cui il segnale rimane basso è pari al valore a 8 bit memorizzato nel byte basso del registro del modulo di capture/compare (PCA0CPLn). La rappresentazione grafica del segnale è quella di Figura 6.
Ad ogni overflow del low-byte del PCA counter (PCA0L), il byte alto del registro di compare viene copiato nel byte basso (PCA0CPLn = PCA0CPHn). Il duty cycle viene modificato aggiornando PCA0CPHn. Questa procedura assicura l’assenza di glitch in uscita. Il duty cycle dell’onda in uscita (in %) è dato da:
Poiché PCA0CPHn può contenere un valore tra 0 e 255 il duty cycle sarà compreso tra 0.38 % (quando PCA0CP0H = 0xFF) e il 100 % (quando PCA0CP0H = 0x00). La risoluzione del duty cycle quindi risulta:
Il vantaggio della gestione dei segnali PWM in questo modo risiede principalmente nel fatto che non serve un intervento della CPU per mantenere la forma d’onda in uscita ad un duty cycle fisso. Impostando il bit CIDL del registro PCA0MD a "0" la forma d’onda viene mantenuta anche quando la CPU va in idle. La modifica del duty cycle può essere effettuata da una singola scrittura a 8-bit di PCA0CPHn. Se si desidera un duty cycle dello 0% è necessario disabilitare l’uscita CEXn settando a "0" il bit ECOMn del registro PCA0CPMn di configurazione del modulo. L’uscita può essere riabilitata o scrivendo un "1" nella stessa posizione oppure scrivendo un qualsiasi valore in PCA0CPHn. Se serve eseguire un’operazione in corrispondenza del fronte di discesa di CEXn è sufficiente impostare i bit MATn (registro PCA0CPMn) e ECCFn (registro PCA0CPMn) a "1". Vediamo adesso come realizzare un PWM con una precisione di 16 bit. Per far ciò configuriamo il modulo PCS in modalità "High-Speed Output mode" come si può vedere in Figura 7.
In questa modalità operativa il pin CEXn cambia continuamente valore tra 0 e 1 e un interrupt opzionale può essere generato confrontando il registro main timer/counter (PCA0H:PCA0L) e il registro capture/compare del modulo (PCA0CPHn:PCA0CPLn). Nel codice di esempio che segue, la gestione dell’interrupt del modulo PCA prevede due stati: uno stato "fronte di salita" e uno stato "fronte di discesa", a seconda del fronte di CEXn che determina l’interrupt. Notare che il valore effettivo di CEXn viene decodificato come variabile di stato. Durante lo stato "fronte di salita" il registro di capture/compare del modulo viene aggiornato con il valore di compare per il successivo fronte di discesa (chiamato PWM nel codice che segue). Durante lo stato "fronte di discesa" il registro di capture/compare del modulo viene caricato con il valore di confronto per il successivo fronte di salita che è zero (0x0000). Lo si può vedere in Figura 8.
Il periodo dell’onda PWM è di 65536 clock della PCA. Il duty cycle in % è dato da:
Il minimo e il massimo duty cycle consentiti sono determinati dal tempo massimo impiegato per aggiornare il valore di confronto dopo che CEXn cambia valore. Per il codice di esempio si è misurato 7 clock della PCA (ossia 28 clock di sistema). Questo significa che il duty cycle può variare da 0.01 % a 99.99 %. La risoluzione del ducty cycle, sempre in %, è:
Ossia circa 15 ppm (parti per milione). L’overhead di CPU per processare questo interrupt è minimo, e lo si può lasciare in IDLE finché l’arrivo dell’interrupt del modulo PCA sveglierà il core al fabbisogno. La modifica del duty cycle si effettua semplicemente modificando la variabile PWM (a 16 bit) nel codice. Esistono casi in cui è necessaria una risoluzione intermedia tra 8 e 16 bit per il PWM, allo stesso tempo si vuole sfruttare la massima frequenza del segnale PWM ottenibile. In tal caso le variabili a 16 bit sono due: PWM_HIGH con il numero di clock del PCA in cui la forma d’onda PWM rimane alta, e PWM_LOW il corrispondente valore basso. Il periodo della forma d’onda PWM in uscita è dato dalla somma delle due variabili, mentre il duty cycle (in %) è dato da:
La risoluzione invece risulta:
Anche in questo caso come il PWM a 16 bit, la gestione dell’interrupt viene fatta in due stati, uno per il fronte di salita e uno per quello di discesa. Rispetto al caso del PWM a 16 bit non viene più caricato nel registro di compare un valore zero, ma viene sommato al valore esistente una delle due costanti PWM_HIGH o PWM_LOW. L’operazione di somma richiede un numero di cicli maggiori rispetto al semplice caricamento di un valore costante, questo riduce ulteriormente il range di duty cycle ottenibile rispetto al PWM a 16 bit.
//——————————————————————————————————————- // PWM8_1.c //——————————————————————————————————————- // // AUTH: BW // // Target: C8051F000, F001, F002, F005, F006, F010, F011, or F012 // Tool chain: KEIL C51 // // Description: // Example source code for implementing 8-bit PWM. // The PCA is configured in 8-bit PWM mode using // SYSCLK/4 as its time base. <PWM> holds the number of // PCA cycles for the output waveform to remain low per 256- // count period. The waveform is high for (256 - PWM) cycles. // The duty cycle of the output is equal to (256 - PWM) / 256. // // Because the 8-bit PWM is handled completely in hardware, // no CPU cycles are expended in maintaining a fixed duty // cycle. Altering the duty cycle requires a single 8-bit // write to the high byte of the module’s compare register, // PCA0CP0H, in this example. // // Achievable duty cycle ranges are 0.38% (PCA0CP0H = 0xff) // to 100% (PCA0CP0H = 0x00). // //——————————————————————————————————————- // Includes //——————————————————————————————————————- #include <c8051f000.h> // SFR declarations //——————————————————————————————————————- // Global CONSTANTS //——————————————————————————————————————- #define PWM 0x80 // Number of PCA clocks for // waveform to be low // duty cycle = (256 - PWM) / 256 // Note: this is an 8-bit value //——————————————————————————————————————- // Function PROTOTYPES //——————————————————————————————————————- void main (void); //——————————————————————————————————————- // MAIN Routine //——————————————————————————————————————- void main (void) { WDTCN = 0xde; // Disable watchdog timer WDTCN = 0xad; OSCICN = 0x07; // set SYSCLK to 16MHz, // internal osc. XBR0 = 0x08; // enable CEX0 at P0.0 XBR2 = 0x40; // enable crossbar and weak // pull-ups PRT0CF = 0x01; // set P0.0 output state to // push-pull PRT1CF = 0x20; // set P1.6 output to // push-pull (LED) // configure the PCA PCA0MD = 0x02; // disable CF interrupt // PCA time base = SYSCLK / 4 PCA0CPL0 = PWM; // initialize PCA PWM value PCA0CPH0 = PWM; PCA0CPM0 = 0x42; // CCM0 in 8-bit PWM mode PCA0CN = 0x40; // enable PCA counter while (1) { PCON |= 0x01; // set IDLE mode } }// *** END OF FILE ***
Listato 1: Generazione di un segnale PWM a 8 bit con il modulo PCA |