9- PWM 9 MODULACIÓN DE ANCHO DE PULSO (PWM) La técnica de PWM consiste en producir un pulso rectangular con un ciclo de trabajo determinado, este ciclo de trabajo puede variar de 0 a 100%. En la Figura 9-1 se muestra un pulso con un ciclo de trabajo del 50%, es decir Ton/T = 0.5. Figura 9-1. Ciclo de trabajo de 50% [16] En la Figura 9-2 se muesta un pulso con un ciclo de trabajo del 10%; Ton/T = 0.1. Figura 9-2. Ciclo de trabajo del 10% [16] Un ciclo de trabajo del 0% significa que la señal siempre está nivel bajo; y un ciclo de trabajo del 100% significa la señal siempre en nivel alto, el número de casos intermedios posibles es un número finito llamado resolución del PWM y se expresa como Log2(número de casos). Por ejemplo si puede haber 256 ciclos de trabajo posibles se dice que el PWM tiene una resolución de 8 bits. Si conectamos una señal rectangular a un analizador de espectros para ver su contenido de frecuencias, veríamos que está formada por tres partes (Figura 9-3): - Una componente de DC, cuya amplitud es proporcional al ciclo de trabajo. - Una sinusoide en la frecuencia fundamental (f=1/T). - Un número infinito de armónicas cuyas frecuencias son múltiplos de la fundamental (2f, 3f, 4f, 5f, 6f, …). Ing. Juan Ramon Terven Salinas 139 9- PWM Figura 9-3. Espectro de la señal PWM De tal forma que si conectamos un filtro “ideal” pasa bajas a la salida del PWM para remover todas las frecuencias de la fundamental para arriba, obtendríamos un señal de CD limpia, cuya amplitud sería directamente proporcional al ciclo de trabajo. Aquí radica la importancia del PWM. Podemos aproximar este filtro pasabajas con un simple filtro pasivo RC (de primer orden) o un filtro activo de un orden superior. En la Figura 9-4 se muestra la componente de CD (línea punteada) para una señal con un ciclo de trabajo de 50% y otra con un ciclo de trabajo de 10%. Figura 9-4. Salida analogica del PWM y el filtro pasa bajas Ing. Juan Ramon Terven Salinas 140 9- PWM Módulo Output Compare (OC) La familia PIC32 posee un módulo llamada Output Compare, el cual es usado para generar un pulso o un tren de pulsos en respuesta a eventos seleccionados [10] . Este módulo tiene varios modos de funcionamiento, entre ellos uno llamado Modo PWM en el cual el PIC32 genera un pulso rectangular con un ciclo de trabajo establecido por nuestro código. 9.1.1 Pasos para configurar el modo PWM 1. Ajuste el periodo del PWM escribiendo en el registro de periodo del timer seleccionado (PRx). (se puede usar el Timer2 o el Timer3) 2. Ajuste el ciclo de trabajo del PWM escribiendo en el registro OCxRS. 3. Escriba el registro OCxR con el ciclo de trabajo inicial. 4. Habilite las interrupciones para el timer usado y el módulo OCx usado (en caso de ser necesarias). La interrupción del módulo OC es necesaria para el modo PWM con protección de falla. 5. Configure el módulo Output Compare para algún modo de operación de PWM escribiendo en los bits OCM (OCxCON<2:0>). 6. Seleccione el Timer a usar en el bit OCTSEL (OCxCON<3>). Si OCTSEL = 0, se usa el Timer2, si OCTSEL = 1 se usa el Timer3. 7. Ajuste el prescaler del TMRx y habilitelo activando el bit TON (TxCON<15>). Tome en cuenta lo siguiente: • El registro OCxR debe ser inicializado antes de que el módulo Output Compare sea habilitado. • El registro OCxR se convierte de solo-lectura cuando el módulo Output Compare opera en modo PWM. • El valor en OCxR se convierte en el ciclo de trabajo del primer periodo del PWM. • El contenido del registro OCxRS se transfiere a OCxR cuando ocurre una coincidencia del Timer. En la Figura 9-5 se muestra el funcionamiento del PWM del módulo Output Compare. Ing. Juan Ramon Terven Salinas 141 9- PWM Figura 9-5. Funcionamiento del PWM [10] 1) El Timerx es puesto en 0 y se carga un nuevo ciclo de trabajo de OCxRS a OCxR. 2) El valor del Timer iguala al valor de OCxR, el pin OCx pasa a nivel bajo. 3) El Timerx termina de contar (iguala a PRx), se carga el valor de OCxRS a OCxR, el pin OCx pasa a nivel alto. Se genera una interrupción del Timerx. Ejemplo 1. Generación de voltaje analógico usando el módulo OC en modo PWM. En este ejemplo se configura el registro de periodo del Timer3 con un valor de 2000, este valor a su vez representa la resolución del PWM, ya que podemos tener 2000 valores de voltajes diferentes. Si nuestro PBCLK es de 80MHz, entonces tendremos un pulso rectangular de 5kHz, podemos conectar un filtro pasabajas a la salida del PWM como se muestra en la Figura 9-6. El filtro pasabajas de la figura tiene una frecuencia de corte de 15.9 Hz atenuando de manera efectiva los componentes armónicos del PWM. Figura 9-6. Filtro Pasa bajas a la salida del PWM De esta manera podemos usar el PWM como un convertidor digital-análogo o DAC. Ing. Juan Ramon Terven Salinas 142 9- PWM Programa 9-1. Salida de voltaje analógico /* * Uso del modulo Output Compare en modo PWM * Utiliza el OC1, sale por el pin RD0 */ #include <p32xxxx.h> #include <plib.h> //Bits de configuracion #pragma config POSCMOD = HS, FNOSC = PRIPLL, FPLLMUL = MUL_20 #pragma config FPLLIDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FPBDIV = DIV_1, FWDTEN = OFF, UPLLEN = ON #pragma config UPLLIDIV = DIV_2, FVBUSONIO = ON, FUSBIDIO = ON #pragma config FSOSCEN = OFF, CP = OFF, FCKSM = CSECMD //PBCLK = 80MHz #define FPB 80000000L int main(void) { //Frecuencia del PWM int freqPWM = 10000; // inicializa el modulo OC1 con Timer2 OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // inicializa Timer2 y PR2 //La siguiente función inicializa el valor de PR2 = FPB/freqPWM //en este ejemplo PR2 = 80,000,000/10,000 = 8000 //Esto quiere decir que tenemos 8000 ciclos de trabajo distintos OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT,FPB/freqPWM); //Ajusta el ciclo de trabajo al 50%, es decir OC1RS = 4000 OC1RS = PR2/2; while(1); return 0; } Ing. Juan Ramon Terven Salinas 143 9- PWM Ejemplo 2. Generación de una señal diente de sierra El ejemplo anterior provoca un voltaje fijo por el pin RD0, sin embargo podemos hacer que este voltaje cambie con el tiempo. Podemos aprovechar la rutina de interrupción del Timer usado para cambiar el ciclo de trabajo del PWM como se muestra en el siguiente ejemplo. Programa 9-2. Generación de señal diente de sierra /* * Uso del modulo Output Compare en modo PWM * para generar una señal diente de sierra * Utiliza el OC1, sale por el pin RD0 */ #include <p32xxxx.h> #include <plib.h> //Bits de configuracion #pragma config POSCMOD = HS, FNOSC = PRIPLL, FPLLMUL = MUL_20 #pragma config FPLLIDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FPBDIV = DIV_1, FWDTEN = OFF, UPLLEN = ON #pragma config UPLLIDIV = DIV_2, FVBUSONIO = ON, FUSBIDIO = ON #pragma config FSOSCEN = OFF, CP = OFF, FCKSM = CSECMD //PBCLK = 80MHz #define FPB 80000000L //Frecuencia del PWM #define freqPWM 10000 // VARIABLES GLOBALES //Periodo del PWM (FPB/freqPWM = 8000) short valorMax = FPB/freqPWM; //Contador usado para generar el diente de sierra short count = 0; int main(void) { // inicializa el modulo OC1 con Timer2 OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // inicializa Timer2 y PR2 //La siguiente función inicializa el valor de PR2 = FPB/freqPWM //en este ejemplo PR2 = 80,000,000/10,000 = 8000 //Esto quiere decir que el diente de sierra subira //desde 0 hasta 8000 (valorMax) OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT,valorMax); //Ajusta la interrupción de Timer2 con prioridad de 2 IPC2bits.T2IP = 2; //prioridad 2 IPC2bits.T2IS = 1; //subprioridad 1 IFS0bits.T2IF = 0; //Limpia la bandera de interrupcion // Activa el modo MultiVectored INTEnableSystemMultiVectoredInt(); Ing. Juan Ramon Terven Salinas 144 9- PWM IEC0bits.T2IE = 1; //Activa la interrupcion del Timer2 //Ajusta el ciclo de trabajo incial a 0%, es decir OC1RS = 0 OC1RS = 0; while(1); return 0; } //RUTINA DE INTERRUPCIÓN DE TIMER2 //Cambia el ciclo de trabajo del PWM para generar un diente de sierra //de 125Hz (1/8ms) //Esta interrupción se ejecuta cada 100us, y el voltaje //sube linealmente desde 0 hasta el máximo en 80 interrupciones //por lo tanto el periodo del diente de sierra es de 8ms (80 x 100us) void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void) { //Ajusta el Nuevo ciclo de trabajo OC1RS = count; count+=100; //Si el contador llega a valorMax, regresa a 0 if (count >= valorMax) count = 0; //Limpia la bandera de interrupcion del Timer2 IFS0bits.T2IF = 0; } Esta rutina producirá una señal diente de sierra de aproximadamente VDD de amplitud con una rampa gradual del ciclo de trabajo de 0 a 100% en 80 pasos, seguida de una caida abrupta a 0. Esta señal tendrá una frecuencia de 125 Hz. Ing. Juan Ramon Terven Salinas 145 9- PWM Ejemplo 3. Generación de un Senoidal El siguiente ejemplo es idéntico al anterior solo cambia la rutina de interrupción en la cual se usa la función sin de la librería math.h para generar cada valor de voltaje de una señal senoidal. Programa 9-3. Generación de una señal senoidal usando PWM /* * Uso del modulo Output Compare en modo PWM * para generar una señal senoidal * Utiliza el OC1, sale por el pin RD0 */ #include <p32xxxx.h> #include <plib.h> #include <math.h> //Bits de configuracion #pragma config POSCMOD = HS, FNOSC = PRIPLL, FPLLMUL = MUL_20 #pragma config FPLLIDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FPBDIV = DIV_1, FWDTEN = OFF, UPLLEN = ON #pragma config UPLLIDIV = DIV_2, FVBUSONIO = ON, FUSBIDIO = ON #pragma config FSOSCEN = OFF, CP = OFF, FCKSM = CSECMD //PBCLK = 80MHz #define FPB 80000000L //Frecuencia del PWM #define freqPWM 10000; //Periodo del PWM (FPB/freqPWM = 8000) short valorMax = FPB/freqPWM; //Contador usado para generar la señal senoidal short count = 0; int main(void) { // inicializa el modulo OC1 con Timer2 OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // inicializa Timer2 y PR2 //La siguiente función inicializa el valor de PR2 = FPB/freqPWM //en este ejemplo PR2 = 80,000,000/10,000 = 8000 OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT,valorMax); //Ajusta la interrupción de Timer2 con prioridad de 2 IPC2bits.T2IP = 2; //prioridad 2 IPC2bits.T2IS = 1; //subprioridad 1 IFS0bits.T2IF = 0; //Limpia la bandera de interrupcion // Activa el modo MultiVectored INTEnableSystemMultiVectoredInt(); IEC0bits.T2IE = 1; //Activa la interrupcion del Timer2 //Ajusta el ciclo de trabajo incial a 0%, es decir OC1RS = 0 OC1RS = 0 ; while(1); Ing. Juan Ramon Terven Salinas 146 9- PWM return 0; } //RUTINA DE INTERRUPCIÓN DE TIMER2 //Cambia el ciclo de trabajo del PWM para generar una senoidal //de 100Hz (1/10ms) //Esta interrupción se ejecuta cada 100us, y el voltaje //cambia senoidalmente generando un periodo completo cada //100 interrupciones (2pi/100 = pi/50) //por lo tanto el periodo de la senoidal 10ms (100 x 100us) void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void) { // calcula el nuevo valor de la senoidal OC1RS = PR2/2 + PR2/2 * sin(M_PI/50*count); count++; //Limpia la bandera de interrupcion del Timer2 IFS0bits.T2IF = 0; } La función seno de la librería math.h tarda bastante tiempo en ser calculada (unos cuantos milisegundos), de tal manera que si se desea generar una senoidal de alta frecuencia no es recomendable usar la función de la librería. En su lugar, podríamos generar un arreglo con los valores del seno y simplemente mandarlos llamar. Ing. Juan Ramon Terven Salinas 147 9- PWM Ejemplo 4. Generación de una señal senoidal usando una tabla de datos Para el siguiente ejemplo se uso una hoja de cálculo para generar una tabla de 100 valores que representan 1 ciclo de una señal senoidal. Estos valores se mapearon a un rango de 0 a 160, en donde 160 será el valor del 100% del ciclo de trabajo (PR1 = 160). Figura 9-7. Generación de datos Una vez teniendo los datos se pasan a una tabla que se almacena en la memoria Flash del PIC32 y dentro de la RSI del TMR2 se usa cada uno de estos valores para generar una senoidal de aproximadamente 1kHz. Observe que podemos usar la memoria de programa (Flash) para almacenar datos constantes y asi no malgastar la preciada memoria RAM. Para hacer esto simplemente declare su variable de tipo constante con el modificador const y el compilador automáticamente la guarda en memoria Flash. Ing. Juan Ramon Terven Salinas 148 9- PWM Programa 9-4. Generación de senoidal a partir de una tabla de datos /* * Uso del modulo Output Compare en modo PWM * para generar una señal senoidal a partir de * una tabla de datos * Utiliza el OC1, sale por el pin RD0 */ #include <p32xxxx.h> #include <plib.h> #include <math.h> //Bits de configuracion #pragma config POSCMOD = HS, FNOSC = PRIPLL, FPLLMUL = MUL_20 #pragma config FPLLIDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FPBDIV = DIV_1, FWDTEN = OFF, UPLLEN = ON #pragma config UPLLIDIV = DIV_2, FVBUSONIO = ON, FUSBIDIO = ON #pragma config FSOSCEN = OFF, CP = OFF, FCKSM = CSECMD //PBCLK = 80MHz #define FPB 80000000L //Contador usado para generar la señal senoidal short count = 0; //Tabla de datos, representa 1 ciclo completo de una señal senoidal const short tabla[100] = { 80,85,90,95,100,105,109,114,119,123,127,131,135,138,142,145,148,150,15 2,154,156,157,159,159,160,160,160,159,159,157,156,154,152,150,148,145, 142,138,135,131,127,123,119,114,109,105,100,95,90,85,80,75,70,65,60,55 ,51,46,41,37,33,29,25,22,18,15,12,10,8,6,4,3,1,1,0,0,0,1,1,3,4,6,8,10, 12,15,18,22,25,29,33,37,41,46,51,55,60,65,70,75}; int main(void) { // inicializa el modulo OC1 con Timer2 OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // inicializa Timer2 y PR2 //La siguiente función inicializa el valor de PR2 = 160 OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT,160); //Ajusta la interrupción de Timer2 con prioridad de 2 IPC2bits.T2IP = 2; //prioridad 2 IPC2bits.T2IS = 1; //subprioridad 1 IFS0bits.T2IF = 0; //Limpia la bandera de interrupcion // Activa el modo MultiVectored INTEnableSystemMultiVectoredInt(); IEC0bits.T2IE = 1; //Activa la interrupcion del Timer2 //Ajusta el ciclo de trabajo incial a 0%, es decir OC1RS = 0 OC1RS = 0; while(1); return 0; } //RUTINA DE INTERRUPCIÓN DE TIMER2 //Cambia el ciclo de trabajo del PWM para generar una senoidal Ing. Juan Ramon Terven Salinas 149 9- PWM //de 5kHz (1/200us) //Esta interrupción se ejecuta cada 2us, y el voltaje //cambia senoidalmente generando un periodo completo cada //100 interrupciones //por lo tanto el periodo de la senoidal es de 200us (2us x 100) void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void) { // Obtiene el nuevo valor de la senoidal // de la tabla de datos OC1RS = tabla[count]; count++; //Si llega al final del arreglo, reinicia if (count > 99) count = 0; //Limpia la bandera de interrupcion del Timer2 IFS0bits.T2IF = 0; } Generación de Audio Podemos aprovechar el ejemplo anterior y conectar una bocina a la salida del PWM para escuchar el tono límpio de una senoidal de 5kHz. Podriamos agregarle amplificador operacional y un potenciómetro que varie la ganancia para que funcione como control de volumen. Sin embargo un amplificador operacional no es capaz por si solo de entregar corrientes muy grandes por la salida, por lo que no podemos conectarles directamente un altavoz y oir el tono; necesitamos entonces añadir algún dispositivo que sea capaz de ampliar esa corriente. Podemos usar el arreglo que se muestra en la Figura 9-8. El amplificador operacional empleado es un LM833, especial para audio. La etapa de transistores formada por Q1 y Q2 tiene como única finalidad suministrar la corriente que el operacional no puede. Los transistores empleados son los BC547 y BC557, Estos no son de gran potencia por lo que se deberá usar una bocina pequeña. Ing. Juan Ramon Terven Salinas 150 9- PWM Figura 9-8. Amplificador sencillo de Audio Ing. Juan Ramon Terven Salinas 151