viernes, 9 de febrero de 2018

Fast PWM en Arduino Leonardo

Este documento fué previamente publicado en inglés en el blog R6500.

Hoy hablaré sobre la generación de señales PWM.
Las placas Arduino proporcionan salidas pseudo-analógicas usando la función analogWrite (). Esta función no está disponible en todos los pines, solo los marcados con el símbolo ~. La función de escritura analógica no proporciona una salida analógica real sino una señal PWM.

Placa Arduino Leonardo

Una señal PWM (Pulse Width Modulation) es una señal binaria pulsada. Como es binaria, solo puede tener dos estados de salida "ALTO" y "BAJO". La información analógica no está en los niveles de señal sino en el ancho de los pulsos generados. Definimos ancho de pulso como el ancho de los pulsos ALTO y ciclo de trabajo, representado con una letra delta en minúscula, como la fracción del ancho de pulso respecto del período total T de la señal. La frecuencia de la señal PWM se define como la inversa del período.

El valor medio de la señal PWM depende del Ciclo de trabajo y los valores de voltaje asociados a los niveles ALTO y BAJO:


Si el nivel BAJO es cero, el valor medio de la señal PWM es:






De esta forma, el valor medio de la señal es proporcional al Ciclo de trabajo.

La función Arduino analogWrite () asigna un valor de entrada de 0 a 255 a un Ciclo de trabajo del 0% al 100%. Como el nivel ALTO es de aproximadamente 5 V y el nivel BAJO está cerca de cero, el valor medio de la señal generada usando analogWrite (pin, x) es:

Si el dispositivo conectado al pin tiene un ancho de banda menor que la frecuencia de la señal PWM, funcionará como paso bajo respecto de la señal y solo se verá el valor medio. Los pines Arduino Leonardo PWM usan frecuencias de 488 Hz o 976 Hz. Cuando utiliza analogWrite en un LED, debido a que los LEDS generalmente tienen un ancho de banda superior a 1kHz, el LED se ENCENDERÁ y APAGARÁ por completo como se indica en la señal PWM. Nuestro ojo, sin embargo, tiene un ancho de banda (frecuencia de fusión) de hasta aproximadamente 100 Hz, dependiendo de los niveles de luz ambiente. Cuando el LED pulsa a una frecuencia más alta que el ancho de banda del ojo, solo vemos el valor medio y parece que el LED se enciende en niveles intermedios entre ON y OFF.
Lo mismo aplica si usamos una señal PWM para atacar un motor. Los motores de contínua normales, debido a su inercia, tienen anchos de banda cercanos o inferiores a la frecuencia Arduino PWM, por lo que el motor funciona como si se aplicara un voltaje variable constante. De hecho, el funcionamiento de PWM a baja frecuencia puede hacer que el motor funcione mejor a bajas velocidades.

En general, la frecuencia PWM de 500Hz a 1kHz que proporciona Arduino es adecuada para atacar motores. Sin embargo, si queremos generar una señal de audio, la función analogWrite no funciona. El ancho de banda de audición en humanos es de alrededor de 20 kHz, mucho más que la frecuencia Arduino PWM. Los altavoces típicos generalmente se diseñan dentro de la banda auditiva humana, por lo que al aplicarles una señal PWM se producirá un tono audible a la frecuencia PWM. Bastante molesto, por cierto.

Una de las utilidades para generar una señal PWM de alta frecuencia está en ir más allá del ancho de banda de los altavoces y la audición humana de manera que podamos usar una señal PWM para generar audio.


PWM en Arduino Leonardo

Empezaremos explicando cómo se implementa PWM en la placa Arduino Leonardo. En la mayoría de las placas Arduino, las señales PWM se generan utilizando temporizadores. Los periféricos temporizadores proporcionan la generación de PWM mediante hardware para que la CPU no necesite utilizar ninguno de sus recursos de ejecución para generar las señales. Cada temporizador tiene un número limitado de señales PWM que puede generar el hardware.

Arduino Leonardo, como Micro y Esplora usan el microcontrolador (MCU) ATmega 42u4. Este incluye cuatro temporizadores: Temporizador 0, Temporizador 1, Temporizador 3 y Temporizador 4. Los temporizadores 1 y 3 son iguales, pero el resto de los temporizadores son bastante diferentes. Otras placas Arduino hacen uso de otros temporizadores proporcionados por las MCU que utilizan.

La siguiente tabla muestra el uso de temporizadores para generar PWM en la placa Arduino Leonardo. Esta tabla, junto con más información, está dispobible en mi hoja de cálculo de Arduino Leonardo.

Temporizadores en Arduino Leonardo

Arduino usa los temporizadores para otras funcionalidades. Por ejemplo, el Temporizador 0 también se utiliza para registrar el paso del tiempo que se necesita para las funciones millis(), micros(), delay() y delayMicroseconds(). Es mejor no perturbar el funcionamiento del temporizador 0 ya que alterará todas las funciones básicas de temporización.
Algunas bibliotecas de Arduino también usan temporizadores. La biblioteca Servo usa el temporizador 1 en Leonardo mientras que la biblioteca MsTimer2 usa el temporizador 4. Has de saber que usar cualquier biblioteca que necesite un temporizador afectará cualquier pin PWM asociado a ese temporizador.

Hay varias maneras de crear una señal PWM usando un temporizador.


Single Slope PWM

 Los temporizadores generalmente se basan en un contador. El contador usa una entrada de reloj y, en cada flanco activo del reloj, el contador incrementa su valor. Un modo de operación típico para un temporizador implica aumentar el contador en cada flanco del reloj hasta que alcanza un valor máximo. Una vez que se alcanza este valor, el contador vuelve a cero y el proceso se repite. Esto da una forma de onda de diente de sierra en los valores del contador como se muestra en la figura siguiente. Un ciclo completo del diente de sierra necesitará contar un número de ciclos de reloj igual al valor máximo del contador mas uno.

Single Slope PWM
Los periféricos del temporizador generalmente incluyen varios registros de captura / comparación. Para crear una señal PWM, el registro de comparación está vinculado a una salida de hardware del MCU de una manera que hace que esta salida sea alta si el contador está por debajo o igual al valor de registro de comparación. Eso dará, para cada registro de comparación, una señal PWM cuyo ciclo de trabajo depende del valor de comparación.
Todas las señales PWM generadas por el mismo temporizador tendrán la misma frecuencia, ya que el inicio de los pulsos generados estará siempre en el borde descendente de la señal de diente de sierra.

Si tenemos varias señales PWM regeneradas con diferentes registros de comparación en el mismo temporizador, todas tendrán el flanco ascendente al mismo tiempo.

Las funciones Arduino analogWrite() hacen operar el temporizador 0 del ATmega 32u4 Timer 0 en pendiente simple. El reloj de 250 kHz utilizado se obtiene dividiendo por 64 el reloj del sistema de 16MHz. La frecuencia final de PWM será de 976Hz.
El temporizador 0 tiene dos canales de comparación A y B asociados a dos registros de comparación OCR0A y OCR0B. Los dos canales están vinculados a dos salidas de hardware OC0A y OC0B que se utilizan como pines Arduino números 11 y 3.


Dual Slope PWM

Otra forma de generar una señal PWM se basa en configurar el contador para que aumente en cada flanco activo del reloj hasta alcanzar el valor máximo. A partir de este momento se decrementa el contador en cada ciclo de reloj hasta llegar a cero. Desde este momento el patrón serepite. Ello dará lugar a una forma de onda triangular en lugar de una de diente de sierra.

Dual Slope PWM

Nuevamente podemos configurar el registro de comparación para que la señal de salida sea alta cuando el contador esté debajo del valor de este registro. Ello generará también una señal PWM. La diferencia es que, si tenemos varios registros de comparación, la señal PWM generada con cada uno se sincronizará en el centro de los pulsos generados.
Esto es útil en algunos tipos de control de motor, pero los detalles van más allá de este artículo.
En cualquier caso, la frecuencia PWM será la misma para todos los canales de comparación y también será la mitad del valor que obtendríamos si usáramos un contador Single Slope.

La función Arduino analogWrite() operan con los temporizadores ATmega 32u4 MCU 1, 3 y 4 en modo Dual Slope. Una posible razón por la cual el Temporizador 0 no se utiliza también en Doble Pendiente puede deberse probablemente al hecho de que el Temporizador 0 se usa para registrar el paso del tiempo y, este uso, es difícil de implementar en un temporizador de doble pendiente. Trabajando en doble pendiente, todos los pines PWM asociados a esos pines utilizan la mitad de la frecuencia del Temporizador 0 por lo que operarán a 488Hz.El temporizador 1 incluye tres canales de comparación A, B y C asociados a los registros de comparación OCR1A, OCR1B, OCR1C vinculados a las salidas de hardware OC1A, OC1B y OC1C. El software Arduino solo utiliza los canales A y B relacionados con los pines Arduino 9 y 10. La salida del canal C OC1C está asociada al mismo pin Arduino 11 accionado por el temporizador 0. Por alguna razón, los diseñadores Arduino prefirieron controlar el pin 11 usando el temporizador 0 en una sola pendiente que utilizar el temporizador 1 en pendiente doble.El temporizador 3 solo incluye una salida de hardware OC3A en el pin 5 de Arduino, asociada al registro de comparación OCR3A.El temporizador 4 es un temporizador especial, pero respecto a PWM, opera como los temporizadores 1 y 3 en modo de doble pendiente. Incluye tres salidas de hardware OC4A, OC4B y OC4D asociadas a los registros OCR4A, OCR4B y OCR4D. Solo los canales A y D se utilizan en stock Arduino para los pines 13 y 6. El canal B está asociado al mismo pin accionado por OC1B, por lo que se emplea el temporizador 1, en lugar del temporizador 4,  para este pin.Toda esta información se halla en la tabla anterior de los 7 pines Arduino habilitados con PWM.

Fast PWM en Timer 1

Si la frecuencia PWM máxima de 976 Hz que usa la función Arduino analogWrite no es suficiente, tendremos que desarrollar nuestra propia funcionalidad PWM. El temporizador 1 es un buen candidato ya que tiene tres canales de comparación disponibles y no se entromete con las funciones de retardo de Arduino. Se debe tener cuidado si usa la biblioteca Servo porque también usa el Temporizador 1.

El temporizador 1 se basa en un contador de 16 bits. Ello significa que puede contar desde 0 hasta 65535 antes de desbordarse. El temporizador tiene varios modos de operaciones que incluyen 12 modos PWM. El modo PWM más rápido disponible es el conteo de 8 bits de una sola pendiente con contador entre 0 y 255. Como la pendiente única es más rápida que la pendiente doble, también se denomina modo PWM rápido (Fast).
También es posible emplear modos PWM de 9 bits y 10 bits con valores 511 y 1023 para la cuenta máxima y que pueden operar en modos de pendiente simple y doble. Los modos de 8, 9 y 10 bits con pendiente simple y doble dan un total de 6 modos PWM. Existen otros 6 modos PWM adicionales que usan valores máximos de conteo programables que pueden tener cualquier valor de 16 bits y no están restringidos a 255, 511 o 1023.

Opciones PWM y de Preescalado

Usando doble pendiente, la frecuencia máxima será la mitad. Vemos que la frecuencia máxima de PWM disponible en el Timer 1 es 62.5kHz. Ello es suficiente para generar aseñales de audio ya que está más allá del rango audible de frecuencias.

Para configurar el temporizador debemos programar los registros del Timer 1 TCCR1A y TCCR1B.


 
Los bits 0, 1 y 2 de TCCR1B (CS10, CS11 y CS12) configuran las opciones de reloj de acuerdo con la siguiente tabla.


El bit 1 de TCCR1A (WGM11) y los bits 3 y 4 de TCCR1B configuran la forma de onda para el temporizador de acuerdo con la tabla:


 Los modos 1, 2, 3, 5, 6 y 7 corresponden a los 6 modos PWM estándar.

Una vez que el temporizador se configura en un modo PWM, cada uno de los tres canales de comparación A, B y C se puede habilitar para generar una señal PWM. Para ello, los bits COM1x0 y COM1x1 en el registro TCCR1A asociados a un canal x en particular (A, B, C) se deben configurar de acuerdo con la tabla:


El valor de PWM para cada canal debe programarse en cada registro de comparación de canal OCR1A, OCR1B y OCR1C.

Todas las tablas anteriores se han tomado de la hoja de datos ATmega32u4.

La configuración de los bits TCCR1A no es suficiente para generar la señal PWM en los pines de salida. También necesitamos configurarlos en modo de salida. En el chip ATmega32u4 podemos ver que las salidas de temporizador 1 OC1A, OC1B y OC1C están asociadas a las líneas de puerto PB5, PB6 y PB7 (Pines Arduino 9, 10 y 11). Necesitamos configurar esos pines en modo de salida usando el Registro de Dirección de Datos del Puerto B DDRB.


Fast PWM en Timer 4

Usando los Temporizadores 0, 1 o 3 podemos tener señales de PWM de hasta 62,5kHz, pero la frecuencia PWM máxima posible para la placa Lenonardo solo está disponible en el Temporizador 4.
El temporizador 4 es un temporizador de 10 bits que puede funcionar a una velocidad muy rápida debido a sus opciones de fuente de reloj. 
El MCU ATmega32u4 incluye un periférico USB. Este periférico necesita una frecuencia de reloj de 48 MHz que va más allá del reloj del sistema máximo de 16MHz. Para generar la frecuencia USB, el MCU incorpora un PLL interno. En Arduino Leonardo, el PLL toma como entrada el reloj del sistema de 16MHz y lo multiplica por 6 para generar una frecuencia de salida de 96MHz.
No es buena idea modificar la configuración de PLL en Arduino Leonarda ya que hará fallar las comunicaciones USB. Lo únicio que hay que saber sobre el PLL es que su salida puede ser fuente del periférico Timer 4.

El registro PLL PLLFRQ incluye, entre otras cosas, dos bits PLLTM0 y PLLTM1 que determinan el reloj de entrada al Temporizador 4.


Hay cuatro opciones, no usar el PLL en el temporizador (lo obtendrá del reloj del sistema) o utilizar la salida PLL dividida entre 1, 1.5 o 2. Ello dará frecuencias de reloj de 96MHz, 64MHz o 48MHz.


En el momento de esta escritura de este documento, la tabla anterior faltaba en la hoja de datos actual de ATmega32u4. Necesité hallar una hoja de datos más antigua para obtener el contenido completo del registro de configuración de PLL.

El temporizador 4 tiene un divisor adicional configurado con los bits CS40 a CS43 del registro de configuración TCCR4B.

Las opciones disponibles son:



De esta forma, utilizando los divisores PLL y Timer 4 es posible tener frecuencias de reloj entre 96MHz y 5859 Hz.

De manera similar que en el Temporizador 1, en el Temporizador 4 se debe seleccionar una forma de onda utilizando los bits WGM40 y WGM41 del registro TCCR4D.


Las opciones disponibles son:

  
El Modo 00 es Single Slope mientras que el Modo 01 es Dual Slope.

Observese que la señal PWM tiene un valor máximo de conteo definido en el registro OCR4C. Como el registro de comparación del canal C se usa para el conteo máximo, no hay salida PWM para el canal C.

PWM6 es un modo de operación PWM especial que utiliza los tres canales disponibles A, B y D para atacar un motor. Los detalles, que están más allá del alcance de este documento, se pueden encontrar en la hoja de datos del MCU.

Después de configurar la entrada del reloj, cada canal x = A, B y D del temporizador 4, se puede configurar con sus propios bits COM4x0, COM4x1 y PWM4x en los registros TCCR4A y TCCR4C.
Como hemos explicado, el canal C, utilizado para el conteo máximo, no tiene unidad de salida por lo que no puede usarse para generar PWM.



Para operar un canal x en el modo PWM, es necesario configurar "1" el correspondiente bit PWM4x. Después de ello, COM4x0 y COM4x1 determinan el modo de funcionamiento PWM. En el caso del canal A en modo PWM rápido, podemos elegir:


El funcionamiento normal de PWM corresponde al modo 10. El modo 11 dará una salida complementaria mientras que el modo 01 proporciona dos salidas PWM complementarias en diferentes pines. Las señales complementarias pueden tener una banda muerta entre ellas, de modo que una señal y su complemento nunca estén activos al mismo tiempo.
Se pueden encontrar tablas similares para los canales B y D.


Código de test para Fast PWM

He reunido todos los métodos anteriores para generar PWM en un sketch Arduino FastPWM.


El sketch incluye el código necesario para operar 5 pines asociados a los Temporizadores 1 y 4 en modo Fast PWM.


Código para Timer 1

El código asociado al temporizador 1 incluye 4 funciones y varios defines. Se pueden utilizar los tres canales de comparación del Timer 1 para generar señales PWM en los pines Arduino 9, 10 y 11.

La función pwm91011configure debe llamarse previamente a la llamada de cualquier otra función asociada a este temporizador. Configura el temporizador para que funcione en modo PWM rápido de pendiente única y establece el preescaler en el modo indicado en el argumento de la función.
Los cinco modos posibles son PWM62k, PWM8k, PWM1k, PWM244 y PWM61 y están asociados a las cuatro frecuencias disponibles en modos PWM de 8 bits de pendiente única.

Las funciones pwmSet9, pwmSet10 y pwmSet11 configuran los canales del temporizador 1 asociados a los pines 9, 10 y 11 para que funcionen en el modo PWM y configuran el valor PWM dado (0 a 255).

Después de llamar a una de las tres funciones anteriores, el valor de PWM se puede cambiar rápidamente usando un acceso directo al registro de comparación correspondiente. Para facilitar el acceso, tres definiciones PWM9, PWM10 y PWM11 están asociadas a los registros de comparación OCR1A, OCR1B y OCR1C.


Código para Timer 4

El código asociado al temporizador 4 incluye también 3 funciones y varios defines. Utiliza dos de los tres canales de comparación disponibles A y D asociados a los pines Arduino 13 y 6. No usa el canal B porque su pin Arduino 10 entra en conflicto con el canal B del temporizador 1 usado previamente.
La función
pwm613configure es similar a la definida para el temporizador 1. Establece los modos PLL y PWM para configurar correctamente el temporizador 4 en modo de alta velocidad desde la salida del PLL de 48 MHz y establece la cuenta máxima en OCR4C a 8 bits (255).
El argumento de entrada establece una de las siete frecuencias de preescalador disponibles para una entrada de reloj con temporizador 48MHz: PWM187k (187500 Hz), PWM94k (93750 Hz), PWM47k (46875 Hz), PWM23k (23437 Hz), PWM12k (11719 Hz), PWM6k ( 5859 Hz) y PWM3k (2930 Hz). Recordemos que el Timer4, tiene 15 opciones de preescalador de reloj. Las de frecuencia más baja no se implementan en las definiciones, ya que no ofrecen ninguna ventaja sobre la funcionalidad de la función analogWrite normal.

Las funciones pwmSet13 y pwmSet6 configuran los canales del temporizador 4 asociados a los pines 13, 6 para que funcionen en el modo PWM y configuran el valor PWM dado (0 a 255).
De forma similar al temporizador 1, después de llamar a una de las dos funciones anteriores, el valor de PWM se puede cambiar rápidamente utilizando un acceso directo al registro de comparación correspondiente. Dos definiciones PWM13 y PWM6 están asociadas a los registros de comparación OCR4A y OCR4D. Se ha agregado una definición PWM6_13_MAX adicional para acceder al registro OCR4C que establece la cuenta máxima PWM que, de forma predeterminada, se establece en 255 (8 bits).


Código para Setup y Loop

Para verificar todas las funciones anteriores, el temporizador 1 se configura para generar señales PWM a 62,5 kHz y el temporizador 4 se configura para 187kHz. 
Se generan 4 señales. En los pines 11 y 13 se establecen valores PWM fijos del  11% y 75%. 
Para los pines 6 y 9, se programa un valor de PWM de 0% a 100% variable dentro de la función loop.


Formas de onda generadas

La siguiente figura muestra las formas de onda generadas capturadas con el Analizador Lógico de Analog Discovery.


 
Las señales del temporizador 1 en los pines 9 y 11 tienen el mismo flanco de subida dado que operan en modo PWM de rampa simple. Lo mismo se aplica a las señales en los pines 6 y 13 que dependen del temporizador 4. Las señales en diferentes temporizadores tienen una frecuencia diferente, por lo que los bordes ascendentes no suelen coincidir.

Eso es todo por ahora. Los temporizadores son periféricos muy útiles. Generar PWM es sólo una de las aplicaciones de los temporizadores. En el futuro, planeo hablar también sobre la generación de eventos periódicos.


Código en Github (11/02/2018)

 El código está ahora disponible en Github:

 https://github.com/R6500/Leonardo/blob/master/FastPWM.ino
 

No hay comentarios:

Publicar un comentario