martes, 13 de octubre de 2015

Arduino a bajo nivel (I): Programando los registros del PWM

En esta entrada voy a comentar algunas cosillas de Arduino, cuatro puntos básicos sobre cómo mejorar código y cómo sacarle jugo al PWM. En la próxima entrada sobre Arduino hablaré del ADC y cómo sincronizar PWM y ADC.

¿Qué es Arduino?

No voy a entrar al detalle, pero principalmente Arduino es una plataforma de hardware/software abierta, orientada al público general. La idea es que permita utilizar diferentes hardwares, sin tener que preocuparse de"esos detallitos" que hacen el hardware... pues hardware.

Se ha extendido muchísimo como plataforma para hobbies, debido a su simplicidad y disponibilidad. Si buscamos en ebay "arduino" nos saldrán desde clones hasta placas de expansión a patadas, con precios entre $1 y $100, dependiendo de lo que busquemos.

Ventajas de Arduino, arma de doble filo.

Arduino tiene como principales ventajas:
  1. Entorno de desarrollo simple
  2. Montones de librerías disponibles desde origen
  3. Montones de librerías descargables de internet
  4. Multitud de información gratuita
  5. Montañas de programas de ejemplo
  6. Montañas de programas hechos por usuarios
  7. Placas de expansión preparadas para ser enchufadas "sin más", sin tener que pelearse con el hardware en sí, por ejemplo, LCDs.
No obstante, estas ventajas son un arma de doble filo:
  • El entorno de desarrollo es relativamente limitado: para alquien que trabaje habitualmente con entornos de desarrollo "profesionales", Arduino se queda corto.
  • Dada la "abstracción del hardware" no explota las características especificas de cada microcontrolador, es necesario instalar librerías concretas.
    • Se desaprovechan características de los timers, ADC, comparador, ...
    • Se usan tamaños y tipos de datos incorrectos
  • Las librerías disponibles en internet no tienen por qué estar optimizadas.
  • Dada la facilidad de desarrollar código, muchos de los códigos disponibles en internet tienen problemas:
    • No están optimizados para tamaño.
    • No están optimizados para velocidad.
    • Algunos ni siquiera están bien planteados.
Se podría resumir en que Arduino permite programar sin saber, pero si realmente queremos aprovechar la potencia del microcontrolador deberemos, al menos, aprender cuatro cosillas básicas.
En el siguiente punto intentaré poner algunos ejemplos.

Antes de nada, comentar que no soy un experto ni en programación ni en microcontroladores AVR, por lo que para mucha gente lo siguiente puede ser trivial, pero para los principiantes puede suponer la diferencia entre un programa lento encallado y un programa funcional.

Aprovechando un poco el hardware

Si bien no necesitamos saber nada acerca del micro, cuanto más sepamos acerca del mismo más y mejor podremos programar. Si nuestra intención es programar una misma gama de micros, por ejemplo la serie AT Mega xx8 (48, 88, 168, 328) deberemos bajarnos el datasheet correspondiente. A grandes rasgos se trata de un micro de 8bits, funciona hasta 20MHz, tiene varios canales de ADC, varios canales de PWM y 3 timers. Típicamente lo encontramos en placas a 5V y 16MHz.

Algunas normas básicas para aprovechar el hardware (HW) y por qué programando directamente los registros se consiguen ventajas.
  1. Es un micro de 8bits, por tanto:
    1. Si no necesitas numeros mayores a 255 usa "unsigned char" en lugar de "int"
    2. ¿Estás seguro que necesitas decimales? Huye de los "doubles"...
    3. Las divisiones entre potencias de 2 (2,4,8,16...) suelen ser más rápidas.
  2. Si has de generar eventos de forma síncrona, utiliza los timers, nada de delays ni contadores.
  3. Programando los registros para hacer PWM puedes:
    1. Hacer PWM a frecuencias determinadas, síncronamente con el micro.
    2. Utilizar el PWM a 62.5kHz, comparado con 1kHz del modo por defecto en Arduino.
    3. Seleccionar pines y modos del PWM (normal, invertido, triangular...)
  4. Programando los registros para el ADC puedes:
    1. Cambiar la frecuencia de muestreo
    2. Controlar cuantos bits quieres utilizar para la conversión
    3. Sincronizar el ADC con otros eventos
    4. Automatizar el proceso de recolección de datos
    5. Implementar reducción de ruido
  5. Programando los registros para pines de entrada/salida puedes:
    1. Controlar exactamente el estado de cada pin
    2. Reducir el retraso en cambiar un pin de ~2us a menos de 250ns.

Trasteando con el PWM, Timer0:

Como comentaba, Arduino por defecto tiene el PWM a 1kHz, no obstante, el HW permite trabajar a 62.35kHz manteniendo los 8bits de resolución y dos canales sincronizados de PWM. Si quisiéramos trabajar a diferente frecuencia tendríamos que sacrificar uno de los canales.
Antes de seguir: el timer0 está reliacionado con los retardos por "delay" y las lecturas de "millis". Si queremos conservar dichas fuciones, sugiero usar Timer2 (y el PWM asociado) en lugar de Timer0. Personalmente, intento no usar millis() ni delay().

Configuración del Timer0, registros TCCR0A y TCCR0B.
Los dos registros permiten configurar el Timer0 y su uso como generador de formas de onda o PWM, disponible en los puertos PWM 5 y 6, correspondientes a los canales A y B del timer respectivamente.

Registros de configuracion del Timer0
TCCR0A nos permite definir qué tipo de generación de forma de onda se llevará a cabo en el canal A y el canal B. Para desactivar la generación, pondremos COMx1 y COMx0 a 00, para activar el PWM "normal" (se pone a '1' cuando el contador se resetea) lo pondremos a 10 y para PWM invertido (se pone a '0' cuando el contador se resetea) lo pondremos a 11. La 'x' será A o B dependiendo del canal elegido.
TCCR0B nos permite definir qué frecuencia se utilizará para el PWM, ya que CS0x selecciona el divisor de los 16MHz que se aplicará al contador del PWM, y WGM02 define qué se utilizará como límite de contador, si el propio overflow del contador o OCR0A.

La tabla de configuración de WGM0x determina cómo funciona el Timer0. Los modos relevantes para PWM son el 3 y el 7, si prescindimos del PWM triangular. La diferencia entre el modo 3 y el 7 es que el segundo nos permite forzar la frecuencia del PWM a un valor exacto (y aumentarla de 62.5kHz) pero a cambio, perderemos el canal A de PWM y resolución (1bit por cada duplicación de frecuencia).

WGM AtMega, table 15-8 datasheet
Tabla de configuración PWM del datahseet
Ejemplo 1: PWM 'no invertido' a 62.5kHz en el canal A (pin 6)
Este sería un caso normal de PWM, pero aumentando la frecuencia de 1kHz a 62.5kHz. Mantenemos los 8bits de resolución:
  1. Activar la salida del canal A poniendo ACOM0A1 y ACOM0A0 como '10'
  2. Desactivaremos la salida del canal B con ACOM0B1 y ACOM0B0 como '00'
  3. Seleccionaremos modo PWM con WGM01 y WGM00 como '11'
  4. Seleccionar 62.5kHz, configurando el divisor de frecuencia como 1 (16MHz/256=62.5kHz). CS2, CS1, CS0 = '001'
Por lo que pondríamos TCCR0A y TCCR0B, según los puntos anteriores:

  TCCR0A=0b10000011;  //PWM fast mode, pin 6 (0A) activo non inverting
  TCCR0B=0b00000001;  //PWM a 62.5kHz, fast de 0 a 0xFF

Para utilizar PWM en el puerto A (pin D6 de Arduino) también deberemos activar el puerto como salida, con la función de arduino pinMode(6,OUTPUT).

Para seleccionar el ciclo de trabajo, deberemos escribir en el registro OCR0A un valor entre 0 y 255. El ciclo de trabajo será d=(OCR0A+1)/256, es decir, para OCR0A=0 tendremos un pico de 5V durante 62.5ns, y para OCR0A=255 tendremos una salida constante a 5V.



Ejemplo 2: PWM 'no invertido' a 100kHz en el canal B (pin 5)
En este caso, se tendría que "ajustar" la frecuencia del PWM. Para ello se cambiará el límite del contador a un valor determinado, por lo que perderemos el canal A del PWM y parte de resolución ya que no llegaremos a 0xFF. Programaremos:
  1. Desactivar la salida del canal A poniendo ACOM0A1 y ACOM0A0 como '00'
  2. Activar la salida del canal B con ACOM0B1 y ACOM0B0 como '10'
  3. Seleccionaremos modo PWM con WGM01 y WGM00 como '11'
  4. Configurar 100kHz:
    1. Divisor de frecuencia=1 dará 62.5kHz. CS2, CS1, CS0 = '001'
    2. Modo PWM con ajuste de frecuencia superior por OCR0A: WGM2=1
    3. Reinicio de contador PWM a 10us: límite en OCR0A=(10us/62.5ns)-1 = 159
Por lo que pondríamos TCCR0A, OCR0A y TCCR0B, según los puntos anteriores:
  TCCR0A=0b00100011;  //PWM fast, pin 5 (0B) non-invert
  OCR0A=159;          //10us: (159+1)x62.5ns--> 100kHz
  TCCR0B=0b00001001;  //Tbase 62.5ns, rango de 0 a OCR0A

Recordar activar el puerto como salida: pinMode(5,OUTPUT).

En este caso, el ciclo de trabajo vendrá definido por OCR0B y estará limitado entre 0 y OCR0A, por lo que variará entre 0 y 159. De modo similar al caso anterior, con OCR0B=0 tendremos un pico de 5V durante 62.5ns, y para OCR0B=159 tendremos una salida constante a 5V.

Es importante ver que el tiempo de cada "bit" en OCR0x se mantiene independientemente de la configuración de frecuencia del PWM.

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.

2 comentarios:

  1. Aunque el tema del arduino me sobrepasa veo por tu entrada que las posibilidades del pwm son mas de las que he visto en algunos bricos.
    Me parece muy internaste tu aportación.
    Saludos

    ResponderEliminar
  2. Gracias!
    La verdad es que el microcontrolador es bastante potente dentro de las limitaciones de un micro de 8bits.
    En una próxima entrada comentaré el ADC y pondré códigos de ejemplo, se pueden hacer bastantes cosas relativamente rápido.

    La ventaja de Arduino es que hay multitud de tutoriales y empezar es relativamente simple: no hacen falta ni instalaciones complejas ni herramientas caras, podemos empezar a trastear con un clon de 6€

    ResponderEliminar