lunes, 23 de noviembre de 2015

Arduino a bajo nivel (III): "Piruleando" el PWM para aumentar resolución / frecuencia

En mi primera entrada sobre Arduino comentaba que el ATmega328 permite más flexibilidad con el PWM de la que se proporciona desde la plataforma de Arduino, y exploraba el uso de los diferentes registros del microcontrolador para aumentar las prestaciones del mismo.
En la entrega de hoy añado dithering para aumentar la resolución.

¿Por qué 'pirulear' el PWM?

A veces nos interesa tener una frecuencia de conmutación más alta que los 62.5kHz disponibles "de base". Como comentaba en la entrada anterior, esto se puede conseguir utilizando un "fast PWM" utilizando OCRA como límite de conteo. Por ejemplo, con OCRA=160 tendríamos una frecuencia de conmutación de 100kHz. La desventaja de esto es que reducimos la resolución del PWM, ya que irá entre 0 y OCRA (D_max). El paso mínimo en la salida será el de pasar de D a D+1, por lo que la resolución será de 1/D_max. 
¿Como afecta esto a un caso real? Convertidor reductor (buck) con 12V de entrada, alimentando un LED CREE XM-L2
Para una entrada a 12V, a 62.5kHz tendríamos que el paso mínimo que podemos tener en la salida es de 12V/256= 50mV. Si eleváramos la frecuencia de conmutación a 125kHz, D_max sería la mitad, y por tanto el paso sería de 100mV, y si subiéramos a 250kHz ya estaríamos hablando de saltos de 200mV.
Alimentar LED CREE XM-L2 con saltos de 200mV supone la diferencia entre alimentarlo a 1.4A y 2.4A de corriente para el salto de "1 posición" en D(*)

(*) Los leds se han de alimentar con una fuente de corriente, no con una fuente de tensión, pero igualmente, en este caso, la fuente de corriente resultante no tendría la resolución necesaria.


Aumentando la frecuencia y resolución con dithering

Dithering es una forma de aumentar la resolución generando "estados intermedios": si en lugar de estar en D o en D+1 estamos 7 estados en D y 1 en D+1, a efectos prácticos, en promedio estaremos en (7*D+1*(D+1))/8, o lo que es lo mismo, hemos añadido 1/8 a D.
A la hora de añadir los estados intermedios, se pueden añadir de forma determinista, por ejemplo, AAABAAABAAAB... pero como se puede ver, tiene un elemento de "de ruido periódico" muy claro.
Por otro lado, si se añade como ruido, esta variación "se esparcirá" en las frecuencias y no será posible encontrar esta "pulsación" concreta. Se podría ver como hacer PWM dentro de PWM.

En el caso del LED anterior: si quisiéramos mantener la conmutación a 250kHz pero reducir el salto mínimo de tensión de 200mV a 13mV tendríamos que dividir por 16. Dado que la aplicación es iluminación y el ojo humano no aprecia cambios a más de 25Hz(**), se podría realizar de forma determinista, ya que la pulsación resultante se encontraría a 15kHz (250kHz/16), completamente fuera del rango visible. En este caso, con 13mV tendríamos aproximadamente un salto de unos 78mA.

Llevando el caso anterior al extremo: podríamos subir la frecuencia de conmutación a 250kHz utilizando Dmax=63, y podríamos añadir 256 estados intermedios, obteniendo así un salto menor a 1mV, con una pulsación de 1kHz, completamente inapreciable al ojo humano.

(**) Los más puristas dirán que sí, pero lo que está claro es que por encima de unos 900Hz seguro que no.

¿Pero cómo se implementa? (Timer 2, PWM en pin3)

El hecho de seleccionar 256 niveles no ha sido arbitrario: en un "unsigned int" caben 256 "unsigned char", por lo que simplemente tendríamos que implementar una auto-actualización de OCRB tal que la parte alta sea D directamente, y la parte baja diga cuantos ciclos de los 256 se encuentran a D+1 y cuantos a D. El hecho de hacerlo así permite una implementación muy compacta y eficiente, usando un entero.

Para realizar los saltos entre D y D+1 utilizaremos la interrupción de overflow:
ISR(TIMER2_OVF_vect){ //el valor del PWM es global, entra por overflow
    static unsigned char nciclo=0;
    if(nciclo<(valor_PWM&0xFF))
    {
             OCR2B=(valor_PWM>>8)+1;
    } else {
             OCR2B=(valor_PWM>>8);
    }
    nciclo++;
}

Por otro lado, el código general para configurar el Timer2:
void setup() {
  //Primero, inicialización
  valor_PWM=15*256+33;  //15*256=1.098   16*256=1.167   +31=1.175/+32=1.176
  TCCR2A=0b00100011; //PWM fast, pin 3 (0B) non-invert
  OCR2A=63;          //4us: (63+1)x62.5ns--> 250kHz
  OCR2B=16;  
  TCCR2B=0b00001001;  //Tbase 62.5ns, rango de 0 a OCR2A
  TIMSK2=0x01;

  pinMode(3,OUTPUT);
}

Con este código, y alimentando el Arduino a 5V se ha conseguido aumentar la resolución, bajando los saltos de 70mV a 1mV o menos.
Cambiando un poco los parámetros, se puede subir la frecuencia a 500kHz, manteniendo la resolución en aproximadamente 0.6mV (Comprobado con salto de 430.7mV a 431.2mV). El efecto secundario de esto es la aparición de una pulsación... a 2kHz.
Espero que sea útil! Un saludo y gracias por la visita!

Si queréis comprar un Arduino compacto con USB/TTL incorporado, podéis comprar este en banggood por $3. Si lo hacéis desde mi enlace ayudaréis a dar soporte al blog.
También hay disponibles varios en Aliexpress. Este, por algo más de 1.50€ no tiene convertidor USB/TTL. Este otro por algo más de 2€ incorpora el convertidor USB/TTL CH340G con envío gratis.

No hay comentarios:

Publicar un comentario