Mejora tu programación en Arduino: manejo de registros

Para ahondar un poco más en cómo aprovechar el máximo la capacidad de procesamiento de un microcontrolador se requiere conocer su arquitectura interna y la forma en que la información pasa a través del dispositivo. Para ello observemos el siguiente diagrama:

Arquitectura del AVR ATMEGA328P, para el Arduino UNO por ejemplo.

Dentro de nuestro microcontrolador podemos identificar la estructura anterior, que en esencia nos muestra como los datos se mueven a través del sistema. Podemos identificar algunos elementos importantes, como la unidad lógico-aritmética o ALU, que permite realizar operaciones, los registros de propósito general, los módulos de entrada y salida entre otros. Los registros, básicamente, son unidades de memoria a las que podemos accesar y leer o escribir datos.

También existen registros de propósito específico, como los temporizadores y los que controlan la comunicación serial. Los registros de propósito específico, además de manejar información permiten configurar y manejar hardware, como las entradas analógicas, son muy importantes y entre más aprendamos de ellos podremos aprovecharlos mejor.

Consideremos, por ejemplo, como configurar las entradas y salidas digitales, pero controlando los registros. Para ello observemos el manual del AVR, y observemos como se distribuyen las entradas y salidas en el integrado. Como podemos observar tenemos un código de colores para determinar si son pines digitales o analógicos además de un denominador PBx, PCx o PDx, donde la P es de puerto, B, C o D es el conjunto al que pertence y x es el número de pin.

Después, busquemos la sección que describe como manejar las entradas y salidas. En esencia esta sección indica que registros están relacionados con las entradas/salidas y como escribir en ellos para indicarle cómo configurarlos y como obtener un dato de ellos.

Si pasamos a la sección que describe a detalle los registros, podremos ver cómo están compuestos y que efecto tendrá escribir en ellos. Por ejemplo, para el Puerto B tenemos los tres registros DDRB, PORTB, y PINB, que configuran la dirección del pin, las resistencias pull-up y el dato a leer.

Por ejemplo, hagamos un pequeño programa en el que leamos un botón y reflejemos el dato en un LED. Para ello, en un primer nivel de abstracción, el programa se vería así, con las funciones de lectura y escritura convencionales.

Pero ahora queremos hacerlo manejando directamente los recurso, asi que para ello primero debemos saber que puertos están relacionados con los pines que elegimos para crear nuestro programa. Para ello debemos consultar el diagrama esquemático de, en este caso, el Arduino UNO.

Podemos observar que el Pin 2 y 5 estan conectados a PD2 y PD5, respectivamente.

Una vez identificados los pines que debemos modificar, podemos pasar a escribir las líneas que relacionen estos pines con sus registros, es decir, vamos a modificar el registro DDRD, PORTD Y PIND para manejar los pines PD2 y PD5. Primero hagamos los cambios en el setup, donde definimos anteriormente que el pin 2 seria una entrada y el pin 5 una salida. Para ello debemos escribir en DDRD el byte ‘00100000’ y en PORTD, ‘00000100’.

Notese que usamos una B antes de la cifra a escribir para denotar que es un dato binario. Inmediatamente PD2 se configura como entrada y PD5 como salida con DDRD. Cuando modificamos PORTD le decimos que PD2 tendrá una resistencia pull up.

El programa debe funcionar con normalidad con estos cambios. Ahora trataremos de cambiar la sección loop, donde leemos la entrada y escribimos en el pin de salida. Para ello tendremos que leer y guardar los datos que tenga PIND y escribir de nuevo en PORTD. Empecemos por cambiar las partes en que encendemos y apagamos PORTD. Cuando queremos editar un bit unicamente tenemos que mover el bit hasta la posición deseada y hacer una operación OR con el registro que queremos editar, de ese modo no modificamos los demás bits del registro. Veamos el código:

Si decidimos modificar el registro PORTD como hicimos en la configuración vamos a cambiar la resistencia pull up de la entrada también, por lo que el programa no funcionará.

Para mandar un 1 a un bit en particular, lo desplazamos hasta la posición y hacemos una operación OR con el registro; así, no modificaremos los demas bits y sólo cambiamos el bit PD5. Cuando queremos mandar un 0 al bit, usamos la operación not, escrita con una tilde ‘ ~ ‘, en el dato (1 << led_bit) y lo operamos con ‘and’ al registro PORTD. Esto mantendrá los demás bits intactos, mandando un 0 al bit PD5.

Por ultimo leamos el bit PD2, para esto necesitamos el registro PIND y operarlo con (1 << btn_pin), seguido de un corrimiento a la posición 0, esto nos devuelve el valor del bit y lo guarda en btn, de modo que podamos evaluarlo después. EL código se verá así al final:

Probablemente esto sea muy rebuscado para sólo encender y a pagar un LED con un interruptor, pero es útil para explicar como debemos leer y escribir los registros. Además permite exponer un asunto muy importante de manera clara y es el de optmizar el código. Observen el tamaño del programa cuando usamos las funciones normales y cuando escribimos directamente a los registros.

Tamaño del programa con funciones regulares.
Tamaño del programa con operaciones a registros. Notese que el programa practicamente se reduce a la mitad.

En conclusión, cuando uno desee hacer un primer programa y hacer funcionar una idea, es útil utilizar un código con funciones y expresiones que puedan ser leidas fácilmente (lenguaje de alto nivel). Cuando busquemos optimizar el código y hacerlo mas rápido y eficiente escribimos directamente a los registros. Recuerda siempre consultar el manual del microcontrolador que estes utilizando, ya que los nombres pueden varíar de acuerdo al intregrado que se use.

Referencias:

Level up your Arduino Code: Registers

Datasheet ATMEGA328P

Arduino Reference: bit math

Comentarios