Muchas veces en nuestro proyectos necesitamos generar señales PWM, ya sea para manejar servos, ESCs, o cualquier dispositivo manejado por estas señales. Para hacerlo con Arduino, es tan fácil como descargar e incluir la librería “Servo” y usarla.

Sin embargo, si somos capaces de generarla nosotros, vamos a tener mucho más control sobre lo que estamos haciendo y lo mejor, vamos a aprender mucho sobre cómo utilizar los temporizadores de los AVR.

Introducción

Si no sabes que es una señal PWM, o quieres ver cómo funciona la librería “Servo”, te aconsejo que eches un vistazo a este proyecto donde explico qué es una señal PWM, y como se maneja con esta un servo.

Para recordar rápidamente, es una señal de periodo 20 milisegundos, donde la posición del servo viene definida por la duración del pulso, entre 1 y 2 milisegundos.

Diagrama de funcionamientos del protocolo
Protocolo de los servos

Temporizadores en AVR

En nuestro caso vamos a ver el ejemplo con el ATmega328p, que es el microcontrolador que llevan algunos Arduinos Pro Mini. Este microcontrolador dispone de 3 contadores/temporizadores, 2 de 8-bits y uno de 16-bits.

El número de bits del contador nos indica el número hasta el que puede contar, y cuánta definición tendrá el contador. Así, que nosotros vamos a usar el “16-bit Timer/Counter1 with PWM” [Atmega_datasheet.Pag89] que, aunque a primera vista pueda asustar, enseguida lo controlaremos.

Diagrama de bloques del Timer/Counter1 del ATmega328p
Diagrama de bloques del Timer/Counter1 del ATmega328p

De manera ultra resumida, el Timer/Counter recibe un reloj desde el microcontrolador. Cada vez que llega un pulso, un contador se va incrementando (TCNTn). Además, estos temporizadores tienen incluidas funciones alrededor de este valor, por ejemplo, podemos saber cuál era el valor de nuestro contador cuando pase cierto evento. O podemos decirle que nos avise cada vez que el valor del contador llegue a un valor que hemos definido nosotros.

Vayamos por partes

Si te quieres ahorrar toda la parte de la teoría, puedes pasar toda esta parte, donde explicaré la funcionalidad que nos interesa del contador, y cómo manejarla. Todo el esquema anterior se puede separar en básicamente tres bloques principales:

  • El Contador
  • El Capturador de Entrada
  • La salida del Comparador

Bloques

Por un lado tenemos la parte de el contador (Counter Unit), que como su nombre indica, se encarga de contar pulsos. Este módulo es muy útil para conectar nuestro ATmega328p a contadores de agua (funcionan por pulsos cada X litros), sensores hall para contar revoluciones de ruedas… Todo lo que sea contar cosas.

El Capturador de Entrada (Input Capture Unit), sirve principalmente para guardar el momento en el sucedió un evento, de manera muy precisa. Esta parte, guarda el valor del contador, en el momento en el que ha sucedido un cambio de valor en su entrada (ICPn), de esta manera podemos saber de manera muy precisa, cuándo ha sucedido algo. Generalmente esto se suele hacer con interrupciones, sin embargo, de esta manera nos evitamos interferir en el hilo de ejecución del código.

Por último, pero para nosotros el más importante, la salida del comparador (Output Compare Unit), que se encarga de generar la salida del contador, en el pin de salida (OCnA). En esta entrada, vamos a ver cómo podemos configurar este módulo para generar la señal que queramos.

Modos de operación

Muy por encima, el Timer/Counter1 del AVR, tiene cuatro modos de funcionamiento: Normal, CTC, Fast PWM, Phase Correct PWM y Phase and Frequency Correct PWM. Estos modos, definen cómo se comporta el contador, y los módulos que hemos visto arriba por su parte dicen que se va a hacer con el valor del contador: poner una salida a alto, o cambiar el valor de un pin.

En el modo normal, el contador simplemente va incrementándose con cada ciclo de reloj. Si nuestro reloj va a 16Mhz, el contador incrementará en uno su valor cada 0.0625 microsegundos . Cómo, nuestro contador es de 16 bits, el valor máximo será de 0xFFFF que es lo mismo que 65535, y por tanto una vez que llegue a 4095.9375 us, se reiniciará.

El modo CTC (Clear Timer on Compare Match), es muy parecido al modo normal, solo que en este caso, podemos decirle al contador que en vez de llegar a contar hasta el valor máximo (0xFFFF), cuando llegue a cierto valor, se reinicie y empiece a contar de nuevo. De esta manera, tenemos un temporizador como el normal, solo que podemos configurar cada cuanto queremos que nos avise con una interrupción.

Los modos de PWM, son todas diferentes formas de generar señales PWM. Cada uno tiene sus peculiaridades, y es mejor para una aplicación u otra. Cómo esto es una introducción a los temporizadores, vamos a ir al más sencillo de entender, el Fast PWM.

Funcionamiento del Fast PWM
Esquema de funcionamiento del Fast PWM.

El modo Fast PWM, consiste en que el contador va incrementando hasta llegar al valor máximo, pero mientras está subiendo, cuando el valor del contador es igual a uno que le hemos definido nosotros, cambia la salida (OCnx) a valor alto. Hasta que al llegar al valor máximo, lo vuelve a poner a nivel bajo. De esta manera, cambiando el valor del Compare Match en el cuál se va cambiar la salida, podemos variar la anchura del pulso.

Los preescalers

A veces puede pasar, y nos va a pasar, que el temporizador llega al máximo demasiado rápido. Recordad, que hemos visto que a 16Mhz, el contador alcanza el máximo en aproximadamente 4 milisegundos. Cómo vamos a tener un periodo de 20ms, si a los 4 ya se nos va a reiniciar el contador?

Para ello existen los preescalers, estos amigos, nos ayudan decrementando la velocidad del reloj que ven nuestros Timers. De esta manera podemos, por ejemplo, hacer que el contador de nuestro temporizador, sólo se incremente una vez cada 8 ciclos del reloj principal. Con esto ya podemos contar hasta 32ms. Sin embargo, los preescalers pueden llegar a valores muy altos, por ejemplo, en el AVR tenemos preescalers de hasta 1024. Es decir, que el contador solo aumentase una vez cada 1024 ciclos del reloj principal, pudiendo contar hasta 4 segundos, antes de reiniciar el contador!

Los registros

Para manejar todo esto, tenemos que modificar una serie de registros. Un registro, no es más que una posición de memoria que el microcontrolador usa, para alterar sus funciones internas. Es como una variable, pero que siempre está en la misma posición de memoria, y que luego internamente la usa el micro para sus cosas.

Para manejar los contadores hay que modificar unos cuantos registros. Estos registros y su descripción se puede ver en su datasheet [Atmega_datasheet.Pag108]. Los que vamos a necesitar nosotros son:

  • TCCR1A/B:
    • COMnAx y COMnBx: Para configurar el bloque de Compare Output y definir cómo va a funcionar el pin de salida.
    • WGM10-WGM13: Para seleccionar el modo de operación.
    • CS10-CS13: Para elegir el preescaler
  • OCR1AH/L: Elegir el valor del Compare Output para decir en que valor del contador, la salida se va a poner en 1.
  • ICR1H/L: Elegir el valor que hará de valor máximo para que se reinicie el contador, y la salida se ponga a 0.

A trabajar: La señal PWM

Ya sabemos que tenemos que generar una señal PWM para nuestro servo/dispositivo. Queremos que la señal tenga una frecuencia de 50Hz, es decir un periodo de 20 milisegundos, y que la parte alta de la señal dure entre 1 y 2 milisegundos. Para ello tenemos que primero, configurar el modo de operación a Fast-PWM.

De todos los modos Fast-PWM que podemos configurar, queremos poder definir cuando el contador se reiniciará, esto nos permitirá hacer que se reinicie cada 20ms. Así que elegimos los que tienen TOP=ICR1, y el único que tiene esa configuración es el Modo 14, Fast PWM.

  • WGM10: 0
  • WGM11: 1
  • WGM12: 1
  • WGM13: 1

Además, queremos que la salida se ponga a alto desde el inicio del pulso, hasta que el contador valga lo que queremos. Sin embargo, para controlar la salida, vemos que hay dos registros, y es que este temporizador puede controlar dos salidas, son los pines OC1A y OC1B.

Esquema de Pines del ATmega328p

Nosotros vamos a elegir el pin PB1 para la salida (en el Arduino Pro Mini es el pin 9, para el resto tendréis que buscar el OC1A). Así, que tenemos que poner el pin PB1 en modo OUTPUT, y luego configurar el COM1A1 y COM1A0, para que cuando se reinicie el contador la salida esté en alto y cuando llegue a la comparación, se ponga a bajo.

  • COM1A0: 0
  • COM1A1: 1

Antes de ponernos ya con los valores de verdad de TOP y OCR1A, tenemos que calcular el preescaler que necesitamos. Tenemos que poder llegar a nuestro periodo, pero lo más apurado que podamos. Con un preescaler de 8 el máximo periodo es de 32ms, pero si ponemos el anterior que es 4, solo llegamos a 18ms. Por lo tanto, está claro que necesitamos el de 8.

Ya solo nos queda decidir, hasta qué valor tiene que subir el contador principal antes de reiniciarse, y en cuál es el valor del comparador. La frecuencia de nuestra señal PWM, se puede calcular, según el datasheet [Atmega_datasheet.Pag102] como:

    \[f_{PWM} = \frac{f_{clk}}{N\cdot (1+TOP)} \Rightarrow TOP = \frac{f_{clk}}{N \cdot f_{PWM}} - 1 =\]

    \[= \frac{16 \cdot 10^6}{8 \cdot 50} - 1 = 39999\]

Donde N es el preescalado, que en nuestro caso es 8, y la frecuencia del reloj, es en nuestro caso 16Mhz, esto dependerá de la placa que estéis usando. Con esto, ya tenemos nuestro valor de TOP para que la señal tenga un periodo de 20ms. Y el valor del comparador, dependerá de la anchura que queramos del pulso.

El código

Bueno, solo hemos necesitado un millón de líneas para llegar a este código, que además, es muy sencillo:

void setup(){  
  DDRB |= ( 1<< PB1 );  // Configuramos el PB1 como salida.
  TCNT1 = 0; // Reiniciamos el contador inicial (por siacaso)
  ICR1 = 39999; // Configuramos el periodo de la señal (el TOP de nuestra PWM)

  TCCR1A =  (1 << COM1A1) | (0 << COM1A0) ; // Ponemos a 'bajo' el OCR1A cuando coincida el Compare Match
  TCCR1A |=  (1 << WGM11) | (0 << WGM10) ; // Fast PWM: TOP: ICR1
  TCCR1B = (1 << WGM13) | (1 << WGM12); // // Fast PWM: TOP: ICR1
  TCCR1B |= (0 << CS12) | (1 << CS11) | ( 0 << CS10 ); // Preesc = 8

  OCR1A = 2000; // pulso de 1 ms
}

void loop(){
}

¿Funciona?

Os quería enseñar el resultado por osciloscopio (review de mi osciloscopio) para que pudierais comprobar cómo se ve la señal para diferentes tamaños de pulsos. Además, abajo podéis ver que el valor de Prd[1] es 20 ms, Prd viene de Period, y el valor de +Width[1] es 1 ms, ‘+Width’ es la Anchura en la parte positiva.

Pulso de 1 ms

Además os traigo unas cuantas más imágenes para que veais más casos.

¿Y ahora qué?

Ya tenéis todas las claves para poder mover los servos a vuestro antojo, pero OS AVISO: el que un servo se ponga en 0º con un pulso de 1500us, es más bien la teoría, en realidad luego cada servo tiene un rango un poco más variable y no funcionan exactamente todos igual. Además, muchos admiten rangos de 400us hasta 2500us… Así que si quereis precisión, tendreis que calibrar vuestros servos.

Además, esto que has aprendido lo puedes usar para fabricar un receptor de Radiocontrol. También puedes utilizar este pequeño truco para generar pulsos de duración super precisa y de anchuras de tan solo 62 nanosegundos.

Y ahora a crear!


Gluón

Teleco con ganas de aprender más y compartirlo. Viajero empedernido y amante de la fotografía y la tecnología. Espero dejar mi granito de arena y que este pueda servir de ayuda.

0 comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *