You are on page 1of 127

Current maintainers of this work are Jose Antonio de la Torre las Heras and Julio Daniel Dondo

Gazzano.

Reservados todos los derechos. No se permite la reproduccion total o parcial de esta obra, ni su
incorporacion a un sistema informatico, ni su transmision en cualquier forma o por cualquier medio
(electronico, mecanico, fotocopia, grabacion u otros) sin autorizacion previa y por escrito de los
titulares del copyright. La infraccion de dichos derechos puede constituir un delito contra la propiedad
intelectual.

First edition: Apr 2016

Digital edition
This book includes illustrations and index.
ISBN 978-84-608-7339-6

Jose Antonio de la Torre las Heras, 2016


Julio Daniel Dondo Gazzano, 2016
All rights reserved.
Queremos expresar nuestro mas sincero agradecimiento al grupo de investigacion ARCO, de la Es-
cuela Superior de Informatica de la Universidad de Castilla-La Mancha en Ciudad Real, por su apoyo
en la elaboracion de este libro.

Los autores.
A Juan Santiago, Ambar, Bruno, Gabriel, Laureano y Franca...

Julio.
A mi familia por su apoyo incondicional y por creer en mi desde el primer momento. En especial a
mis padres, cuyos consejos y compresion han sido un apoyo fundamental en el desarrollo tanto de mi
vida profesional como personal. Agradecer tambien a Julio Daniel Dondo y al grupo de investigacion
ARCO la confianza depositada en m.

Jose Antonio de la Torre las Heras.


6
PREFACIO

El motivo de este libro es dotar al lector de los conocimientos necesarios para


poder disenar sus propios circuitos y aportar soluciones para automatizar y controlar
diferentes procesos tanto domesticos, para domotica, hobbies, etc., as como tambien
procesos industriales.
Para ello, adoptaremos una metodologa incremental y lo mas practica posible, em-
pezando desde lo basico como es encender un LED, hasta automatizar una cadena de
montaje y gestionar la misma con diferentes tecnologas apoyandonos en el ecosistema
Arduino.

7
8
INDICE GENERAL

1. Que es Arduino? 13
1.1. Elementos de Arduino . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2. Primeros pasos en Arduino 19


2.1. Instalando el entorno Arduino . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.1. GNU/Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.2. Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.2. Probando nuestra placa Arduino . . . . . . . . . . . . . . . . . . . . . 20
2.2.1. Conexion y configuracion de drivers . . . . . . . . . . . . . . . 20
2.2.2. Primer programa . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3. Manejando entradas/salidas 25
3.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2. Utilizando los pines de salida . . . . . . . . . . . . . . . . . . . . . . . 25
3.2.1. Ejemplo 1: Encendiendo un LED . . . . . . . . . . . . . . . . . 26
3.3. Utilizando los pines de entrada . . . . . . . . . . . . . . . . . . . . . . 30
3.3.1. Ejemplo 2: Utilizacion de un boton o interruptor . . . . . . . . 32
3.3.2. Ejemplo 3: Leyendo temperatura y senales analogicas . . . . . 36

4. Comunicaciones 43
4.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2. Comunicacion serie mediante USART . . . . . . . . . . . . . . . . . . 43
4.2.1. Ejemplo 1: Hola mundo por Serie . . . . . . . . . . . . . . . . . 45
4.2.2. Ejemplo 2: Recibiendo informacion . . . . . . . . . . . . . . . . 49
4.2.3. Ejemplo 3: Comunicacion entre Arduinos . . . . . . . . . . . . 52
4.3. Comunicacion I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.3.1. Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.3.2. Protocolo I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.3.3. Ejemplo 1: Hola mundo mediante I2C . . . . . . . . . . . . . . 60
4.3.4. Ejemplo 2: Obteniendo datos de un IMU . . . . . . . . . . . . 64
4.4. Protocolo SPI (Serial Peripheral Interface . . . . . . . . . . . . . . . . 67

9
INDICE GENERAL 10

5. Interrupciones 73
5.1. Interrupciones en el ATmega328 . . . . . . . . . . . . . . . . . . . . . 74
5.2. Manipulacion software . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.2.1. Librera avr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2.2. Librera Arduino . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.2.3. Consideraciones importantes . . . . . . . . . . . . . . . . . . . 77
5.3. Ejemplo 1: Primera rutina de interrupcion . . . . . . . . . . . . . . . . 78
5.3.1. Tabla de entrada/salida . . . . . . . . . . . . . . . . . . . . . . 78
5.3.2. Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.4. Ejemplo 2: Midiendo distancias . . . . . . . . . . . . . . . . . . . . . . 79
5.4.1. Tabla de entrada/salida . . . . . . . . . . . . . . . . . . . . . . 80
5.4.2. Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

6. Multitasking y Timers 83
6.1. Timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.1.1. Registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6.1.2. Modos de funcionamiento . . . . . . . . . . . . . . . . . . . . . 85
6.1.3. Ejemplos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . 87
6.2. Multitasking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.2.1. Encendiendo y apagando un led de manera profesional . . . . . 98
6.2.2. Encendiendo y apagando un led de manera mas profesional . . 100

A. Construyendo nuestro propio Arduino 103


A.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
A.2. Componentes necesarios . . . . . . . . . . . . . . . . . . . . . . . . . . 103
A.3. Ensamblado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
A.4. Programacion del Bootloader . . . . . . . . . . . . . . . . . . . . . . . 107

B. Manipulacion de registros 111


B.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
B.2. Que es un registro? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
B.3. Operaciones con registros . . . . . . . . . . . . . . . . . . . . . . . . . 113
B.3.1. Activar un bit . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
B.3.2. ORing, activacion de un bit . . . . . . . . . . . . . . . . . . . . 114
B.3.3. Bit Shifting, movimiento de bits . . . . . . . . . . . . . . . . . 114
B.3.4. ANDing, desactivando bits . . . . . . . . . . . . . . . . . . . . 116

C. Entorno Eclipse con Arduino 119


C.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
C.2. Que es Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
C.3. Instalacion del entorno . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
C.4. Configuracion del entorno . . . . . . . . . . . . . . . . . . . . . . . . . 121
C.5. Creando el proyecto: ArduinoCore . . . . . . . . . . . . . . . . . . . . 122
C.6. Creando el proyecto final . . . . . . . . . . . . . . . . . . . . . . . . . 124
C.7. Subiendo el proyecto a nuestro Arduino . . . . . . . . . . . . . . . . . 125
INDICE DE FIGURAS

1.1. Placa Arduino Mega 2560 . . . . . . . . . . . . . . . . . . . . . . . . . 14

2.1. Instalador Arduino para Windows . . . . . . . . . . . . . . . . . . . . 20


2.2. Editor Arduino . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3. Led y puerto 13 en Arduino Mega 2560 . . . . . . . . . . . . . . . . . 23

3.1. Flujo normal de ejecucion para sistemas de control . . . . . . . . . . . 26


3.2. Composicion de un LED . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3. Ejemplo 1: Esquema de montaje . . . . . . . . . . . . . . . . . . . . . 28
3.4. Ejemplo 1: Protoboard, esquema de montaje . . . . . . . . . . . . . . 28
3.5. Ejemplo 1: Diagrama de flujo . . . . . . . . . . . . . . . . . . . . . . . 29
3.6. Osciloscopio digital . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.7. Frecuencmetro digital . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.8. Senal digital vs Senal analogica . . . . . . . . . . . . . . . . . . . . . . 33
3.9. Interruptor SPST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.10. Rebote de un boton y valores obtenidos . . . . . . . . . . . . . . . . . 34
3.11. Modulo boton Grove . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.12. Ejemplo 2: Diagrama de flujo . . . . . . . . . . . . . . . . . . . . . . . 35
3.13. Ejemplo 3: Diagrama de flujo . . . . . . . . . . . . . . . . . . . . . . . 40

4.1. Trama con formato 8N1 . . . . . . . . . . . . . . . . . . . . . . . . . . 44


4.2. Conexion entre dispositivos UART con control de flujo . . . . . . . . . 45
4.3. Representacion de comunicacion serie . . . . . . . . . . . . . . . . . . . 45
4.4. Ejemplo 1 - Comunicaciones: Diagrama de flujo . . . . . . . . . . . . . 47
4.5. Ejemplo 2 - Comunicaciones: Diagrama de flujo . . . . . . . . . . . . . 50
4.6. Ejemplo 3 - Comunicaciones: Diagrama de flujo . . . . . . . . . . . . . 55
4.7. Esquema de comunicacion I2C . . . . . . . . . . . . . . . . . . . . . . 59
4.8. Trama de direccion I2C . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.9. Trama de datos I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.10. Sensor MPU6050 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.11. Conexiones MPU6050 . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

11
INDICE DE FIGURAS 12

4.12. Pines SPI en el proc. ATmega328 . . . . . . . . . . . . . . . . . . . . . 68


4.13. Contenidos del registro de entrada del DAC . . . . . . . . . . . . . . . 69
4.14. Forma de onda obtenida conversion D/A . . . . . . . . . . . . . . . . . 70

5.1. Ejemplo de indireccion . . . . . . . . . . . . . . . . . . . . . . . . . . . 74


5.2. Sensor emisor/receptor ultrasonidos . . . . . . . . . . . . . . . . . . . 80

6.1. Diagrama TIMER 8 bits ATmega328 . . . . . . . . . . . . . . . . . . . 84


6.2. Modo CTC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
6.3. Modo Fast PWM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
6.4. Registro TCCR1A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
6.5. Bits de configuracion del presclarer . . . . . . . . . . . . . . . . . . . . 90
6.6. Registro TCCR1B . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.7. Bits para configuracion del pin de salida . . . . . . . . . . . . . . . . . 91
6.8. Configuracion modo CTC . . . . . . . . . . . . . . . . . . . . . . . . . 92
6.9. Diagrama de bloques del sistema de control . . . . . . . . . . . . . . . 94
6.10. Servo de Grove-Starter Kit for Arduino . . . . . . . . . . . . . . . . . 94
6.11. Diagrama de tiempos de un servo . . . . . . . . . . . . . . . . . . . . . 95
6.12. Ejemplo de programa en ladder . . . . . . . . . . . . . . . . . . . . . . 97

A.1. Protoboard de referencia . . . . . . . . . . . . . . . . . . . . . . . . . . 105


A.2. Esquema de conexiones . . . . . . . . . . . . . . . . . . . . . . . . . . 105
A.3. Patillaje ATmega328 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
A.4. Conectores ICSP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
A.5. Captura de pantalla del Arduino IDE . . . . . . . . . . . . . . . . . . 109

B.1. Jerarqua de memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . 112


B.2. Registro de 8 bits (byte) como una ((caja)) . . . . . . . . . . . . . . . . 113
B.3. Operacion: 1 << 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
B.4. Activacion de pin 3 sobre registro previamente configurado . . . . . . 115
B.5. Configuracion del pin 3 como entrada sobre registro previamente con-
figurado (procedimiento erroneo) . . . . . . . . . . . . . . . . . . . . . 116
B.6. Configuracion del pin 3 como entrada sobre registro previamente con-
figurado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

C.1. Pantalla inicial de Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . 120


C.2. Descarga del plugin AVR . . . . . . . . . . . . . . . . . . . . . . . . . 122
C.3. Configuracion de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . 123
C.4. Configuracion de las libreras . . . . . . . . . . . . . . . . . . . . . . . 124
C.5. Configuracion de AVRdude . . . . . . . . . . . . . . . . . . . . . . . . 126
CAPITULO 1
QUE ES ARDUINO?

En este libro vamos a describir una serie de disenos y desarrollos practicos para
automatizar el control de sistemas utilizando el entorno Arduino. El entorno Arduino
es una plataforma electronica de desarrollo de codigo abierto (open source) que ofre-
ce un conjunto de herramientas tanto software como hardware que nos permitiran
realizar nuestros prototipos de una manera rapida, sencilla y segura.
Esta formado principalmente por una placa de desarrollo, equipada con un mi-
crocontrolador AVR, puertos de conexion de entrada y salida, mas los elementos
necesarios para programar el microcontrolador, y un ambiente de desarrollo software
con una serie de libreras para construir nuestros disenos.

1.1. Elementos de Arduino


Podemos dividir el entorno Arduino en dos partes bien diferenciadas, por un lado
tenemos un conjunto de soluciones hardware y por otro lado una suite de desarrollo
software.

Hardware

Placa de desarrollo Arduino: Desde que se lanzo el primer Arduino


al mercado en 2005 se han disenado numerosas placas de desarrollo por
diferentes empresas de ensamblado, tales como Funduino, FreeDuino. . .
Cada placa esta compuesta normalmente por un microcontrolador de la
marca Atmel. Dependiendo de la gama, la placa dispondra de un modelo de
microcontrolador u otro. En la figura (Figura 1.1) se muestra como ejemplo
el Arduino Mega 2560 que cuenta con un microcontrolador ATmega2560
de 8 bits con una velocidad de 16 MHz ademas de 86 pines de entrada
salida y 5 puertos de comunicacion SPI.
Las placas Arduino son ((libres)) y por lo tanto en Internet se pueden encon-
trar los esquematicos de cada placa e incluso si quieres puedes crearte la

13
1.1. ELEMENTOS DE ARDUINO 14

Figura 1.1: Placa Arduino Mega 2560

tuya propia o anadir funcionalidades a la misma. Diferentes placas de desa-


rrollo pueden verse en la web oficial de Arduino: http://www.arduino.cc/
en/Main/Products .
Esta lista es solo una pequena seleccion de las numerosas placas que han
desarrollado en Arduino, si quieres ver la lista completa accede a la web
oficial de Arduino: http://www.arduino.cc/en/Main/Products .
Tarjetas (Shields) de expansion: Unas de las caractersticas que han
hecho que Arduino gane terreno entre otras placas de desarrollo son el
bajo precio y la capacidad de expansion que estas placas tienen. En el
mercado existen diferentes tipos de tarjetas de expansion para componentes
varios, como controladores de motores, modulos de comunicacion, etc. En la
pagina de Arduino se pueden ver algunos shields, aunque existen numerosas
tiendas como Adafruit 1 que nos ofrecen una variedad mayor.
Modulos conectar y listo: Ademas de las shields de expansion, Arduino
cuenta con un gran numero de modulos del tipo ((conectar y listo)). La dife-
rencia con los shields es que estos ultimos unicamente son interfaces para
diferentes sensores y actuadores de modo que sea mas sencillo utilizarlos
en nuestros proyectos. Por ejemplo existen modulos conectar y listo para
sensores de temperatura, humedad, luz, botones, potenciometros, LEDs,
etc.
Software
Arduino IDE: Ademas de las placas de desarrollo, Arduino proporciona
un IDE (Entorno de Desarrollo Integrado) basado en processing con el que
podremos disenar nuestros proyectos. Este IDE consta de un editor de tex-
to para escribir nuestros programas, un area de mensajes, una consola de
comandos y una barra de herramientas con diferentes menus. Este entorno
1 http://www.adafruit.com/category/17
Tipo Procesador Flash EEPROM Numero de puertos Comunicaciones
Arduino Mega ATmega1280 128KB 4KB 54 digitales, 15 SPI, I2C, Se-
(16MHz) PWM, 16 analogi- rial(x2)
cos
Arduino Mega ATmega2560 256KB 4KB 54 digitales, 15 TWI,SPI, Se-
2560 (16MHz) PWM, 16 analogi- rial(x3)
cos
Arduino Board AT91SAM3X8E 512 KB 51 digitales, 12 TWI(x2), Se-
Due (84MHz) PWM, 12 analogi- rial(x4), SPI,
cos CAN
1.1. ELEMENTOS DE ARDUINO

Arduino Yuna ATmega32u4 32 KB 1KB 20 digitales, 7 TWI, Serial, SPI,


(16MHz) PWM, 12 analogi- Serial, WiFi
cos
Arduino Micro ATmega32u4 32KB 1 KB 20 digitales, 7 SPI, Serial, I2C
(16MHz) PWM, 12 analogi-
cos
Arduino Uno ATmega328 32KB 1KB 14 digitales, 6 I2C, SPI, Serial
(16MHz) PWM, 6 analogi-
cos
Arduino Leonardo ATmega32u4 32KB 1KB 20 digitales, 7 I2C, SPI, TWI, Se-
(16MHz) PWM, 12 analogi- rial
cos
a Arduino Yun es ligeramente diferente a las demas placas, ademas del microcontrolador ATmega32u4 posee un procesador Atheros AR9331 el cual soporta

OpenWrt, una distribucion Linux para lo comunicacion Ethernet y WiFi


15
1.1. ELEMENTOS DE ARDUINO 16

se conecta a la placa Arduino para programarla con el codigo que hemos


desarrollado y para comunicarse con ella. El codigo desarrollado en el Ar-
duino IDE se denomina sketch y es la unidad de codigo que es cargado y
ejecutado en una placa Arduino.
Librera ((Arduino.h)): Arduino ha conseguido minimizar la curva de
aprendizaje del lenguaje de programacion C++ mediante una librera que
nos evita lidiar al principio con problemas como el uso de operadores
de bits, configuracion de registros del microcontrolador, etc. La librera
((Arduino.h))2 nos ofrece una interface de alto nivel que nos provee de
funcionalidad extra con la cual realizar todas las tareas en nuestro mi-
crocontrolador de una manera muy sencilla y descriptiva. Por ejemplo, si
quisieramos configurar un GPIO(Pin de Entrada Salida de uso General)
para que actue como pin de salida, unicamente tendramos que invocar a la
funcion pinMode(PIN,OUTPUT). A lo largo de este libro iremos utilizan-
do esta librera en todos nuestros proyectos y poco a poco, segun vayamos
avanzando, veremos la forma equivalente de hacer las mismas operaciones
en C++ ((puro)).
Atmel Studio: Aunque no es un producto de Arduino conviene nombrarlo
debido a la potencia que tiene esta herramienta. Atmel Studio es un IDE
profesional para los microcontroladores del fabricante Atmel. Al contrario
que el entorno de Arduino, Atmel Studio es mas complejo y unicamente esta
disponible para la plataforma Windows, pero tiene muchas mas utilidades
como debugger, gestor de proyectos, etc.
Arduino Studio: Arduino Studio es el que pretende ser el nuevo entorno
de Arduino. Al contrario del principal IDE de Arduino, este se basa en
una arquitectura modular y no monoltica como as lo haca su antecesor.
Arduino Studio aprovecha las capacidades de ((Adobe Brackets)). Actual-
mente se encuentra en version alpha, pero ya cuenta con caractersticas
como:
Sistema basado en la nube.
Soportado por las principales plataformas.
Escrito de cero en Javascript y Node.js.
Interface de usuario amigable.
Autocompletado de codigo en tiempo real y documentacion en vivo.
Debugger para el M0 Pro.
Toolchain AVR: En los libros de texto normalmente no se habla de este
componente pero es sin duda el corazon de Arduino.
Como hemos dicho anteriormente las placas de desarrollo Arduino estan
formadas por un microcontrolador de la marca Atmel3 , normalmente un
AVR, por lo que podremos utilizar todas las herramientas que nos pro-
porciona Atmel. Por ejemplo, Atmel posee un compilador libre para GNU
llamado avr-g++, el cual es invocado por el IDE Arduino para compilar
el codigo C++ de nuestro Sketch. Ademas, una vez que hemos compilado
2 Aunque ((Arduino.h)) es la librera general, en realidad Arduino se apoya en mas libreras como

las indicadas en https://www.arduino.cc/en/Reference/Libraries


3 Atmel es la empresa desarrolladora de los microcontroladores http://www.Atmel.com
1.1. ELEMENTOS DE ARDUINO 17

el programa, el toolchain nos permite subir el codigo maquina a nuestra


placa por medio de avrdude, tambien libre 4 .
El uso de este toolchain nos permite ((saltarnos)) la capa de abstraccion
que nos proporciona Arduino y programar a mas bajo nivel, lo que a su
vez nos permitira tener una vision mas detallada de la programacion de
microcontroladores.

4 El toolchain de AVR puede ser descargado de forma gratuita desde http://www.nongnu.org/

avr-libc
1.1. ELEMENTOS DE ARDUINO 18
CAPITULO 2
PRIMEROS PASOS EN ARDUINO

2.1. Instalando el entorno Arduino


El entorno de desarrollo Arduino esta programado en Java por lo que es multi-
plataforma. El entorno lo podemos encontrar en la pagina oficial de Arduino (http:
//www.arduino.org/en/Main/Software), dependiendo del sistema operativo se de-
bera descargar una version u otra. El lector puede preguntarse por que existen varias
versiones si el entorno esta basado en Java y en ultima instancia en la maquina
virtual de Java (JVM), esto ocurre porque junto al entorno nos descargamos el tool-
chain AVR mencionado en la Seccion 1.1 junto con los drivers de AVR por lo que
dependiendo del sistema operativo tendremos que instalar unos drivers u otros. En
la pagina de la comunidad de Arduino llamada Arduino Srl (Smart Projects Srl)
(http://www.arduino.org/software)es posible encontrar ademas del Arduino IDE,
el entorno Arduino Studio que es un ambiente de desarrollo open source basado en el
editor multiplataforma Brackets de Adobe.

2.1.1. GNU/Linux
Los usuarios de GNU/Linux pueden encontrar el entorno Arduino en sus gestores
de paquetes, por ejemplo, si tenemos una distribucion ((Debian)), para instalar todo
el entorno unicamente tendremos que poner los siguientes comandos en la terminal
como ((superusuario)):

apt - get install arduino arduino - core

Es importante anadir a nuestro usuario a los grupos: uucp, lock y dialout, para poder
manejar los puertos series y los puertos USB de nuestro sistema GNU/Linux. Para
anadir nuestro usuario a los grupos unicamente ingresaremos el siguiente comando:
gpasswd -a $USER uucp
gpasswd -a $USER lock

Con esto ya tendremos nuestro entorno listo.

19
2.2. PROBANDO NUESTRA PLACA ARDUINO 20

Figura 2.1: Instalador Arduino para Windows

2.1.2. Windows
En Windows una vez que nos hemos descargado el instalador para nuestra arqui-
tectura, tendremos que ejecutar el ((.exe)) y nos aparecera una ventana como la de la
Figura 2.1 en donde se nos ira guiando para completar la instalacion.

2.2. Probando nuestra placa Arduino


2.2.1. Conexion y configuracion de drivers
Una vez que tenemos instalados los drivers de la placa y el entorno de desarro-
llo Arduino, ya podemos probar que nuestro entorno de desarrollo este funcionando
correctamente con un simple ejemplo de prueba. Lo primero que debemos hacer es co-
nectar nuestra placa al ordenador mediante un cable USB A/B. Con este mismo cable
podremos alimentar nuestro Arduino, gracias a los 5V que nos proporciona nuestro
ordenador por el puerto USB.
Una vez conectado el Arduino en entornos Windows nos aparecera un mensaje
mostrando el proceso de configuracion y busqueda de drivers, Windows nos informara
si todo va bien y ya estaremos en disposicion de ejecutar nuestro IDE.
En los entornos GNU/Linux podremos saber si nuestro Arduino ha sido reconocido
correctamente mediante el comando:
dmesg | tail

Este comando en realidad se compone de dos ordenes enlazadas mediante una tubera
o Pipe. La primera orden indica al Kernel que nos informe de todo lo sucedido desde
el arranque, mientras que la orden tail nos permite obtener las ultimas lneas del
registro.
2.2. PROBANDO NUESTRA PLACA ARDUINO 21

Figura 2.2: Editor Arduino

Si el Kernel ha reconocido el Arduino veremos algo como: detected FT232RL o Pro-


duct: Arduino XXX donde XXX puede variar en funcion de la placa que tengamos,
por ejemplo, para una placa UNO aparecera Product: Arduino UNO.

2.2.2. Primer programa


Nuestro primer programa unicamente nos servira para comprobar que durante
todo el Workflow no se genera ningun error, es decir, podemos programar, compilar
y ((subir)) sketchs sin ningun problema.
Lo primero que tenemos que hacer es abrir el IDE, una vez que lo tengamos abierto
iremos a: Archivo, Nuevo con lo que se nos abrira un editor como el de la Figura 2.2
.
A continuacion, copia el codigo Cod. 2.1
Este programa lo unico que hace es encender y apagar repetidamente un LED
conectado al pin de entrada/salida 13.
Los programas desarrollados para Arduino constan en su forma mas basica de dos
funciones: una funcion setup(), donde las variables y los pines a utilizar se declaran e
2.2. PROBANDO NUESTRA PLACA ARDUINO 22

1 int led = 13;


2 void setup () {
3 pinMode ( led , OUTPUT ) ;
4 }
5
6 void loop () {
7 digitalWrite ( led , HIGH ) ;
8 delay (1000) ;
9 digitalWrite ( led , LOW ) ;
10 delay (1000) ;
11 }

Codigo 2.1: Primer ejemplo

inicializan y una funcion loop() donde se define el codigo a implementar. Es importante


recordar que los pines se pueden configurar como Entradas o Salidas dependiendo del
uso que se le den. En este caso es necesario definir al pin 13 como de salida (OUTPUT ),
como esta indicado en la lnea 3 del codigo. Seguidamente en las lneas 7 y 9 alternamos
el estado del pin pasandolo de estado alto (HIGH) a estado bajo (LOW). La funcion
delay() retarda la ejecucion de la instruccion siguiente en la cantidad de milisegundos
indicado por el valor entre parentesis, en nuestro caso 1000 ms.
Para ver el funcionamiento de este diseno el lector debera conectar un LED entre
el pin 13 y tierra. Generalmente es necesario conectar una resistencia de 470 Ohms
para limitar la corriente y evitar que el LED se queme. En este caso no hara falta ya
que este pin ya tiene una resistencia incorporada.
El pin y el led de prueba estan senalados en la Figura 2.3.
Ahora unicamente nos resta seleccionar el modelo de placa que estemos usando,
compilar el codigo y subirlo a nuestra placa de desarrollo. Para ello iremos a la pes-
tana: Herramientas, Tarjeta y seleccionamos nuestra placa, despues tenemos que
habilitar el puerto serie para programar el Arduino, en esta ocasion, navegaremos has-
taHerramientas, Puerto Serial y seleccionaremos el puerto USB de nuestra placa.
Normalmente unicamente tendremos un puerto conectado, pero si dispusieramos de
varios tendramos que ver cual corresponde a la placa segun el sistema operativo.
Si en GNU/Linux no aparece ningun puerto serial es probable que el usuario no
tenga los permisos adecuados. En este caso revise cada uno de los pasos mencionados
en Subseccion 2.1.1.
Una vez hecho esto vamos a compilar y subir el codigo a la placa, para ello vamos a
la pestana: Sketch, Verificar/Compilar o simplemente pulsamos ((Ctrl-R)) que hara
que el IDE primero verifique la sintaxis del codigo y si no hay errores ((suba)) el mismo
a la placa.
Si todo va bien podremos ver como el LED de prueba conmuta cada segundo.
2.2. PROBANDO NUESTRA PLACA ARDUINO 23

Figura 2.3: Led y puerto 13 en Arduino Mega 2560


2.2. PROBANDO NUESTRA PLACA ARDUINO 24
CAPITULO 3
MANEJANDO
ENTRADAS/SALIDAS

3.1. Introduccion
En este captulo vamos a trabajar con la placa de desarrollo Arduino enfocando-
nos principalmente en el manejo de sus entradas y salidas. En la siguiente seccion de
este captulo (Seccion 3.2) abordaremos las diferentes tecnicas para controlar actua-
dores desde Arduino. Mas adelante, en la Seccion 3.3 veremos como obtener datos del
exterior, tanto analogicos como digitales.
Para simplificar en la medida de lo posible el montaje de los circuitos y centrar-
nos la mayor parte del tiempo en la programacion del Arduino, utilizaremos un kit
de iniciacion llamado Grove Starter Kit for Arduino de la marca Seeed el cual se
puede adquirir por Internet a un precio relativamente bajo.1 . Ademas del kit Grove,
utilizaremos el kit de expansion Sidekick Basic Kit for Arduino V2.2

3.2. Utilizando los pines de salida


Normalmente, a la hora de disenar cualquier sistema de control nos encontramos
con una secuencia de ejecucion como la mostrada en Figura 3.1.
Como se observa en la Figura 3.1 una parte esencial en un sistema de control son
los actuadores, pero Que es un actuador?. La definicion formal de actuador es la
siguiente:

1 El kit se puede obtener desde: http://www.seeedstudio.com/depot/


Grove-Starter-Kit-for-Arduino-p-1855.html
2 Este kit puede se puede obtener desde: http://www.seeedstudio.com/depot/
Sidekick-Basic-Kit-for-Arduino-V2-p-1858.html?cPath=84_13

25
3.2. UTILIZANDO LOS PINES DE SALIDA 26

Figura 3.1: Flujo normal de ejecucion para sistemas de control

Actuador
Dispositivo capaz de transformar la energa neumatica, electrica o hidraulica en
la activacion de un proceso con la finalidad de generar un efecto sobre un proceso
automatizado.
Un ejemplo claro es un motor de un robot. Un motor de un robot cumple con los
dos elementos principales de la definicion. Por un lado transforma la energa electrica
en un efecto (movimiento del robot) y por otro lado, este movimiento se realiza sobre
un proceso automatizado (logica del robot). Al igual que el ejemplo del robot, existen
otros muchos actuadores. Se deja al lector la tarea de buscar en la red mas informacion
sobre los diferentes tipos.
El objetivo de esta seccion es controlar estos actuadores, de modo que podamos
actuar sobre el entorno desde Arduino.
La manera mas inmediata de controlar un actuador en Arduino es mediante los
pines de entrada/salida. La cantidad de pines disponibles depende del tipo de placa
que se utilice. Por ejemplo, en la placa ATmega328 que ((montamos)) en la Seccion A.1
tenemos un total de 23 pines que pueden ser configurados como entrada o salida.
Veamos algunos ejemplos:

3.2.1. Ejemplo 1: Encendiendo un LED


En este primer ejemplo, veremos como encender un LED, aunque ya se vio de
manera rapida en Subseccion 2.2.2 en esta seccion lo veremos de manera mas detallada.
El LED (Figura 3.2) es un elemento notificador muy utilizado a da de hoy. Una de las
razones que han llevado a la industria a utilizar estos dispositivos es el bajo consumo
3.2. UTILIZANDO LOS PINES DE SALIDA 27

Figura 3.2: Composicion de un LED

que requieren, aproximadamente entre 10 mA y 20 mA. Aunque aqu utilizaremos el


LED unicamente con el proposito de notificar o avisar sobre un evento. Hoy en da su
uso se ha extendido a otras areas como la de la iluminacion e incluso la comunicacion.
En las placas oficiales de Arduino se incorpora un LED notificador conectado
al pin 13. Este LED ya tiene una resistencia limitadora de corriente, por lo que
no tendremos que preocuparnos por el consumo de corriente. En el caso de que el
lector haya montado el Arduino siguiendo la gua del Seccion A.1, entonces debera
conectar el LED y la resistencia al pin 13. Para identificar la posicion de cada uno
de los pines se puede consultar la hoja de datos del ATmega que se puede descargar
desde el siguiente enlace: 3 . Si esta utilizando una placa con otro microcontrolador,
busque la hoja de datos correspondiente. En el apartado 1 del datasheet veremos los
diferentes encapsulados en los cuales el fabricante nos ofrece el microcontrolador. El
encapsulado utilizado en Seccion A.1 es el 28 PDIP (Plastic Dual In-line Package).
En este encapsulado el pin 13 se encuentra en la esquina superior derecha teniendo
en el norte el semicrculo de referencia. Ademas del datasheet, para saber el mapeado
de pines es necesario ir al esquematico de cada placa y buscar a que pin de la librera
corresponde cada pin fsico.4
En el caso del Arduino realizado en la Seccion A.1, el montaje debe ser parecido
al mostrado en: Figura 3.3, Figura 3.4.

Tabla de entrada/salida
Para obtener unos disenos fiables, seguros y de calidad se deben seguir procedi-
mientos y normas de calidad de diseno. Por ello, aunque este ejemplo sea sencillo,
seguiremos una metodologa a la hora de afrontar el problema.
El primer paso en cualquier diseno es analizar los elementos de control en base a las
especificaciones. En nuestro caso unicamente tenemos un unico elemento a controlar
(el LED) por lo que este paso se podra obviar, sin embargo documentar es una buena
costumbre en cualquier diseno. En la Tabla 3.1 se puede ver un ejemplo de analisis de
elementos de control. En primer lugar, se indica si el elemento es de entrada o salida.
En segundo lugar, se realiza una descripcion sobre el elemento, de modo que siempre
se sepa la funcion del mismo. Por otro lado, se indica el nombre de la variable que se
3 http://goo.gl/S3oBA2
4 Los esquematicos se pueden encontrar en la pagina de Arduino o en el caso de una placa ensam-

blada por otro fabricante en la pagina del mismo.


3.2. UTILIZANDO LOS PINES DE SALIDA 28

Figura 3.3: Ejemplo 1: Esquema de montaje

5v-12v

Azul = Tierra
Rojo = Entrada
Naranja = 5v estables
Cyan = Cristal
-------------------
Marron = Rx(Arduino) - Tx(FTDI)
Morado = Tx(Arduino) - Tx(FTDI)

16MHz Cristal

Figura 3.4: Ejemplo 1: Protoboard, esquema de montaje


3.2. UTILIZANDO LOS PINES DE SALIDA 29

Cuadro 3.1: Ejemplo 1: Tabla de entrada/salida


Entrada/Salida Descripcion Nombre variable Pin
Salida Conmutar cada led 13
1/2 segundo

Figura 3.5: Ejemplo 1: Diagrama de flujo

utilizara en el codigo para hacer referencia a dicho elemento y por ultimo el pin fsico
al cual se conectara.

Diagrama de flujo
En base al diagrama general mostrado en la Figura 3.1 al que normalmente se
adapta cualquier diseno, vamos a realizar el diagrama correspondiente para nuestro
diseno.
La herramienta utilizada para crear dicho diagrama es DIA una herramienta de
codigo abierto, gratuita y que permite realizar diagramas de diversos tipos como por
ejemplo diagramas de clases UML o diagramas de flujo.
En la Figura 3.5 se puede ver un ejemplo de diagrama de flujo para nuestro caso.
Este ejemplo no es la unica manera de representar nuestro diseno. Un mismo problema
puede tener diferente soluciones correctas.

Codigo
Ahora que tenemos toda la documentacion lista y se ha analizado el problema es
el momento de realizar el diseno de la solucion y su implementacion. Para programar
la placa Arduino utilizaremos el entorno de programacion instalado descrito en el
Captulo 1. Como se adelanto en el captulo anterior todo programa que vaya a ser
ejecutado en el entorno Arduino debe tener dos funciones principales y obligatorias:

setup(void): Es la primera funcion que se ejecuta tras cargar el programa. En


3.3. UTILIZANDO LOS PINES DE ENTRADA 30

1 int led = 13; // De acuerdo a la tabla de entrada / salidas


2
3 void setup () {
4
5 // pinMode c o n f i g u r a un pin como OUTPUT ( salida ) o INPUT ( entrada )
6 pinMode ( led , OUTPUT ) ;
7
8 }
9
10 void loop () {
11 // d i g i t a l W r i t e " pone " el pin ( led ) en estado HIGH 5 v o LOW 0 v
12 digitalWrite ( led , HIGH ) ;
13 // delay bloquea el m i c r o c o n t r o l a d o r hasta que pasen x ms
14 // delay se i m p l e m e n t a m e d i a n t e el uso del TIMER0 por lo que
15 // si usamos ese TIMER t e n d r e m o s p r o b l e m a s con esta funcion
16 delay (500) ;
17 digitalWrite ( led , LOW ) ;
18 delay (500) ;
19
20 }

Codigo 3.1: Ejemplo 1: Encendiendo y apagando un led

esta funcion se suelen realizar tareas como la inicializacion de variables, pines,


comunicaciones, informacion de inicio al usuario, tareas de login. etc.

loop(void): Tambien llamado bucle principal del programa, se ejecuta de for-


ma constante. Cada pasada por el bucle se suele llamar un ciclo de scan. En
esta funcion es donde programaremos la logica siguiendo el diagrama general
mostrado en Figura 3.1.

Es importante mantener un orden en la funcion loop. Un mal encapsulamiento


funcional puede hacer que nuestro programa sea poco mantenible y finalmente fracase.
El mismo programa mostrado en Cod. 3.1 puede ser reescrito utilizando funciones
de modo que sea mucho mas sencillo de leer tal y como se muestra en: Cod. 3.2.
En este nuevo codigo se utilizan dos funciones ademas de las funciones setup() y
loop() basicas: la funcion read sensors() y la funcion perform actions() La funcion
read sensors() es una funcion que se encarga de la lectura de los sensores para que,
de acuerdo a sus valores, ejecutar las actuaciones correspondientes. En este caso es
una funcion vaca.
La segunda contiene el codigo de encendido y apagado de los LEDs.
Como se puede observar, el codigo es muy simple y legible. Gracias a la librera
((Arduino.h)), para configurar un pin como entrada o salida unicamente tenemos que
llamar a la funcion pinMode() indicando en sus parametros tanto el pin como el modo.
Por otro lado, para obtener 5V o 0V en el pin, usaremos la funcion digitalWrite().

3.3. Utilizando los pines de entrada


En la Subseccion 3.2.1 vimos como utilizar los pines de entrada/salida del AT-
mega328 para informar al usuario de una accion mediante un LED o de forma mas
3.3. UTILIZANDO LOS PINES DE ENTRADA 31

1 int led = 13; // De acuerdo a la tabla de entrada / salidas


2
3 void setup () {
4
5 // pinMode c o n f i g u r a un pin como OUTPUT ( salida ) o INPUT ( entrada )
6 pinMode ( led , OUTPUT ) ;
7
8 }
9
10 void read_sensors () {
11 // funcion dummy
12 }
13
14 void p er fo rm _ ac ti on s () {
15 // d i g i t a l W r i t e " pone " el pin ( led ) en estado HIGH 5 v o LOW 0 v
16 digitalWrite ( led , HIGH ) ;
17 // delay bloquea el m i c r o c o n t r o l a d o r hasta que pasen x ms
18 // delay se i m p l e m e n t a m e d i a n t e el uso del TIMER0 por lo que
19 // si usamos ese TIMER t e n d r e m o s p r o b l e m a s con esta funcion
20 delay (500) ;
21 digitalWrite ( led , LOW ) ;
22 delay (500) ;
23 }
24
25 void loop () {
26 read_sensors () ;
27 p er fo rm _ ac ti o ns () ;
28 }

Codigo 3.2: Ejemplo 1: Recodificacion utilizando funciones


3.3. UTILIZANDO LOS PINES DE ENTRADA 32

Figura 3.6: Osciloscopio digital

generica como interaccionar con un actuador. En esta seccion, veremos todo lo con-
trario, es decir, aprenderemos como captar una accion del usuario (por ejemplo la
pulsacion de un boton).
Los pines de entrada salida, como su nombre indica, se pueden utilizar como
entrada o como salida. La seleccion del modo de funcionamiento de cada pin se realiza
mediante unos registros hardware. En estos primeros ejemplos no se vera el manejo de
dichos registros. Sin embargo, en la Seccion B.1 el lector puede estudiar como utilizar
dichos registros mediante operaciones de desplazamiento de bits.
A la hora de captar una accion o magnitud en un microcontrolador lo primero
que debemos preguntarnos es que tipo de magnitud necesitamos leer o captar. De
forma generica y simplificando, existen dos grandes grupos en los cuales se clasifican
las senales (Figura 3.8):
1. Senales analogicas: Las senales analogicas son continuas en el tiempo, es
decir, se puede representar mediante una funcion matematica continua. Que
elementos de la senal analogica se pueden medir? principalmente se analizan dos
variables: la amplitud y el periodo o frecuencia. Ejemplos de senales analogicas
son el sonido, senales de radio frecuencia, senales electromagneticas.
En electronica para medir las senales analogicas se utilizan instrumentos como
los osciloscopios (ver Figura 3.6) o frecuencimetro (ver Figura 3.7). Los oscilos-
copios nos permiten desde visualizar en una pantalla la forma de onda hasta
realizar funciones matematicas como la FFT (transformada rapida de fourier).
2. Senales digitales: Las senales digitales son aquellas en las cuales sus valores
estan completamente discretizados. Aunque cualquier fenomeno electromagneti-
co como el producido al realizar contacto en un pulsador es continuo, sus estados
pueden ser discretizados en funcion del numero de bits utilizados en la conver-
sion, en este caso dos estados (pulsado y no pulsado).

3.3.1. Ejemplo 2: Utilizacion de un boton o interruptor


En este ejemplo nos centraremos en las senales digitales. Mas adelante hablaremos
sobre las senales analogicas y veremos diferentes casos de uso.
Un boton o mejor dicho y en sentido mas amplio, un interruptor electrico es un
dispositivo con dos estados: en uno de estos estados (cerrado) se permite la circulacion
3.3. UTILIZANDO LOS PINES DE ENTRADA 33

Figura 3.7: Frecuencmetro digital

Seal
continua

Tiempo

Seal
discreta

1100
1010
1000
0110
0100
0010
0000 Tiempo

Figura 3.8: Senal digital vs Senal analogica


3.3. UTILIZANDO LOS PINES DE ENTRADA 34

Figura 3.9: Interruptor SPST

Rebote
VOH
Valor lgico alto
VIH

Indeterminado

VIL
Valor lgico bajo
VOL
1 1 1 1 X 1 0 1 0 X 0 0 0 0 0

Figura 3.10: Rebote de un boton y valores obtenidos

de corriente y en el otro estado se impide su paso (abierto). En los circuitos electronicos


puedes encontrarlo representado con diferentes smbolos. Sin embargo, el mas comun
es el que se muestra en la Figura 3.9. Existen muchos tipos de interruptores en funcion
de los polos, vas, etc, pero nosotros utilizaremos los simples, es decir, de un polo y
una va.
Los interruptores como cualquier dispositivo mecanico son imperfectos y tienen
un desgaste por el propio uso. Aunque parezca que en un diseno esto no es necesario
tenerlo en cuenta, una mala eleccion puede que haga que nuestro diseno tenga una
duracion menor a la estimada y por lo tanto, se incremente el coste de mantenimiento.
Ademas, hay que tener en cuenta que los interruptores tiene un efecto de rebote,
dicho efecto puede provocar que nuestro programa trabaje con valores falsos. Con
el objetivo de eliminar estos valores intermedios se utilizan circuitos eliminadores de
rebotes (debounce) como el que se muestra en la figura Figura 3.10.
Otro metodo es realizar una maquina de estados que detecte varias lecturas con-
secutivas con el mismo valor de modo que todos los valores intermedios no se tengan
en cuenta. En la Figura 3.10 puedes ver una grafica que ilustra lo comentado.
Para simplificar el diseno y centrarnos en la programacion del microcontrolador,
en este ejemplo utilizaremos uno de los modulos ((conectar y listo)) del kit Groove.
En Figura 3.11 se puede ver el aspecto del modulo. Dicho modulo nos simplifica el
montaje dado que lleva incorporada la resistencia de pull-down anteriormente citada.

Figura 3.11: Modulo boton Grove


3.3. UTILIZANDO LOS PINES DE ENTRADA 35

Cuadro 3.2: Ejemplo 2: Tabla de entrada/salida


Entrada/Salida Descripcion Nombre variable Pin
Salida Led notificador de led 13
pulsacion
Entrada Pulsador button 12

Figura 3.12: Ejemplo 2: Diagrama de flujo

Tabla de entrada/salida
En este ejemplo utilizaremos el codigo de la Subseccion 3.2.1 de modo que cuando
el pulsador se encuentre accionado se encendera el LED y cuando el pulsador no se
encuentre pulsado el LED se apagara. Como en el ejemplo anterior, lo primero que hay
que realizar en el diseno es una tabla con las entradas y salidas que utilizaremos. Como
se puede ver en Tabla 3.2 tenemos dos elementos. Por un lado, el LED notificador
que vimos en el ejemplo anterior y por otro lado, el pulsador como entrada.

Diagrama de flujo
En el diagrama de flujo mostrado en Figura 3.12 se muestra un elemento nuevo, el
condicional. En este caso en funcion del estado de un elemento (condicion) actuaremos
de una manera o de otra.

Codigo
Con el diagrama estudiado, trasladar la solucion a codigo es muy sencillo cuan-
do se tiene un poco de practica. Como dijimos en la Subseccion 3.2.1 tenemos dos
3.3. UTILIZANDO LOS PINES DE ENTRADA 36

partes diferenciadas, el setup() y el loop(). En el setup() se ha anadido la configu-


racion del boton como entrada mediante la funcion pinMode() con el correspondiente
parametro INPUT. Un punto importante a tener en cuenta cuando configuramos un
pin como entrada es la capacidad del microcontrolador para activar resistencias de
pull-down internas. Mediante las resistencias de pull-down internas no es necesario
que agreguemos una resistencia para evitar los rebotes. Para habilitar las resistencias
de pull-down, se debe utilizar la funcion digitalWrite() dentro del setup() y sobre un
pin configurado anteriormente como entrada.
Una vez configurados los pines de entrada/salida, debemos programar la logica
del bucle. Si revisas el Cod. 3.2 creamos dos funciones dummy 5 : read sensors() y
perform actions(). En este caso utilizaremos tambien la funcion read sensors() en
la cual leeremos el valor del boton mediante la funcion digitalRead(). Esta funcion
devuelve HIGH o LOW en funcion del valor del pin 5v o 0v.
Una vez que hemos ledo el valor del pin de entrada mediante la funcion digital-
Read() solo falta implementar la toma de decisiones en funcion de ese valor ledo. Para
ello usamos la funcion perform actions() que consulta sobre el valor de la variable y
si es igual a HIGH (boton pulsado) se enciende el LED, de lo contrario se apaga.
Como puedes observar tratar con senales digitales es muy sencillo. Existen sensores
como el sensor de temperatura DHT11 que aun monitorizando una senal analogica
(temperatura) son capaces de ofrecer dicha informacion como un valor digital. Mas
adelante veremos como se consigue esto y de que manera lo podemos utilizar en los
disenos con Arduino.

3.3.2. Ejemplo 3: Leyendo temperatura y senales analogicas


Hasta ahora hemos trabajado con senales digitales. Normalmente el entorno que
nos rodea es continuo y sus efectos se manifiestan de forma analogica. Un ejemplo
podra ser la energa que desprendemos cuando tenemos calor o incluso el sonido que
producimos al hablar. Como puedes imaginar la necesidad de analizar y controlar
estas magnitudes esta presente en el diseno con microcontroladores. En esta seccion
veremos como leer la temperatura ambiente y un ejemplo de actuacion en funcion de
dichos valores.

Fundamentos
En el mercado existen sensores de muchos tipos, los hay digitales como por ejemplo
los sensores de paso y tambien los hay analogicos como los sensores de temperatura.
Normalmente, estos sensores producen senales de un voltaje bajo. Para poder tratar
con dichas senales se suelen realizar diferentes etapas de amplificacion y procesamien-
to.
Como seguramente ya sepas, los computadores y los microcontroladores unica-
mente trabajan con bits, es decir, dgitos que unicamente pueden tomar el valor 1
o 0. Para convertir una senal analogica a una senal digital se emplean los llamados
((Conversores Analogicos Digitales)) o de forma abreviada ADC. Si lo que queremos
es producir una senal analogica a partir de una digital se utilizan los ((Conversores
Digitales Analogicos)) o de forma abreviada DAC. La teora que hay detras de estos
5 Las funciones dummy son aquellas que no tienen ninguna utilidad pero que se incorporan al

codigo por alguna razon, normalmente por estandarizacion


3.3. UTILIZANDO LOS PINES DE ENTRADA 37

1 int led = 13; // De acuerdo a la tabla de entrada / salidas


2 int button = 12; // De acuerdo a la tabla de entrada / salidas
3
4 int button_state ; // Estado del boton
5
6 void setup () {
7
8 // pinMode c o n f i g u r a un pin como OUTPUT ( salida ) o INPUT ( entrada )
9 pinMode ( led , OUTPUT ) ;
10 pinMode ( button , INPUT ) ;
11
12 }
13
14 void read_sensors () {
15 button_state = digitalRead ( button ) ;
16 }
17
18 void p er fo rm _ ac ti on s () {
19 if ( button_state == HIGH ) {
20 // HIGH es una c o n s t a n t e e q u i v a l e n t e a 5 v
21 digitalWrite ( led , HIGH ) ;
22 } else {
23 digitalWrite ( led , LOW ) ;
24 }
25 }
26
27 void loop () {
28 read_sensors () ;
29 p er fo rm _ ac ti o ns () ;
30 }

Codigo 3.3: Ejemplo 2: Codigo para la utilizacion de un boton


3.3. UTILIZANDO LOS PINES DE ENTRADA 38

dispositivos es muy extensa y no es objeto de este libro el desarrollarla en profundi-


dad. A continuacion se dara una breve explicacion sobre dos de los parametros claves
a la hora de identificar si nuestro microcontrolador es adecuado para una determinada
senal.

Numero de bits: El numero de bits del conversor se puede ver como la resolu-
cion del mismo. Un ejemplo ilustrara de forma clara este concepto. Imaginemos
que tenemos una sensor que produce valores desde 0V hasta 5V y que tenemos
un ADC de 10 bits (como es el caso del ATmega328) cual es la resolucion
maxima con la que se podra trabajar en el diseno?. El calculo es sencillo. Si
el conversor codifica los valores con 10 bits, significa que puede tomar valores
entre 0 y 1023 (210 1) por lo tanto si dividimos los 5v (rango 5-0) entre los
1024 valores (rango 0-1023) tenemos una division de 0.0049 o lo que es lo mismo
4,9 mV por cada valor. De este modo si obtenemos una lectura con el valor 53,
significara que el sensor ha generado 259 mV.

Velocidad: La velocidad de muestreo es la capacidad del conversor para tratar


con senales de una frecuencia determinada. Segun el teorema de muestreo, la
velocidad de muestreo debe ser como mnimo el doble del ancho de banda de la
senal de entrada.

Aunque estos dos parametros son muy importantes a la hora de seleccionar un


conversor, como ya hemos dicho existen muchos parametros estaticos y dinamicos
que influyen en la eleccion de los mismos.
Otro de los aspectos que debemos tener en cuenta a la hora de realizar una con-
version es el valor de referencia. Como ya hemos explicado mediante el numero de
bits, la resolucion de nuestra conversion estara dada por el numero de bits del conver-
sor pero tambien hablamos de un rango fijo de 5 V. Este rango fijo se llama voltaje
de referencia. Para que sirve este voltaje?. Imaginemos que tenemos un sensor que
unicamente aporta valores entre 0 y 1.5 voltios o que en el entorno en el que estamos,
el mnimo valor de un sensor de temperatura es 1.5 y el maximo 3.5. Como puedes
observar los voltajes de referencia varan. La solucion planteada anteriormente de
usar un voltaje de referencia de 5 voltios es valida para ambos casos pero estaremos
perdiendo resolucion dado que el Arduino esta teniendo en cuenta valores que son
imposibles de alcanzar (desde 1.5 V - 5 V). Para solucionar esta situacion en Arduino
poseemos una funcion llamada analogReference() que nos permite variar el voltaje de
referencia pudiendo pasar los siguientes valores como parametro:

DEFAULT: Voltaje de referencia de 5 voltios para placas de 5V y 3.3 para


placas de 3.3 voltios.

INTERNAL: Dependiente del microcontrolador. En los microcontroladores


ATmega168 y 328 este voltaje es de 1.1, sin embargo, en los microcontroladores
ATmega8 es de 2.56 voltios.

INTERNAL1V1: Referencia de 1.1V (solo para los Arduino Mega)

INTERNAL2V56: Referencia de 2.56V (solo para los Arduino Mega)

EXTERNAL: Voltaje entre 0 y 5 voltios aplicados a la entrada AREF del


Arduino
3.3. UTILIZANDO LOS PINES DE ENTRADA 39

Cuadro 3.3: Ejemplo 2: Tabla de entrada/salida

Entrada/Salida Descripcion Nombre variable Pin


Salida Led notificador de led 13
mnima temperatu-
ra
Entrada Sensor de tempera- temperature sensor A1
tura

Si seleccionamos alguno de estos valores es muy importante que se realice la


llamada a la funcion analogReference() antes de cualquier llamada a la funcion analo-
gRead() de lo contrario podremos danar la placa.
Si el parametro pasado es EXTERNAL se debera aplicar un voltaje de entrada al
pin AREF. Existen diferentes maneras de aplicar dicho voltaje. Una manera sencilla
y estable es un diodo zener del valor requerido. Por otro lado, podemos utilizar un
divisor de tension. Hay que tener en cuenta que Arduino posee una resistencia de
32 Kohms interna por lo que la formula para el divisor de tension quedara como se
muestra en la Ecuacion 3.1. Teniendo en cuenta esto con una resistencia de 75 Khoms
y un voltaje de entrada de 5v aproximadamente obtendramos un valor de referencia
de 1.5V lo que nos permitira tener una resolucion de 1,5V /1024 = 1,5mV frente a
los 5V /1024 = 4,88mV anteriores.
32K
V Ref = V in (3.1)
32K + X

Tabla de entrada/salida
Para este ejemplo utilizaremos el modulo conectar y listo de Grove termostato.
Existen numerosos sensores de temperatura. Algunos como por ejemplo el DHT11 o
el DHT22 permiten leer la temperatura unicamente mediante senales digitales. En
este caso hemos decidido utilizar el modulo de Grove porque ya tiene incorporado los
condensadores de filtro necesarios y diversas protecciones. Ademas simplifica mucho el
diseno y como ya dijimos en otros ejemplos, nos permite centrarnos en la programacion
del microcontrolador.
Como siempre lo primero que debemos realizar es la tabla de entradas y salidas
de tal modo que nuestro diseno este siempre documentado. En este ejemplo vamos a
utilizar un termostato y un LED notificador Tabla 3.3.

Diagrama de flujo
El diagrama de flujo es fundamental en cualquier diseno, muchas herramientas
como Scratch for Arduino son capaces de generar todo el codigo a partir de un buen
diagrama de flujo. En este ejemplo unicamente buscamos encender un LED cuando
pase de la temperatura mnima configurada. El diagrama resultante es el mostrado
en la Figura 3.13
Como se puede observar en el diagrama de la Figura 3.13 hay dos procesos a la
hora de captar la temperatura. El primero obtiene la temperatura en formato RAW,
3.3. UTILIZANDO LOS PINES DE ENTRADA 40

Figura 3.13: Ejemplo 3: Diagrama de flujo

es decir, un valor sin tratar. Una vez que tenemos dicho valor tenemos que realizar un
proceso de conversion (normalmente especificado por el fabricante) mediante el cual
el valor RAW se convierte en un valor util para el usuario. En Figura 3.3.2 se vera
como realizar dicha conversion.

Codigo
Esta solucion anade a los demas ejemplos el tratamiento de la senal analogica por
lo que unicamente se hara enfasis en dicha parte. En la seccion setup configuramos
el pin A1 como entrada. Arduino no necesita especificar que dicho pin se comportara
como un pin analogico, por lo que este procedimiento es exactamente igual al realizado
con el boton en Subseccion 3.3.1. En la funcion read sensors() se realiza la lectura del
sensor conectado a la entrada A1 del Arduino. La funcion analogRead() devuelve un
valor entre 0 y 1023 tal y como se explico en los fundamentos de este ejercicio.
Una vez que tenemos el valor RAW hay que realizar una conversion a un valor en
grados celsius. Para ello hay que utilizar una formula especificada por el fabricante en
el datasheet. Cada sensor utiliza una conversion diferente por lo que este paso vara
de un sensor a otro, incluso aunque el sensor capte la misma magnitud.
La funcion perform actions() unicamente se encarga de comprobar si la tempera-
tura es menor al mnimo en cuyo caso activara el led.
3.3. UTILIZANDO LOS PINES DE ENTRADA 41

1 int led = 13; // De acuerdo a la tabla de entrada / salidas


2 int t e m p e r a t u r e _ s e n s o r = A0 ; // De acuerdo a la tabla de
3 // entrada / salidas
4
5 float t e m p e r a t u r e _ c e l s i u s ;
6 float mi n_ t em pe ra t ur e = 25;
7
8 void setup () {
9
10 // pinMode c o n f i g u r a un pin como OUTPUT ( salida ) o INPUT ( entrada )
11 pinMode ( led , OUTPUT ) ;
12 pinMode ( temperature_sensor , INPUT ) ;
13 Serial . begin (9600) ;
14
15 }
16
17 void read_sensors () {
18 // Obtener la t e m p e r a t u r a en formato crudo
19 int t em pe ra t ur e_ ra w = analogRead ( t e m p e r a t u r e _ s e n s o r ) ;
20 // C o n v e r t i r la t e m p e r a t u r a en base a la formula del f a b r i c a n t e
21 t e m p e r a t u r e _ c e l s i u s = c o n v e r t _ t e m p e r a t u r e ( t em p er at ur e _r aw ) ;
22 }
23
24 float c o n v e r t _ t e m p e r a t u r e ( int t em pe r at ur e_ r aw ) {
25 int factor = 3975;
26 float resistance = ( float ) (1023 - te m pe ra tu r e_ ra w )
27 * 10000 / te mp er a tu re _r a w ;
28 float ctemperature = 1 / ( log ( resistance / 10000)
29 / factor + 1 / 298.15) - 273.15;
30 return ctemperature ;
31 }
32
33 void p er fo rm _ ac ti on s () {
34 if ( t e m p e r a t u r e _ c e l s i u s < mi n_ t em pe ra t ur e ) {
35 digitalWrite ( led , HIGH ) ;
36 } else {
37 digitalWrite ( led , LOW ) ;
38 }
39 }
40
41 void loop () {
42 read_sensors () ;
43 p er fo rm _ ac ti o ns () ;
44 Serial . println ( t e m p e r a t u r e _ c e l s i u s ) ;
45 }

Codigo 3.4: Ejemplo 3: Codigo de ejemplo para senales analogicas


3.3. UTILIZANDO LOS PINES DE ENTRADA 42
CAPITULO 4
COMUNICACIONES

4.1. Introduccion
Uno de los aspectos importantes a considerar en el diseno de sistemas con micro-
controladores son las comunicaciones. Si bien hay aspectos teoricos y matematicos en
el estudio de las comunicaciones muy importantes y que es menester conocer, en este
captulo siguiendo el enfoque practico, aprenderemos a utilizar estas comunicaciones
sin necesidad de entrar en profundidad en los aspectos puramente teoricos.
La eleccion del modo de comunicacion a utilizar depende de las necesidades de
nuestro diseno. Aspectos tales como protocolos de comunicacion, medios fsicos, ancho
de banda, atenuacion, distorsion, ente otros, deben ser considerados a la hora de elegir
el modo de comunicacion entre dispositivos.
A continuacion veremos algunos modos de comunicacion que son soportados por
Arduino.

4.2. Comunicacion serie mediante USART


UART significa Universal Asynchronous Receiver/Transmitter, mientras que USART
significa Universal Synchronous/Asynchronous Receiver/Transmitter, y es un dispo-
sitivo que trabaja entre dos elementos que se quieren comunicar, tomando bytes de
datos de uno de ellos (transmisor) y transmitiendolos hacia el otro (receptor) de ma-
nera secuencial bit por bit a una determinada tasa de transmision. La informacion
que transmiten estos bits dependera del protocolo y de la codificacion elegidos. El
protocolo nos indica la manera de comunicarnos, esto es, la cantidad y el tamano de
las tramas de datos que se envan, los bits de paridad, codigo de error, etc., mientras
que la codificacion determina el contenido de la informacion y la manera en que los
bits son ordenados para transmitirla. La tasa de transmision se mide en baudios, bit-
s/seg y deben ser configurados de la misma manera tanto en el transmisor como en
el receptor para que puedan entenderse.
La unidad USART es igual a la unidad UART con la diferencia que la prime-
ra permite la comunicacion sncrona y asncrona y la ultima unicamente asncrona.

43
4.2. COMUNICACION SERIE MEDIANTE USART 44

Start Data 8b Parity Stop


1b 0b 1b
Figura 4.1: Trama con formato 8N1

Las diferencias entre la comunicacion asncrona serie y sncrona serie se explicaran a


continuacion.

Comunicacion asncrona serie: Si configuramos la unidad USART en modo


asncrono tendremos que configurar diferentes parametros para que tanto emisor
como receptor sepan cuando tienen que leer o escribir los datos. Estos parame-
tros son el numero de bits de datos, de paridad y de parada, as como tambien
la velocidad en baudios. Existen numerosas configuraciones pero la mas tpica
es la siguiente: 8N1 que significa 8 bits de datos, ninguno de paridad y uno
de parada. (ver Figura 4.1) En el entorno Arduino la velocidad de transmision
puede configurarse entre diferente valores desde 300 hasta 115200 baudios.

Comunicacion sncrona serie: La unidad USART del ATmega328 se puede


configurar para funcionar en modo sncrono, sin embargo no esta soportado por
la librera oficial de Arduino. Esto no significa que no pueda ser usado, pero si
queremos usar este modo tendremos que desarrollar nuestra propia librera o
programar la configuracion de la unidad en cada uno de nuestros programas. La
comunicacion sncrona se caracteriza por tener una lnea de reloj1 que permite
que ambos dispositivos lean y escriban en el evento de reloj. Esto encarece la
solucion dado que necesitamos un cable mas, sin embargo, permite eliminar
todos los bits de control de la comunicacion de tal modo que unicamente se
trasmitiran bits con informacion util.

Se ha hablado del modulo UART, sin embargo, hay que tener en cuenta que la
unidad UART es un modulo hardware y no un protocolo de comunicacion. La unidad
UART se encarga de obtener en paralelo y en un unico ciclo de reloj los datos del
registro de datos y los multiplexa en el tiempo segun la configuracion establecida.
Existen diversas configuraciones, simplex, duplex, full-duplex, de dos dispositivos
o en bus. Sin embargo, la comunicacion serie mediante UART fue desarrollada con el
objetivo de comunicar unicamente dos dispositivos.
A continuacion veremos diferentes ejemplos de como Arduino implementa la co-
municacion serie en sus dispositivos.
Una unidad UART es un modulo hardware y no un protocolo de comunicacion. La
unidad UART se encarga de obtener en paralelo y en un unico ciclo de reloj los datos
del registro de datos, y los serializa en el tiempo segun la configuracion establecida.
Posee ademas soporte para el control de flujo por hardware. Este control de flujo es
realizado a traves de dos conexiones RTS (Request to Send) y CTS (Clear to Send)
que permite a cada lado de la comunicacion indicar al otro que esta listo para manejar
datos. El cableado para la comunicacion serie asncrona es muy sencillo y se puede
ver en la Figura 4.2. Existen diversas configuraciones, simplex (en un solo sentido),
1 En el ATmega328 la lnea de control se llama XCK0 y se encuentra en el pin 6 del encapsulado

DIP28
4.2. COMUNICACION SERIE MEDIANTE USART 45

RTS RTS

CTS CTS

Dispositivo A Dispositivo B

Tx Tx

Rx Rx

Gnd Gnd

Figura 4.2: Conexion entre dispositivos UART con control de flujo

duplex (en ambos sentidos pero no simultaneamente), y full-duplex (en ambos sentidos
al mismo tiempo), con o sin control de flujo.
Una UART se usa comunmente con los estandares de comunicacion RS-232, RS-
422 o RS-485 y tiene soporte para el control de flujo mediante hardware.
Dependiendo de la implementacion fsica se utilizara un determinado nivel de
voltaje u otro. El protocolo RS-232 utiliza de -3V a -15V para el nivel logico ((1)) y
de +3V a +15V para el nivel logico ((0)) Sin embargo, los procolos con logica TTL o
CMOS trabajan con otros niveles. Existen circuitos integrados como el MAX232 que
permiten realizar la conversion de niveles.

Figura 4.3: Representacion de comunicacion serie

En la Figura 4.3 se puede ver una representacion de lo explicado anteriormente.

4.2.1. Ejemplo 1: Hola mundo por Serie


Como ya hemos dicho, Arduino tiene soporte para la comunicacion serie, tanto
hardware como software. A nivel hardware dependiendo del microcontrolador tendre-
mos mas o menos unidades UART. La API que proporciona Arduino para el manejo de
4.2. COMUNICACION SERIE MEDIANTE USART 46

Cuadro 4.1: Ejemplo 1: Tabla de entradas/salidas


Entrada/Salida Descripcion Nombre variable Pin
Entrada Boton button 8
Entrada Rx0 RX 0
Salida Tx0 Tx 1

Cuadro 4.2: Ejemplo 1: Comunicaciones

Interfaz serie
Entrada/Salida Comando Evento Descripcion
Salida Pin 8 == Enva hola mundo
HIGH

las comunicaciones es muy sencilla de utilizar y aprenderemos a manejarla mediante


los siguientes ejemplos.
En este primer ejemplo realizaremos un ((hola mundo)) mediante el puerto serie.
Cuando se pulse un boton el microcontrolador mandara por el puerto serie la cadena
((hola mundo))

Tabla de entrada/salida
Como en los anteriores ejemplos, lo primero que vamos a hacer es la tabla de
entrada/salida. En el enunciado se han senalado dos de los elementos principales. Por
un lado la comunicacion serie y por otro lado el boton. Es buena costumbre reservar
un apartado ntegro para las comunicaciones. En un diseno mas complejo, lo mas
probable es que tengamos diferentes tipos de comunicaciones con diversos protocolos.
En ese apartado, podremos definir todos los protocolos y las interfaces que proporciona
nuestro diseno al ((exterior)). Volviendo a las entradas y salidas, si bien en este primer
ejemplo lo unico que haremos es enviar un string por el puerto Tx (pin 1) dejamos ya
indicada la entrada Rx que sera utilizada en los ejemplos que siguen.

Comunicaciones
Este ejemplo es muy sencillo, por lo que no definiremos un protocolo completo, es
decir, sintaxis, semantica y temporizacion. Unicamente describiremos la comunicacion
en terminos de los mensajes de salida. En la Tabla 4.2 se puede ver un ejemplo de
dicha tabla.

Diagrama de flujo
Las comunicaciones se pueden disenar como eventos asncronos y sncronos, todo
dependera de como implementemos la solucion. En este caso y para no anadir com-
plejidad innecesaria en este punto, modelaremos la solucion en base a los ejemplos
anteriores, es decir, de forma sncrona y secuencial.
En la Figura 4.4 puedes ver el diagrama propuesto.
4.2. COMUNICACION SERIE MEDIANTE USART 47

Figura 4.4: Ejemplo 1 - Comunicaciones: Diagrama de flujo

Codigo

En Cod. 4.1 se puede ver una posible solucion a este ejemplo. Lo primero que
debemos hacer es configurar los elementos de entrada salida y las comunicaciones.
Cuando el diseno sea mas complejo, puede que sea necesario encapsular cada una de
estas partes en otras subfunciones como por ejemplo init communications().

Una de las partes mas importantes de este tutorial se encuentra en la funcion


Serial.begin(9600). Esta funcion forma parte de la API de Arduino para la gestion y
uso del puerto serie. Como habras podido imaginar, el primer parametro se refiere a la
velocidad en baudios, recuerda que tanto receptor como emisor deben tener la misma
velocidad. Por otro lado, en la Seccion 4.2 se hablo de la configuracion de los bits de
parada, datos y paridad. Estos parametros se pueden pasar como segundo argumento
a la funcion Serial.begin(). En la siguiente web se pueden ver los valores que puede
tomar este parametro: http://www.arduino.cc/en/pmwiki.php?n=Serial/Begin

En la funcion perform actions() es donde se realiza la actuacion en funcion del


estado del boton. Para este primer ejemplo hemos utilizado la funcion Serial.println().
Esta funcion enva al registro de datos de la unidad UART la cadena indicada como
parametro, ademas termina la cadena con los caracteres ((retorno de carro)) y ((salto
de lnea)). La funcion devuelve un ((long)) que ndica el numero de bytes escritos en el
puerto serie. Es muy importante que tengas en cuenta que esta funcion no enva
los bytes de ((golpe)) por el ((cable)). Esta funcion unicamente escribe los bytes en el
registro de datos de la UART llamado, en el caso del Atmega328, UDRn.

Si no quieres que finalice la cadena con los caracteres de fin de lnea, puedes utilizar
la funcion Serial.print()
4.2. COMUNICACION SERIE MEDIANTE USART 48

1 i n t b ut to n = 8 ; //De a c u e r d o a l a t a b l a de
2 // e n t r a d a / s a l i d a s
3
4 i n t b u t t o n s t a t e = LOW;
5
6 void setup ( ) {
7
8 // pinMode c o n f i g u r a un p i n como OUTPUT ( s a l i d a ) o INPUT ( e n t r a d a )
9 pinMode ( button , INPUT) ;
10 S e r i a l . begin (9600) ;
11
12 }
13
14 void r e a d s e n s o r s ( ) {
15 b u t t o n s t a t e = d i g i t a l R e a d ( bu tt on ) ;
16 }
17
18 void perform actions ( ) {
19 i f ( b u t t o n s t a t e == HIGH) {
20 S e r i a l . p r i n t l n ( " Hola mundo " ) ;
21 }
22 }
23
24 void loop ( ) {
25 read sensors () ;
26 perform actions () ;
27 }

Codigo 4.1: Ejemplo 1 - Comunicaciones: Codigo


4.2. COMUNICACION SERIE MEDIANTE USART 49

Cuadro 4.3: Ejemplo 2: Tabla de entrada/salida

Entrada/Salida Descripcion Nombre variable Pin


Entrada Rx0 RX 0
Salida Led led red 8
Salida Led led yellow 9
Salida Tx0 Tx 1

4.2.2. Ejemplo 2: Recibiendo informacion


Hasta ahora unicamente hemos enviado informacion al exterior mediante la comu-
nicacion serie. En este segundo ejemplo veremos otra de las partes claves en cualquier
diseno, la captacion de informacion mediante el puerto serie. La recepcion de infor-
macion al igual que el envo de la misma normalmente esta ligado a un protocolo de
aplicacion y a una serie de parsers que se ocupan de decodificar dicho protocolo.
Protocolos de comunicacion de bajo nivel dependen del medio fsico de comuni-
cacion, por ejemplo: punto a punto, por buses, usando Ethernet, etc., y establecen el
modo en que la comunicacion se lleva a cabo. Por ejemplo establece quien inicia la
comunicacion, como se direcciona el elemento receptor, como se envan los datos, si
uno a uno o a rafagas, etc. Protocolos de comunicacion de alto nivel o de aplicacion se
refiere al conjunto de comandos de una aplicacion que se utilizan en la comunicacion
para gestionar el funcionamiento de un sistema. El diseno de protocolos de aplicacion
es una tarea que requiere de experiencia y no es el objetivo de este libro el entrar
en detalle, sin embargo, se anima al lector a definir sus propias codificaciones y a
practicar con la decodificacion de la informacion, dado que es un problema recurrente
en la soluciones basadas en microcontroladores.
El ejemplo es muy sencillo, en funcion de la cadena recibida se encendera un LED
u otro (se utilizan LEDs para no complicar el diseno de forma innecesaria, el uso de
uno u otro actuador normalmente radica en diferencias meramente electricas).

Tabla de entrada/salida
Para este ejemplo utilizaremos dos LEDs, uno de color amarillo y otro de color rojo.
En funcion de la cadena que es recibida se encendera un LED u otro. El ((protocolo))
es el mostrado en la Tabla 4.4. En la Tabla 4.3 se puede ver la tabla de entrada/salida
con los nombres de las variables y los pines donde se conectaran los actuadores.

Comunicaciones y protocolos de aplicacion


Cuando se disena un protocolo de comunicacion de alto nivel lo primero que nos
deberamos preguntar es que objetivo tiene. Si no es un elemento crtico del diseno
o unicamente se utiliza como protocolo de gestion, seguramente prefiramos definir el
protocolo mediante caracteres ascii. Sin embargo, si lo que buscamos es una comuni-
cacion rapida, de poco tamano, lo mas recomendable es una codificacion binaria.
Un caracter ascii se codifica mediante 8 bits (ascii extendido) En que nos afec-
ta este hecho?. Imaginemos que queremos mandar un comando que tiene 8 modos,
podramos utilizar un caracter ascii y poner cualquier valor desde 0 a 9. Esto su-
pondra una carga de 8 bits para la comunicacion. Sin embargo, si utilizamos 3 bits y
4.2. COMUNICACION SERIE MEDIANTE USART 50

Cuadro 4.4: Ejemplo 2 - Comunicaciones: Comunicaciones


Interfaz serie
Entrada/Salida Comando Evento Descripcion
Entrada led,yellow,high Pin 9 = HIGH Enciende el led ama-
rillo
Entrada led,yellow,low Pin 9 = LOW Apaga el led amarillo
Entrada led,red,high Pin 8 = HIGH Enciende el led rojo
Entrada led,red,low Pin 8 = LOW Apaga el led rojo

Figura 4.5: Ejemplo 2 - Comunicaciones: Diagrama de flujo

codificamos las posibilidades en binario, unicamente necesitaremos 3 bits, es decir, el


tamano es un 37.5 % del tamano inicial.
Normalmente para los prototipos y para las comunicaciones de gestion, se utilizan
caracteres ascii dado que son mas sencillos de depurar. En este ejemplo nosotros
utilizaremos un protocolo de aplicacion ascii muy sencillo para que el lector pueda
apreciar los fundamentos de la recepcion de informacion mediante el puerto serie.
En la Tabla 4.4 se puede ver la tabla de comunicaciones con los comandos y el
formato.
Como se puede observar en la Tabla 4.4 los campos estan separados por comas, que
se utilizaran para la decodificacion de comandos tal y como veremos en la Figura 4.2.2.

Diagrama de flujo
En la Figura 4.5 se puede ver el diagrama en el que nos apoyaremos a la hora de
disenar nuestro codigo. A medida que vayamos avanzando en los disenos, estos diagra-
mas cada vez seran mas genericos y subiran mas el nivel de abstraccion. Un ejemplo
de este nivel de abstraccion es la funcion check communications() y parse command()
donde no se entra en detalle en la implementacion interna de la misma, unicamente
se situa dentro del flujo de programa. Si quisieramos detallar una funcion, una opcion
sera la de realizar un diagrama separado para la misma y mas tarde unirlos.
4.2. COMUNICACION SERIE MEDIANTE USART 51

Normalmente, en cada ciclo del bucle de chequeo de comunicacion se realizara la


comprobacion del numero de datos recibido. Si existen datos se guardaran en el buffer
de procesado. Una vez detectado el fin de lnea se procesara el comando y realizara
la accion.

Codigo
Este ejemplo, aunque sencillo, requiere un poco mas lneas de codigo que el ante-
rior. Un factor clave y determinante para un buen diseno es la modularizacion. Siempre
que una parte del codigo se repita debemos plantearnos el convertirlo en una funcion.
Vamos a analizar el codigo de ejemplo. En este ejemplo (Cod. 4.2), hemos anadido dos
funciones genericas: check communications() y la funcion parse command(). La pri-
mera funcion hace lo siguiente: Primero comprueba que si buffer de recepcion tiene
datos. La funcion del buffer de recepcion es la de guardar todo dato que entre al puer-
to serie, hasta 64 bytes, de modo que pueda ser consultado en cualquier momento. La
funcion Serial.available de la librera Arduino, nos proporciona un metodo para saber
cuantos bytes se encuentran en el buffer de recepcion. Mediante la funcion Serial.read
leemos el dato entrante y anadimos el dato al buffer de procesado que hemos creado
y que hemos llamado serial command. Es responsabilidad del programador vaciar el
buffer mediante la llamada a Serial.read. Esta llamada devuelve el byte mas antiguo
en el buffer, es decir, el primero trasmitido (cola FIFO). Un detalle a tener en cuenta:
en Internet y en la bibliografa sobre Arduino se pueden ver muchos ejemplos donde
se hace la comprobacion Serial.available()>0. Aunque esto puede funcionar, hay que
tener mucho cuidado. Puede que en el buffer haya mas de 0 bytes, sin embargo, esto
no significa que toda la trama este en el buffer, por lo que si se hace esta comprobacion
habra que tener especial cuidado a la hora de formar la trama. Otra manera de evitar
este comportamiento podra ser el comprobar que el tamano del buffer de recepcion
sea mayor que el tamano de trama.
Otro de los puntos claves a la hora de tratar con protocolos ascii es el detectar el fin de
lnea. Dependiendo del sistema operativo y de la consola que se utilice para mandar
los datos, se utilizara un caracter de fin de lnea u otro. En el entorno Arduino uno
puede abrir un monitor de terminal serie usando (CTRL+Shift+m). En este monitor,
por defecto, esta seleccionada la opcion de mandar como fin de lnea los caracteres
((\r \n)), sin embargo en el bloque ((if)) de nuestro codigo, se comprueba el fin de linea
unicamente detectando si el valor recibido es igual a \n por lo tanto el caracter \r sera
insertado en nuestra trama y seguramente ocasione problemas en el diseno. La manera
mas sencilla de modificar este comportamiento es cambiar la opcion en la consola o
en este caso el monitor serial para que unicamente enve el caracter \n como caracter
fin de lnea.
Una vez detectado el fin del comando y almacenado en el buffer de procesado,
lo siguiente que se debe hacer es pasar dicho buffer a una funcion que decodifique
la trama y convierta los datos en informacion util para la logica del programa. Con
este fin se invoca a la funcion parse command() (linea 32). Esta funcion se ha creado
apoyandose en las funciones strcpy() y strtok(), que divide y copia los valores ledos en
la estructura Command protocol() (lneas 9 a 14). Se recomienda al lector que revise
la documentacion sobre la funcion strtok().
La ultima fase es la de actuar en funcion de la entrada, en nuestro caso unicamente
tenemos que detectar el primer campo y detectar a que tipo de actuador se refiere la
comunicacion. La funcion perform action led() comprueba que led se va a modificar
4.2. COMUNICACION SERIE MEDIANTE USART 52

y en funcion de dicho valor y del estado actua llamado a la funcion digitalWrite().


Como puedes observar el dividir el codigo en modulos con responsabilidad limitada,
permite que el codigo sea mucho mas legible y mantenible. Si quisieramos anadir la
posibilidad de encender o apagar un motor, unicamente tendramos que crear otra
funcion que podramos llamar perform action motor() y hacer las comprobaciones
pertinentes.

4.2.3. Ejemplo 3: Comunicacion entre Arduinos


En los ejemplos anteriores hemos visto como manejar actuadores y como recibir
informacion de los sensores. Ademas se ha practicado con la comunicacion serie con
el ordenador. En este ejemplo veremos como comunicar dos Arduinos entre si.
Normalmente en un diseno de complejidad media los microcontroladores no se en-
cuentran aislados, por el contrario forman parte de una red de sensores y actuadores.
Un ejemplo claro de exito donde se puede apreciar esta jerarqua de controladores es
el BMW X5. En BMW decidieron anadir un controlador y sensores en cada amorti-
guador con el objetivo de recibir informacion de manera inmediata de las condiciones
de las ruedas y del sistema de amortiguacion. En funcion del valor de los sensores,
el ordenador central del vehculo modifica diferentes parametros de modo que la con-
duccion sea mas suave y comoda.
Como puedes observar estamos ante un sistema distribuido en el que cada mi-
crocontrolador es responsable de un conjunto de sensores y un microcontrolador es
responsable de la actuacion.
En este ejemplo vamos a captar informacion en un Arduino y mediante las tecnicas
aprendidas en los ejemplos anteriores informaremos a otro Arduino de dichos valores
siendo este ultimo el responsable de actuar en funcion de los valores recibidos.

Tabla de entrada/salida

Para simplificar el montaje en este ejemplo vamos a trabajar con sensores uti-
lizados en otros ejemplos. Utilizaremos un boton, un sensor de temperatura como
sensores y dos LEDs como actuadores. Un primer Arduino (esclavo) tendra conecta-
dos los sensores. Leera los datos del sensor de temperatura y del boton y los enviara a
un segundo Arduino (maestro) cuando este lo solicite. Este segundo Arduino tendra
conectados dos LEDs. Un led se encendera si el boton ha sido apretado, mientras que
el segundo led lo hara de acuerdo al valor de temperatura recibido.

Cuadro 4.5: Ejemplo 3: Tabla de entrada/salida Arduino esclavo

Entrada/Salida Descripcion Nombre variable Pin


Entrada Boton button 8
Entrada Sensor de tempera- temperature sensor A1
tura
Entrada Rx0 RX 0
Salida Tx0 Tx 1
4.2. COMUNICACION SERIE MEDIANTE USART 53

1 #i n c l u d e < s t r i n g . h>
2 #i n c l u d e < s t d l i b . h>
3
4 const int led red = 13;
5 const int led yellow = 8;
6
7 char serial command [ 1 9 ] ;
8 i n t index = 0 ;
9 typedef struct {
10 char actuator [ 5 ] ;
11 char type [ 1 0 ] ;
12 char s t a t e [ 2 ] ;
13 } Command protocol ;
14 Command protocol command protocol ;
15
16 void setup ( ) {
17 pinMode ( l e d r e d , OUTPUT) ;
18 pinMode ( l e d y e l l o w , OUTPUT) ;
19 S e r i a l . begin (9600) ;
20 }
21
22 void check communications ( ) {
23 i n t data = 0 ;
24 while ( S e r i a l . available () ) {
25 data = S e r i a l . r e a d ( ) ;
26 i f ( data != \ n ) {
27 s e r i a l c o m m a n d [ i n d e x ] = data ;
28 i n d e x ++;
29 } else {
30 s e r i a l c o m m a n d [ i n d e x ] = \0 ;
31 index = 0 ;
32 parse command ( ) ;
33 }
34 }
35 }
36
37 v o i d parse command ( ) {
38 char token ;
39 int token index = 0;
40 s t r c p y ( command protocol . a c t u a t o r , s t r t o k ( s e r i a l c o m m a n d , " ," ) ) ;
41 s t r c p y ( command protocol . type , s t r t o k (NULL, " ," ) ) ;
42 s t r c p y ( command protocol . s t a t e , s t r t o k (NULL, " ," ) ) ;
43
44 i f ( strcmp ( command protocol . a c t u a t o r , " led " ) == 0 ) {
45 p e r f o r m a c t i o n l e d ( command protocol . type , command protocol . s t a t e )
;
46 }
47
48 }
49
50 v o i d p e r f o r m a c t i o n l e d ( c h a r type , c h a r s t a t e ) {
51 i f ( strcmp ( type , " yellow " ) == 0 ) {
52 i f ( strcmp ( s t a t e , " on " ) == 0 ) {
53 d i g i t a l W r i t e ( l e d y e l l o w , HIGH) ;
54 } else {
55 d i g i t a l W r i t e ( l e d y e l l o w , LOW) ;
56 }
57 } e l s e i f ( type , " red " ) {
58 i f ( strcmp ( s t a t e , " on " ) == 0 ) {
59 d i g i t a l W r i t e ( l e d r e d , HIGH) ;
60 } else {
61 d i g i t a l W r i t e ( l e d r e d , LOW) ;
62 }
63 }
64
65 }
66
67 void loop ( ) {
68 check communications ( ) ;
69 }

Codigo 4.2: Ejemplo 2 - Comunicaciones


4.2. COMUNICACION SERIE MEDIANTE USART 54

Cuadro 4.6: Ejemplo 3: Tabla de entrada/salida Arduino maestro

Entrada/Salida Descripcion Nombre variable Pin


Entrada Rx0 RX 0
Salida Tx0 Tx 1
Salida Led notificador de temperature led 8
temperatura
Salida Led notificador de button led 13
boton

Comunicaciones
El protocolo para este ejemplo es muy sencillo. El maestro (Arduino actuador)
manda una peticion de informacion. Esta peticion unicamente contiene el id del sensor
del cual desea obtener la informacion. Para simplificar los id se definen mediante
la directiva de preprocesador ((#DEFINE)). Ambos Arduinos tendran que tener los
mismos valores. Una vez que el esclavo detecta la peticion enva un mensaje con el
identificador del sensor y el valor, separado por una coma. El maestro entonces debera
capturar la trama y analizar de que sensor ha recibido el valor, si el sensor es un boton,
esperara un int, si el sensor, por el contrario, es el de temperatura, entonces esperara
un float.
Es importante enviar delimitadores de trama de modo que sepamos en todo mo-
mento cuando se ha iniciado una trama y cuando ha finalizado la misma. En nuestro
caso hemos utilizado como inicio de trama el caracter \t y como finalizador de trama
\r.

Diagrama de flujo
El diagrama de flujo esta dividido en 3 partes. La primera de ellas es comun a
todos los disenos y es el setup() del microcontrolador. Una vez se ha realizado el setup
correctamente, el siguiente paso es obtener la informacion de los sensores (en nuestro
caso el boton y el sensor de temperatura). Por ultimo comprobaremos si tenemos
alguna comunicacion que atender. Si hay una comunicacion por atender haremos una
decodificacion de la misma (mediante el parser ) y mandaremos un mensaje u otro en
funcion de la peticion.
En este esquema el Arduino que va a actuar es el que pide los datos. Podra
plantearse otra situacion en el que el Arduino con los sensores fuera el que enva de
forma constante los valores captados.
Cabe destacar que este diagrama de flujo es el correspondiente al Arduino Esclavo,
se deja como ejercicio al lector la creacion del diagrama para el Arduino Maestro.

Codigo
El codigo es muy parecido al realizado en la Subseccion 4.2.2. En Cod. 4.4 puedes
ver el codigo del Arduino que actua como esclavo. En Cod. 4.3 se encuentra todo el
codigo del Arduino que realiza las peticiones de informacion, es decir, el maestro. Ten
en cuenta que se han omitido partes de codigo repetidas como por ejemplo la lectura
de valores de temperatura con el objetivo de no hacer muy extenso el codigo.
4.2. COMUNICACION SERIE MEDIANTE USART 55

Figura 4.6: Ejemplo 3 - Comunicaciones: Diagrama de flujo

El codigo del esclavo te debera resultar familiar ya que es una recopilacion de los
ejemplos anteriores. La parte mas importante ahora es la decodificacion del protocolo
que es practicamente igual a la realizada en Subseccion 4.2.2. Se comprueba si hay
alguna trama sin procesar preguntado por el caracter \n, si hay alguna se obtienen
todos los bytes del puerto serie hasta llegar al caracter \n que indica el fin de lnea.
Por otro lado, el maestro solicita los valores enviando una cadena por el puerto
serie. Cuando recibe la informacion realiza un parser y en funcion del tipo de infor-
macion (temperatura o boton) realiza una accion u otra.
Otra parte importante del codigo es la utilizacion de la librera SoftwareSerial.
Mediante esta librera podemos implementar un emulador de UART en software (co-
mo una UART fsica) dandole un nombre a la instancia creada y asignandoles los
pines de RX y TX correspondientes (lnea 13 del codigo). Utilizar estos puertos nos
ayuda mucho a la hora de programar el Arduino dado que si utilizamos los pines
correspondientes a los puertos RX y TX del puerto serie 0 para la comunicacion entre
Arduinos, tendremos que desconectar dichos cables cada vez que queramos programar
alguno de los Arduinos ya que la programacion tal y como se explico en el Seccion A.1
se realiza mediante el puerto serie.
Este tipo de comunicacion aunque muy sencilla puede llevar a problemas muy
difciles de depurar. Uno de los problemas podra ser que uno de los Arduinos desbor-
dara el buffer del otro. Para evitar esta condicion en la funcion check petition() del
Arduino maestro se queda bloqueado hasta recibir el caracter fin de trama. Este sim-
ple bucle actua como un mecanismo de control de flujo no permitiendo enviar ningun
mensaje hasta que no se reciba la contestacion. Aunque esta solucion es muy sencilla
tiene muchos problemas dado que si el esclavo no contestara el Arduino maestro se
quedara constantemente en espera. Para evitar estos problemas existen soluciones
que utilizan ((ACKs)) o mensajes de verificacion para mantener un control de flujo.
Se deja como ejercicio al lector la implementacion del mecanismo de ((ACKs)) a la
solucion.
1 #d e f i n e BUTTON 0
4.2. COMUNICACION SERIE MEDIANTE USART 56

2 #d e f i n e TEMPERATURE SENSOR 1
3 #d e f i n e MAX TEMPERATURE 26
4 #i n c l u d e < s t d l i b . h>
5 #i n c l u d e < s t r i n g . h>
6 #i n c l u d e <S o f t w a r e S e r i a l . h>
7
8 S o f t w a r e S e r i a l s e r i a l (10 , 11) ;
9 const int button led = 13;
10 const int temperature led = 8;
11 i n t index = 0 ;
12 char serial command [ 1 9 ] ;
13 typedef struct {
14 int sensor ;
15 } petition t ;
16
17 void setup ( ) {
18 S e r i a l . begin (9600) ; S e r i a l . begin (9600) ;
19 pinMode ( b u t t o n l e d , OUTPUT) ; pinMode ( t e m p e r a t u r e l e d ,OUTPUT) ;
20 }
21
22 void send petition temperature ( ) {
23 s e r i a l . p r i n t ( "\t" ) ;
24 s e r i a l . p r i n t (TEMPERATURE SENSOR) ;
25 s e r i a l . p r i n t ( \ n ) ;
26 }
27
28 void s e n d p e t i t i o n b u t t o n ( ) {
29 s e r i a l . p r i n t ( \ t ) ;
30 s e r i a l . p r i n t (BUTTON) ;
31 s e r i a l . p r i n t ( \ n ) ;
32 }
33
34 void c h e c k p e t i t i o n ( ) {
35 c h a r data = 0 ;
36 w h i l e ( data != \ n ) {
37 i f ( s e r i a l . a v a i l a b l e ( ) > 0) {
38 data = s e r i a l . r e a d ( ) ;
39 i f ( data == \ t ) {
40 index = 0 ;
41 } e l s e i f ( data == \ n ) {
42 s e r i a l c o m m a n d [ i n d e x ] = \0 ;
43 index = 0 ;
44 parse command ( ) ;
45 } else {
46 s e r i a l c o m m a n d [ i n d e x ] = data ;
47 i n d e x ++;
48 }
49 }
50 }
51 }
52
53 v o i d parse command ( ) {
54 c h a r t o k e n = s t r t o k ( s e r i a l c o m m a n d , " ," ) ;
55 i n t s e n s o r = a t o i ( token ) ;
56 switch ( sensor ) {
57 c a s e BUTTON:
58 p e r f o r m a c t i o n b u t t o n ( s t r t o k (NULL, " ," ) ) ;
59 break ;
60 c a s e TEMPERATURE SENSOR:
61 p e r f o r m a c t i o n t e m p e r a t u r e ( s t r t o k (NULL, " ," ) ) ;
62 break ;
4.2. COMUNICACION SERIE MEDIANTE USART 57

63 }
64 }
65
66 void perform action button ( char value ) {
67 int state = atoi ( value ) ;
68 digitalWrite ( button led , state ) ;
69 }
70
71 void perform action temperature ( char value ) {
72 f l o a t temp = a t o f ( v a l u e ) ;
73 i f ( temp > MAX TEMPERATURE) {
74 d i g i t a l W r i t e ( t e m p e r a t u r e l e d , HIGH) ;
75 S e r i a l . p r i n t ( " Max temperature reached : " ) ;
76 S e r i a l . p r i n t l n ( temp ) ;
77 }else{
78 d i g i t a l W r i t e ( t e m p e r a t u r e l e d ,LOW) ;
79 }
80 }
81 void loop ( ) {
82 send petition button () ;
83 S e r i a l . p r i n t l n ( " Sent button " ) ; c h e c k p e t i t i o n ( ) ;
84 send petition temperature () ;
85 S e r i a l . p r i n t l n ( " Sent temperature " ) ; c h e c k p e t i t i o n ( ) ;
86 delay (500) ;
87 }

Codigo 4.3: Ejemplo 3 - Comunicaciones: maestro

1 #i n c l u d e < s t r i n g . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <S o f t w a r e S e r i a l . h>
4 #d e f i n e BUTTON 0
5 #d e f i n e TEMPERATURE SENSOR 1
6 c o n s t i n t b ut ton = 8 ;
7 c o n s t i n t t e m p e r a t u r e s e n s o r = A1 ;
8 float temperature celsius ;
9 int button state = 0;
10 char serial command [ 1 9 ] ;
11 i n t index = 0 ;
12
13 SoftwareSerial s e r i a l (10 ,11) ;
14
15 typedef struct {
16 int sensor ;
17 } petition t ;
18 petition t last petition ;
19 void setup ( ) {
20 pinMode ( button , INPUT) ;
21 pinMode ( t e m p e r a t u r e s e n s o r , INPUT) ;
22 S e r i a l . begin (9600) ;
23 s e r i a l . begin (9600) ;
24 }
25 void check communications ( ) {
26 c h a r data = 0 ;
27 i f ( s e r i a l . a v a i l a b l e ( ) > 0) {
28 data = s e r i a l . r e a d ( ) ;
29 i f ( data == \ t ) {
30 index = 0 ;
31 } e l s e i f ( data == \ n ) {
32 s e r i a l c o m m a n d [ i n d e x ] = \0 ;
33 index = 0 ;
4.3. COMUNICACION I2C 58

34 parse command ( ) ;
35 } else {
36 s e r i a l c o m m a n d [ i n d e x ] = data ;
37 i n d e x ++;
38 }
39 }
40 }
41 v o i d parse command ( ) {
42 l a s t p e t i t i o n . sensor = a t o i ( serial command ) ;
43 switch ( l a s t p e t i t i o n . sensor ) {
44 c a s e BUTTON:
45 send button state () ;
46 break ;
47 c a s e TEMPERATURE SENSOR:
48 send temperature () ;
49 break ;
50 }
51 }
52 void send button state ( ) {
53 s e r i a l . p r i n t ( \ t ) ; s e r i a l . p r i n t ( l a s t p e t i t i o n . s e n s o r ) ;
54 s e r i a l . p r i n t ( " ," ) ; s e r i a l . p r i n t ( b u t t o n s t a t e ) ;
55 s e r i a l . p r i n t ( \ n ) ;
56 }
57 void send temperature ( ) {
58 s e r i a l . p r i n t ( \ t ) ; s e r i a l . p r i n t ( l a s t p e t i t i o n . s e n s o r ) ;
59 s e r i a l . p r i n t ( " ," ) ; s e r i a l . p r i n t ( t e m p e r a t u r e c e l s i u s ) ;
60 s e r i a l . p r i n t ( \ n ) ;
61 }
62 void loop ( ) {
63 read sensors () ;
64 check communications ( ) ;
65 }

Codigo 4.4: Ejemplo 3 - Comunicaciones: esclavo

4.3. Comunicacion I2C


En la Seccion 4.2 vimos un mecanismo de comunicacion muy sencillo (Comuni-
cacion mediante UART). En esta seccion veremos otro protocolo de comunicacion
llamado I2C.
I2C fue disenado por la empresa Philips en 1992. Actualmente muchos perifericos
implementan este bus de comunicacion por su sencillez, fiabilidad y el bajo numero
de cables requerido (unicamente 2).
Es un protocolo de dos cables utilizado para conectar uno o mas maestros a uno
o mas esclavos. Los esclavos pueden ser sensores de temperatura, humedad, de mo-
vimiento, memorias series, etc. Cada esclavo se A diferencia de la UART, mediante
I2C podemos direccionar a varios esclavos, es decir, es un bus con multiples esclavos
cada uno de ellos identificados por una direccion de 7 bits o 10 bits en funcion del
estandar utilizado.

4.3.1. Hardware
Una de las ventajas de I2C frente a otros buses como SPI (Serial Peripheral Inter-
face) es el bajo numero de cables requeridos para la comunicacion. I2C unicamente
4.3. COMUNICACION I2C 59

requiere 2 cables dentro de un mismo circuito y tres cables para la comunicacion entre
circuitos. La 3 senales principales son:

SCL (Serial CLock): La senal de reloj siempre la genera el dispositivo que


actua como maestro. Los esclavos pueden forzar al maestro a que el reloj deje
de oscilar. En la Subseccion 4.3.2 veremos este caso.

SDA (Serial Data): La senal SDA se utiliza para enviar los datos.

GND (Ground): Esta senal no es obligatoria en comunicaciones dentro de un


mismo circuito pero si la comunicacion es entre circuitos, entonces se necesitara
con el objetivo de proporcionar un mismo nivel de referencia.

4.3.2. Protocolo I2C

Figura 4.7: Esquema de comunicacion I2C

El valor de las senales SCL y SDA esta en alto hasta que uno de los componentes
los ponga en bajo. Los dos resistores de pull-up fuerzan el valor de los cables a VCC
(3,3 - 5 volts). El maestro controla una senal de reloj por la lnea SCL, determinando
la tasa de transferencia de datos, y controla o deja controlar por el esclavo la lnea de
direcciones SDA.
La comunicacion entre un maestro y un esclavo consiste en una secuencia de
transacciones de datos a traves de SDA controladas por el maestro o por el esclavo,
sincronizadas por la senal SCL.
Hay tres tipos de transacciones, todas iniciadas por el maestro: escritura (el maes-
tro escribe en la linea SDA, lectura (el esclavo escribe en la linea SDA) y combinadas
(ambos escriben). La comunicacion a traves de I2C esta formada por cuatro pasos:

Condicion de Inicio

Trama de Direccionamiento

Trama de Datos

Condicion de Parada

Cada transaccion comienza con una condicion de inicio (S) y termina con una
condicion de Parada (P). Estas condiciones son controladas por el maestro. La
condicion de inicio la realiza el maestro con una transicion de la senal SDA de 1
a 0 en el semiciclo positivo del reloj SCL, mientras que la condicion de parada se
realiza con una transicion de la senal SDA de 0 a 1 en el semiciclo positivo de la senal
SCL. En I2C la transicion en la lnea SDA durante las transacciones se hace en la
4.3. COMUNICACION I2C 60

Figura 4.8: Trama de direccion I2C

parte baja del ciclo reloj. Unicamente en la condicion de inicio y parada se realiza la
transicion en la parte alta del ciclo de reloj.
Una vez que se ha indicado la condicion de inicio el siguiente paso es enviar una
trama con la direccion del dispositivo que se desea seleccionar. Existen dos modos
de direccionamiento: de 7 bits o de 10 bits. El uso de 10 bits de comunicacion no
es frecuente y no esta cubierto en este libro. Al final de los 7 bits de direccion se
agrega un octavo bit que indicara el tipo de transaccion que se hace (0 = escritura,
1 = lectura). Hay que tener en cuenta que la direccion del esclavo se colocara en
los 7 bits mas significativos de la trama de direcciones. Una vez enviada la trama de
direccionamiento los esclavos leen cada bit en la transicion del reloj, teniendo en cuenta
que el primer bit es el mas significativo de la direccion y que el ultimo bit corresponde
al tipo de transferencia. Para comprobar que el esclavo ha detectado la peticion de
comunicacion del maestro, el esclavo debe poner la lnea SDA a nivel bajo antes del
decimo pulso de reloj, de lo contrario el maestro puede interpretar que la comunicacion
no se ha podido establecer. Una vez que se ha establecido la comunicacion el siguiente
paso es enviar la trama de datos. La trama de datos unicamente se trasmite bit por bit
mediante codificacion MSB. En funcion de si la transferencia es de lectura o escritura,
sera el maestro o el esclavo el encargado de poner los datos en el bus. Es importante
que el receptor en todo momento valide cada byte recibido mediante un ACK. Si el
receptor enva un NACK despues de recibir un byte, indicara al receptor que quiere
cerrar la comunicacion.
El ultimo paso consiste en la transicion del estado de transferencia al estado de
parada. Una vez que todos los datos han sido enviados y se ha recibido un NACK, el
maestro generara la condicion de parada.
El protocolo I2C admite la comunicacion continuada entre un maestro y un esclavo.
Para ello, una vez terminado el primer flujo de informacion (Condicion de inicio +
direccion + ack + datos + ack) en vez de enviar la condicion de parada, el maestro
mantiene ocupado el bus poniendo la senal de datos en alto y manteniendo el reloj
en alto. De este modo, no se permite que otro maestro adquiera el bus. Una vez
completada esta fase se puede enviar otra vez la direccion del esclavo (cualquiera) y
se procede al envo de datos.
En la Figura 4.8 se puede ver una ilustracion de la trama de direccion I2C y en
Figura 4.7 un esquema donde se ilustra la comunicacion anteriormente explicada. Es
importante tener en cuenta que el ACK lo enva el receptor y no el emisor. En la
Figura 4.9 se puede observar la estructura que presenta una trama de datos, donde
unicamente se mandan los datos y un bit de ACK que enva el receptor.
En los siguientes ejemplos veremos de manera simplificada como realizar comuni-
caciones mediante el protocolo I2C en Arduino.

4.3.3. Ejemplo 1: Hola mundo mediante I2C


Arduino posee una librera llamada Wire que implementa el protocolo I2C. Me-
diante esta librera la utilizacion de protocolo se reduce a unas sencillas funciones que
4.3. COMUNICACION I2C 61

Figura 4.9: Trama de datos I2C

Cuadro 4.7: Ejemplo 1 - I2C: Tabla de entrada/salida

Entrada/Salida Descripcion Nombre variable Pin


Entrada Linea SCL SCL A5
Entrada Linea SDA SDA A4
Salida Led notificador notificator led 13

nos abstraeran por completo de detalles de mas bajo nivel.


Los pines I2C variaran en funcion del microcontrolador que estemos utilizando,
recuerda que siempre puedes recurrir a la pagina oficial de Arduino o al datasheet
del microcontrolador para mas informacion. En el caso de utilizar el bootloader del
Atmega328 los pines correspondientes a SDA y SCL son A4 y A5 respectivamente.
Algunas tarjetas de expansion poseen varios conectores I2C que permite expandir el
bus para varios esclavos.
En este ejemplo utilizaremos dos placas Arduino de modo que una enve un men-
saje y el otro reciba el mensaje y en funcion del mismo encienda o apague un LED. De
nuevo el LED es nuestro mejor amigo a la hora de comprobar que las cosas funcionan.

Tabla de entrada/salida

La tabla de entrada salida es muy simple (ver Tabla 4.7), unicamente tenemos un
LED en el nodo esclavo que servira para comprobar que el mensaje ha sido recibido
con exito.

Diagrama de flujo

Los diagramas de flujo que contienen eventos y comunicaciones suelen ser mas
complejos dado que tenemos que atendar a eventos que no son sncronos. Se reco-
mienda que el lector cree el diagrama de flujo correspondiente basandose en el codigo
mostrado en Tabla 4.3.3.

Codigo

Para evitar tratar con detalles de implementacion del protocolo I2C utilizaremos
la librera Wire cuyas funciones mas utilizadas se explicaran a continuacion. La docu-
mentacion oficial de la librera Wire puede ser consultada desde la siguiente direccion:
http://playground.arduino.cc/Main/WireLibraryDetailedReference.

begin(): Esta funcion nos permite iniciar la librera. Esta funcion es recurrente
en la mayora de las libreras. La implementacion suele reiniciar o poner en el
estado inicial la maquina de estados y los registros de trabajo.
4.3. COMUNICACION I2C 62

requestFrom(direccion, tamano): Mediante esta funcion ponemos al maes-


tro a la escucha del esclavo hasta que reciba el tamano fijado en el segundo
parametro. La direccion tiene que ser de 7 bits.

beginTransmission(direccion): Si queremos enviar datos desde el maestro al


esclavo, despues de llamar a la funcion begin, tenemos que utilizar la funcion
beginTransmission. Mediante esta funcion indicamos a la librera que pase al
estado preparado y configure el buffer de direccionamiento. Ten en cuenta que
esta funcion no enviara ningun bit, unicamente hace operaciones a nivel interno
con el objetivo de no ocupar el bus de forma innecesaria.

send(buffer): Una vez que se ha establecido la direccion la funcion send nos


permite configurar el buffer de datos de la interfaz TWI2 .

endTransmission(): La ultima funcion en cada comunicacion es endTransmis-


sion() que mandara todos los datos del buffer a la direccion indicada mediante
beginTransmission() y por ultimo enviara el bit de parada.

onReceive((void*)func(int)): Cuando la librera detecta que existe un dato


llama a la funcion handler pasada como parametro. Esta funcion tiene que tener
la ((firma)) void func(int), el parametro que recibe contiene el numero de bytes
capturados. El bus no sera liberado hasta que se retorne de la funcion handler.

receive(): Devuelve el siguiente byte en el buffer de recepcion.

onRequest((void*)func(void)): Si el esclavo recibe la senal SLA+R, es decir,


la direccion del dispositivo y la opcion de lectura, la librera Wire llama a esta
funcion handler donde se debera insertar los datos en el buffer de escritura con
el objetivo de iniciar el envo.

available(): Devuelve el numero de bytes del buffer de recepcion.

Una vez que se tiene claro cada una de las funciones de la librera, el codigo
mostrado en Cod. 4.5 y Cod. 4.6 no debe suponer ningun problema. En primer lugar
el Arduino maestro enva un byte de datos con el caracter ascii ((h)) (recuerda que un
caracter ascii extendido es igual a un byte) y espera medio segundo. Por otro lado,
el Arduino esclavo unicamente espera los datos mediante la funcion handler. Esta
funcion handler se inscribe al evento del bus mediante la funcion onRequest(). Ten en
cuenta que aunque en este codigo y a modo de ejemplo se ha realizado la operacion
de encender el led dentro de la funcion, esto no es recomendable dado que mientras
permanezcamos en dicha funcion el bus se mantendra ocupado.
Por ultimo, se recomienda que el lector modifique el codigo del esclavo y del
maestro con el objetivo de apagar y encender el led mediante algun evento, este
evento puede ser la insercion de una cadena en el puerto serie o un evento temporal.
Ademas, se recomienda que se realice la modificacion del pin de salida fuera de la
funcion handler.
2 TWI (Two Wire Interface) es otra forma de nombrar al protocolo I2C
4.3. COMUNICACION I2C 63

1 #i n c l u d e <Wire . h>
2
3
4 void setup ( ) {
5 Wire . b e g i n ( ) ;
6
7 }
8
9 void loop ( ) {
10 Wire . b e g i n T r a n s m i s s i o n ( 1 ) ;
11 Wire . w r i t e ( " h " ) ;
12 Wire . e n d T r a n s m i s s i o n ( ) ;
13 delay (500) ;
14 }

Codigo 4.5: Ejemplo 1 - Comunicaciones I2C: maestro

1 #i n c l u d e <Wire . h>
2 const int n o t i f i c a t i o n l e d = 13;
3
4 void setup ( ) {
5 pinMode ( n o t i f i c a t i o n l e d ,OUTPUT) ;
6 Wire . b e g i n ( 1 ) ;
7 S e r i a l . begin (9600) ;
8 Wire . o n R e c e i v e ( h a n d l e r ) ;
9
10 }
11 v o i d h a n d l e r ( i n t num bytes ) {
12 char value = 0 ;
13 w h i l e ( Wire . a v a i l a b l e ( ) > 0 ) {
14 v a l u e = Wire . r e a d ( ) ;
15 }
16 S e r i a l . p r i n t ( " Read : " ) ;
17 S e r i a l . p rin tln ( value ) ;
18 i f ( v a l u e == h ) {
19 d i g i t a l W r i t e ( n o t i f i c a t i o n l e d , HIGH) ;
20 }else{
21 d i g i t a l W r i t e ( n o t i f i c a t i o n l e d ,LOW) ;
22 }
23
24 }
25 void loop ( ) {
26 delay (100) ;
27 }

Codigo 4.6: Ejemplo 1 - Comunicaciones I2C: esclavo


4.3. COMUNICACION I2C 64

Figura 4.10: Sensor MPU6050

4.3.4. Ejemplo 2: Obteniendo datos de un IMU


En este ejemplo veremos como utilizar uno de los muchos sensores IMU (unidad
de medicion inercial) mediante el protocolo I2C para obtener los angulos de rotacion
con respecto al eje ((X)) y el eje ((Y)).
El uso de estos sensores requiere de practica y de conocimientos trigonometricos
para tratar los valores ((crudos))3 . En este ejemplo, no nos centraremos en las funciones
matematicas, la parte mas interesante para este capitulo es la comunicacion con el
sensor.
Para este ejemplo hemos utilizado el sensor MPU6050 (ver Figura 4.10) este sen-
sor proporciona 6 grados de libertad y posee un controlador DMP (Digital Motion
Processing). Con el objetivo de simplificar las cosas unicamente veremos como ob-
tener el valor del giroscopio, aunque tambien obtengamos el del acelerometro para
utilizarlo en el filtro complementario4 .
Debido a la sencillez del ejemplo y al hecho de que unicamente utilizaremos un
sensor, en este caso no realizaremos diagrama de flujo ni tabla de comunicaciones,
unicamente mantendremos la tabla de entrada salida con el objetivo de mostrar las
conexiones necesarias.

Tabla de entrada/salida
Como ya hemos comentado, la estructura de este ejemplo es diferente debido a que
unicamente estamos utilizando un sensor y no estamos haciendo ningun tipo actuacion
en funcion de los valores obtenidos. En la Figura 4.11 se puede observar la conexion
de este sensor. Unicamente utilizaremos el bus principal I2C. El MPU6050 tiene otro
bus I2C que utiliza para gestionar una cola FIFO.
Es muy importante que te asegures del voltaje de entrada de tu sensor. Algunos
MPU6050 tienen un conversor que permiten conectar el sensor a 5V, pero si dudas
conectalo a 3.3V.
Ten en cuenta que en este caso no hace falta una resistencia de pull-up, esto se
debe a que el sensor trae una resistencia interna. Muchos sensores que se comunican
3 Los valores ((crudos)) o ((RAW)) son aquellos valores que no han sido tratados y que son resultado

de una captura
4 El filtro complementario es la union de un filtro de paso bajo con un filtro de paso alto
4.3. COMUNICACION I2C 65

Figura 4.11: Conexiones MPU6050

mediante I2C incorporan esta resistencia.

Codigo
Se puede revisar el codigo en Cod. 4.7. A continuacion, describiremos las partes
mas importantes del mismo.
1 #i n c l u d e <Wire . h>
2 #d e f i n e IMU 0 x68
3 #d e f i n e A R 1 6 3 8 4 . 0
4 #d e f i n e G R 1 3 1 . 0
5 #d e f i n e RAD TO DEG 5 7 . 2 9 5 7 7 9
6 i n t 1 6 t AcX , AcY , AcZ , GyX, GyY, GyZ ;
7 f l o a t Acc [ 2 ] ;
8 f l o a t Gy [ 2 ] ;
9 f l o a t Angle [ 2 ] ;
10 void setup ( ) {
11 Wire . b e g i n ( ) ;
12 Wire . b e g i n T r a n s m i s s i o n (IMU) ;
13 Wire . w r i t e ( 0 x6B ) ;
14 Wire . w r i t e ( 0 ) ;
15 Wire . e n d T r a n s m i s s i o n ( t r u e ) ;
16 S e r i a l . begin (9600) ;
4.3. COMUNICACION I2C 66

17 }
18
19 void loop ( ) {
20 Wire . b e g i n T r a n s m i s s i o n (IMU) ;
21 Wire . w r i t e ( 0 x3B ) ;
22 Wire . e n d T r a n s m i s s i o n ( f a l s e ) ;
23 Wire . requestFrom (IMU, 6 , t r u e ) ;
24 AcX = Wire . r e a d ( ) << 8 | Wire . r e a d ( ) ;
25 AcY = Wire . r e a d ( ) << 8 | Wire . r e a d ( ) ;
26 AcZ = Wire . r e a d ( ) << 8 | Wire . r e a d ( ) ;
27
28 Acc [ 1 ] = atan (1 (AcX / A R ) / s q r t ( pow ( ( AcY / A R ) , 2 ) + pow ( (
AcZ / A R ) , 2 ) ) ) RAD TO DEG ;
29 Acc [ 0 ] = atan ( ( AcY / A R ) / s q r t ( pow ( ( AcX / A R ) , 2 ) + pow ( ( AcZ /
A R ) , 2 ) ) ) RAD TO DEG ;
30
31 Wire . b e g i n T r a n s m i s s i o n (IMU) ;
32 Wire . w r i t e ( 0 x43 ) ;
33 Wire . e n d T r a n s m i s s i o n ( f a l s e ) ;
34 Wire . requestFrom (IMU, 4 , t r u e ) ;
35 GyX = Wire . r e a d ( ) << 8 | Wire . r e a d ( ) ;
36 GyY = Wire . r e a d ( ) << 8 | Wire . r e a d ( ) ;
37
38 Gy [ 0 ] = GyX / G R ;
39 Gy [ 1 ] = GyY / G R ;
40
41 Angle [ 0 ] = 0 . 9 8 ( Angle [ 0 ] + Gy [ 0 ] 0 . 0 1 0 ) + 0 . 0 2 Acc [ 0 ] ;
42 Angle [ 1 ] = 0 . 9 8 ( Angle [ 1 ] + Gy [ 1 ] 0 . 0 1 0 ) + 0 . 0 2 Acc [ 1 ] ;
43
44 S e r i a l . p r i n t ( " Angle X : " ) ; S e r i a l . p r i n t ( Angle [ 0 ] ) ; S e r i a l . p r i n t ( " \ n
") ;
45 S e r i a l . p r i n t ( " Angle Y : " ) ; S e r i a l . p r i n t ( Angle [ 1 ] ) ; S e r i a l . p r i n t ( " \n
- - - - - - - - - - - -\ n " ) ;
46
47 delay (10) ;
48
49 }

Codigo 4.7: Ejemplo 2 - Comunicaciones I2C: Sensor MPU-6050

En la funcion de setup() iniciamos el dispositivo. La inicializacion del sensor de-


pendera de cada uno, en este caso tenemos que mandar el comando ((0)) al registro
0x6B. Normalmente el procedimiento suele ser el siguiente: En primer lugar se enva
un byte con el registro donde se quiere leer o escribir (en este caos el 0x6B) y segui-
damente se enva o recibe el dato del registro (en este caso enviamos el comando 0
que inicia el dispositivo). Una vez que hemos enviado la peticion de inicio cerramos
la comunicacion mediante la llamada a la funcion endTransmission() de la librera
Wire.
Por otro lado, en cada ciclo de ((scan)) (funcion loop()) obtenemos los valores de
cada uno de los registros del sensor5 . Para obtener el valor del registro que contiene los
valores del acelerometro tenemos que enviar la peticion a la direccion 0x3B. Este paso
es muy importante y te ayudara a entender cualquier comunicacion I2C mediante
Arduino. Una vez que hemos escrito el valor 0x3B en el buffer de datos del bus I2C del
Arduino, el siguiente paso es enviar dicho buffer por el canal SDA. Ten en cuenta que
como se explico en la Tabla 4.3.3 los datos no se envan hasta que se llama a la funcion
5 Todos las direcciones as como el valor de los comandos se obtienen del datasheet de cada sensor
4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 67

endTransmission. Al contrario que en el caso del comando de inicializacion, en este


caso vamos a recibir datos del sensor. Cuando se enva la peticion 0x3B tenemos que
activar el bit de lectura del bus I2C con el objetivo de que el esclavo (sensor) pueda
escribir en el maestro (Arduino). Para activar el bit de lectura primero finalizamos
la fase de transmision de datos mediante endTransmission(false). Despues indicamos
que queremos obtener datos del esclavo y esperamos hasta tener 6 bytes, todo ello
mediante la funcion requestFrom(). Los datos se envan byte a byte. Como los datos
son de 16 bits tendremos que utilizar operaciones de manipulacion de bits para formar
una palabra de 16 bits. En las lneas 24,25,26 se pueden ver dichas operaciones.
Para obtener el valor del giroscopio se sigue el mismo procedimiento, la unica
diferencia esta en el numero de bytes que recibimos del esclavo y la direccion del
registro.
En las lneas 41 y 46 se aplica un filtro complementario que no sera explicado
debido a que no es el objetivo de este libro.
Si abres el monitor serial podras ver los valores obtenidos cada 10ms.

4.4. Protocolo SPI (Serial Peripheral Interface


En esta seccion veremos otro protocolo de comunicacion serial SPI. SPI es un
protocolo de comunicacion serie sncrono para comunicacion entre microcontroladores
y uno o mas perifericos a corta distancia como displays, tarjetas de memoria y sensores.
Cada instancia de SPI tiene un maestro y uno o varios esclavos.
Posee cuatro senales basicas: Tres desde el maestro hacia el esclavo

SS (Slave Select): Senal que se replica para cada esclavo. El maestro pone a
cero una de las senales SS para seleccionar el esclavo correspondiente

MOSI (Master Out Slave In): Senal por donde se transmite una palabra
(byte) desde el maestro hacia el esclavo

SCK (Serial Clock): Senal que sincroniza la comunicacion Y una senal desde
el esclavo hacia el maestro

MISO (Master In Slave Out): Senal por donde se transmite una palabra
(byte) desde el esclavo hacia el maestro

El procesador ATmega328 tiene una interfaz SPI para comunicacion. Las senales
SS, MOSI, MISO y SCK corresponden a los pines 16 al 19 respectivamente. En el
modulo Arduino estos pines estan conectados a los pines 10 al 13 respectivamente.
(Figura 4.12).
El mecanismo de transferencia se realiza de la siguiente manera: Toda la comunica-
cion es controlada por el master que selecciona el esclavo poniendo en bajo la senal SS
correspondiente. Entonces enva en modo serie una palabra (byte) por la lnea MOSI
y simultaneamente acepta un byte proveniente desde el esclavo por la lnea MISO.
Esta transferencia se realiza generando 8 pulsos sobre la lnea de sincronizacion SCK
En Arduino existe una librera SPI.h que nos proporciona una serie de funciones
para controlar el proceso de comunicacion.

SPIbegin: Inicializa el bus configurando SS como salida con pullup interno en


alto y MOSI y SCLK como salida en bajo.
4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 68

Figura 4.12: Pines SPI en el proc. ATmega328

SPISetting: Esta funcion permite configurar algunos parametros de comuni-


cacion tales como los siguientes, los cuales se deben poner en el orden que se
describen:

Velocidad de comunicacion (generalmente se pone la frecuencia a que opera


el microcontrolador en HZ).
Si el primer bit enviado es el mas significativo (MSB) o el menos significa-
tivo (LSB)
El modo de comunicacion SPI, que nos indica en que flanco del reloj los
datos son enviados o recibidos (Fase del reloj) y si el reloj esta inactivo
cuando esta en alto o en bajo (Polaridad) (ver tabla Tabla 4.8).

SPI.beginTransaction: Inicializa el bus SPI con SPISetting

SPI.endTransaction

SPI.transfer

Los pasos para establecer una comunicacion SPI son los siguientes,

1. SPI.begin(): Iniciar la librera SPI (normalmente en el setup() del programa)

2. SPI.beginTransaction(SPISetting()): La librera se apropia de las interrup-


ciones.

3. SPI.transfer(): Se transfieren 8 bits

4. SPI.endTransaction(): Se libera el bus y se habilitan de nuevo las interrup-


ciones.
4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 69

Cuadro 4.8: Modos de comunicacion SPI

Modo Clock Polarity Clock Phase


MODO 0 0 0
MODO 1 0 1
MODO 2 1 0
MODO 3 1 1

Figura 4.13: Contenidos del registro de entrada del DAC

Ejemplo: Ejemplo de uso del bus SPI


En este ejemplo mostraremos como generar una senal sinusoidal en el procesador y
enviarla a un conversor DAC (Conversor Digital/Analogico) a traves del puerto SPI.
El codigo del ejemplo esta en la figura Cod. 4.8. En la primera parte del codigo
se crea una tabla de consulta (look-up table) que en realidad es un arreglo de me-
moria que llamamos Waveform con valores que forman una sinusoide. Este arreglo
es recorrido y cada valor es enviado a traves del bus SPI, al conversor DAC externo
conectado al Arduino para generar la sinusoide. Para este ejemplo se utilizo un modu-
lo de converson D/A de 8 canales PMODDA4 de DIGILENT que usa el conversor
AD5628 de ANALOG DEVICES, 6
La comunicacion se realiza enviando transacciones de 32 bits a traves del puerto
MOSI al esclavo (ver figura Figura 4.137 Sin entrar en detalles tecnicos acerca del
DAC AD5628 es necesario saber que los bits C3 al C0 se utilizan para indicar el
comando a ejecutar por el AD5628. En el caso de un Reset el valor es el 0111, y
para configurar el valor de referencia en la conversion es el 1000. El bit DB0 se debe
poner a 1 si se utiliza valor de referencia. Los bits A3 a A0 indican la direccion de
uno de los 8 canales del conversor desde el 0000, hasta el 0111. Para seleccionar
todos los canales se coloca el valor 1111.
El primer paso es inicializar la comunicacion SPI. Para ello hemos creado la fun-
cion init spi que pone en alto la senal slave select y llama a la funcion begin de la
librera SPI (SPI.begin). El siguiente paso es configurar el modulo esclavo, segun se
indica en la lnea 27 del codigo, donde se indica la frecuencia del reloj, si el primer
bit es el mas significativo, y el modo de comunicacion. Para configurar el esclavo es
necesario enviarle un comando de reset y luego otro comando con el setup del va-
lor de referencia. Para ello es necesario colocar la senal slave selecta 0 durante la
transaccion. En este ejemplo se realizan cuatro transacciones de 8 bits cada una (tanto
en la funcion config dac como en la funcion Loop), pero se puede utilizar la funcion
6 Informacion acerca del conversor PMODDA4 de DIGILENT puede encontrarse en este enlace

https://reference.digilentinc.com/pmod:pmod:DA4
7 tomada de https://reference.digilentinc.com/pmod:pmod:DA4
4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 70

Figura 4.14: Forma de onda obtenida conversion D/A

SPI.Transfer16() de Arduino y realizar dos transacciones de 16 bits.

Una vez configurado se entra en la funcion Loop y se comienzan a enviar los datos
que se sacan de memoria al conversor. La salida obtenida en nuestro ejemplo puede
verse en la figura Figura 4.14
4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 71

1 # include < SPI .h >


2 const int slave_select = 22;
3 const long waveform [120]
4 {
5 0 x7ff , 0 x86a , 0 x8d5 , 0 x93f , 0 x9a9 , 0 xa11 , 0 xa78 , 0 xadd , 0 xb40 ,
6 0 xba1 , 0 xbff , 0 xc5a , 0 xcb2 , 0 xd08 , 0 xd59 , 0 xda7 , 0 xdf1 , 0 xe36 ,
7 0 xe77 , 0 xeb4 , 0 xeec , 0 xf1f , 0 xf4d , 0 xf77 , 0 xf9a , 0 xfb9 , 0 xfd2 ,
8 0 xfe5 , 0 xff3 , 0 xffc , 0 xfff , 0 xffc , 0 xff3 , 0 xfe5 , 0 xfd2 , 0 xfb9 ,
9 0 xf9a , 0 xf77 , 0 xf4d , 0 xf1f , 0 xeec , 0 xeb4 , 0 xe77 , 0 xe36 , 0 xdf1 ,
10 0 xda7 , 0 xd59 , 0 xd08 , 0 xcb2 , 0 xc5a , 0 xbff , 0 xba1 , 0 xb40 , 0 xadd ,
11 0 xa78 , 0 xa11 , 0 x9a9 , 0 x93f , 0 x8d5 , 0 x86a , 0 x7ff , 0 x794 , 0 x729 ,
12 0 x6bf , 0 x655 , 0 x5ed , 0 x586 , 0 x521 , 0 x4be , 0 x45d , 0 x3ff , 0 x3a4 ,
13 0 x34c , 0 x2f6 , 0 x2a5 , 0 x257 , 0 x20d , 0 x1c8 , 0 x187 , 0 x14a , 0 x112 ,
14 0 xdf , 0 xb1 , 0 x87 , 0 x64 , 0 x45 , 0 x2c , 0 x19 , 0 xb , 0 x2 , 0 x0 , 0 x2 ,
15 0 xb , 0 x19 , 0 x2c , 0 x45 , 0 x64 , 0 x87 , 0 xb1 , 0 xdf , 0 x112 , 0 x14a ,
16 0 x187 , 0 x1c8 , 0 x20d , 0 x257 , 0 x2a5 , 0 x2f6 , 0 x34c , 0 x3a4 , 0 x3ff ,
17 0 x45d , 0 x4be , 0 x521 , 0 x586 , 0 x5ed , 0 x655 , 0 x6bf , 0 x729 , 0 x794
18 };
19 void init_spi () {
20 pinMode ( slave_select , OUTPUT ) ;
21 digitalWrite ( slave_select , HIGH ) ;
22 SPI . begin () ;
23 }
24 void config_dac () {
25 unsigned long reset = 0 x07000000 ;
26 unsigned long reference = 0 x08000001 ;
27 SPI . b e g i n T r a n s a c t i o n ( SPISettings (12000000 , MSBFIRST , SPI_MODE0 ) ) ;
// gain control of SPI bus
28 digitalWrite ( slave_select , LOW ) ;
29 SPI . transfer (( reset & 0 xFF000000 ) >> 24) ;
30 SPI . transfer (( reset & 0 x00FF0000 ) >> 16) ;
31 SPI . transfer (( reset & 0 x0000FF00 ) >> 8) ;
32 SPI . transfer (( reset & 0 x000000FF ) ) ;
33 digitalWrite ( slave_select , HIGH ) ;
34 SPI . en dTransac tion () ; // release the SPI bus
35 SPI . b e g i n T r a n s a c t i o n ( SPISettings (12000000 , MSBFIRST , SPI_MODE0 ) ) ; //
gain control of SPI bus
36 digitalWrite ( slave_select , LOW ) ;
37 SPI . transfer (( reference & 0 xFF000000 ) >> 24) ;
38 SPI . transfer (( reference & 0 x00FF0000 ) >> 16) ;
39 SPI . transfer (( reference & 0 x0000FF00 ) >> 8) ;
40 SPI . transfer (( reference & 0 x000000FF ) >> 0) ;
41 digitalWrite ( slave_select , HIGH ) ;
42 SPI . en dTransac tion () ; // release the SPI bus
43 }
44 void setup () {
45 init_spi () ;
46 config_dac () ;
47 }
48 void loop () {
49 unsigned long value = 0 x03F00000 ;
50 unsigned long temp_value = 0 x00000000 ;
51 for ( int i = 0; i < 120; i ++) {
52 temp_value |= (( waveform [ i ] << 8) ) ;
53 SPI . b e g i n T r a n s a c t i o n ( SPISettings (16000000 , MSBFIRST , SPI_MODE0 ) ) ;
// gain control of SPI bus
54 digitalWrite ( slave_select , LOW ) ;
55 unsigned long value2 = value | temp_value ;
56 SPI . transfer ((( value2 & 0 xFF000000 ) >> 24) ) ;
57 SPI . transfer ((( value2 & 0 x00FF0000 ) >> 16) ) ;
58 SPI . transfer ((( value2 & 0 x0000FF00 ) >> 8) ) ;
59 SPI . transfer ((( value2 & 0 x000000FF ) >> 0) ) ;
60 digitalWrite ( slave_select , HIGH ) ;
61 SPI . en dTransac tion () ; // release the SPI bus
62 temp_value = 0 x00000000 ;
63 }
64 }

Codigo 4.8: Ejemplo - Bus SPI: Codigo


4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 72
CAPITULO 5
INTERRUPCIONES

Hasta ahora toda la obtencion de informacion se ha realizado de manera unidirec-


cional y siempre iniciando la peticion desde el Arduino. En este capitulo veremos como
trabajar con las interrupciones en el microcontrolador Atmega328. Las interrupciones
son senales recibidas por el procesador que indican que se ha producido un evento en
alguna de sus entradas o que un periferico quiere comunicarse con el procesador.
Las peticiones de interrupcion se pueden implementar de dos maneras diferentes.
La primera es utilizando la tecnica de polling que consiste en mirar cada cierto tiempo,
de manera regular, si un periferico o el procesador requiere de atencion. Como puedes
imaginar este procedimiento es altamente ineficiente dado que el procesador pierde
mucho tiempo de procesamiento en comprobaciones, por lo que hoy en da no se suele
utilizar. El segundo metodo es a traves de pedidos de interrupcion enviados por el
periferico o componente que quiere ser atendido. Al recibir el pedido de interrupcion
el procesador deja de ejecutar su tarea actual y atiende a la interrupcion mediante la
ejecucion de la rutina de interrupcion correspondiente al tipo de interrupcion que se
produjo..
La rutina de interrupcion es la funcion que se ejecuta cada vez que ocurre una
interrupcion en el procesador y es una pieza de codigo que depende del elemento
que interrumpe. Por ejemplo cuando apretamos una tecla el controlador del teclado
enva un pedido de interrupcion que hace que el contador de programa en logar de
continuar lo que estaba ejecutando salta a una direccion de memoria donde esta a su
vez la direccion de la rutina de atencion al teclado.
Existen diversas maneras de configurar la direccion de salto cuando ocurre una
interrupcion. A continuacion, se nombran las mas importantes:
Direccion fija: Las direcciones se encuentran ((cableadas)) al procesador por lo
que no se puede modificar la posicion de las interrupciones.
Direccion variable: Las direcciones se pueden encontrar en cualquier lugar
y pueden variar de un procesador a otro del mismo tipo (configuracion del
procesador).
El metodo que nos interesa a nosotros es el de las direcciones variables, dentro de
este grupo, existen diversas implementaciones. Una de las mas utilizadas y que ademas

73
5.1. INTERRUPCIONES EN EL ATMEGA328 74

Figura 5.1: Ejemplo de indireccion

sera la que utilizaremos, es el direccionamiento vectorizado o indirecto. Mediante


esta tecnica una lnea de interrupcion tiene asociada una posicion en el vector de
interrupciones donde se encuentra la direccion de la funcion que finalmente se lanzara.
En la Figura 5.1 se puede ver una ilustracion del procedimiento.
En este captulo veremos como trabajar con las interrupciones del microcontrola-
dor ATmega328 y realizaremos algunos ejemplos con el objetivo de asentar todos los
conceptos teoricos.

5.1. Interrupciones en el ATmega328


Cuando afrontamos la tarea de desarrollar una solucion basada en microcontro-
ladores, lo primero que debemos realizar es un analisis de caractersticas del mismo.
Algunas de las caractersticas son el numero de pines de entrada/salida, tipos de co-
municaciones soportadas, tipos de timers, interrupciones, etc. . . En este caso vamos
a analizar las interrupciones del microcontrolador Atmega328. Este microcontrolador
soporta hasta 27 tipos de interrupciones distintas, cada una con su nivel de priori-
dad correspondiente (ver Tabla 5.1). La prioridad va de mayor a menor, es decir, la
interrupcion 1 (reset) tiene la maxima prioridad y la numero 26 (spm ready) tiene la
mnima prioridad.
En la Tabla 5.1 podemos ver las interrupciones, internas y externas, soportadas
por el ATmega328. En la tabla se observa claramente lo explicado anteriormente en el
ejemplo del teclado, la interrupcion asociada al puerto de interrupcion INT0 movera
el contador de programa a la direccion 0x0002 y sera en ese lugar en el cual pondremos
el codigo para tratar dicha interrupcion.

5.2. Manipulacion software


A la hora de utilizar las interrupciones en el entorno Arduino, tenemos 3 posibili-
dades:

Librera AVR: Utilizar la librera avr que implementa todas las interrupciones.

Librera Arduino: Gestionar las interrupciones mediante las herramientas pro-


porcionadas por Arduino, aunque muchas interrupciones no se han implemen-
tado aun en esta librera.
5.2. MANIPULACION SOFTWARE 75

Cuadro 5.1: Tabla de interrupciones


VectorNo. Program Address(2) Source Interrupt Definition
1 0x0000(1) RESET External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset
2 0x0002 INT0 External Interrupt Request 0
3 0x0004 INT1 External Interrupt Request 1
4 0x0006 PCINT0 Pin Change Interrupt Request 0
5 0x0008 PCINT1 Pin Change Interrupt Request 1
6 0x000A PCINT2 Pin Change Interrupt Request 2
7 0x000C WDT Watchdog Time-out Interrupt
8 0x000E TIMER2 COMPA Timer/Counter2 Compare Match A
9 0x0010 TIMER2 COMPB Timer/Counter2 Compare Match B
10 0x0012 TIMER2 OVF Timer/Counter2 Overflow
11 0x0014 TIMER1 CAPT Timer/Counter1 Capture Event
12 0x0016 TIMER1 COMPA Timer/Counter1 Compare Match A
13 0x0018 TIMER1 COMPB Timer/Coutner1 Compare Match B
14 0x001A TIMER1 OVF Timer/Counter1 Overflow
15 0x001C TIMER0 COMPA Timer/Counter0 Compare Match A
16 0x001E TIMER0 COMPB Timer/Counter0 Compare Match B
17 0x0020 TIMER0 OVF Timer/Counter0 Overflow
18 0x0022 SPI, STC SPI Serial Transfer Complete
19 0x0024 USART, RX USART Rx Complete
20 0x0026 USART, UDRE USART, Data Register Empty
21 0x0028 USART, TX USART, Tx Complete
22 0x002A ADC ADC Conversion Complete
23 0x002C EE READY EEPROM Ready
24 0x002E ANALOG COMP Analog Comparator
25 0x0030 TWI 2-wire Serial Interface
26 0x0032 SPM READY Store Program Memory Ready

Manipulando directamente los registros correspondientes: Mediante las opera-


ciones explicadas en Seccion B.1 puedes manipular los registros de interrupciones
y gestionar al nivel mas bajo posible las interrupciones. Hay que tener en cuenta
que esta manera es la menos portable, ademas de ser la mas complicada.

5.2.1. Librera avr


Como ya se ha comentado a lo largo del libro, el entorno Arduino realmente es un
conjunto de libreras de abstraccion que hacen las cosas mas sencillas aunque puede
ocurrir que si queremos realizar tareas especficas, es probable que en algunos casos
esta librera no nos proporcione todas las comodidades que necesitemos, dado que ha
sido pensado para prototipos sencillos principalmente. En esta seccion, veremos como
utilizar la librera avr-libc para el manejo de las interrupciones. Esta librera es la que
utiliza Arduino y por lo tanto no tendremos que instalar ningun paquete adicional.
Para empezar a utilizar las interrupciones lo primero que debemos hacer es incluir
el archivo de cabecera que contiene las definiciones de las funciones que vamos a
utilizar. Este archivo se llama avr/interrupt.h, en el podemos encontrar diferentes
macros y definiciones que nos simplificaran la tarea de gestionar las interrupciones.
El ATmega, al igual que la mayora de los microcontroladores, utiliza un bit de
habilitacion global de interrupciones. Este bit es el bit 7 del registro de estado y control
SREGdel ATmega. Ademas cada periferico tiene su registro de interrupciones con su
bit de habilitacion correspondiente. Luego para que una habilitacion sea atendida
debera tener en 1 el bit de habilitacion global y a 1 el bit de habilitacion especfico.
Para habilitar o deshabilitar el bit de habilitacion global utilizaremos las macros
sei() o cli() respectivamente. Estas macros escriben un 1 o un 0 en el bit 7 de la
direccion de memoria 0x3F, que corresponde al registro de estado y control SREG.
Cuando una interrupcion ocurre, se pone a 0 el bit de habilitacion global y se
deshabilitan todas las interrupciones. El software de usuario puede poner a 1 otra
5.2. MANIPULACION SOFTWARE 76

1 # include < avr / interrupt .h >


2 ISR ( INT0 )
3 {
4 // P r o c e s a m i e n t o
5 evento = 1;
6 }

Codigo 5.1: Ejemplo de definicion de ISR

1 # include < avr / interrupt .h >


2 ISR ( BADISR_vect )
3 {
4 error =1;
5 }

Codigo 5.2: Captura de interrupcion no esperada

vez el bit de habilitacion para poder atender interrupciones anidadas, de manera que
cualquier interrupcion habilitada pueda interrumpir la rutina de interrupcion actual.
Luego, cuando se retorna de la interrupcion, el bit de habilitacion global se pone
automaticamente a 1.
Para habilitar las interrupciones correspondientes a cada periferico debemos poner
a 1 el bit correspondiente en el registro de mascara de Interrupcion. Si el bit de mascara
esta a 1 y a su vez el bit de habilitacion global esta a 1 entonces el procesador atendera
la interrupcion cuando ocurra. Para el caso de interrupciones externas existen registros
que permiten configurar que tipo de eventos se atenderan. Como se detallara mas
adelante, estos eventos pueden ser un cambio de nivel en un pin, un flanco ascendente
o descendente, etc. Si se desea profundizar mas acerca del manejo de registros de
interrupciones se recomienda al lector la hoja de datos del fabricante.
Ahora que sabemos como habilitar o deshabilitar las interrupciones, el siguiente
paso para poder atender a la misma es definir la rutina de interrupcion.
Para definir la rutina debemos utilizar la macro ISR. Esta macro tiene como
parametro una constante que define la interrupcion que se desea atender. Normal-
mente el nombre de la constante es el mismo que el especificado en el datasheet del
fabricante. Como ejemplo, si quisieramos definir una rutina de interrupcion para INT0
el codigo sera similar al mostrado en Cod. 5.1
Otra practica que es recomendable sobre todo en disenos que hacen uso intensivo
de las interrupciones, es capturar todas aquellas interrupciones para las cuales no se
ha definido ninguna rutina de interrupcion (normalmente esto indica un bug). Para
capturar todas las interrupciones se debe pasar la constante BADISR vect a la macro
ISR. En Cod. 5.2 se puede ver un ejemplo.
Otro caso particular, y para el cual existe una solucion, es cuando se desea utilizar
una misma rutina de interrupcion para dos interrupciones diferentes. Para conseguir
este comportamiento debemos utilizar la macro ISR ALIASOF junto a la macro ISR.
En Cod. 5.3 se puede ver un ejemplo de uso, en el cual la rutina de interrupcion de
la interrupcion PCINT1 pasa a ser igual a la de PCINT0.
5.2. MANIPULACION SOFTWARE 77

1 # include < avr / interrupt .h >


2 ISR ( PCINT0_vect )
3 {
4 // P r o c e s a m i e n t o
5 evento = 1;
6 }
7 ISR ( PCINT1_vect , ISR_ALIASOF ( PCINT0_vect ) ) ;

Codigo 5.3: Captura de interrupcion no esperada

5.2.2. Librera Arduino


Arduino provee de un conjunto de funciones para el tratamiento de interrupciones
en sus placas de desarrollo. La librera de Arduino solo da soporte a un numero
limitado de interrupciones, a continuacion explicaremos cada una de las funciones de
la librera:

noInterrupts(): Esta funcion al igual que cli nos permite deshabilitar las in-
terrupciones.

interrupts(): Habilita las interrupciones del mismo modo que sei.

attachInterrupt(): Permite seleccionar una rutina de interrupcion para una


interrupcion determinada. En funcion de la placa de desarrollo el numero de pin
variara, se recomienda ver el datasheet, as como el esquematico para averiguar el
pin correspondiente a la interrupcion. Las interrupciones que estan soportadas
por esta funcion son las siguientes: INT0, INT1, INT2, INT3, INT4, INT5.
Ademas, mediante un parametro podemos indicar en que momento queremos
que se active la rutina de interrupcion pudiendo ser:

LOW: Cuando el pin esta en estado ((bajo)).


CHANGE: Cuando se produce un cambio de estado en el pin (de alto a
bajo o viceversa).
RISING: En el flanco positivo de un cambio de estado.
FALLING: En el flanco negativo. de un cambio de estado

detachInterrupt(): Mediante esta funcion podemos eliminar la rutina de in-


terrupcion que se indique como parametro.

En general se recomienda usar la librera de AVR dado que aparte de ser mas
eficiente es mucho mas flexible.

5.2.3. Consideraciones importantes


A la hora de utilizar las interrupciones tenemos que tener algunas consideraciones
en cuenta.
Las interrupciones por defecto estan habilitadas desde el inicio, por lo que no es
necesario utilizar la macro sei en un primer instante, aunque si es recomendable.
Algunas funciones como delay() o millis() utilizan las interrupciones para su funcio-
namiento. Es importante que se tenga en cuenta el uso que hacen las funciones de
5.3. EJEMPLO 1: PRIMERA RUTINA DE INTERRUPCION 78

Cuadro 5.2: Ejemplo 1: Tabla de entrada/salida

Entrada/Salida Descripcion Nombre variable Pin


Entrada Boton button 2
Salida Led notification led 13

las interrupciones ya que por ejemplo, para usar una funcion delay() dentro de una
rutina de interrupcion debemos habilitar otra vez el bit de habilitacion global, dado
que por defecto al entrar en una rutina de interrupcion se llama de forma implcita
a la macro cli.

5.3. Ejemplo 1: Primera rutina de interrupcion


En este primer ejemplo veremos un ejemplo de utilizacion de las interrupciones en
el procesador ATmega328 usando las libreras avr. El ejemplo consistira en encender
y apagar un led pulsando un boton. Seguramente al lector le resultara familiar pues
en la Seccion 3.2 se estudio, sin embargo la principal diferencia es que en este caso no
leeremos el valor de un pin, sino que el propio evento sera el encargado de realizar la
accion.
A partir de esta seccion no realizaremos los diagramas de flujo con el objetivo de
no hacer tan extensos los ejemplos, pero se recomienda que el lector tenga presente
siempre este paso en sus disenos.

5.3.1. Tabla de entrada/salida


Lo mas importante de la Tabla 5.2 es la eleccion del pin para el boton. Si ob-
servamos el datasheet del ATmega328 veremos que los pines que tienen asociadas a
las interrupciones externas INT0 e INT1 son los pines PD1 y PD2 los cuales, en
el Arduino, estan conectados a los pines fsicos 2 y 3. El pin para el led puede ser
cualquiera de los de salida digitales.

5.3.2. Codigo
El codigo para manejar las interrupciones en los microcontroladores ATmega es
muy sencillo y se puede resumir en los siguientes pasos:
Habilitar registro global de interrupciones: Mediante las macros sei y cli
podemos habilitar y deshabilitar el bit global de interrupciones.
Habilitar interrupcion especfica: Cada interrupcion tiene asociado un bit
de mascara de activacion individual. Mediante este bit podemos permitir in-
terrumpir al procesador mediante dicha interrupcion. En nuestro caso y para
nuestro procesador el registro se llama EIMSK y el bit que pondremos en 1 es
el correspondiente a la INT0 (lnea 13).
Configurar la interrupcion: Algunas interrupciones tienen registros que per-
miten configurar cuando lanzar la misma. En nuestro caso, para la interrupcion
INT0 seleccionaremos el flanco descendente en el pin 2 poniendo un 1 en el
bit ISC01.
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 79

1 #i n c l u d e <a v r / i n t e r r u p t . h>
2
3 const int n o t i f i c a t i o n l e d = 13;
4 c o n s t i n t b ut ton = 2 ;
5
6
7 void setup ( void )
8 {
9 pinMode ( button , INPUT) ;
10 pinMode ( n o t i f i c a t i o n l e d , OUTPUT) ;
11 d i g i t a l W r i t e ( button , HIGH) ;
12 sei () ;
13 EIMSK |= ( 1 << INT0 ) ;
14 EICRA |= ( 1 << ISC01 ) ;
15 }
16
17 void loop ( void )
18 {
19
20 }
21
22 ISR ( INT0 vect )
23 {
24 digitalWrite ( notification led , ! digitalRead ( n o t i f i c a t i o n l e d ) ) ;
25 }

Codigo 5.4: Ejemplo 1 - Interrupciones

Definir la rutina de interrupcion: Mediante la macro ISR podemos indicar


las acciones que se deberan tomar al atender una interrupcion.

En Cod. 5.4 se puede ver la instanciacion de los pasos anteriormente nombrados.


Un procesamiento intensivo dentro de la rutina de interrupcion no es una buena idea
dado que durante el tiempo en que el procesador se encuentre en esta funcion no
realizara ninguna otra accion. En este ejemplo se ha modificado el estado del pin 13
dentro de la funcion unicamente con el objetivo de simplificar el codigo, sin embargo
se deja como ejercicio al lector modificar el ejercicio para que la modificacion no se
haga dentro de dicha rutina de interrupcion1 .
Como puedes observar, el bucle principal esta vaco, sin embargo si pulsas al boton
veras como el led se enciende y apaga. Este comportamiento se llama asincrona, dado
que no hay ningun lugar donde se llame a la funcion que enciende y apaga el led, es
el propio suceso el que llama a dicha funcion.

5.4. Ejemplo 2: Midiendo distancias


En este ejemplo vamos a utilizar las interrupciones con la ayuda de la librera de
Arduino para medir las distancias a la que se encuentran los objetos haciendo uso
tambien del sensor HC-SR04 que se puede observar en la Figura 5.2.
El funcionamiento de este sensor es muy sencillo. El HC-SR04 posee 4 pines. A
continuacion se explicara cada uno de ellos:
1 Como pista se podra utilizar algun tipo de ((bandera)) o ((flag))
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 80

Figura 5.2: Sensor emisor/receptor ultrasonidos

Cuadro 5.3: Ejemplo 2: Tabla de entrada/salida

Entrada/Salida Descripcion Nombre variable Pin


Entrada Pin de recepcion echo pin 2
del sensor HC-
SR04
Salida Pin gatillo del sen- tigger pin 3
sor HC-SR04
Salida Transmision puerto Tx 1
serie

VCC: Este sensor trabaja a 5V por lo que podra ser conectado directamente
al Arduino a traves del pin de 5V del mismo.

GND: Comun.

Trigger: El trigger o gatillo nos permite indicar al sensor de que queremos


hacer una medicion. Para que el sensor sepa que queremos realizar la medicion
y que no es un falso positivo debemos activar este pin como mnimo durante 10
microsegundos.

Echo: Este pin n0s indicara la distancia a la que se encuentra el objeto. Como?
muy sencillo. Una vez que se recibe la senal del trigger el sensor manda una
senal de 40Khz que cuando rebota contra un objeto y vuelve al sensor de nuevo
hace que el sensor emita un pulso positivo con una longitud proporcional a la
distancia del objeto por el pin echo.

5.4.1. Tabla de entrada/salida

La Tabla 5.3 es bastante clara por lo que no se explicara nada mas al respecto.
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 81

5.4.2. Codigo
El codigo se puede ver en Cod. 5.5 a continuacion veremos las partes mas impor-
tantes del mismo.
Cuando el Arduino entra en la fase de setup configuramos la comunicacion serie
para poder mostrar la distancia por pantalla y ademas configuramos los pines de
entrada/salida. En este caso tal y como nombramos en la Tabla 5.3 tenemos un pin
de entrada y dos de salida. Los de salida corresponden uno al trigger del sensor y el otro
al pin de transmision Tx del puerto serie. El pin de entrada esta conectado a la senal
echo del sensor. Es importante en este punto recordar que tenemos lneas limitadas de
interrupcion en nuestra placa y que estas estan conectadas a unos pines determinados.
En este caso, la lnea de interrupcion que hemos utilizado es la conectada al pin 2 del
Arduino uno, que corresponde a la interrupcion INT0 (ver en el datasheet).
El siguiente paso y seguramente el mas importante en este ejemplo es la habilita-
cion de la interrupcion del pin 2. Para ello utilizamos la funcion attachInterrupt de
la librera de Arduino. Esta funcion tiene tres parametros: N de interrupcion, rutina
de interrupcion que se ejecutara, y el tipo de evento que disparara la interrupcion
(cambio de estado, flanco, etc). Como puedes observar esto es lo mismo que hemos
hecho en las lneas 13 y 14 del ejemplo 1, solo que en lugar de escribir en los registros
utilizando las libreras avr, lo que hacemos es llamar a esta funcion que se encarga de
eso. Por lo tanto no incluimos el archivo de cabecera avr/interrupt en este ejemplo.
En este ejemplo el primer parametro es un 0. Este parametro no se refiere al pin al
que esta conectado el sensor, sino a la interrupcion del microcontrolador. En este caso
el pin 2 esta conectado como ya hemos dicho a la interrupcion 0 por lo que este sera
el numero que tendremos que utilizar. En las ultimas versiones del entorno Arduino
se ha anadido una macro llamada digitalPinToInterrupt(pin) que permite despreocu-
parnos de este parametro. Comprueba si tu entorno ha anadido este macro y si es
as puedes utilizarla. El otro parametro importante es la funcion que sera llamada
cuando recibamos la interrupcion (rutina de Interrupcion). La funcion no debera re-
tornar ningun tipo (void). Por ultimo el parametro CHANGE indica que nos avise
en cualquier cambio en el pin, esto es, alto a bajo o bajo a alto. (en Subseccion 5.2.2
puedes ver las opciones que se pueden pasar a la funcion).
Como ya comentamos en Seccion 5.4 para saber la distancia a la que se encuentra
un objeto debemos medir el tiempo entre el flanco de subida del pin echo y el flanco de
bajada del mismo. Recordemos que la distancia es proporcional a dicho tiempo. Como
la funcion interrupt echo() se llama cada vez que hay una variacion en el pin echo lo
unico que tenemos que registrar es cuando empieza (flanco de subida) y cuando termi-
na (flanco de bajada). Esta comprobacion se hace mediante la funcion digitalRead()
que permite saber en que estado estamos y en funcion del mismo registra el tiempo
en las variables correspondientes. El tiempo se registra con la funcion micros(). Se
podra usar la funcion millis() para medir tiempos en milisegundos pero esto no es
posible dentro de una rutina de interrupcion.
En el datasheet del sensor se indica que el tiempo se debe dividir entre 58 para
obtener la distancia en centmetros.
Otro punto a resaltar es la activacion del pin trigger que debe estar en estado
alto durante al menos 10 microsegundos. Aunque esto se podra realizar mediante un
timer e interrupciones, en este caso hemos optado por un delay en microsegundos.
El retardo final de 100 milisegundos (linea 21) unicamente tiene la funcion de
mejorar la visualizacion de la distancia.
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 82

1 const int echo pin = 2;


2 const int t r i g g e r p i n = 3;
3
4 long echo end = 0 ;
5 long e c h o s t a r t = 0;
6 long echo duration = 0;
7
8
9 void setup ( ) {
10 S e r i a l . begin (9600) ;
11 pinMode ( e c h o p i n , INPUT) ;
12 pinMode ( t r i g g e r p i n , OUTPUT) ;
13 attachInterrupt ( digitalPinToInterrupt ( echo pin ) , interrupt echo ,
CHANGE) ;
14 }
15
16 void loop ( ) {
17 d i g i t a l W r i t e ( t r i g g e r p i n , HIGH) ;
18 delayMicroseconds (50) ;
19 d i g i t a l W r i t e ( t r i g g e r p i n ,LOW) ;
20 S e r i a l . p r i n t l n ( echo duration / 58) ;
21 delay (100) ;
22 }
23
24 void i n t e r r u p t e c h o ( ) {
25 i f ( d i g i t a l R e a d ( e c h o p i n ) == HIGH) {
26 echo end = 0 ;
27 e c h o s t a r t = micros ( ) ;
28 }else{
29 echo end = micros ( ) ;
30 echo duration = echo end e c h o s t a r t ;
31 }
32 }

Codigo 5.5: Ejemplo 2 - Midiendo distancias


CAPITULO 6
MULTITASKING Y TIMERS

Hasta ahora cada vez que buscamos repetir una accion en un tiempo determinado
hemos utilizado la funcion delay() que nos provoca un retardo en la ejecucion duran-
te el tiempo especificado en la misma, en milisegundos. Como se explicara en este
captulo la utilizacion de dicha funcion no debe realizarse en todos los casos, es mas,
por regla general debera evitarse su uso.
Cuando se utiliza la funcion delay() el Arduino se queda en un bucle activo. Por
lo tanto, durante ese tiempo el microcontrolador no realizara ninguna otra funcion,
esto es, estara consumiendo energa pero no estara haciendo ningun trabajo util como
por ejemplo podra ser registrar diferentes valores de los sensores.
En este captulo veremos de manera resumida como utilizar otras funciones como
por ejemplo millis() para evitar los problemas que trae consigo la funcion delay().

6.1. Timers
Los microcontroladores como ya sabras estan compuestos de un conjunto de pe-
rifericos como por ejemplo los TIMER, USARTS, WDG, etc que nos permiten expan-
dir su funcionalidad. En este caso hablaremos de un periferico muy importante y que
sin duda alguna es la base para todas las funciones relacionadas con el tiempo. Este
periferico es el timer. Un timer es un circuito que nos permite contar eventos, por
ejemplo ciclos de reloj, eventos externos, etc, para generar formas de ondas, circuitos
de reloj, comparadores, etc.
Dependiendo de en que microcontrolador este basado nuestro Arduino tendremos
mas o menos timers, en el caso del microcontrolador ATmega328 podemos encontrar
tres timers: dos de 8 bits y uno mas de 16 bits.
En la Figura 6.1 puedes encontrar un diagrama en bloques del timer de 8 bits del
ATmega328. A continuacion pasaremos a explicar cada uno de los componentes que
forman este modulo de tal modo que podamos tener una vision amplia a la hora de
utilizar cualquier funcion relacionada con el tiempo en nuestros disenos.
Basicamente un timer esta formado por un circuito de control (Control Logic) que
se encarga de contar de manera ascendente o descendente los eventos que se producen

83
6.1. TIMERS 84

Figura 6.1: Diagrama TIMER 8 bits ATmega328

a su entrada, guardando la cuenta en un registro especfico (TCNTn). Este valor de


cuenta se puede utilizar para generar diferentes funcionalidades tales como comparar
con otro valor contenido en otro registro, generar una forma de onda o generar una
interrupcion cuando se llega a un determinado valor, por ejemplo. El circuito selector
de reloj (Clock Select) determina cual es la entrada que se lee, reloj o evento externo
y en que flanco del evento se produce la captura, y si se va a tener en cuenta o no un
factor de escala. El factor de escala es un circuito denominado prescaler que genera un
evento cada cierta cantidad de ciclos de reloj. Esta generacion de factor de escala es
configurable desde el registro Clock prescaler del ATMega328, ubicado en la direccion
de memoria 0x61. Empezaremos explicando los registros para continuar con los modos
de funcionamiento del timer.

6.1.1. Registros
TCNTn: Este registro nos permite saber en todo momento el valor del timer
correspondiente (la letra n indica el numero de timer que puede ser 0 o 1). Este
valor se incrementa o decrementa, segun sea el modo habilitado, cada vez que
se produzca un evento de reloj, o de evento externo, a la entrada del circuito de
control. El registro TCNT0 es un registro de 8 bits por lo que el valor maximo
que se podra alcanzar es 255 o 0xFF en hexadecimal.

OCR0a: El valor de este registro se compara cada ciclo del timer con el valor
del registro TCNTn. Cuando se alcanza el valor configurado en este registro se
activara el bit OCF0A. El resultado de la comparacion se puede utilizar para
generar distintas formas de onda o PWM como se vera mas adelante.

OCR0b: El mismo comportamiento que OCR0a.


6.1. TIMERS 85

TCCR0A, TCCR0B: Estos dos registros de control nos permiten configurar


el timer de tal modo que se comporte como un comparador, como un generador
de funciones, etc.

TIMSK0 : Mediante este registro podremos habilitar o deshabilitar las diferen-


tes interrupciones generadas por el TIMER (ver datasheet).

TIFR0 : Parecido a TIMSK0 la diferencia radica en que este ultimo unicamente


nos indica que se ha generado una interrupcion, como por ejemplo cuando se
llega al desbordamiento que se activa el bit TOV0.

6.1.2. Modos de funcionamiento


A continuacion veremos los distintos modos de funcionamiento que posee el timer.
Estos modos se veran de manera general, lo suficiente como para abordar los ejemplos
de este captulo, y se deja como tarea para el lector una lectura mas detallada de los
mismos.

Modo normal: Es el modo mas simple de utilizacion. Para activar este modo
se deben poner a 0 los bits WGM0:2 ubicados en los registros TCCR0A y
TCCR0B (cuidado, los dos primeros bits se encuentran en el registro TCCR0A,
sin embargo el ultimo bit WGM02 se encuentra en el registro TCCR0B ). En
este modo, el registro TCNT0 se incrementa en cada ciclo de reloj hasta llegar
al valor 0xFF donde se desborda y vuelve al valor 0x00, activando a la vez la
senal de overflow (TOV0). Esta senal no volvera a 0 de forma automatica, sera
el programador el encargado de realizar dicho cambio. Si esta habilitado el bit
de interrupcion por desbordamiento, cuando se produzca el mismo se generara
una interrupcion.

Modo CTC o Clear Timer on Compare Match: Para activar este modo
debemos configurar los bits WGM02:0 al valor 2, esto es, ((010)). En este modo
el registro OCR0A se utiliza para variar la resolucion del timer. La diferencia
con el modo normal es que en este modo cuando se alcanza el valor configurado
en el registro OCR0a, el registro TCNT0 se pone de nuevo al valor 0. En la
Figura 6.2 se puede ver un esquema que ejemplifica su uso. TCNT0 empieza
en el valor 0 y va ascendiendo hasta llegar al valor configurado en OCR0a
(raya horizontal que indica el valor de comparacion). Una vez que se alcanza
dicho valor la bandera OC0a del registro TIFR0 se activa de tal modo que si
el programador comprueba dicho bit en su programa sabra si se ha alcanzado
el valor de comparacion. La ejecucion repetida del timer en modo CTC nos
genera una forma de onda en el pin de salida OCn. Esta forma de onda se
puede ver en un osciloscopio si conectamos una de las puntas del mismo al
pin asociado a la senal OC0 en el caso de que el timer este configurado para
permutar dicho pin en cada acierto de comparacion (esto se configura en el
registro TCCR0A, TCCR0B ). En el diagrama de la Figura 6.2 se muestran
diferentes valores del registro de comparacion OCR0a, variando el periodo de la
senal. Segun el datasheet para generar una senal cuadrada con una frecuencia
determinada podemos utilizar la formula mostrada en la Ecuacion 6.1. Mas
adelante, en los ejemplos, veremos como utilizar esta formula y como seleccionar
los valores para cada uno de los parametros.
6.1. TIMERS 86

Figura 6.2: Modo CTC

Figura 6.3: Modo Fast PWM

Modo Fast PWM: El modo Fast PWM o desde ahora PWM (Pulse Width
Modulation) (con el objetivo de simplificar), se habilita mediante la activacion
de los bit WGM02:0 = 3 o 7. Con este modo se nos proporciona un manera
de generar formas de onda PWM de alta frecuencia y alta precision. El timer
cuenta desde el valor indicado en BOTTOM hasta el valor indicado en TOP,
cuando se alcanza el valor TOP se vuelve a iniciar la cuenta desde BOTTOM.
El valor de TOP es 0xFF cuando WGM02:0 es 3, sino toma el valor del registro
OCR0A cuando es 7. Posee dos modos, salida de comparacion no-invertida o
invertida. En el modo no-invertida la salida se pone a 0 cuando se alcanza el
valor de comparacion y a 1 cuando se llega al valor BOTTOM. En los ejemplos
veremos como se materializa este modo pero para que el lector se haga una idea,
este modo nos permite ((indicar)) al microcontrolador que porcentaje de tiempo
queremos que el pin este en alto y cuanto tiempo queremos que este en bajo,
pudiendo emular valores analogicos variando el ancho del semiciclo positivo de
la senal generada. Si por ejemplo alimentamos un motor con una frecuencia de
trabajo ((X)), podremos variar su velocidad modificando el porcentaje de tiempo
que se entrega energa al motor. En la Figura 6.3 se puede ver un ejemplo de
uso del modo PWM, a continuacion pasaremos a explicar el mismo aunque en
los ejemplos se vera de manera mas detallada.
6.1. TIMERS 87

Figura 6.4: Registro TCCR1A

El contador en este caso empieza en 0 y el valor de OCRn no ha sido seleccionado


de modo que hasta que el mismo no se selecciona (tercera barra horizontal) no
se modifica el estado del pin OCnX. Una vez que se selecciona un valor para
OCRnx (OCR0a,b), cuando el contador alcanza el valor del registro OCRnx se
pone a estado bajo el pin OCnx en el caso de la senal normal y se pone en alto
en el caso de la senal invertida. El contador continua su incremento hasta que
llega al valor top. Sera cuando alcance este valor cuando el pin vuelva a cambiar
su estado.
Como puedes observar, en funcion del valor del registro OCRnx podremos man-
tener mas tiempo la senal en alto, o lo que es lo mismo para nuestro ejemplo
podremos entregar mas energa al motor.
La ecuacion para el calculo de la frecuencia se puede consultar en Ecuacion 6.2.

Modo Fast PWM con correccion de fase: Este modo es muy parecido
al Fast PWM con la unica diferencia que la salida esta en fase y ademas la
frecuencia maxima que se puede alcanzar es la mitad con respecto al modo
PWM ya que ahora el contador trabaja en modo ascendente y descendente.
La formula para calcular el valor de TOP y del prescaler es la mostrada en
Ecuacion 6.3.

f clk
f 0Cnx = (6.1)
2 N (1 + OCRnX)

f clk
f 0Cnx = (6.2)
N (1 + T OP )

f clk
f 0CnxP CP W M = (6.3)
2 N T OP

6.1.3. Ejemplos de uso


Una vez que tenemos claro los modos de uso de los timer y cada uno de los registros
que estan implicados en su funcionamiento es el momento de programar el ATmega
para poner en practica lo aprendido.
Como el Arduino utiliza el timer 0 para diferentes funciones como por ejemplo la
funcion delay() dejaremos de lado este timer y utilizaremos en su lugar el timer 1. La
diferencia entre el timer 0 y el timer 1 es unicamente la cantidad de bits de ancho as
como el numero de funciones que poseen cada uno. Tambien se podra haber utilizad
el timer 2 el cual es de 8 bits.
6.1. TIMERS 88

Ejemplo de uso basico

En este primer ejemplo veremos como podemos configurar el timer unicamente


para que nos ((avise)) cuando haya pasado un determinado tiempo que indicaremos.
Lo primero que debemos tener claro a la hora de configurar un timer en este
modo es, como parece logico, el tiempo en el cual queremos que nos avise. En este
caso, vamos a imaginar que queremos que nos avise cada 4 segundos de tal manera
que cada vez que se alcance ese instante de tiempo imprimiremos por el puerto serie
un mensaje.
Una vez que tenemos claro el tiempo en el que queremos que nos avise el TIMER,
el siguiente paso es configurar cada uno de los registros implicados. A continuacion,
explicaremos como configurar cada registro. Es importante tener en cuenta que todas
esta informacion esta extrada del datasheet del microcontrolador. Es imposible
configurar bien un microcontrolador sin leerse el datasheet varias veces.
El registro TCCR1A posee diferentes bits que nos permiten modificar el com-
portamiento de la unidad de comparacion y dos de los bits que configuran el com-
portamiento del propio timer (WGM11 y WGM10 ). La razon por la cual solo se
pusieron dos bits de los cuatro necesarios para configurar el timer en este registro es
una decision de diseno pero los bits WGM12 y WGM13 se encuentran en el registro
TCCR1B. En este caso buscamos configurar el timer en modo normal y deshabilitar
las unidades de comparacion pero todava nos falta un dato mas, el prescaler. Para
calcular el valor del prescaler en primer lugar calculamos el tiempo que tarda nuestro
ATmega en incrementar el valor del registro TCNT1. Si estamos en un sistema con
reloj de 16Mhz, el tiempo por cada ciclo sera igual a 1/16000000 o lo que es lo mismo:
62.5 ns. Teniendo en cuenta esto, el tiempo que tardara el timer en desbordarse sera
igual a: 216 62,59 = 4ms. Como puedes observar este valor es muy pequeno y no
nos sirve para alcanzar esos 4 segundos que buscamos. La solucion podra ser aumen-
tar el numero de bits del timer o por otro lado aumentar el tiempo que tarda en
incrementar el registro TCNT. Como la primera opcion (aumentar el numero de bits)
no depende de nosotros y solo podramos tenerla en cuenta si tuvieramos un timer
con mas bits (no lo tenemos), abordaremos la segunda solucion, es decir, aumentar el
tiempo que tarda el timer en incrementar el registro TCNT. Si utilizamos un pres-
caler de 1024 entonces significara que por cada 1024 ciclos de reloj incrementaremos
en uno el valor del registro TCNT1 por lo que volviendo a los calculos anteriores el
tiempo que tardara en desbordarse el timer sera igual a: 1024 216 62,59 = 4,194s.
Lo conseguimos! ya tenemos el timer configurado para que tarde 4 segundos. . . En
realidad esto no es del todo cierto dado que segun nuestros calculos el tiempo es de
4.194 s por lo que intentaremos ajustar mas el valor, recuerda, 1 decima de segundo
puede significar ganar o perder una carrera en formula uno pero para una primera
aproximacion no esta nada mal. Si queremos un tiempo exactamente igual a 4 segun-
dos unicamente tenemos que realizar una ((regla de tres)) sabiendo que el tiempo que
1
tarda en incrementar el registros TCNT es de 1024 16000000 obteniendo el resultado
de que el numero de incrementos que se realizaran en 4 segundos sera igual a 62500.
Con todos estos calculos ya sabemos que el valor del prescaler es de 1024 y que nos
gustara que en vez de a los 62536 incrementos el TIMER se desbordara a los 62500.
Para esto ultimo unicamente tenemos que modificar el valor de TOP o modificar el
valor en el que empieza el registro TCNT.
En el codigo mostrado en Cod. 6.1 puedes ver como se instancian los pasos ante-
riormente mencionados. A continuacion explicaremos las partes mas importantes del
6.1. TIMERS 89

codigo.
En el setup() se llama a la macro cli(), como ya se explico en la Captulo 5 esta
macro unicamente deshabilita las interrupciones de manera global, de manera que
podamos realizar toda la configuracion sin miedo a ser interrumpidos. Una vez desha-
bilitadas las interrupciones, el siguiente paso es configurar tanto el registro TCCR1A
como el registro TCCR1B. El valor 0 asignado a TCCR1A tiene que ver con lo ex-
plicado anteriormente. En la Figura 6.4 puedes la definicion del registro. El registro
TCCR1B sin embargo s que tiene un valor ((util)) asignado. En un primer momento
el lector podra preguntarse por que se asigna dicho valor. La razon se encuentra en
la configuracion del prescaler. Si echamos un vistazo a la Figura 6.6 y a la Figura 6.5
podemos ver que para aplicar un prescaler de 1024 tenemos que asignar el valor ((101))
a los bits ((CS1:2-0)). Como todos los demas bits deben estar a cero (ver explicacion
anterior y datasheet) el valor ((00-00101)) en decimal es 5.
El siguiente paso consiste en configurar el registro TIMSK1. En este caso hemos
optado por otro modo de asignacion el cual se explica en Seccion B.1. El bit que hay
que habilitar es el TOIE1 que nos permitira indicar al microcontrolador que queremos
interrumpir nuestro programa cuando se produzca un overflow.
Por ultimo, en la fase de setup() tenemos que poner el valor inicial de 536 al registro
contador. Es muy importante que siempre que modifiquemos un registro de 16 bits
en la arquitectura AVR, en primer lugar modifiquemos el registro que representa la
parte alta (H) y despues el que representa la parte baja (L). En este caso el valor
en binario de 536 es ((00000010 00011000)) por lo que la parte alta la escribimos en
el registro TCNT1H y la parte baja en el registro TCNT1L. Como puedes observar
en este caso hemos utilizado la notacion binaria con el objetivo de que se cubran
todas las posibilidades, aunque la mas portable es la notacion utilizada en el registro
TIMSK1.
Lo que resta del codigo ya ha sido explicado en captulos anteriores y por lo tanto
no se volvera a explicar.

Ejemplo de uso modo CTC


En este ejemplo cubriremos el modo de uso CTC. Este modo es el mas utilizado
cuando queremos medir tiempos dado que es mas sencillo que el modo normal y
permite actuar sobre un pin de manera inmediata sin necesidad de ninguna instruccion
software, ademas de contar con una precision mayor al poder modificar el valor TOP.
Se configurara el timer 1 de modo que cada medio segundo se encienda un led y ca-
da segundo se encienda otro. En este caso podremos ver como sin necesidad de ningun
codigo extra podemos realizar acciones cada X segundos y con una precision bastante
buena. Si sustituyes el led por un transistor que sustituya el boton de la camara de
fotos tendras un temporizador de obturacion. (Solo realizar esta modificacion si se
tienen los conocimientos suficientes sobre electronica)
El modo de proceder es muy parecido al de Subseccion 6.1.3 con la diferencia que
en este caso tendremos que configurar el pin de salida y ademas modificaremos el
valor de ((overflow)) del timer (aunque como ya veremos, realmente no es el valor de
((overflow))).
Los calculos para el tiempo en este caso se dejaran como tarea para el lector
de modo que pueda comprobar si ha comprendido los conceptos explicados en la
Subseccion 6.1.3
En Figura 6.7 podemos ver los diferentes modos que posee el timer para actuar
6.1. TIMERS 90

1 v o l a t i l e long last update = 0;


2 v o l a t i l e long last compare update = 0;
3 int led = 13;
4 void setup ( ) {
5 c l i () ;
6 TCCR1A = 0 ;
7 TCCR1B = 5 ; // 0 b00 0101
8 TIMSK1 |= ( 1 << TOIE1 ) ;
9 TCNT1H = 0 b00000010 ;
10 TCNT1L = 0 b00011000 ;
11 S e r i a l . begin (9600) ;
12 sei () ;
13
14 }
15
16
17 void loop ( ) {
18 i f ( l a s t u p d a t e != l a s t c o m p a r e u p d a t e ) {
19 Serial . println ( last update ) ;
20 last compare update = last update ;
21 }
22 }
23
24 ISR ( TIMER1 OVF vect ) {
25 last update = micros ( ) ;
26 TCNT1H = 0 b00000010 ;
27 TCNT1L = 0 b00011000 ;
28 }

Codigo 6.1: Ejemplo de uso TIMER

Figura 6.5: Bits de configuracion del presclarer

Figura 6.6: Registro TCCR1B


6.1. TIMERS 91

Figura 6.7: Bits para configuracion del pin de salida

sobre la salida. En nuestro caso elegiremos el modo ((01)) que nos permite permutar
la salida en cada comparacion con los registros OCR1A/B.
El siguiente paso consiste en configurar el timer para que actue en modo CTC.
En la Figura 6.8 se puede ver la tabla de configuracion en funcion de los bits WGM.
A continuacion, explicaremos los referentes al modo CTC.
Si configuramos los bits WGM en el modo ((0100)) el timer empezara a contar
hasta llegar al valor configurado en el registro OCRxA y OCRxB, en este momento se
generara una interrupcion siempre y cuando el bit OCIEXa o OCIEXb del registro
TIMSKX este habilitado, una vez que ocurra ese evento, el contador seguira ascen-
diendo hasta llegar al valor TOP donde se generara la interrupcion de overflow y
finalmente se volvera a contar desde 0.
El lector puede observar que lo que estamos realizando en este ejercicio es generar
dos frecuencias a partir de un unico timer, esto no siempre sera posible, en este
caso al ser una frecuencia multiplo de la otra no habra ningun problema.
Para ejemplificar todo lo explicado pasaremos a analizar el codigo Cod. 6.2.
En primer lugar, se bloquean las interrupciones con la macro cli() esto mismo
se podra haber hecho con la funcion noInterrupts(), sin embargo, es mas eficiente
y portable la segunda opcion. Seguidamente se ponen a cero los dos registros de
configuracion del timer de modo que tengamos certeza de que partimos de inicialmente
de 0. Basandonos en el datasheet del ATmega328, configuramos el registro TCCR1A.
Como el lector puede observar el unico bit que tiene que estar habilitado es el 0 de los
comparadores, es decir, el COM1B0 y el COM1A0. Observa como en este caso se ha
utilizado la notacion que se considera mas portable (lnea 5). Con el registro TCCR1B
hacemos lo mismo, solo que en este caso los bits a activar son los del prescaler y los
del modo de funcionamiento del timer.
Para ((permitir)) al timer enviar interrupciones al procesador, debemos activar el
bit OCIE1A. Con esto conseguiremos generar dos frecuencias distintas. La frecuencia
de dos hercios corresponde al comparador ((A)) mientras que la frecuencia de un hercio
pertenece al registro ((B)). Por otro lado, el registro ICR1 es el encargado de situar el
valor de TOP. Es importante observar que tanto el valor de ICR1 como el de OCR1B
es el mismo por que?.
Una vez configurados todos los registros se procede a poner los pines de entra-
da/salida en modo salida. Hay que tener en cuenta que hemos anadido un pin mas,
esto nos permitira generar una senal de referencia para saber que nuestros calculos
son correctos. La eleccion de estos pines se debe al mapeo que realiza el Arduino
de los pines del ATmega328. Si quieres saber mas sobre esto se recomienda ver el
esquematico del Arduino UNO as como el datasheet del ATmega328.
En la rutina de interrupcion TIMER1 COMPA vect() se comprueba el valor del
6.1. TIMERS 92

Figura 6.8: Configuracion modo CTC

registro y en funcion de si es menor que el registro ((B)) se modifica el valor de la


siguiente comparacion. Se deja al lector el razonar el porque de estas comprobaciones.
En el loop se genera una senal de un hercio con el objetivo de compararla con la
generada por nosotros mediante el timer.

Ejemplo de uso modo Fast PWM

El ultimo modo que veremos en profundidad sera el modo Fast PWM. Como ya
se comento anteriormente, con este modo podemos modificar la cantidad de tiempo
que pasa una senal en alto y en bajo, es decir, podemos modificar el ciclo de trabajo.
Existen diversas aplicaciones en las que se utilizan este tipo de senales, como
ejemplos podemos nombrar los servos de los aviones radio control o los variadores de
los motores industriales, etc.
En este ejemplo veremos como utilizar el modo Fast PWM para mover un servo
como el de la Figura 6.10. Este servo esta incluido en el kit ((Grove-Starter Kit for
Arduino)).
Para mover un servo el primer paso que tenemos que seguir es el de estudiar los
fundamentos de los servos. Un servo es un dispositivo electromecanico, que mediante
una logica empotrada consigue decodificar una senal de entrada para generar como
resultado un desplazamiento a una posicion determinada. Estos dispositivos se suelen
utilizar en aeromodelismo pero su campo de aplicacion es mucho mas extenso, otro
ejemplo podran ser los robots.
A continuacion pasaremos a explicar cada una de las partes que forman un servo-
motor:

Control de posicion: El control de posicion basicamente es un pequeno con-


trolador que decodifica la senal de entrada, la compara con el feedback y decide
si mover o no el motor de corriente continua. (Ver Figura 6.9)

Motor de corriente continua: Es la parte fundamental en el apartado mecani-


co y unicamente consiste en un motor conectado a un ((puente en H)) y una
reductora que permite transformar la mayor parte del giro en torsion.
6.1. TIMERS 93

1 void setup ( ) {
2 c l i () ;
3 TCCR1A = 0 ;
4 TCCR1B = 0 ;
5 TCCR1A = (1<<COM1B0) | (1<<COM1A0) ;
6 TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS12 ) ;
7
8 TIMSK1 = (1<<OCIE1A) ;
9
10 ICR1H = 0 b11110100 ;
11 ICR1L = 0 b00100011 ;
12
13 OCR1AH = 0 b01111010 ;
14 OCR1AL = 0 b00010001 ;
15
16 OCR1BH = 0 b11110100 ;
17 OCR1BL = 0 b00100011 ;
18
19 pinMode ( 9 ,OUTPUT) ;
20 pinMode ( 1 0 ,OUTPUT) ;
21 pinMode ( 7 ,OUTPUT) ;
22
23 S e r i a l . begin (9600) ;
24
25 sei () ;
26
27 }
28
29 ISR ( TIMER1 COMPA vect ) {
30 // S e r i a l . p r i n t l n ( Entro ) ;
31 i f (OCR1A < OCR1B) {
32 OCR1A = OCR1B;
33 }else{
34 OCR1AH = 0 b01111010 ;
35 OCR1AL = 0 b00010001 ;
36 }
37
38 }
39 void loop ( ) {
40 d i g i t a l W r i t e ( 7 ,HIGH) ;
41 delay (500) ;
42 d i g i t a l W r i t e ( 7 ,LOW) ;
43 delay (500) ;
44 }

Codigo 6.2: Ejemplo de uso TIMER modo CTC


6.1. TIMERS 94

Figura 6.9: Diagrama de bloques del sistema de control

Figura 6.10: Servo de Grove-Starter Kit for Arduino

Engranajes o gearbox: El sistema de engranajes es la parte donde se dife-


rencia un servo de otro. En funcion de la calidad de los mismos el movimiento
sera mas o menos suave. Normalmente el potenciometro de feedback va unido
al sistema de engranajes.
Para mover un servo a una posicion tenemos que enviar una senal con unos parame-
tros concretos. Normalmente los servomotores de aeromodelismo utilizan una frecuen-
cia de 20 Hz por lo que la senal viajara con un periodo de 50 ms. En la Figura 6.11
puedes ver el principio de funcionamiento.
Normalmente se confunden los terminos PWM y PPM, a continuacion intentare-
mos clarificar ambos conceptos. Desde el ATmega328 generaremos una senal a una
frecuencia determinada, esa senal la podemos modificar de modo que el ciclo de tra-
bajo vaya desde el 0 % hasta 100 %. El servomotor espera un pulso de una duracion
concreta, por ejemplo, siguiendo los tiempos de la Figura 6.11, el servo espera una
senal de 1ms para colocarse en la posicion 0. Si nuestro periodo es de 50 ms, un pulso
de 1ms sera equivalente a un 2 % de la senal en alto. Si modificamos el valor de la
frecuencia por ejemplo a 200 hz, el periodo sera 5 ms y un 2 % de dicha senal sera
0.1 ms. Con esto llegamos a la conclusion de que para codificar un movimiento el
servomotor espera pulsos de un tiempo determinado y no pulsos de un determinado
voltaje (PWM).
En resumidas cuentas, para mover nuestro servo lo primero que tendremos que
hacer sera configurar un timer en nuestro caso el timer 1 en modo Fast PWM, luego
calcular el valor del registro TOP para generar una frecuencia de 20 Hz (se deja como
tarea para el lector el calculo de dicho valor) y por ultimo anadir la logica de control
para calcular los ciclos de trabajo en funcion de los grados que se desee mover. (Aqu
6.1. TIMERS 95

1 ms

1,5 ms

2 ms

20 ms

Figura 6.11: Diagrama de tiempos de un servo

1 void setup ( ) {
2 c l i () ;
3 TCCR1A = 0 ;
4 TCCR1B = 0 ;
5 TCCR1A |= ( 1 << COM1A1) | ( 1 << WGM11) ;
6 TCCR1B |= ( 1 << WGM13) | ( 1 << WGM12) | ( 1 << CS11 ) | ( 1 << CS10 ) ;
7 ICR1 = 5 0 0 0 ;
8 pinMode ( 9 ,OUTPUT) ;
9 pinMode ( 1 0 ,OUTPUT) ;
10 pinMode ( 7 ,INPUT) ;
11 S e r i a l . begin (9600) ;
12 sei () ;
13 OCR1A = 3 7 5 ;
14 }
15
16 void loop ( ) {
17 i f ( Serial . available () ){
18 OCR1A = S e r i a l . p a r s e I n t ( ) ;
19 }
20 }

Codigo 6.3: Ejemplo de uso PWM con servo

unicamente anadiremos la logica de control para modificar el ciclo de trabajo a un


valor fijo, se deja como tarea para el lector el anadir al codigo la logica necesaria para
generar un ciclo de trabajo u otro en funcion de los grados que se desee mover).
En Cod. 6.3 se puede estudiar el codigo implementado. A continuacion explicare-
mos como siempre las partes mas interesantes del mismo.
En primer lugar, como siempre, se deshabilitan las interrupciones mediante la
macro cli(), seguidamente se configura el registro TCCR1A siguiendo el datasheet de
modo que la senal permute en cada comparacion con OCR1A.
En el bucle loop() unicamente comprobamos si existen datos en el buffer de recep-
cion serial. Si existe algun dato lo convertimos a entero y lo escribimos en el registro de
comparacion. Esto nunca se debe realizar dado que no hemos realizado ningun control
de entrada y el usuario podra ingresar cualquier valor, incluso letras o smbolos que
podran causar serios problemas en un diseno en produccion.
Los valores recomendados son 125,250,375,500 que corresponden a 0,45,90,135,180
6.2. MULTITASKING 96

grados, la manera de calcular estos valores es muy sencilla. Pongamos un ejemplo: 180
son 2ms o 2.5 ms en funcion del servomotor utilizado, en este caso hemos utilizado 2.5
porque tras probar hemos visto que es el valor al que mejor responde el servomotor.
Ademas hemos utilizado una frecuencia de 50Hz, por lo que tenemos un periodo de 20
ms. 2.5 ms es un 12,5 % del periodo. Si el periodo esta determinado por el valor TOP
almacenado en el ICR1, en este caso 5000, un 12,5 de 5000 sera igual a 625. Como
ya hemos dicho estos valores hay que probarlos y en el caso del servo de ((Seeed)) este
valor hace que el servo se bloquee porque el potenciometro no tiene tanto recorrido,
por esta razon hemos recomendado un valor de 500.

6.2. Multitasking
Hasta ahora hemos visto como funciona el core de tiempos de Arduino, al final
todo se resume en una palabra, TIMERs. En esta seccion veremos como abstraernos
de todos los detalles de implementacion y utilizando las libreras de Arduino crear
nuestros programas ((paralelos)).
La idea que reside en esta implementacion del ((multitasking)) es muy sencilla,
nuestro programa principal actuara de ((scheduler)) de tal modo que en su bucle loop()
comprobara para cada elemento si es su turno y si es as le concedera el ((procesador))
para que realice las acciones pertinentes.
En la industria se suele trabajar con dispositivos PLC, estos dispositivos se progra-
man de una manera diferente a como estamos acostumbrados. La principal diferencia
es que no se programa de forma textual (hoy en da se esta incorporando a la industria
esta manera de programar los PLC), por el contrario se programan mediante diagra-
mas Ladder. Estos diagramas tienen un elemento llamado lneas de alimentacion que
representan la entrada de senales a nuestro elemento de proceso y las salidas del mis-
mo. En la Figura 6.12 puedes ver un ejemplo de un programa realizado en ladder.
Cada pasada vertical, se denomina un ciclo de ((scan)). Para adaptar nuestros disenos
a los entornos industriales y que en la medida de lo posible se minimice el coste de
mantenimiento o lo que es lo mismo el tiempo requerido para entender nuestro pro-
grama. En este libro hemos buscado una solucion estructurada que ayudara bastante
a la consecucion de este objetivo.
Apoyandonos en la orientacion a objetos encapsularemos cada elemento en una
clase y dotaremos a la misma de un metodo llamado ((scan)), este metodo para simpli-
ficar el diseno nos devolvera un valor ((boolean)) con la informacion del ciclo (correcto,
defecto), aunque lo correcto sera encapsular el valor de retorno en otra clase que nos
brindara mas informacion.
La orientacion a objetos no es el objetivo de este libro, unicamente lo utilizaremos
como una manera de mostrar al usuario la verdadera importancia de estructurar el
codigo y para que pueda ver los beneficios inmediatos de su utilizacion. El codigo
que se utilizara en los ejemplos tratara utilizara los conceptos mas sencillos posibles
en cuanto a orientacion a objetos se refiere intentando evitar aspectos mas complejos
como el polimorfismo, herencia, etc.
Este es un buen momento para que te animes a leer el Seccion C.1 y prepares
tu entorno de desarrollo para estos ejemplos, aunque nosotros sigamos utilizando el
entorno Arduino te servira para ver las bondades de Eclipse en cuanto tenemos un
programa medianamente complejo.
Como ya se comento en la Seccion 6.1, para medir tiempos podemos utilizar varias
6.2. MULTITASKING 97

Figura 6.12: Ejemplo de programa en ladder


6.2. MULTITASKING 98

funciones de Arduino, nosotros utilizaremos la funcion millis() que nos permite saber
cuanto tiempo ha pasado desde que se ha iniciado el programa. Para aplicaciones que
estaran activas durante mucho tiempo hay que tener en cuenta que el contador se
pondra a 0 en 50 anos, por lo que tendremos que salvar de alguna manera el tiempo
(por ejemplo guardandolo en la memoria EEPROM).

6.2.1. Encendiendo y apagando un led de manera profesional


El led ha sido nuestro companero desde el primer captulo, con el hemos realizado
la mayora de ejemplos y como no puede ser de otra manera, en este primer ejemplo
sobre multitasking recurriremos de nuevo a el. En la mayora de los ejemplos que
puedes encontrar por Internet veras que para encender y apagar un led se utiliza la
funcion delay(). En entornos industriales esta funcion es muy temida y la razon es
bastante sencilla: Si un programador que esta encargado de mover un motor utiliza la
funcion delay() para programar las esperas en el arranque ((estrella-triangulo)) puede
hacer que nuestro LCD deje de refrescarse y no avise de un error grave en un cilindro.
En este ejemplo vamos a retomar la tarea de encender y apagar un led cada
segundo. Para ello en vez de dejar que pase el tiempo vamos a cuantificar este tiempo
y mientras tanto aprovecharemos para hacer trabajo util.
En Cod. 6.4 puedes encontrar el codigo que a continuacion comentaremos. En
primer lugar en el setup() como siempre se inicializan las comunicaciones as como
se configuran los pines en el modo correspondiente (hasta aqu nada nuevo para el
lector).
Lo verdaderamente importante se encuentra dentro del bucle loop(), es aqu donde
podemos ver dos funciones: scan led() y scan communications() estas funciones son las
encargadas de encender el led y mandar mensajes por el puerto serie respectivamente.
Los ((timing)) de estas acciones seran los siguientes:
Led: Se encendera y apagara cada segundo.
Comunicaciones: Se mandara un mensaje con la cadena .Estoy aqu mas el
tiempo en milisegundos cada 100 milisegundos.
Empecemos por la funcion scan led(), se utiliza una variable de tipo estatica. La
razon por la cual hemos utilizado este tipo de variable es unicamente educativa de
modo que el lector sepa que estas variables solo se inicializan una vez y luego cada vez
que se entra a la funcion se mantiene el valor anterior, es decir, es practicamente igual
que una variable declarada fuera de todos los metodos tal y como habamos hecho
anteriormente. Mediante esta funcion registramos el valor de la ultima ejecucion de la
accion a controlar. En el ((if)) es donde se encuentra el ((scheduler)). En este ((if)) com-
probamos si ha pasado el tiempo mnimo que habamos programado. Es importante
observar que solo podemos asegurar un tiempo mnimo y no un tiempo maximo. Si lo
que buscamos es acotar el tiempo en el cual se debe realizar una determinada accion,
en ese caso necesitaremos dotar a nuestro sistema de un ((scheduler)) apropiativo de
modo que cuando se alcance un determinado tiempo maximo cancele todas las ta-
reas y vuelva a la tarea que requiere atencion. Un sistema operativo muy liviano que
permite dotar al sistema de un ((scheduler)) apropiativo es el sistema FreeRTOS. Si
ejecutas el codigo podras ver que efectivamente por cada mensaje y permutacion del
led, se imprimen 5 mensajes con el mensaje ((Im here xxxx)).
En la Subseccion 6.2.2 veremos una manera mas elegante de ordenar el codigo como
se explico anteriormente, con el objetivo de facilitar el mantenimiento del mismo.
6.2. MULTITASKING 99

1
2 const int led pin = 13;
3
4 #d e f i n e TIME TOOGLE LED 500
5 #d e f i n e TIME COMMUNICATION SEND 100
6
7 bool scan led () {
8 s t a t i c long l a s t t i m e l e d = 0;
9 i f ( ( m i l l i s ( ) l a s t t i m e l e d )>= TIME TOOGLE LED) {
10 last time led = millis () ;
11 digitalWrite ( led pin , ! digitalRead ( led pin ) ) ;
12 S e r i a l . p r i n t ( " Led toogle at : " ) ;
13 Serial . println ( last time led ) ;
14 }
15 return true ;
16 }
17
18 bool scan communications ( ) {
19 s t a t i c long last time communication = 0;
20 i f ( ( m i l l i s ( ) l a s t t i m e c o m m u n i c a t i o n ) >= TIME COMMUNICATION SEND)
{
21 last time communication = m i l l i s () ;
22 S e r i a l . p r i n t ( " I m here : " ) ;
23 S e r i a l . println ( last time communication ) ;
24 }
25
26 }
27
28 void setup ( ) {
29 pinMode ( l e d p i n , OUTPUT) ;
30 S e r i a l . begin (9600) ;
31 }
32
33 void loop ( ) {
34 scan led () ;
35 scan communications ( ) ;
36 }

Codigo 6.4: Ejemplo de multitasking


6.2. MULTITASKING 100

6.2.2. Encendiendo y apagando un led de manera mas profe-


sional
La orientacion a objetos, como ya se ha comentado, puede reducir muchsimo los
costes de mantenimiento as como posibles fallos en la implementacion. Mantener un
programa en un unico fichero y de forma completamente acoplada no es buena idea.
En este ejemplo vamos a suponer que somos programadores de una empresa a la que
se le ha asignado la tarea de implementar un modulo ((led)) para proyecto futuro.
La empresa en su gua de estilos tiene definida una interfaz clara de que metodos
se deben exponer. En este caso, para simplificar, supondremos que unicamente esta
especificado el metodo scan.
Para que nuestro trabajo pueda ser reutilizado en los demas proyectos tendremos
que encapsular el mismo lo maximo posible.
En Cod. 6.5 puedes ver el ejemplo completamente implementado. A continuacion
explicaremos las partes mas importantes del mismo.
En C++, para declarar una clase, hay que utilizar la palabra reservada class. En
este caso hemos creado una clase que se llama LED. Esta clase ofrece al exterior un
metodo llamado scan(), tal y como se nos indico en la gua de estilos. La clase tambien
tiene un metodo privado update() que es el encargado de comprobar si ha llegado el
tiempo de permutacion. Ten en cuenta que este metodo es privado y por lo tanto no
le afecta la gua de estilos. Por otro lado, el constructor recibe el numero de pin y el
tiempo en el cual se quiere permutar el led (lnea 16).
Como puedes ver esta manera es mucho mas elegante dado que si queremos crear
otro led, unicamente tenemos que anadir otra lnea como LED led1 (13, 500). En este
ejemplo tan sencillo puede que no se vean las virtudes de este metodo, pero imagina
tener que controlar 200 LEDs, ves ahora la diferencia?, probablemente con el metodo
tradicional (copia de codigo) y variables globales nos quedaramos sin memoria en el
microcontrolador y la tarea de mantenimiento sera muy tediosa, con el incremento
en el coste asociado.
Se recomienda al lector extender este ejemplo con una nueva clase ServoMotor
que controle un motor de manera sencilla apoyandose en el codigo Cod. 6.3 y en todo
lo aprendido hasta ahora. Una vez creada la clase anadela al proyecto y observa las
bondades del multitasking en Arduino.
6.2. MULTITASKING 101

1 c l a s s LED {
2 private :
3 int pin led ;
4 long last update time ;
5 long time to update ;
6
7 v o i d update ( ) {
8 i f ( ( m i l l i s ( ) t h i s > l a s t u p d a t e t i m e ) >= t i m e t o u p d a t e ) {
9 t h i s > l a s t u p d a t e t i m e = m i l l i s ( ) ;
10 digitalWrite ( pin led , ! digitalRead ( pin led ) ) ;
11 }
12 }
13
14 public :
15
16 LED( i n t p i n l e d , l o n g t i m e t o u p d a t e ) {
17 t h i s > p i n l e d = p i n l e d ;
18 t h i s > l a s t u p d a t e t i m e = 0 ;
19 t h i s >t i m e t o u p d a t e = t i m e t o u p d a t e ;
20 pinMode ( p i n l e d , OUTPUT) ;
21 }
22
23 bool scan ( ) {
24 update ( ) ;
25 }
26 };
27
28 LED l e d 1 ( 1 3 , 5 0 0 ) ;
29
30 void setup ( ) {
31
32 }
33
34 void loop ( ) {
35 led1 . scan ( ) ;
36
37 }

Codigo 6.5: Ejemplo de multitasking orientado a objetos


6.2. MULTITASKING 102
APENDICE A
CONSTRUYENDO NUESTRO
PROPIO ARDUINO

A.1. Introduccion
En este apendice se guiara al lector en la construccion de su propio Arduino Uno,
el cual sera completamente compatible con un Arduino comprado en la tienda oficial.
Como se comento en el Captulo 1, Arduino mantiene la filosofa del software libre y
nos proporciona todos los esquemas necesarios para construir o mejorar su plataforma.
El Arduino que vamos a construir en este apendice es muy sencillo y unicamente
tendra lo indispensable para que el lector pueda conectarlo al ordenador y ejecutar
los ejemplos realizados en este libro, por lo que eliminaremos muchos elementos de
seguridad y filtrado que tiene el Arduino original con el objetivo de simplificar su
montaje.

A.2. Componentes necesarios


A continuacion, se detallara una lista con los componentes que se necesitan para
el montaje. Muchos de los componentes tienen equivalentes por lo que en este manual
hemos optado por los mas comunes, no obstante, si el lector encuentra un equivalente
al componente no habra problema en sustituirlo, aunque se recomienda escoger en la
medida de lo posible los citados en la siguiente lista.

1. Protoboard

2. Fuente de alimentacion 5-18V CC

3. Cables Jumper

4. LM7805

5. Condensador electroltico 10 uF (x2)

103
A.3. ENSAMBLADO 104

6. Reloj de cristal de 16MHz


7. ATmega328
8. Condensador electroltico de 22 pf (x2)
9. FTDI232RL
10. USBASP1

A.3. Ensamblado
El primer paso es situar los componentes en la placa de montaje (protoboard des-
de ahora), aunque pueda parecer que la colocacion es algo meramente decorativo y
((superfluo)) es muy importante tenerlo en cuenta. La diferencia entre poner el con-
densador de desacoplo a 1 centmetro del microcontrolador o ponerlo a 20 centmetros
puede ser determinante en algunos sistemas2 . En la Figura A.1 se han dispuesto los
componentes de modo que al lector le sea sencillo identificar los mismos y situarlos
en el circuito por lo que, se recomienda que realice ligeras modificaciones mantenien-
do las reglas de conexion (mismo esquema) de modo que los componentes esten mas
proximos entre ellos.
El circuito se puede dividir en tres partes. A continuacion, describiremos cada una
de ellas:
Etapa de alimentacion: El circuito esta preparado para ser alimentado con
un voltaje desde 5 V a 18 V de corriente continua, es importante que la
corriente sea continua pues de lo contrario el circuito no funcionara, pudiendo
llegar a dejarlo inservible. Si el lector no tuviera una fuente de corriente conti-
nua con estas caractersticas, puede montarse una pequena fuente, para lo cual
necesitara entre otras cosas un transformador (fuente con transformador) y un
puente de diodos (en Internet hay mucha informacion a cerca de las fuentes de
alimentacion).
Lo siguiente es conectar los terminales a la protoboard. Existe un convenio de
conexion por el cual se recomienda conectar el terminal positivo a la primera
fila de la protoboard y el negativo a la segunda. El lector podra reconocer estas
filas porque normalmente vienen separadas del resto de la protoboard.
Ahora que la placa esta alimentada, solo queda situar los componentes tal cual
aparecen en el esquema Figura A.2 tambien puedes guiarte por la Figura A.1
aunque recuerda, esta no es la distribucion optima. Es importante que se respete
la polaridad del condensador electroltico dado que de lo contrario, si se
supera la tension inversa umbral, podremos romper el mismo. Para saber como
conectar el condensador busca una lnea vertical de color blanco que indica el
terminal negativo. (ver Figura A.1)
A la salida del integrado LM7805 (lnea naranja) tendremos 5 V estables que
sera la ((fuente de alimentacion)) del microcontrolador ATmega328.
1 USBASP es un programador para los microcontroladores AVR libre, por lo que desde la web del
autor (http://www.fischl.de/usbasp/) el lector podra acceder tanto al firmware como al circuito
pudiendo construir su propio programador.
2 Los cables tienen una capacitancia y una inductancia que afecta al circuito, en disenos donde la

precision es muy importante, hay que tener en cuenta la longitud de los cables entre otros factores
A.3. ENSAMBLADO 105

5v-12v

Azul = Tierra
Rojo = Entrada
Naranja = 5v estables
Cyan = Cristal
-------------------
Marron = Rx(Arduino) - Tx(FTDI)
Morado = Tx(Arduino) - Tx(FTDI)

16MHz Cristal

Figura A.1: Protoboard de referencia

Figura A.2: Esquema de conexiones


A.3. ENSAMBLADO 106

Figura A.3: Patillaje ATmega328

Microcontrolador: El microcontrolador ATmega328 es el corazon del Arduino


UNO, es en el donde se cargaran los programas. En esta parte del montaje nos
encargaremos de que el corazon empiece a ((latir)). Para ello, lo primero que hay
que saber es como estan distribuidos los terminales. En la Figura A.3 se puede
observar que los pines para alimentar el microcontrolador son el 7 y 8, pero...
Donde empieza la numeracion? para ello se sigue un convenio por el cual todos
los circuitos integrados disponen de una marca que indica el pin con el inicio de
la numeracion. En la Figura A.1 se puede observar el punto que indica el inicio.
Una vez que el los terminales de alimentacion del microcontrolador estan co-
nectados a la salida del LM7805, ahora solo resta el incorporar un componente
que permita al ATmega328 latir a una frecuencia determinada (siguiendo la
analoga del corazon humano) este componente es un reloj de cuarzo. El reloj
de cuarzo genera una onda cuadrada con una frecuencia determinada, normal-
mente los microcontroladores poseen un reloj RC3 pero debido a su precision
y a que normalmente oscilan a una velocidad inferior, anadiremos el reloj de
cuarzo.
Siempre que se va a dotar al microcontrolador de un reloj externo se utiliza el
mismo circuito, que consta de un reloj y dos condensadores, cada uno de ellos
en paralelo al reloj. Cuanto mas cerca este el reloj de los terminales de conexion
del microcontrolador mayor precision, por lo que de nuevo se recomienda que
unicamente se siga la Figura A.2 para guiarse y se modifique la disposicion de
los elementos.
Finalmente falta comunicar el pin 20 (AVCC) con VCC, esto se mantendra
as hasta que el lector quiera hacer uso del ADC (Digital Analogic Converter)
cuando tendra que conectar el pin 20 a VCC a traves de un filtro de paso bajo.

Comunicacion: Ahora que el microcontrolador ya es capaz de oscilar a una


frecuencia determinada (en nuestro caso 16MHz) tenemos que situar el canal de
comunicacion entre el computador y el microcontrolador. Si el microcontrolador
tuviera integrado un chip para USB unicamente tendramos que conectar los
cables del USB al mismo, pero como el ATmega328 solo posee de comunicacion
serial (para la programacion) tendremos que hacer una serie de modificaciones
de modo que el Arduino se pueda programar desde el computador:

Ordenador con puerto serie: Si tu ordenador posee de puerto serie, entonces


3 Los relojes RC suelen tener una frecuencia inferior y una precision menor frente al reloj de

cuarzo.
A.4. PROGRAMACION DEL BOOTLOADER 107

no hay ningun problema, unicamente tendras que conectar el pin Tx al Rx


del ATmega328 (pin 2) y el pin Rx al Tx (pin 3).
Ordenador con USB: Si tu ordenador unicamente dispone de puertos USB,
tendras que utilizar un conversor como puede ser FTDI232RL que realiza
la labor de identificar el dispositivo y gestionar la UART de modo que los
datos que escribamos bajo la pila USB se trasmitan de forma correcta bajo
el protocolo serial.

En el caso de estar utilizando el conversor FTDI232RL y estando conectados


los puertos Rx y Tx a los pines Tx y Rx del ATmega328 respectivamente.
Solo queda alimentar el microcontrolador FTDI232RL desde el LM7805 y ya
tendremos el Arduino listo para ser programado.

A.4. Programacion del Bootloader


Para que el ATmega328 se convierta en un Arduino, necesitamos cargar el bootloa-
der, para ello podemos seguir diferentes metodos. En el este libro se explicara el
((quemado)) con un programador USBasp el cual es completamente libre.
Antes de empezar a quemar el bootloader en primer lugar se explicara que es y
que funciones realiza por nosotros.

El ATmega328 tiene una memoria Flash de 32 KB en la que se podra almacenar


un programa compilado para este microcontrolador y ejecutarlo posteriormente. Para
programar esta memoria se necesitan herramientas como el programador STK-500 por
lo que Arduino decidio anadir una interfaz USB de modo que realice la conversion de
los datos que llegan por USB a un protocolo serial y los multiplexe en el tiempo del
mismo modo en que lo hara un programador como el STK-500.
El bootloader ocupa un espacio determinado en la memoria flash dado que real-
mente es un programa que se inicia cuando alimentamos el microcontrolador eje-
cutando un conjunto de instrucciones que permite al ATmega saber si hay alguna
peticion para programar el Arduino y si es as seleccionar la direccion de memoria
donde ((situar)) dicho programa. Si el bootloader no encuentra ningun peticion de pro-
gramacion entonces salta a la direccion de memoria del bucle de setup del programa
y a continuacion empieza el bucle de operacion.
Como el lector habra podido apreciar, no es necesario ((quemar)) este bootloader
pero si no se quema el lector tendra que contar con algun programador AVR para
((quemar)) el programa y ademas tendra que realizar la programacion por ISP mediante
el conector ICSP4 lo cual es mucho mas incomodo.
A continuacion vamos a proceder a ((quemar)) el bootloader, para ello lo primero
que necesitamos es un programador de AVR, en este libro, como ya se ha dicho
anteriormente se utilizara el programador libre USBasp. La instalacion de los drivers
del mismo se deja como trabajo para el lector. Una vez que el programador esta
correctamente instalado, tenemos que realizar las conexiones, para ello hay que tener
en cuenta que el conexionado ICSP hay que realizarlo con mucho cuidado.
4 ICSP son las siglas de In Chip Serial Programming, con este conector, se permite reprogra-

mar el microcrocontrolador sin necesidad de desoldarlo y por lo tanto aumentando la vida util del
microcontrolador
A.4. PROGRAMACION DEL BOOTLOADER 108

(a)Conector ICSP de 6 pines (b)Conector ICSP de 10 pines

Figura A.4: Conectores ICSP

Existen dos tipos de conectores ICSP, de 10 pines (ver Figura A.4b) y de 6 pines
(ver Figura A.4b).
Para localizar la ubicacion de cada uno de los pines se puede utilizar un multmetro
y medir la diferencia de potencial (voltaje) entre un pin y tierra de tal manera que
el pin que devuelva el voltaje de programacion (3.3-5V) sera el VTG o VCC y el
pin comun sera tierra. El lector tambien se puede guiar por el cable rojo, de modo
que este sera el pin 1. Existen alternativas para facilitar la conexion del programador
al Arduino como por ejemplo el conversor ((ICSP Header)) cuya unica funcionalidad
es proporcionar una interfaz ((inline)) que se podra conectar a la protoboard siendo
mucho mas sencilla la conexion final al ATmega.
Una vez que el lector haya localizado cada uno de los pines del conector o haya
adquirido un conversor, ya esta en disposicion de realizar la conexion entre el ICSP y
el microcontrolador. Los pines son los proporcionados por la interfaz de comunicacion
ISP, es decir, GND, VCC, MISO (Master Input Slave Output), MOSI(Master Output
Slave Input), SCK (Clock), RESET. La conexion ISP es muy sencilla gracias a lo
descriptivo de los nombres de sus pines, sin embargo, a continuacion se detalla las
conexiones a realizar:
Pin MISO programador Pin 18 ATmega328
Pin MOSI programador Pin 17 ATmega328
Pin SCK programador Pin 13 ATmega328
Pin RESET programador Pin 1 ATmega328
Ahora solo queda cargar el bootloader al ATmega328, para ello hay que conectar
el programador al conversor ICSP o en caso de no haber utilizado el conversor, conec-
tar cada uno de los pines del conector ICSP al microcontrolador. Despues conectar
el programador al computador, es importante que los drivers esten completamente
configurados, de lo contrario el proceso no se completara con exito. El siguiente pa-
so es abrir el Arduino IDE y seleccionar Herramientas tarjeta Arduino Uno
(Figura A.5) con lo que indicaremos al IDE que vamos a programar un Arduino Uno,
despues seleccionamos Herramientas programador USBasp, por ultimo hay
A.4. PROGRAMACION DEL BOOTLOADER 109

Figura A.5: Captura de pantalla del Arduino IDE

que pulsar sobre Herramientas Grabar secuencia de inicio. Si todo ha ido bien
el lector podra quemar sus propios sketch.
A.4. PROGRAMACION DEL BOOTLOADER 110
APENDICE B
MANIPULACION DE REGISTROS

B.1. Introduccion
Cuando un disenador se enfrenta a la tarea de disenar una solucion informatica en
un microcontrolador, debe manejar una serie de conceptos que normalmente no son
necesarios o indispensables cuando se programa una solucion en un computador de
proposito general. Tener claro donde almacenar una cadena de texto o que espacio de
memoria asignar para una determinada tarea son algunos de estos conceptos.
En este apendice se va a guiar al lector en el aprendizaje de las operaciones logicas
con bits. Es importante tener en cuenta que la informacion en un microcontrolador
se suele almacenar en registros. Estos registros pueden almacenar datos de diferente
naturaleza, por ejemplo, un microcontrolador que forme parte de una impresora puede
tener un registro para el control de la misma (calentar rodillo, situar en posicion inicial,
iniciar operacion) y otro registro para los datos como por ejemplo la hoja a imprimir.
Cuando el disenador programa la logica de la impresora, debera obtener y validar la
informacion contenida en estos registros. En este apendice se mostraran las diferentes
tecnicas que se utilizan para obtener y tratar los datos de los registros.

B.2. Que es un registro?


Antes de tratar de realizar operaciones con los registros, debemos tener claro que
es realmente un registro. Se puede explicar que es un registro desde dos perspectivas:
la perspectiva logica y la perspectiva fsica. En este apendice nos centraremos en la
primera dado que buscamos el tratar con la informacion almacenada en los registros
y no el implementar fsicamente el mismo.
El registro es la unidad de almacenamiento que se encuentra en el nivel superior
dentro de la jerarqua de memorias (ver Figura B.1), es decir, es la memoria mas
rapida y mas cara de todas. Este es el motivo por el cual es un recurso limitado en
los computadores y tan preciado por los compiladores. Como ya hemos nombrado en
la introduccion, existen registros de muchos tipos como por ejemplo los registros de
datos que tienen la funcion principal de almacenar datos. Un ejemplo de este tipo de

111
B.2. QUE ES UN REGISTRO? 112

registros es el registro acumulador de la ALU de los microcontroladores PIC. Tambien


existen registros especficos como el registro SP (stack pointer) de la arquitectura
ARM el cual unicamente se utiliza para almacenar la direccion de memoria del
((top)) de la pila.

Registros del
procesador

Memoria cach
(L1,L2,L3)
Capacidad Velocidad y
Coste por bit
Memoria RAM
(random access memory)

Disco duro
Almacenamiento secundario

Cloud
(Copias de seguridad)

Figura B.1: Jerarqua de memoria

Todos estos registros tienen unas propiedades comunes como son:

1. Capacidad (Ancho): Los registros tienen un ancho determinado que depen-


dera del proposito para el que se haya disenado. Por ejemplo, un registro que
mantenga la direccion de la siguiente instruccion a ejecutar IR (instruction regis-
ter) debera tener la capacidad suficiente que permita codificar las instrucciones
de un programa. Si se quiere un computador con la capacidad de ejecutar 256
instrucciones se debera disenar un registro con un ancho de 8 bits.1

2. Tecnologa: Los registros fsicamente se disenan con una tecnologa determina-


da. Esta tecnologa sera determinante a la hora de comparar otras caractersticas
como la latencia, capacidad de integracion. . .

3. Direccion: Un registro queda identificado univocamente por su direccion de


memoria como por ejemplo podra ser la direccion 0x00001A1B

Podemos imaginar un registro como una caja (ver Figura B.2) que tiene diferentes
compartimentos. Cada uno de estos compartimentos es un bit. Dependiendo del tipo
de registro nos interesara toda la ((caja)) (registro) o unicamente uno de sus ((cajones))
(bit). Por ejemplo, si tenemos un registro de datos, seguramente nos interese el registro
entero y no uno de sus bit dado que normalmente cuando se accede a un registro de
datos se busca el valor del numero almacenado, esto no implica que no se pueda
acceder a un unico bit, por ejemplo para saber si el numero es positivo o negativo en
un numero representado en signo magnitud.
1 Con 8 bits se pueden codificar 28 instrucciones
B.3. OPERACIONES CON REGISTROS 113

0 0 0 0 0 0 0 0
Figura B.2: Registro de 8 bits (byte) como una ((caja))

B.3. Operaciones con registros


Ahora que ya sabemos que es un registro, estamos en disposicion de realizar dife-
rentes operaciones con ellos. En esta seccion y con el objetivo de que el lector pueda
poner sus conocimientos en practica vamos a utilizar registros reales del microcontro-
lador ATmega328, a continuacion se realizara una pequena descripcion de la utilidad
de cada uno de estos registros:
DDRC (Data Direction Register C ): Este registro se encuentra en la direc-
cion 0x07 y permite configurar los pines del puerto2 como entrada(0) o salida(1),
en funcion del bit que se situe en cada uno de las posiciones del registro, por
ejemplo la configuracion 0b00000001 configura todos los puertos como entra-
da(0) y el primero3 como salida(1)4 .
PORTC (Port Output Register C ): Este registro sirve para activar las
resistencias de pull-up que el microcontrolador posee en cada uno de los pines
configurados como entrada. Si el pin esta configurado como salida (DDRX(n)
== 1) este registro determinara el voltaje ((alto)) o ((bajo)) a la salida del pin.
PINC (Pin Input Register C ): Este registro normalmente formara parte de
las estructuras ((IF ... THEN)), en cada uno de sus bits obtendremos el valor de
un pin configurado como entrada. Si el valor del bit es 1 significara que el pin
que representa tiene VCC como entrada, sin embargo, si el valor es 0 entonces
significara que el voltaje de entrada es GND.

B.3.1. Activar un bit


Imaginemos que queremos configurar el pin 0 del puerto ((c)) como salida, para
ello sabemos que tenemos un registro especfico como es el registro DDRC. Sabemos
tambien que la direccion de memoria del registro es la 0x07 y que podemos acceder
a el mediante el puntero DDRC que apunta a dicha direccion5 . Si queremos activar
el led 0 como salida podramos asignar el siguiente valor a DDRC :
1
0b00000001
0x01
DDRC = 1 << PIN0
2 Los pines de entrada/salida se agrupan en los llamados ((puertos)). Cada puerto controla un
determinado numero de pines.
3 Es importante tener en cuenta que los bits se empiezan a numerar por 0.
4 Toda esta informacion se puede obtener del Datasheet del microcontrolador ATmega328.
5 Estos punteros son mapeados normalmente por la librera del fabricante.
B.3. OPERACIONES CON REGISTROS 114

Los cuatro elementos de la lista anterior hacen referencia al mismo valor, es decir,
al 1 en decimal. El primer elemento representa el 1 en decimal. Se sabe que un
valor esta expresado en binario cuando le antecede el prefijo ((0b)). El tercer elemento
(0x01 ) representa el mismo valor pero en hexadecimal, es importante recordar, que
un dgito hexadecimal equivale a cuatro bits (el conjunto de 4 bits se le conoce como
nibble). La ultima representacion es la mas comun pero no sera explicada hasta la
Subseccion B.3.3
Ahora imaginemos que pasa si ocurrira si quisieramos configurar el pin 1 del
puerto ((c)) como salida. En principio actuaramos de la misma manera, es decir,
configuraramos el valor 0b0000010, pero como el lector habra podido observar, el
valor del pin 0 cambiara y se configurara como entrada (0). La manera mas sencilla
de solventar este comportamiento es asignar el valor 0b00000011 pero si no se conoce
que pines estan como salida en un estado anterior, como se calcula el valor a escribir?
esta pregunta sera contestada en la siguiente seccion.

B.3.2. ORing, activacion de un bit


Para evitar el comportamiento observado en la seccion anterior, se hace uso de la
operacion OR. Esta operacion permite activar un bit sin ((afectar)) a los demas bits
del registro.

Imaginemos el mismo ejemplo que el de la Subseccion B.3.1, es decir, deseamos


configurar el primer y segundo bit como salida. La solucion planteada en la primera
seccion es incomoda e ineficiente dado que se necesita saber que bits estan activados
anteriormente para poder calcular el valor a escribir. Por lo que vamos a aplicar la
funcion OR de la siguiente manera:
1 DDRC = DDRC | 0 b10

De este modo conseguiremos el siguiente valor:

0b00000001
OR 0b00000010
-------------
0b00000011

Como el lector habra observado, el resultado es el esperado, tanto el pin 1 como


el 2 estan configurados como salida. La operacion ORing unicamente afecta a los
valores que poseen un 1 logico, esto es debido a que el 0 actua como operador neutro
(A OR 0 = A). El operando que utilizamos para realizar la operacion OR sobre el
registro se llama ((mascara)).

B.3.3. Bit Shifting, movimiento de bits


La aritmetica booleana proporciona herramientas extremadamente utiles al pro-
gramador. Por ejemplo, una de las mejoras que un compilador puede realizar es la
transformacion de una multiplicacion de potencia 2 que normalmente lleva varios ci-
clos (las unidades de multiplicacion suelen ser lentas) en una unica operacion logica
llamada ((Bit Shifting)). En este apartado vamos a explotar una de sus utilidades
B.3. OPERACIONES CON REGISTROS 115

7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 1

0 0 0 0 0 0 1 0

Figura B.3: Operacion: 1 << 1

7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 1
1 << 3

0 0 0 0 1 0 0 0
OR

0 0 0 0 0 0 1 1
=

0 0 0 0 1 0 1 1

Figura B.4: Activacion de pin 3 sobre registro previamente configurado

que nos permitira hacer nuestro codigo mas legible y nos ahorrara mucho codigo in-
necesario.

Continuando con nuestro ejemplo de la configuracion del pin 0 y 1 como salida,


en este apartado se vera como construir o hallar el valor que debe ser utilizado como
operando extra en la operacion OR.
En la Subseccion B.3.2 se utilizo el operando ((extra)) 0b00000010, a este operando
como ya hemos dicho anteriormente, se le llama mascara. Para obtener este valor
se recurre a la operacion Bit Shifting. Si queremos crear una mascara para el pin 1
unicamente tenemos que utilizar la siguiente operacion (1 << 1) (en la Figura B.3
esta representada graficamente la operacion).
Si quisieramos configurar el pin 3 como salida procederamos del mismo modo,
es decir, calcular la mascara y aplicarla mediante la operacion OR. Para calcular la
mascara utilizamos la tecnica del Bit Shifting (1 << 3). En la Figura B.4 se puede
ver de forma grafica el procedimiento.
En el Cod. B.1 se puede ver un extracto de codigo donde se configura el pin PC6
y PC7 como salida. Esta es la forma mas comun para la configuracion de registros. Es
importante observar que la operacion OR se realiza sobre el valor actual del registro
(| =) y que se pueden realizar varias operaciones ORing en una unica asignacion.
B.3. OPERACIONES CON REGISTROS 116

1 # include < avr / io .h >


2
3 int main () {
4
5 // Data D i r e c t i o n R e g i s t e r C = 0 b 0 1 1 0 0 0 0 0
6 // H a b i l i t a m o s como salida el pin PC6 y PC7
7 DDRC |= (1 << DDC6 ) | (1 << DDC7 ) ;
8 // Port Output R e g i s t e r C = 0 b 0 1 0 0 0 0 0 0
9 // Salida del pin PC6 como valor HIGH
10 PORTC |= (1 << DDC6 ) ;
11
12 }

Codigo B.1: Ejemplo de asignacion mediante oring

7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
0 << 3

0 0 0 0 0 0 0 0
OR

0 0 0 0 1 0 1 1
=

0 0 0 0 1 0 1 1

Figura B.5: Configuracion del pin 3 como entrada sobre registro previamente con-
figurado (procedimiento erroneo)

B.3.4. ANDing, desactivando bits


Hasta ahora unicamente hemos configurado bits con el valor 1 pero Se puede
configurar del mismo modo el pin con el valor 0?. La mejor manera de ilustrar esta
situacion es mediante un ejemplo. Imaginemos que sobre el ultimo valor del registro
que hemos utilizado para los ejemplos (0b00001011 ) deseamos configurar el pin 3
como entrada (0).
El valor 0 en binario ((ocupa)) un unico bit por lo que para crear la mascara
podramos pensar en utilizar el mismo metodo que en apartados anteriores. En la
figura Figura B.5 se ilustra el proceso.
Como se puede observar el resultado obtenido es 0b00001011, es decir, no es el
resultado esperado dado que el bit 3 esta a 1. A continuacion, explicaremos el modo
de realizar este tipo de operaciones.
Si utilizamos la mascara normal, (la utilizada para configurar un bit a 1) y negamos
la misma, podemos observar que el resultado tras realizar la operacion AND es el
esperado. Se recurre al elemento neutro de la operacion AND, es decir, al 1, de modo
que todos los bit que eran 0 al negarse se convierten a 1 consiguiendo que la operacion
AND no tengan efecto (A AND 1 = A) sobre los bits que ya esta configurados y s
tenga efecto (configurar con el valor 0) sobre los bits que estan en 1 en el operando
B.3. OPERACIONES CON REGISTROS 117

7 6 5 4 3 2 1 0
0 0 0 0 1 0 0 0
NOT

1 1 1 1 0 1 1 1
AND

0 0 0 0 0 0 1 1
=

0 0 0 0 0 0 1 1

Figura B.6: Configuracion del pin 3 como entrada sobre registro previamente con-
figurado

((extra)).
En la Figura B.6 se puede ver el procedimiento seguido paso a paso.
B.3. OPERACIONES CON REGISTROS 118
APENDICE C
ENTORNO ECLIPSE CON
ARDUINO

C.1. Introduccion
En este apendice veremos como configurar el entorno de desarrollo Eclipse de
modo que el mismo pueda ser utilizado para programar y grabar programas en la
plataforma Arduino.
El lector puede preguntarse por que utilizar otro IDE si ya ha utilizado el oficial
de Arduino y no ha tenido ningun problema.
Arduino fue pensado como una plataforma con una curva de aprendizaje muy
baja, la ventaja obvia es la rapida adaptacion de cualquier persona con mnimos
conocimientos de informatica al entorno. El problema, no tan obvio en un primer
momento, es la carencia de herramientas tan importantes como: control de versiones,
gestion de proyectos, ((tasks list)), etc.
Con el objetivo de mostrar al lector el potencial de Arduino, en este apendice con-
figuraremos un entorno con todas las herramientas necesarias en cualquier ambiente
profesional.

C.2. Que es Eclipse


Eclipse es un entorno de desarrollo integrado de codigo libre y multiplataforma.
Eclipse ofrece un ((core)) sobre el que otros desarrolladores realizan sus modificaciones
para crear entornos de desarrollo especficos para cada tecnologa. Un ejemplo podra
ser el entorno para Java o el entorno de Xilinx para el desarrollo de software sobre su
plataforma hardware.
Aunque existen plugins para Eclipse que ofrecen una capa de compatibilidad con
Arduino, en este apendice trabajaremos sobre Eclipse para C++ con el objetivo de
poder tener control sobre cada una de las partes que conforman la construccion de
un binario para Arduino.

119
C.3. INSTALACION DEL ENTORNO 120

Figura C.1: Pantalla inicial de Eclipse

La pagina oficial de Eclipse se puede visitar desde el siguiente enlace: https:


//eclipse.org, en ella podras encontrar toda la informacion de la fundacion Eclipse,
ademas, desde el apartado ((Download)) podras descargar cada una de las versiones
oficiales del entorno.

C.3. Instalacion del entorno


El primer paso sera el descargar nuestro entorno de desarrollo Eclipse sobre el que
realizaremos todas las ((modificaciones)) necesarias para que sea compatible compatible
con la plataforma Arduino.
Para descargar el entorno, en primer lugar, deberemos acceder a la web de Eclipse
y luego al apartado de descargas: http://www.eclipse.org/downloads/ una vez
dentro deberemos elegir la opcion de ((Eclipse IDE for C/C++ Developers)). Esta
version nos ofrece muchas herramientas de gran utilidad como: Mylyn Task, Git o un
sistema de acceso remoto.
Una vez descargado el entorno para nuestra arquitectura, el segundo paso sera la
descompresion del mismo. Eclipse es un programa portable, esto quiere decir que no
requiere instalacion por lo que una vez descomprimido estaremos en disposicion de
utilizar el mismo. El lugar donde se descomprima no es relevante.
El siguiente paso sera ejecutar el programa, en teora si tenemos Java en nuestro
computador el entorno se ejecutara sin ningun tipo de fallo, en caso de no tener Java
el entorno nos avisara y tendremos que instalarlo.
La pantalla inicial tendra la apariencia de la Figura C.1.
Ahora deberemos instalar el entorno Arduino tal y como lo hicimos en Seccion 2.1.
Si ya lo tienes instalado no hace falta que vuelvas a hacerlo.
Con la instalacion del entorno Arduino tendremos todas las herramientas necesa-
rias, como son:

Compilador: Utilizaremos el compilador ((avr-gcc)) y ((avr-g++)) para compilar


C.4. CONFIGURACION DEL ENTORNO 121

nuestros proyectos.

Conjunto de libreras Arduino: Las libreras de Arduino, forman parte del


llamado ((ArduinoCore)). Estas libreras nos simplifican tareas repetitivas como
la configuracion del modulo USART, configuracion de pines, etc.

Libreras AVR: Como ya sabras la librera de Arduino utiliza las libreras de


AVR por lo que tambien tendremos estas ultimas en nuestra instalacion.

Programador: Para programar el Arduino necesitamos un programador ((hardware))


y un programador ((software)). En el caso del programador ((hardware)) ya lo te-
nemos instanciado dentro de la placa Arduino (una vez instalado el bootloader).
Por otro lado, el programador ((software)) que utilizaremos sera ((avrdude)) que
forma parte del conjunto de herramientas de AVR.

Mapeado de pines: Como ya sabras, el entorno Arduino utiliza su propia


numeracion de pines, de tal modo que si pones pin 13 en una funcion, el Arduino
((sabe)) que pin es y a que puerto le corresponde (en realidad el encargado de
hacer esta conversion como es logico, no es Arduino, es el compilador). Esto se
realiza mediante el archivo de variants que veremos mas adelante.

Es importante que anotemos la direccion de instalacion de Arduino para los pasos


siguientes. En el caso de los sistemas GNU/Linux, esta direccion suele ser: /usr/sha-
re/arduino.

C.4. Configuracion del entorno


Ahora que tenemos todo descargado es el momento de realizar la configuracion del
IDE Eclipse. En primer lugar descargaremos un plugin para el desarrollo de soluciones
basadas en microprocesadores AVR dentro de eclipse.
Para descargar un plugin en Eclipse, podemos acceder al menu ((Help, Install new
software)) o ((Ayuda, Instalar nuevo software)) en Espanol. Una vez en esa pantalla
deberemos pulsar al boton ((Add)) y luego en el apartado de ((Name)) poner el nombre
que se desee, por ejemplo avr-descargas, y en location la siguiente direccion: http://
avr-eclipse.sourceforge.net/updatesite, debera quedar como en la Figura C.2.
Una vez rellenado el formulario aparecera ante nosotros un plugin llamado AVR
Eclipse Plugin con un ((checkbox)), deberemos seleccionar dicho ((checkbox)), seguida-
mente pulsaremos ((Next)) y ((Finish)) segun las instrucciones, hasta finalizar la insta-
lacion.
Una vez instalado, el siguiente paso consiste en la configuracion de las variables
que permiten al plugin saber donde se encuentran los binarios del compilador, etc.
Para configurar el plugin hay que ir al menu ((Window, Preferences)) o ((Ventana,
Preferencias)). Una vez en el menu deberemos acceder al apartado de ((AVR, Paths)).
Hay que configurar cada una de las variables para que apunten al binario dentro del
SDK descargado en el paso anterior.

AVR-GCC: <directorioSDK>/hardware/tools/avr/bin

GNU make: Con el entorno descargado para Windows se descarga tambien


la herramienta make por lo que el mismo estara en el directorio del SDK, sin
C.5. CREANDO EL PROYECTO: ARDUINOCORE 122

Figura C.2: Descarga del plugin AVR

embargo para GNU/Linux esta herramienta no viene incluida dado que forma
parte de GNU/Linux y esta de forma nativa para todas las distribuciones por lo
que en este caso seguramente el propio plugin detecte el lugar donde se encuentra
instalado y aparecera algo como ((system)) en esta variable.

AVR Header Files: <directorioSDK>/hardware/tools/avr/avr/include

AVRDude: <directorioSDK>/hardware/tools/avr/bin

En la Figura C.3 puedes ver un ejemplo de como podra quedar la configura-


cion, ten en cuenta que en funcion de donde se realice la instalacion, los path y en
consecuencia la configuracion variara.

C.5. Creando el proyecto: ArduinoCore


Como ya hemos comentado, Arduino nos proporciona un conjunto de libreras
que hacen que sea mucho mas sencillo el utilizar algunos de los modulos ((hardware))
del microcontrolador. Para poder contar con todas estas comodidades tendremos que
compilar las libreras en un proyecto a parte (esto nos permitira reutilizar las librera
en otros proyectos). Esta tarea en el entorno oficial de Arduino se realiza sin que
nosotros tengamos que realizarlo de forma explicita, esto hace que sea mucho mas
rapido para el usuario pero a la vez, hace poco didactico el proceso.
Para compilar las libreras en primer lugar crearemos un proyecto, para ello va-
mos al menu ((New, C++ Project)) y en tipo de proyecto ((AVR Cross Target Static
Library)) el nombre que pondremos al proyecto sera ((ArduinoCore)), el nombre no
es determinante, sin embargo este nombre simplifica la labor de comprension de los
pasos que estamos realizando.
En configuraciones unicamente seleccionaremos la de ((Release)) esto es debido a
que esta librera no la modificaremos y por lo tanto no necesitamos toda la configu-
racion de ((Debug)). En la siguiente pantalla de configuracion se nos preguntara por
C.5. CREANDO EL PROYECTO: ARDUINOCORE 123

Figura C.3: Configuracion de ejemplo

el microcontrolador y por la frecuencia. Ambos parametros deberan ser configurados


en funcion del Arduino sobre el que se vaya a realizar el proyecto. En el caso del
Arduino construido en este libro en la Seccion A.1 configuraremos estos parametros
con ATmega328 y 16000000 (como se puede observar en este ejemplo la frecuencia
viene determinada en hercios).
Una vez creado el proyecto el siguiente paso consiste en anadir el codigo fuen-
te de las libreras, y configurar los includes. Para esto ultimo daremos ((click)) de-
recho sobre el proyecto y buscaremos el menu de propiedades. Una vez dentro del
mismo deberemos buscar el apartado ((C/C++ Build, Settings)) y en el apartado
((AVR Compiler)) ir a la pestana de ((Directories)) y anadir el directorio: <directo-
rioArduino>/hardware/arduino/avr/cores/arduino. Este directorio contiene todos los
((headers)) de las libreras. Por otro lado tenemos que anadir el ((header)) que utiliza
el entorno Arduino para referirse a sus pines. Este fichero como habras podido adi-
vinar vara en funcion del microcontrolador. El fichero se puede encontrar dentro de
<directorioSDK>/hardware/arduino/variants luego selecciona el que necesites, por
ejemplo, para el microcontrolador ATmega328 deberamos utilizar el mapeado stan-
dard. Hay que hacer exactamente lo mismo con el otro apartado llamado ((AVR C++
Compiler)).
Ahora que ya tenemos todas las referencias configuradas, el siguiente paso consiste
en importar el codigo fuente de las libreras, para ello damos boton derecho sobre el
proyecto y seleccionamos la opcion de ((Import, File System)). En el cuadro de busque-
da hay que ingresar el directorio:
<directorioArduino>/hardware/arduino/avr/cores/arduino. Una vez dentro, seleccio-
na todos los archivos (.cpp y .h) menos el archivo main.cpp.
Por ultimo ya solo queda compilar el proyecto, para ello cruzamos los dedos y
damos boton derecho ((Build Project)). Si todo va bien ya tendremos las libreras de
Arduino compiladas.
C.6. CREANDO EL PROYECTO FINAL 124

Figura C.4: Configuracion de las libreras

C.6. Creando el proyecto final


Ahora que tenemos compilado el conjunto de libreras de Arduino para el mi-
crocontrolador que estamos utilizando, ya podemos crear un proyecto tal y como
lo haramos en el IDE oficial de Arduino.
Para crear un proyecto en primer lugar accedemos al menu: ((New, C++ Project))
y en tipo de proyecto ponemos: ((AVR Cross Target Application)) tal y como en la
Seccion C.5. El nombre del proyecto en este caso no es relevante.
Como este proyecto s que pasara por la fase de ((debug)) y de ((release)) dejaremos
habilitadas ambas configuraciones.
El siguiente paso consiste en anadir los mismos directorios que en la Seccion C.5
con el objetivo que se pueda referenciar a los ficheros de cabecera del ((core)). Ademas,
hay que anadir al propio proyecto en la lista de directorios. Para ello anadir la cadena
((${workspace loc:/${ProjName}})) , tanto en ((AVR Compiler)) como en ((AVR C++
compiler)), que indica al compilador que compruebe los ficheros de cabecera de este
mismo proyecto.
Una vez que tenemos las tres direcciones completadas podemos proceder a enlazar
este proyecto con el ((core)). Para enlazar los codigo objeto, el primer paso es ir al menu
del linker llamado ((AVR C++ Linker)) y en el apartado ((General)) sustituir la cadena
del cuadro de texto ((Command line pattern)) por la siguiente ((${COMMAND} -s -Os
${OUTPUT FLAG}${OUTPUT PREFIX}
${OUTPUT} ${INPUTS} -lm ${FLAGS})), por ultimo en la apartado ((libraries))
tenemos que indicar donde se encuentra el fichero con la librera, as como el nombre
de la misma. En nuestro caso el nombre era ((ArduinoCore)) y el ejecutable se puede
encontrar en la carpeta release del proyecto ((ArduinoCore)). Para completar este paso
por tanto, en el primer cuadro (((libraries -l)) debemos poner ArduinoCore y en el
cuadro inferior con el nombre ((libraries path -L)) la siguiente cadena que es relativa
al workspace: ((${workspace loc:/ArduinoCore/Release})) el aspecto final debera ser
parecido al mostrado en la Figura C.4.
C.7. SUBIENDO EL PROYECTO A NUESTRO ARDUINO 125

Una vez configuradas las libreras deberemos ((decir)) a eclipse que genere el ((.hex))
para el Arduino. Para ello hay que ir a las propiedades de AVR dentro del proyecto
luego ((C/C++ Build, Settings, Additional Tools in Toolchain)) y seleccionar la opcion
((Generate HEX file for flash memory)).

C.7. Subiendo el proyecto a nuestro Arduino


Para ((subir)) el ((.hex)) generado para nuestro proyecto a la memoria flash del
Arduino, lo primero que tenemos que hacer es configurar la herramienta ((avrdude))
que sera el programador software encargado de realizar la comunicacion con el Arduino
para subir el ((.hex)) al mismo.
((avrdude)) utiliza un fichero de configuracion llamado ((avrconf)) para saber los
diferentes tipos de programadores hardware con los que cuenta as como las configu-
raciones que debe realizar para comunicarse con cada uno de ellos.
Para configurar este archivo en ((avrdude)) tenemos que ir a las preferencias genera-
les de ((eclipse)) y buscar el menu ((AVR, AVRdude)). Una vez en el menu, el siguiente
paso consiste en marcar la casilla Use custom configuration file for AVRDude y buscar
el fichero ((avrconf)) en:
<directorioSDK>/hardware/tools/avr/etc/avrdude.conf.
Ahora que tenemos preparado el fichero ((avrconf)) lo siguiente consiste en confi-
gurar las propiedades del proyecto para indicar que tipo de programador ((hardware))
utilizaremos y que acciones debe realizar ((avrdude)) para programar el Arduino. El
primer paso consiste en ir a las propiedades del proyecto y buscar el menu ((AVR,
AVRDude)). Ahora crearemos una nueva configuracion dando al boton ((new)) de la
pestana ((Programmer)). El nombre de la configuracion puede ser el que desees, se
recomienda utilizar el nombre de tu placa as te sera mas sencillo tener todo ordena-
do. En ((programmer hardware)) deberemos buscar ((Wiring)). En el cuadro de texto
llamado ((Override default port)) deberemos poner el puerto en el cual se encuentra
conectado nuestro Arduino. En el caso de GNU/Linux este suele ser /dev/ttyACMX.
En Windows el puerto COM debera escribirse de la siguiente manera ((//./COMX)).
En cuanto a la velocidad en baudios variara en funcion del ((bootloader)) y de la placa,
los valores tpicos son 57600 y 115200. El aspecto de esta ventana de configuracion
debera ser similar a la Figura C.5.
Con todo configurado ya solo queda crear un archivo ((main.cpp)) en nuestro pro-
yecto con el esquema mostrado en Cod. C.1.
Como puedes observar, aqu se puede apreciar como cuando creamos un sketch
en el IDE oficial de Arduino realmente estamos creando las dos funciones principales
como son setup y loop pero nos despreocupamos de iniciar la plataforma (init()) y de
asegurarnos de que el programa nunca finaliza (bucle while).
Una vez guardado el programa tendremos que compilar el mismo dando boton
derecho sobre el proyecto y sobre la opcion ((Build Project)). Si todo esta bien se
creara una carpeta llamada ((Debug)) donde podremos encontrar un fichero llamado
((nombreDelProyecto.hex)). Ahora solo nos queda subirlo a nuestra placa, para ello
pulsamos boton derecho sobre el proyecto y buscamos ((AVR, Upload Project to Target
Device)).
C.7. SUBIENDO EL PROYECTO A NUESTRO ARDUINO 126

Figura C.5: Configuracion de AVRdude

1 # include < Arduino .h >


2 void setup () {
3 // config
4 }
5 void loop ()
6 {
7 // code
8 }
9 int main ( void ) {
10 init () ;
11 setup () ;
12 while ( true ) {
13 loop () ;
14 }
15 }

Codigo C.1: Esqueleto de programa


WARRANTY

NO WARRANTY
There is no warranty for this work. Except when otherwise stated in writing, the
Copyright Holder provides the work as is, without warranty of any kind. The entire
risk as to the quality and performance of the Work is with you. The Copyright Holder,
or any author named in the components of the work, or any other party who may
distribute and/or modify the Work as permitted above, be liable to you for damages
arising out of any use of the work (including, but not limited to, loss of data, data
being rendered inaccurate, or losses sustained by anyone as a result of any failure of
the Work to operate with any other programs), even if the Copyright Holder or said
author or said other party has been advised of the possibility of such damages.

127

You might also like