[go: up one dir, main page]

100% encontró este documento útil (4 votos)
633 vistas125 páginas

Arduino, Un Enfoque Práctico e Incremental

El motivo de este libro es dotar al lector de los conocimientos necesarios para poder diseñar sus propios circuitos y aportar soluciones para automatizar y controlar diferentes procesos tanto domésticos, para domótica, hobbies, etc., así como también procesos industriales. Para ello, adoptaremos una metodología incremental y lo más práctica posible, empezando desde lo básico como es encender un LED, hasta automatizar una cadena de montaje y gestionar la misma con diferentes tecnologías
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
100% encontró este documento útil (4 votos)
633 vistas125 páginas

Arduino, Un Enfoque Práctico e Incremental

El motivo de este libro es dotar al lector de los conocimientos necesarios para poder diseñar sus propios circuitos y aportar soluciones para automatizar y controlar diferentes procesos tanto domésticos, para domótica, hobbies, etc., así como también procesos industriales. Para ello, adoptaremos una metodología incremental y lo más práctica posible, empezando desde lo básico como es encender un LED, hasta automatizar una cadena de montaje y gestionar la misma con diferentes tecnologías
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 125

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

Gazzano.

Reservados todos los derechos. No se permite la reproducción total o parcial de esta obra, ni su
incorporación a un sistema informático, ni su transmisión en cualquier forma o por cualquier medio
(electrónico, mecánico, fotocopia, grabación u otros) sin autorización previa y por escrito de los
titulares del copyright. La infracción 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

©José Antonio de la Torre las Heras, 2016


©Julio Daniel Dondo Gazzano, 2016
All rights reserved.
Queremos expresar nuestro mas sincero agradecimiento al grupo de investigación ARCO, de la Es-
cuela Superior de Informática de la Universidad de Castilla-La Mancha en Ciudad Real, por su apoyo
en la elaboración de este libro.

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

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


poder diseñar sus propios circuitos y aportar soluciones para automatizar y controlar
diferentes procesos tanto domésticos, para domótica, hobbies, etc., ası́ como tambien
procesos industriales.
Para ello, adoptaremos una metodologı́a incremental y lo más práctica posible, em-
pezando desde lo básico como es encender un LED, hasta automatizar una cadena de
montaje y gestionar la misma con diferentes tecnologı́as apoyándonos en el ecosistema
Arduino.

5
6
ÍNDICE GENERAL

1. ¿Qué es Arduino? 11
1.1. Elementos de Arduino . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2. Primeros pasos en Arduino 17


2.1. Instalando el entorno Arduino . . . . . . . . . . . . . . . . . . . . . . . 17
2.1.1. GNU/Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.1.2. Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2. Probando nuestra placa Arduino . . . . . . . . . . . . . . . . . . . . . 18
2.2.1. Conexión y configuración de drivers . . . . . . . . . . . . . . . 18
2.2.2. Primer programa . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3. Manejando entradas/salidas 23
3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2. Utilizando los pines de salida . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.1. Ejemplo 1: Encendiendo un LED . . . . . . . . . . . . . . . . . 24
3.3. Utilizando los pines de entrada . . . . . . . . . . . . . . . . . . . . . . 28
3.3.1. Ejemplo 2: Utilización de un botón o interruptor . . . . . . . . 30
3.3.2. Ejemplo 3: Leyendo temperatura y señales analógicas . . . . . 34

4. Comunicaciones 41
4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.2. Comunicación serie mediante USART . . . . . . . . . . . . . . . . . . 41
4.2.1. Ejemplo 1: Hola mundo por Serie . . . . . . . . . . . . . . . . . 43
4.2.2. Ejemplo 2: Recibiendo información . . . . . . . . . . . . . . . . 47
4.2.3. Ejemplo 3: Comunicación entre Arduinos . . . . . . . . . . . . 50
4.3. Comunicación I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.3.1. Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.3.2. Protocolo I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.3.3. Ejemplo 1: Hola mundo mediante I2C . . . . . . . . . . . . . . 58
4.3.4. Ejemplo 2: Obteniendo datos de un IMU . . . . . . . . . . . . 62
4.4. Protocolo SPI (Serial Peripheral Interface . . . . . . . . . . . . . . . . 65

7
ÍNDICE GENERAL 8

5. Interrupciones 71
5.1. Interrupciones en el ATmega328 . . . . . . . . . . . . . . . . . . . . . 72
5.2. Manipulación software . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.2.1. Librerı́a avr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2.2. Librerı́a Arduino . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2.3. Consideraciones importantes . . . . . . . . . . . . . . . . . . . 75
5.3. Ejemplo 1: Primera rutina de interrupción . . . . . . . . . . . . . . . . 76
5.3.1. Tabla de entrada/salida . . . . . . . . . . . . . . . . . . . . . . 76
5.3.2. Código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.4. Ejemplo 2: Midiendo distancias . . . . . . . . . . . . . . . . . . . . . . 77
5.4.1. Tabla de entrada/salida . . . . . . . . . . . . . . . . . . . . . . 78
5.4.2. Código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

6. Multitasking y Timers 81
6.1. Timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
6.1.1. Registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
6.1.2. Modos de funcionamiento . . . . . . . . . . . . . . . . . . . . . 83
6.1.3. Ejemplos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6.2. Multitasking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
6.2.1. Encendiendo y apagando un led de manera profesional . . . . . 96
6.2.2. Encendiendo y apagando un led de manera más profesional . . 98

A. Construyendo nuestro propio Arduino 101


A.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
A.2. Componentes necesarios . . . . . . . . . . . . . . . . . . . . . . . . . . 101
A.3. Ensamblado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
A.4. Programación del Bootloader . . . . . . . . . . . . . . . . . . . . . . . 105

B. Manipulación de registros 109


B.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
B.2. ¿Qué es un registro? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
B.3. Operaciones con registros . . . . . . . . . . . . . . . . . . . . . . . . . 111
B.3.1. Activar un bit . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
B.3.2. ORing, activación de un bit . . . . . . . . . . . . . . . . . . . . 112
B.3.3. Bit Shifting, movimiento de bits . . . . . . . . . . . . . . . . . 112
B.3.4. ANDing, desactivando bits . . . . . . . . . . . . . . . . . . . . 114

C. Entorno Eclipse con Arduino 117


C.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
C.2. Qué es Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
C.3. Instalación del entorno . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
C.4. Configuración del entorno . . . . . . . . . . . . . . . . . . . . . . . . . 119
C.5. Creando el proyecto: ArduinoCore . . . . . . . . . . . . . . . . . . . . 120
C.6. Creando el proyecto final . . . . . . . . . . . . . . . . . . . . . . . . . 122
C.7. Subiendo el proyecto a nuestro Arduino . . . . . . . . . . . . . . . . . 123
ÍNDICE DE FIGURAS

1.1. Placa Arduino Mega 2560 . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.1. Instalador Arduino para Windows . . . . . . . . . . . . . . . . . . . . 18


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

3.1. Flujo normal de ejecución para sistemas de control . . . . . . . . . . . 24


3.2. Composición de un LED . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3. Ejemplo 1: Esquema de montaje . . . . . . . . . . . . . . . . . . . . . 26
3.4. Ejemplo 1: Protoboard, esquema de montaje . . . . . . . . . . . . . . 26
3.5. Ejemplo 1: Diagrama de flujo . . . . . . . . . . . . . . . . . . . . . . . 27
3.6. Osciloscopio digital . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.7. Frecuencı́metro digital . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.8. Señal digital vs Señal analógica . . . . . . . . . . . . . . . . . . . . . . 31
3.9. Interruptor SPST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.10. Rebote de un botón y valores obtenidos . . . . . . . . . . . . . . . . . 32
3.11. Módulo botón Grove . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.12. Ejemplo 2: Diagrama de flujo . . . . . . . . . . . . . . . . . . . . . . . 33
3.13. Ejemplo 3: Diagrama de flujo . . . . . . . . . . . . . . . . . . . . . . . 38

4.1. Trama con formato 8N1 . . . . . . . . . . . . . . . . . . . . . . . . . . 42


4.2. Conexión entre dispositivos UART con control de flujo . . . . . . . . . 43
4.3. Representación de comunicación serie . . . . . . . . . . . . . . . . . . . 43
4.4. Ejemplo 1 - Comunicaciones: Diagrama de flujo . . . . . . . . . . . . . 45
4.5. Ejemplo 2 - Comunicaciones: Diagrama de flujo . . . . . . . . . . . . . 48
4.6. Ejemplo 3 - Comunicaciones: Diagrama de flujo . . . . . . . . . . . . . 53
4.7. Esquema de comunicación I2C . . . . . . . . . . . . . . . . . . . . . . 57
4.8. Trama de dirección I2C . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.9. Trama de datos I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.10. Sensor MPU6050 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.11. Conexiones MPU6050 . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

9
ÍNDICE DE FIGURAS 10

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


4.13. Contenidos del registro de entrada del DAC . . . . . . . . . . . . . . . 67
4.14. Forma de onda obtenida conversión D/A . . . . . . . . . . . . . . . . . 68

5.1. Ejemplo de indirección . . . . . . . . . . . . . . . . . . . . . . . . . . . 72


5.2. Sensor emisor/receptor ultrasonidos . . . . . . . . . . . . . . . . . . . 78

6.1. Diagrama TIMER 8 bits ATmega328 . . . . . . . . . . . . . . . . . . . 82


6.2. Modo CTC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6.3. Modo Fast PWM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6.4. Registro TCCR1A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6.5. Bits de configuración del presclarer . . . . . . . . . . . . . . . . . . . . 88
6.6. Registro TCCR1B . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
6.7. Bits para configuración del pin de salida . . . . . . . . . . . . . . . . . 89
6.8. Configuración modo CTC . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.9. Diagrama de bloques del sistema de control . . . . . . . . . . . . . . . 92
6.10. Servo de Grove-Starter Kit for Arduino . . . . . . . . . . . . . . . . . 92
6.11. Diagrama de tiempos de un servo . . . . . . . . . . . . . . . . . . . . . 93
6.12. Ejemplo de programa en ladder . . . . . . . . . . . . . . . . . . . . . . 95

A.1. Protoboard de referencia . . . . . . . . . . . . . . . . . . . . . . . . . . 103


A.2. Esquema de conexiones . . . . . . . . . . . . . . . . . . . . . . . . . . 103
A.3. Patillaje ATmega328 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
A.4. Conectores ICSP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
A.5. Captura de pantalla del Arduino IDE . . . . . . . . . . . . . . . . . . 107

B.1. Jerarquı́a de memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . 110


B.2. Registro de 8 bits (byte) como una ((caja)) . . . . . . . . . . . . . . . . 111
B.3. Operación: 1 << 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
B.4. Activación de pin 3 sobre registro previamente configurado . . . . . . 113
B.5. Configuración del pin 3 como entrada sobre registro previamente con-
figurado (procedimiento erróneo) . . . . . . . . . . . . . . . . . . . . . 114
B.6. Configuración del pin 3 como entrada sobre registro previamente con-
figurado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

C.1. Pantalla inicial de Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . 118


C.2. Descarga del plugin AVR . . . . . . . . . . . . . . . . . . . . . . . . . 120
C.3. Configuración de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . 121
C.4. Configuración de las librerı́as . . . . . . . . . . . . . . . . . . . . . . . 122
C.5. Configuración de AVRdude . . . . . . . . . . . . . . . . . . . . . . . . 124
CAPÍTULO 1
¿QUÉ ES ARDUINO?

En este libro vamos a describir una serie de diseños y desarrollos prácticos para
automatizar el control de sistemas utilizando el entorno Arduino. El entorno Arduino
es una plataforma electrónica de desarrollo de código abierto (open source) que ofre-
ce un conjunto de herramientas tanto software como hardware que nos permitirán
realizar nuestros prototipos de una manera rápida, sencilla y segura.
Está formado principalmente por una placa de desarrollo, equipada con un mi-
crocontrolador AVR, puertos de conexión de entrada y salida, más los elementos
necesarios para programar el microcontrolador, y un ambiente de desarrollo software
con una serie de librerı́as para construir nuestros diseños.

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 lanzó el primer Arduino


al mercado en 2005 se han diseñado numerosas placas de desarrollo por
diferentes empresas de ensamblado, tales como Funduino, FreeDuino. . .
Cada placa está compuesta normalmente por un microcontrolador de la
marca Atmel. Dependiendo de la gama, la placa dispondrá 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 además de 86 pines de entrada
salida y 5 puertos de comunicación SPI.
Las placas Arduino son ((libres)) y por lo tanto en Internet se pueden encon-
trar los esquemáticos de cada placa e incluso si quieres puedes crearte la

11
1.1. ELEMENTOS DE ARDUINO 12

Figura 1.1: Placa Arduino Mega 2560

tuya propia o añadir 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 sólo una pequeña selección 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 expansión: Unas de las caracterı́sticas que han
hecho que Arduino gane terreno entre otras placas de desarrollo son el
bajo precio y la capacidad de expansión que estas placas tienen. En el
mercado existen diferentes tipos de tarjetas de expansión para componentes
varios, como controladores de motores, módulos de comunicación, etc. En la
página de Arduino se pueden ver algunos shields, aunque existen numerosas
tiendas como Adafruit 1 que nos ofrecen una variedad mayor.
ˆ Módulos conectar y listo: Además de las shields de expansión, Arduino
cuenta con un gran número de módulos del tipo ((conectar y listo)). La dife-
rencia con los shields es que estos últimos únicamente son interfaces para
diferentes sensores y actuadores de modo que sea más sencillo utilizarlos
en nuestros proyectos. Por ejemplo existen módulos conectar y listo para
sensores de temperatura, humedad, luz, botones, potenciómetros, LEDs,
etc.
Software
ˆ Arduino IDE: Además de las placas de desarrollo, Arduino proporciona
un IDE (Entorno de Desarrollo Integrado) basado en processing con el que
podremos diseñar nuestros proyectos. Este IDE consta de un editor de tex-
to para escribir nuestros programas, un área de mensajes, una consola de
comandos y una barra de herramientas con diferentes menús. Este entorno
1 http://www.adafruit.com/category/17
Tipo Procesador Flash EEPROM Número de puertos Comunicaciones
Arduino Mega ATmega1280 128KB 4KB 54 digitales, 15 SPI, I2C, Se-
(16MHz) PWM, 16 analógi- rial(x2)
cos
Arduino Mega ATmega2560 256KB 4KB 54 digitales, 15 TWI,SPI, Se-
2560 (16MHz) PWM, 16 analógi- rial(x3)
cos
Arduino Board AT91SAM3X8E 512 KB 51 digitales, 12 TWI(x2), Se-
Due (84MHz) PWM, 12 analógi- 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 analógi- Serial, WiFi
cos
Arduino Micro ATmega32u4 32KB 1 KB 20 digitales, 7 SPI, Serial, I2C
(16MHz) PWM, 12 analógi-
cos
Arduino Uno ATmega328 32KB 1KB 14 digitales, 6 I2C, SPI, Serial
(16MHz) PWM, 6 analógi-
cos
Arduino Leonardo ATmega32u4 32KB 1KB 20 digitales, 7 I2C, SPI, TWI, Se-
(16MHz) PWM, 12 analógi- rial
cos
a Arduino Yun es ligeramente diferente a las demás placas, además del microcontrolador ATmega32u4 posee un procesador Atheros AR9331 el cual soporta

OpenWrt, una “distribución” Linux para lo comunicación Ethernet y WiFi


13
1.1. ELEMENTOS DE ARDUINO 14

se conecta a la placa Arduino para programarla con el código que hemos


desarrollado y para comunicarse con ella. El código desarrollado en el Ar-
duino IDE se denomina sketch y es la unidad de código que es cargado y
ejecutado en una placa Arduino.
ˆ Librerı́a ((Arduino.h)): Arduino ha conseguido minimizar la curva de
aprendizaje del lenguaje de programación C++ mediante una librerı́a que
nos evita lidiar al principio con problemas como el uso de operadores
de bits, configuración de registros del microcontrolador, etc. La librerı́a
((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
quisiéramos configurar un GPIO(Pin de Entrada Salida de uso General)
para que actué como pin de salida, únicamente tendrı́amos que invocar a la
función pinMode(PIN,OUTPUT). A lo largo de este libro iremos utilizan-
do esta librerı́a en todos nuestros proyectos y poco a poco, según 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 más complejo y únicamen-
te está disponible para la plataforma Windows, pero tiene muchas más
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 monolı́tica como ası́ lo hacı́a su antecesor.
Arduino Studio aprovecha las capacidades de ((Adobe Brackets)). Actual-
mente se encuentra en versión alpha, pero ya cuenta con caracterı́sticas
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 código en tiempo real y documentación 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 corazón de Arduino.
Como hemos dicho anteriormente las placas de desarrollo Arduino están
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 código C++ de nuestro Sketch. Además, una vez que hemos compilado
2 Aunque ((Arduino.h)) es la librerı́a general, en realidad Arduino se apoya en más librerı́as 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 15

el programa, el toolchain nos permite subir el código máquina a nuestra


placa por medio de avrdude, también libre 4 .
El uso de este toolchain nos permite ((saltarnos)) la capa de abstracción
que nos proporciona Arduino y programar a más bajo nivel, lo que a su
vez nos permitirá tener una visión más detallada de la programación 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 16
CAPÍTULO 2
PRIMEROS PASOS EN ARDUINO

2.1. Instalando el entorno Arduino


El entorno de desarrollo Arduino está programado en Java por lo que es multi-
plataforma. El entorno lo podemos encontrar en la página oficial de Arduino (http:
//www.arduino.org/en/Main/Software), dependiendo del sistema operativo se de-
berá descargar una versión u otra. El lector puede preguntarse por qué existen varias
versiones si el entorno está basado en Java y en última instancia en la máquina
virtual de Java (JVM), esto ocurre porque junto al entorno nos descargamos el tool-
chain AVR mencionado en la Sección 1.1 junto con los drivers de AVR por lo que
dependiendo del sistema operativo tendremos que instalar unos drivers u otros. En
la página de la comunidad de Arduino llamada Arduino Srl (Smart Projects Srl)
(http://www.arduino.org/software)es posible encontrar además 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 distribución ((Debian)), para instalar todo
el entorno únicamente tendremos que poner los siguientes comandos en la terminal
como ((superusuario)):

apt - get install arduino arduino - core

Es importante añadir 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
añadir nuestro usuario a los grupos únicamente ingresaremos el siguiente comando:
gpasswd -a $ USER uucp
gpasswd -a $ USER lock

Con esto ya tendremos nuestro entorno listo.

17
2.2. PROBANDO NUESTRA PLACA ARDUINO 18

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 aparecerá una ventana como la de la
Figura 2.1 en donde se nos irá guiando para completar la instalación.

2.2. Probando nuestra placa Arduino


2.2.1. Conexión y configuración 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 esté 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 aparecerá un mensaje
mostrando el proceso de configuración y búsqueda de drivers, Windows nos infor-
mará si todo va bien y ya estaremos en disposición 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 órdenes enlazadas mediante una tuberı́a
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 últimas lı́neas del
registro.
2.2. PROBANDO NUESTRA PLACA ARDUINO 19

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 función de la placa que tengamos,
por ejemplo, para una placa UNO aparecerá Product: Arduino UNO.

2.2.2. Primer programa


Nuestro primer programa únicamente nos servirá para comprobar que durante
todo el Workflow no se genera ningún error, es decir, podemos programar, compilar
y ((subir)) sketchs sin ningún 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 abrirá un editor como el de la Figura 2.2
.
A continuación, copia el código Cod. 2.1
Este programa lo único 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 más básica de dos
funciones: una función setup(), donde las variables y los pines a utilizar se declaran e
2.2. PROBANDO NUESTRA PLACA ARDUINO 20

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 }

Código 2.1: Primer ejemplo

inicializan y una función loop() donde se define el código 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 está indicado en la lı́nea 3 del código. Seguidamente en las lı́neas 7 y 9 alternamos
el estado del pin pasándolo de estado alto (HIGH) a estado bajo (LOW). La función
delay() retarda la ejecución de la instrucción siguiente en la cantidad de milisegundos
indicado por el valor entre paréntesis, en nuestro caso 1000 ms.
Para ver el funcionamiento de este diseño el lector deberá 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 hará falta ya
que este pin ya tiene una resistencia incorporada.
El pin y el led de prueba están señalados en la Figura 2.3.
Ahora únicamente nos resta seleccionar el modelo de placa que estemos usando,
compilar el código y subirlo a nuestra placa de desarrollo. Para ello iremos a la pes-
taña: “Herramientas, Tarjeta” y seleccionamos nuestra placa, después tenemos que
habilitar el puerto serie para programar el Arduino, en esta ocasión, navegaremos has-
ta“Herramientas, Puerto Serial” y seleccionaremos el puerto USB de nuestra placa.
Normalmente únicamente tendremos un puerto conectado, pero si dispusiéramos de
varios tendrı́amos que ver cuál corresponde a la placa según el sistema operativo.
Si en GNU/Linux no aparece ningún puerto serial es probable que el usuario no
tenga los permisos adecuados. En este caso revise cada uno de los pasos mencionados
en Subsección 2.1.1.
Una vez hecho esto vamos a compilar y subir el código a la placa, para ello vamos
a la pestaña: “Sketch, Verificar/Compilar” o simplemente pulsamos ((Ctrl-R)) que
hará que el IDE primero verifique la sintaxis del código 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 21

Figura 2.3: Led y puerto 13 en Arduino Mega 2560


2.2. PROBANDO NUESTRA PLACA ARDUINO 22
CAPÍTULO 3
MANEJANDO
ENTRADAS/SALIDAS

3.1. Introducción
En este capı́tulo vamos a trabajar con la placa de desarrollo Arduino enfocándo-
nos principalmente en el manejo de sus entradas y salidas. En la siguiente sección de
este capı́tulo (Sección 3.2) abordaremos las diferentes técnicas para controlar actua-
dores desde Arduino. Más adelante, en la Sección 3.3 veremos como obtener datos del
exterior, tanto analógicos 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 programación del Arduino, utilizaremos un kit
de iniciación llamado Grove Starter Kit for Arduino de la marca Seeed el cual se
puede adquirir por Internet a un precio relativamente bajo.1 . Además del kit Grove,
utilizaremos el kit de expansión Sidekick Basic Kit for Arduino V2.2

3.2. Utilizando los pines de salida


Normalmente, a la hora de diseñar cualquier sistema de control nos encontramos
con una secuencia de ejecución 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 ¿Qué es un actuador?. La definición 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

23
3.2. UTILIZANDO LOS PINES DE SALIDA 24

Figura 3.1: Flujo normal de ejecución para sistemas de control

Actuador
Dispositivo capaz de transformar la energı́a neumática, eléctrica o hidráulica en
la activación 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 definición. Por un lado transforma la energı́a eléctrica
en un efecto (movimiento del robot) y por otro lado, este movimiento se realiza sobre
un proceso automatizado (lógica del robot). Al igual que el ejemplo del robot, existen
otros muchos actuadores. Se deja al lector la tarea de buscar en la red más información
sobre los diferentes tipos.
El objetivo de esta sección es controlar estos actuadores, de modo que podamos
actuar sobre el entorno desde Arduino.
La manera más 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 Sección A.1
tenemos un total de 23 pines que pueden ser configurados cómo entrada o salida.
Veamos algunos ejemplos:

3.2.1. Ejemplo 1: Encendiendo un LED


En este primer ejemplo, veremos cómo encender un LED, aunque ya se vio de
manera rápida en Subsección 2.2.2 en esta sección lo veremos de manera mas detallada.
El LED (Figura 3.2) es un elemento notificador muy utilizado a dı́a 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 25

Figura 3.2: Composición de un LED

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


LED únicamente con el propósito de notificar o avisar sobre un evento. Hoy en dı́a su
uso se ha extendido a otras áreas como la de la iluminación e incluso la comunicación.
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 ten-
dremos que preocuparnos por el consumo de corriente. En el caso de que el lector
haya montado el Arduino siguiendo la guı́a del Sección A.1, entonces deberá conectar
el LED y la resistencia al pin 13. Para identificar la posición de cada uno de los pi-
nes se puede consultar la hoja de datos del ATmega que se puede descargar desde el
siguiente enlace: 3 . Si está 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 encapsu-
lado utilizado en Sección 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 semicı́rculo de referencia. Además del datasheet, para saber el mapeado de pines es
necesario ir al esquemático de cada placa y buscar a que pin de la librerı́a corresponde
cada pin fı́sico.4
En el caso del Arduino realizado en la Sección A.1, el montaje debe ser parecido
al mostrado en: Figura 3.3, Figura 3.4.

Tabla de entrada/salida
Para obtener unos diseños fiables, seguros y de calidad se deben seguir procedi-
mientos y normas de calidad de diseño. Por ello, aunque este ejemplo sea sencillo,
seguiremos una metodologı́a a la hora de afrontar el problema.
El primer paso en cualquier diseño es analizar los elementos de control en base a las
especificaciones. En nuestro caso únicamente tenemos un único elemento a controlar
(el LED) por lo que este paso se podrı́a obviar, sin embargo documentar es una buena
costumbre en cualquier diseño. En la Tabla 3.1 se puede ver un ejemplo de análisis de
elementos de control. En primer lugar, se indica si el elemento es de entrada o salida.
En segundo lugar, se realiza una descripción sobre el elemento, de modo que siempre
se sepa la función del mismo. Por otro lado, se indica el nombre de la variable que se
3 http://goo.gl/S3oBA2
4 Los esquemáticos se pueden encontrar en la página de Arduino o en el caso de una placa ensam-

blada por otro fabricante en la página del mismo.


3.2. UTILIZANDO LOS PINES DE SALIDA 26

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 27

Cuadro 3.1: Ejemplo 1: Tabla de entrada/salida


Entrada/Salida Descripción Nombre variable Pin
Salida Conmutar cada led 13
1/2 segundo

Figura 3.5: Ejemplo 1: Diagrama de flujo

utilizará en el código para hacer referencia a dicho elemento y por último el pin fı́sico
al cual se conectará.

Diagrama de flujo
En base al diagrama general mostrado en la Figura 3.1 al que normalmente se
adapta cualquier diseño, vamos a realizar el diagrama correspondiente para nuestro
diseño.
La herramienta utilizada para crear dicho diagrama es DIA una herramienta de
código 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 única manera de representar nuestro diseño. Un mismo problema
puede tener diferente soluciones correctas.

Código
Ahora que tenemos toda la documentación lista y se ha analizado el problema es
el momento de realizar el diseño de la solución y su implementación. Para programar
la placa Arduino utilizaremos el entorno de programación instalado descrito en el
Capı́tulo 1. Como se adelantó en el capı́tulo anterior todo programa que vaya a ser
ejecutado en el entorno Arduino debe tener dos funciones principales y obligatorias:

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


3.3. UTILIZANDO LOS PINES DE ENTRADA 28

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 }

Código 3.1: Ejemplo 1: Encendiendo y apagando un led

esta función se suelen realizar tareas como la inicialización de variables, pines,


comunicaciones, información de inicio al usuario, tareas de login. etc.

loop(void): También 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 función es donde programaremos la lógica siguiendo el diagrama general
mostrado en Figura 3.1.

Es importante mantener un orden en la función 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 más sencillo de leer tal y como se muestra en: Cod. 3.2.
En este nuevo código se utilizan dos funciones ademas de las funciones setup() y
loop() básicas: la función read sensors() y la función perform actions() La función
read sensors() es una función 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 función vacı́a.
La segunda contiene el código de encendido y apagado de los LEDs.
Como se puede observar, el código es muy simple y legible. Gracias a la librerı́a
((Arduino.h)), para configurar un pin como entrada o salida únicamente tenemos que
llamar a la función pinMode() indicando en sus parámetros tanto el pin como el modo.
Por otro lado, para obtener 5V o 0V en el pin, usaremos la función digitalWrite().

3.3. Utilizando los pines de entrada


En la Subsección 3.2.1 vimos como utilizar los pines de entrada/salida del AT-
mega328 para informar al usuario de una acción mediante un LED o de forma más
3.3. UTILIZANDO LOS PINES DE ENTRADA 29

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 }

Código 3.2: Ejemplo 1: Recodificación utilizando funciones


3.3. UTILIZANDO LOS PINES DE ENTRADA 30

Figura 3.6: Osciloscopio digital

genérica como interaccionar con un actuador. En esta sección, veremos todo lo con-
trario, es decir, aprenderemos como captar una acción del usuario (por ejemplo la
pulsación de un botón).
Los pines de entrada salida, como su nombre indica, se pueden utilizar como
entrada o como salida. La selección del modo de funcionamiento de cada pin se realiza
mediante unos registros hardware. En estos primeros ejemplos no se verá el manejo de
dichos registros. Sin embargo, en la Sección B.1 el lector puede estudiar como utilizar
dichos registros mediante operaciones de desplazamiento de bits.
A la hora de captar una acción o magnitud en un microcontrolador lo primero
que debemos preguntarnos es qué tipo de magnitud necesitamos leer o captar. De
forma genérica y simplificando, existen dos grandes grupos en los cuales se clasifican
las señales (Figura 3.8):
1. Señales analógicas: Las señales analógicas son continuas en el tiempo, es de-
cir, se puede representar mediante una función matemática continua. ¿Qué ele-
mentos de la señal analógica se pueden medir? principalmente se analizan dos
variables: la amplitud y el periodo o frecuencia. Ejemplos de señales analógicas
son el sonido, señales de radio frecuencia, señales electromagnéticas.
En electrónica para medir las señales analógicas 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 matemáticas como la FFT (transformada rápida de fourier).
2. Señales digitales: Las señales digitales son aquellas en las cuales sus valores
están completamente discretizados. Aunque cualquier fenómeno electromagnéti-
co como el producido al realizar contacto en un pulsador es continuo, sus estados
pueden ser discretizados en función del número de bits utilizados en la conver-
sión, en este caso dos estados (pulsado y no pulsado).

3.3.1. Ejemplo 2: Utilización de un botón o interruptor


En este ejemplo nos centraremos en las señales digitales. Más adelante hablaremos
sobre las señales analógicas y veremos diferentes casos de uso.
Un botón o mejor dicho y en sentido más amplio, un interruptor eléctrico es un
dispositivo con dos estados: en uno de estos estados (cerrado) se permite la circulación
3.3. UTILIZANDO LOS PINES DE ENTRADA 31

Figura 3.7: Frecuencı́metro digital

Señal
continua

Tiempo

Señal
discreta

1100
1010
1000
0110
0100
0010
0000 Tiempo

Figura 3.8: Señal digital vs Señal analógica


3.3. UTILIZANDO LOS PINES DE ENTRADA 32

Figura 3.9: Interruptor SPST

Rebote
VOH
Valor lógico alto
VIH

Indeterminado

VIL
Valor lógico bajo
VOL
1 1 1 1 X 1 0 1 0 X 0 0 0 0 0

Figura 3.10: Rebote de un botón y valores obtenidos

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


puedes encontrarlo representado con diferentes sı́mbolos. Sin embargo, el más común
es el que se muestra en la Figura 3.9. Existen muchos tipos de interruptores en función
de los polos, vı́as, etc, pero nosotros utilizaremos los simples, es decir, de un polo y
una vı́a.
Los interruptores como cualquier dispositivo mecánico son imperfectos y tienen
un desgaste por el propio uso. Aunque parezca que en un diseño esto no es necesario
tenerlo en cuenta, una mala elección puede que haga que nuestro diseño tenga una
duración menor a la estimada y por lo tanto, se incremente el coste de mantenimiento.
Además, 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 método es realizar una máquina 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 gráfica que ilustra lo comentado.
Para simplificar el diseño y centrarnos en la programación del microcontrolador,
en este ejemplo utilizaremos uno de los módulos ((conectar y listo)) del kit Groove.
En Figura 3.11 se puede ver el aspecto del módulo. Dicho modulo nos simplifica el
montaje dado que lleva incorporada la resistencia de pull-down anteriormente citada.

Figura 3.11: Módulo botón Grove


3.3. UTILIZANDO LOS PINES DE ENTRADA 33

Cuadro 3.2: Ejemplo 2: Tabla de entrada/salida


Entrada/Salida Descripción Nombre variable Pin
Salida Led notificador de led 13
pulsación
Entrada Pulsador button 12

Figura 3.12: Ejemplo 2: Diagrama de flujo

Tabla de entrada/salida
En este ejemplo utilizaremos el código de la Subsección 3.2.1 de modo que cuando
el pulsador se encuentre accionado se encenderá el LED y cuando el pulsador no se
encuentre pulsado el LED se apagará. Como en el ejemplo anterior, lo primero que hay
que realizar en el diseño 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 función del estado de un elemento (condición) actuaremos
de una manera o de otra.

Código
Con el diagrama estudiado, trasladar la solución a código es muy sencillo cuan-
do se tiene un poco de práctica. Como dijimos en la Subsección 3.2.1 tenemos dos
3.3. UTILIZANDO LOS PINES DE ENTRADA 34

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


ración del botón como entrada mediante la función pinMode() con el correspondiente
parámetro 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 función digitalWrite() dentro del setup() y sobre un
pin configurado anteriormente como entrada.
Una vez configurados los pines de entrada/salida, debemos programar la lógica
del bucle. Si revisas el Cod. 3.2 creamos dos funciones dummy 5 : read sensors() y
perform actions(). En este caso utilizaremos también la función read sensors() en
la cual leeremos el valor del botón mediante la función digitalRead(). Esta función
devuelve HIGH o LOW en función del valor del pin 5v o 0v.
Una vez que hemos leı́do el valor del pin de entrada mediante la función digital-
Read() sólo falta implementar la toma de decisiones en función de ese valor leı́do. Para
ello usamos la función perform actions() que consulta sobre el valor de la variable y
si es igual a HIGH (botón pulsado) se enciende el LED, de lo contrario se apaga.
Como puedes observar tratar con señales digitales es muy sencillo. Existen sensores
como el sensor de temperatura DHT11 que aun monitorizando una señal analógica
(temperatura) son capaces de ofrecer dicha información como un valor digital. Más
adelante veremos cómo se consigue esto y de qué manera lo podemos utilizar en los
diseños con Arduino.

3.3.2. Ejemplo 3: Leyendo temperatura y señales analógicas


Hasta ahora hemos trabajado con señales digitales. Normalmente el entorno que
nos rodea es continuo y sus efectos se manifiestan de forma analógica. Un ejemplo
podrı́a ser la energı́a que desprendemos cuando tenemos calor o incluso el sonido que
producimos al hablar. Como puedes imaginar la necesidad de analizar y controlar
estas magnitudes está presente en el diseño con microcontroladores. En esta sección
veremos cómo leer la temperatura ambiente y un ejemplo de actuación en función de
dichos valores.

Fundamentos
En el mercado existen sensores de muchos tipos, los hay digitales como por ejemplo
los sensores de paso y también los hay analógicos como los sensores de temperatura.
Normalmente, estos sensores producen señales de un voltaje bajo. Para poder tratar
con dichas señales se suelen realizar diferentes etapas de amplificación y procesamien-
to.
Como seguramente ya sepas, los computadores y los microcontroladores única-
mente trabajan con bits, es decir, dı́gitos que únicamente pueden tomar el valor 1
o 0. Para convertir una señal analógica a una señal digital se emplean los llamados
((Conversores Analógicos Digitales)) o de forma abreviada ADC. Si lo que queremos
es producir una señal analógica a partir de una digital se utilizan los ((Conversores
Digitales Analógicos)) o de forma abreviada DAC. La teorı́a que hay detrás de estos
5 Las funciones dummy son aquellas que no tienen ninguna utilidad pero que se incorporan al

código por alguna razón, normalmente por estandarización


3.3. UTILIZANDO LOS PINES DE ENTRADA 35

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 }

Código 3.3: Ejemplo 2: Código para la utilización de un botón


3.3. UTILIZANDO LOS PINES DE ENTRADA 36

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


dad. A continuación se dará una breve explicación sobre dos de los parámetros claves
a la hora de identificar si nuestro microcontrolador es adecuado para una determinada
señal.

Número de bits: El número de bits del conversor se puede ver como la resolu-
ción del mismo. Un ejemplo ilustrará 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) ¿cuál es la resolución
máxima con la que se podrı́a trabajar en el diseño?. 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 división 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,
significará que el sensor ha generado 259 mV.

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


con señales de una frecuencia determinada. Según el teorema de muestreo, la
velocidad de muestreo debe ser como mı́nimo el doble del ancho de banda de la
señal de entrada.

Aunque estos dos parámetros son muy importantes a la hora de seleccionar un


conversor, como ya hemos dicho existen muchos parámetros estáticos y dinámicos
que influyen en la elección de los mismos.
Otro de los aspectos que debemos tener en cuenta a la hora de realizar una con-
versión es el valor de referencia. Como ya hemos explicado mediante el número de
bits, la resolución de nuestra conversión estará dada por el número de bits del conver-
sor pero también hablamos de un rango fijo de 5 V. Este rango fijo se llama voltaje
de referencia. ¿Para qué sirve este voltaje?. Imaginemos que tenemos un sensor que
únicamente aporta valores entre 0 y 1.5 voltios o que en el entorno en el que estamos,
el mı́nimo valor de un sensor de temperatura es 1.5 y el máximo 3.5. Como puedes
observar los voltajes de referencia varı́an. La solución planteada anteriormente de
usar un voltaje de referencia de 5 voltios es válida para ambos casos pero estaremos
perdiendo resolución dado que el Arduino está teniendo en cuenta valores que son
imposibles de alcanzar (desde 1.5 V - 5 V). Para solucionar esta situación en Arduino
poseemos una función llamada analogReference() que nos permite variar el voltaje de
referencia pudiendo pasar los siguientes valores como parámetro:

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 37

Cuadro 3.3: Ejemplo 2: Tabla de entrada/salida

Entrada/Salida Descripción Nombre variable Pin


Salida Led notificador de led 13
mı́nima 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 función analogReference() antes de cualquier llamada a la función analo-
gRead() de lo contrario podremos dañar la placa.
Si el parámetro pasado es EXTERNAL se deberá 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 tensión. Hay que tener en cuenta que Arduino posee una resistencia de
32 Kohms interna por lo que la formula para el divisor de tensión quedará como se
muestra en la Ecuación 3.1. Teniendo en cuenta esto con una resistencia de 75 Khoms
y un voltaje de entrada de 5v aproximadamente obtendrı́amos un valor de referencia
de 1.5V lo que nos permitirı́a tener una resolución 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 módulo conectar y listo de Grove termostato.
Existen numerosos sensores de temperatura. Algunos como por ejemplo el DHT11 o
el DHT22 permiten leer la temperatura únicamente mediante señales digitales. En
este caso hemos decidido utilizar el modulo de Grove porque ya tiene incorporado los
condensadores de filtro necesarios y diversas protecciones. Además simplifica mucho el
diseño y como ya dijimos en otros ejemplos, nos permite centrarnos en la programación
del microcontrolador.
Como siempre lo primero que debemos realizar es la tabla de entradas y salidas
de tal modo que nuestro diseño esté 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 diseño, muchas herramientas
como Scratch for Arduino son capaces de generar todo el código a partir de un buen
diagrama de flujo. En este ejemplo únicamente buscamos encender un LED cuando
pase de la temperatura mı́nima 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 38

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 conversión (normalmente especificado por el fabricante) mediante el
cual el valor RAW se convierte en un valor útil para el usuario. En Figura 3.3.2 se
verá como realizar dicha conversión.

Código
Esta solución añade a los demás ejemplos el tratamiento de la señal analógica por lo
que únicamente se hará énfasis en dicha parte. En la sección setup configuramos el pin
A1 como entrada. Arduino no necesita especificar que dicho pin se comportará como
un pin analógico, por lo que este procedimiento es exactamente igual al realizado con
el botón en Subsección 3.3.1. En la función read sensors() se realiza la lectura del
sensor conectado a la entrada A1 del Arduino. La función analogRead() devuelve un
valor entre 0 y 1023 tal y como se explicó en los fundamentos de este ejercicio.
Una vez que tenemos el valor RAW hay que realizar una conversión a un valor en
grados celsius. Para ello hay que utilizar una fórmula especificada por el fabricante en
el datasheet. Cada sensor utiliza una conversión diferente por lo que este paso varı́a
de un sensor a otro, incluso aunque el sensor capte la misma magnitud.
La función perform actions() únicamente se encarga de comprobar si la tempera-
tura es menor al mı́nimo en cuyo caso activará el led.
3.3. UTILIZANDO LOS PINES DE ENTRADA 39

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 }

Código 3.4: Ejemplo 3: Código de ejemplo para señales analógicas


3.3. UTILIZANDO LOS PINES DE ENTRADA 40
CAPÍTULO 4
COMUNICACIONES

4.1. Introducción
Uno de los aspectos importantes a considerar en el diseño de sistemas con micro-
controladores son las comunicaciones. Si bien hay aspectos teóricos y matemáticos en
el estudio de las comunicaciones muy importantes y que es menester conocer, en este
capı́tulo siguiendo el enfoque práctico, aprenderemos a utilizar estas comunicaciones
sin necesidad de entrar en profundidad en los aspectos puramente teóricos.
La elección del modo de comunicación a utilizar depende de las necesidades de
nuestro diseño. Aspectos tales como protocolos de comunicación, medios fı́sicos, ancho
de banda, atenuación, distorsión, ente otros, deben ser considerados a la hora de elegir
el modo de comunicación entre dispositivos.
A continuación veremos algunos modos de comunicación que son soportados por
Arduino.

4.2. Comunicación 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 transmitiéndolos hacia el otro (receptor) de ma-
nera secuencial bit por bit a una determinada tasa de transmisión. La información
que transmiten estos bits dependerá del protocolo y de la codificación elegidos. El
protocolo nos indica la manera de comunicarnos, esto es, la cantidad y el tamaño de
las tramas de datos que se envı́an, los bits de paridad, código de error, etc., mientras
que la codificación determina el contenido de la información y la manera en que los
bits son ordenados para transmitirla. La tasa de transmisión 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 comunicación sı́ncrona y ası́ncrona y la última únicamente ası́ncrona.

41
4.2. COMUNICACIÓN SERIE MEDIANTE USART 42

Start Data 8b Parity Stop


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

Las diferencias entre la comunicación ası́ncrona serie y sı́ncrona serie se explicarán a


continuación.

Comunicación ası́ncrona serie: Si configuramos la unidad USART en modo


ası́ncrono tendremos que configurar diferentes parámetros para que tanto emisor
como receptor sepan cuando tienen que leer o escribir los datos. Estos paráme-
tros son el número de bits de datos, de paridad y de parada, ası́ como también
la velocidad en baudios. Existen numerosas configuraciones pero la más tı́pica
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 transmisión
puede configurarse entre diferente valores desde 300 hasta 115200 baudios.

Comunicación sı́ncrona serie: La unidad USART del ATmega328 se puede


configurar para funcionar en modo sı́ncrono, sin embargo no está soportado por
la librerı́a oficial de Arduino. Esto no significa que no pueda ser usado, pero si
queremos usar este modo tendremos que desarrollar nuestra propia librerı́a o
programar la configuración de la unidad en cada uno de nuestros programas. La
comunicación sı́ncrona se caracteriza por tener una lı́nea de reloj1 que permite
que ambos dispositivos lean y escriban en el evento de reloj. Esto encarece la
solución dado que necesitamos un cable más, sin embargo, permite eliminar
todos los bits de control de la comunicación de tal modo que únicamente se
trasmitirán bits con información útil.

Se ha hablado del módulo UART, sin embargo, hay que tener en cuenta que la
unidad UART es un módulo hardware y no un protocolo de comunicación. La unidad
UART se encarga de obtener en paralelo y en un único ciclo de reloj los datos del
registro de datos y los multiplexa en el tiempo según la configuración establecida.
Existen diversas configuraciones, simplex, duplex, full-duplex, de dos dispositivos
o en bus. Sin embargo, la comunicación serie mediante UART fue desarrollada con el
objetivo de comunicar únicamente dos dispositivos.
A continuación veremos diferentes ejemplos de como Arduino implementa la co-
municación serie en sus dispositivos.
Una unidad UART es un módulo hardware y no un protocolo de comunicación. La
unidad UART se encarga de obtener en paralelo y en un único ciclo de reloj los datos
del registro de datos, y los serializa en el tiempo según la configuración establecida.
Posee además soporte para el control de flujo por hardware. Este control de flujo es
realizado a través de dos conexiones RTS (Request to Send) y CTS (Clear to Send)
que permite a cada lado de la comunicación indicar al otro que está listo para manejar
datos. El cableado para la comunicación serie ası́ncrona 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 lı́nea de control se llama XCK0 y se encuentra en el pin 6 del encapsulado

DIP28
4.2. COMUNICACIÓN SERIE MEDIANTE USART 43

RTS RTS

CTS CTS

Dispositivo A Dispositivo B

Tx Tx

Rx Rx

Gnd Gnd

Figura 4.2: Conexión entre dispositivos UART con control de flujo

duplex (en ambos sentidos pero no simultáneamente), y full-duplex (en ambos sentidos
al mismo tiempo), con o sin control de flujo.
Una UART se usa comúnmente con los estándares de comunicación RS-232, RS-
422 o RS-485 y tiene soporte para el control de flujo mediante hardware.
Dependiendo de la implementación fı́sica se utilizará un determinado nivel de
voltaje u otro. El protocolo RS-232 utiliza de -3V a -15V para el nivel lógico ((1)) y
de +3V a +15V para el nivel lógico ((0)) Sin embargo, los procolos con lógica TTL o
CMOS trabajan con otros niveles. Existen circuitos integrados como el MAX232 que
permiten realizar la conversión de niveles.

Figura 4.3: Representación de comunicación serie

En la Figura 4.3 se puede ver una representación de lo explicado anteriormente.

4.2.1. Ejemplo 1: Hola mundo por Serie


Como ya hemos dicho, Arduino tiene soporte para la comunicación serie, tanto
hardware como software. A nivel hardware dependiendo del microcontrolador tendre-
mos más o menos unidades UART. La API que proporciona Arduino para el manejo de
4.2. COMUNICACIÓN SERIE MEDIANTE USART 44

Cuadro 4.1: Ejemplo 1: Tabla de entradas/salidas


Entrada/Salida Descripción Nombre variable Pin
Entrada Botón button 8
Entrada Rx0 RX 0
Salida Tx0 Tx 1

Cuadro 4.2: Ejemplo 1: Comunicaciones

Interfaz serie
Entrada/Salida Comando Evento Descripción
Salida Pin 8 == Envı́a ”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 botón el microcontrolador mandará 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 señalado dos de los elementos principales. Por
un lado la comunicación serie y por otro lado el botón. Es buena costumbre reservar
un apartado ı́ntegro para las comunicaciones. En un diseño más complejo, lo más
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 diseño al ((exterior)). Volviendo a las entradas y salidas, si bien en este primer
ejemplo lo único que haremos es enviar un string por el puerto Tx (pin 1) dejamos ya
indicada la entrada Rx que será utilizada en los ejemplos que siguen.

Comunicaciones
Este ejemplo es muy sencillo, por lo que no definiremos un protocolo completo, es
decir, sintaxis, semántica y temporización. Únicamente describiremos la comunicación
en términos 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 diseñar como eventos ası́ncronos y sı́ncronos, todo
dependerá de como implementemos la solución. En este caso y para no añadir com-
plejidad innecesaria en este punto, modelaremos la solución en base a los ejemplos
anteriores, es decir, de forma sı́ncrona y secuencial.
En la Figura 4.4 puedes ver el diagrama propuesto.
4.2. COMUNICACIÓN SERIE MEDIANTE USART 45

Figura 4.4: Ejemplo 1 - Comunicaciones: Diagrama de flujo

Código

En Cod. 4.1 se puede ver una posible solución a este ejemplo. Lo primero que
debemos hacer es configurar los elementos de entrada salida y las comunicaciones.
Cuando el diseño sea más complejo, puede que sea necesario encapsular cada una de
estas partes en otras subfunciones como por ejemplo init communications().

Una de las partes más importantes de este tutorial se encuentra en la función


Serial.begin(9600). Esta función forma parte de la API de Arduino para la gestión y
uso del puerto serie. Como habrás podido imaginar, el primer parámetro se refiere a la
velocidad en baudios, recuerda que tanto receptor como emisor deben tener la misma
velocidad. Por otro lado, en la Sección 4.2 se habló de la configuración de los bits de
parada, datos y paridad. Estos parámetros se pueden pasar como segundo argumento
a la función Serial.begin(). En la siguiente web se pueden ver los valores que puede
tomar este parámetro: http://www.arduino.cc/en/pmwiki.php?n=Serial/Begin

En la función perform actions() es donde se realiza la actuación en función del


estado del botón. Para este primer ejemplo hemos utilizado la función Serial.println().
Esta función envı́a al registro de datos de la unidad UART la cadena indicada como
parámetro, además termina la cadena con los caracteres ((retorno de carro)) y ((salto
de lı́nea)). La función devuelve un ((long)) que ı́ndica el número de bytes escritos en el
puerto serie. Es muy importante que tengas en cuenta que esta función no envı́a
los bytes de ((golpe)) por el ((cable)). Esta función únicamente 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 carácteres de fin de lı́nea, puedes utilizar
la función Serial.print()
4.2. COMUNICACIÓN SERIE MEDIANTE USART 46

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 }

Código 4.1: Ejemplo 1 - Comunicaciones: Código


4.2. COMUNICACIÓN SERIE MEDIANTE USART 47

Cuadro 4.3: Ejemplo 2: Tabla de entrada/salida

Entrada/Salida Descripción 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 información


Hasta ahora únicamente hemos enviado información al exterior mediante la comu-
nicación serie. En este segundo ejemplo veremos otra de las partes claves en cualquier
diseño, la captación de información mediante el puerto serie. La recepción de infor-
mación al igual que el envı́o de la misma normalmente está ligado a un protocolo de
aplicación y a una serie de parsers que se ocupan de decodificar dicho protocolo.
Protocolos de comunicación de bajo nivel dependen del medio fı́sico de comuni-
cación, por ejemplo: punto a punto, por buses, usando Ethernet, etc., y establecen el
modo en que la comunicación se lleva a cabo. Por ejemplo establece quién inicia la
comunicación, cómo se direcciona el elemento receptor, cómo se envı́an los datos, si
uno a uno o a ráfagas, etc. Protocolos de comunicación de alto nivel o de aplicación se
refiere al conjunto de comandos de una aplicación que se utilizan en la comunicación
para gestionar el funcionamiento de un sistema. El diseño de protocolos de aplicación
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 decodificación de la información, dado que es un problema recurrente
en la soluciones basadas en microcontroladores.
El ejemplo es muy sencillo, en función de la cadena recibida se encenderá un LED
u otro (se utilizan LEDs para no complicar el diseño de forma innecesaria, el uso de
uno u otro actuador normalmente radica en diferencias meramente eléctricas).

Tabla de entrada/salida
Para este ejemplo utilizaremos dos LEDs, uno de color amarillo y otro de color rojo.
En función de la cadena que es recibida se encenderá 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 conectarán los actuadores.

Comunicaciones y protocolos de aplicación


Cuando se diseña un protocolo de comunicación de alto nivel lo primero que nos
deberı́amos preguntar es que objetivo tiene. Si no es un elemento crı́tico del diseño
o únicamente se utiliza como protocolo de gestión, seguramente prefiramos definir el
protocolo mediante caracteres ascii. Sin embargo, si lo que buscamos es una comuni-
cación rápida, de poco tamaño, lo más recomendable es una codificación binaria.
Un carácter 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,
podrı́amos utilizar un carácter ascii y poner cualquier valor desde 0 a 9. Esto su-
pondrá una carga de 8 bits para la comunicación. Sin embargo, si utilizamos 3 bits y
4.2. COMUNICACIÓN SERIE MEDIANTE USART 48

Cuadro 4.4: Ejemplo 2 - Comunicaciones: Comunicaciones


Interfaz serie
Entrada/Salida Comando Evento Descripción
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, únicamente necesitaremos 3 bits, es decir, el


tamaño es un 37.5 % del tamaño inicial.
Normalmente para los prototipos y para las comunicaciones de gestión, se utilizan
caracteres ascii dado que son más sencillos de depurar. En este ejemplo nosotros
utilizaremos un protocolo de aplicación ascii muy sencillo para que el lector pueda
apreciar los fundamentos de la recepción de información 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 están separados por comas, que
se utilizarán para la decodificación 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
diseñar nuestro código. A medida que vayamos avanzando en los diseños, estos diagra-
mas cada vez serán más genéricos y subirán más el nivel de abstracción. Un ejemplo
de este nivel de abstracción es la función check communications() y parse command()
donde no se entra en detalle en la implementación interna de la misma, únicamente
se sitúa dentro del flujo de programa. Si quisiéramos detallar una función, una opción
serı́a la de realizar un diagrama separado para la misma y más tarde unirlos.
4.2. COMUNICACIÓN SERIE MEDIANTE USART 49

Normalmente, en cada ciclo del bucle de chequeo de comunicación se realizará la


comprobación del número de datos recibido. Si existen datos se guardarán en el buffer
de procesado. Una vez detectado el fin de lı́nea se procesará el comando y realizará la
acción.

Código
Este ejemplo, aunque sencillo, requiere un poco más lı́neas de código que el ante-
rior. Un factor clave y determinante para un buen diseño es la modularización. Siempre
que una parte del código se repita debemos plantearnos el convertirlo en una función.
Vamos a analizar el código de ejemplo. En este ejemplo (Cod. 4.2), hemos añadido dos
funciones genéricas: check communications() y la función parse command(). La pri-
mera función hace lo siguiente: Primero comprueba que si buffer de recepción tiene
datos. La función del buffer de recepción 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
función Serial.available de la librerı́a Arduino, nos proporciona un método para saber
cuantos bytes se encuentran en el buffer de recepción. Mediante la función Serial.read
leemos el dato entrante y añadimos 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 más antiguo
en el buffer, es decir, el primero trasmitido (cola FIFO). Un detalle a tener en cuenta:
en Internet y en la bibliografı́a sobre Arduino se pueden ver muchos ejemplos donde
se hace la comprobación Serial.available()>0. Aunque esto puede funcionar, hay que
tener mucho cuidado. Puede que en el buffer haya más de 0 bytes, sin embargo, esto
no significa que toda la trama esté en el buffer, por lo que si se hace esta comprobación
habrá que tener especial cuidado a la hora de formar la trama. Otra manera de evitar
este comportamiento podrı́a ser el comprobar que el tamaño del buffer de recepción
sea mayor que el tamaño de trama.
Otro de los puntos claves a la hora de tratar con protocolos ascii es el detectar el fin de
lı́nea. Dependiendo del sistema operativo y de la consola que se utilice para mandar
los datos, se utilizará un carácter de fin de lı́nea u otro. En el entorno Arduino uno
puede abrir un monitor de terminal serie usando (CTRL+Shift+m). En este monitor,
por defecto, está seleccionada la opción de mandar como fin de lı́nea los caracteres
((\r \n)), sin embargo en el bloque ((if)) de nuestro código, se comprueba el fin de li-
nea únicamente detectando si el valor recibido es igual a \n por lo tanto el carácter
\r será insertado en nuestra trama y seguramente ocasione problemas en el diseño.
La manera más sencilla de modificar este comportamiento es cambiar la opción en
la consola o en este caso el monitor serial para que únicamente envı́e el carácter \n
como carácter fin de lı́nea.
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 función que decodifique
la trama y convierta los datos en información útil para la lógica del programa. Con
este fin se invoca a la función parse command() (linea 32). Esta función se ha creado
apoyándose en las funciones strcpy() y strtok(), que divide y copia los valores leı́dos en
la estructura Command protocol() (lı́neas 9 a 14). Se recomienda al lector que revise
la documentación sobre la función strtok().
La última fase es la de actuar en función de la entrada, en nuestro caso únicamente
tenemos que detectar el primer campo y detectar a que tipo de actuador se refiere la
comunicación. La función perform action led() comprueba qué led se va a modificar
4.2. COMUNICACIÓN SERIE MEDIANTE USART 50

y en función de dicho valor y del estado actúa llamado a la función digitalWrite().


Como puedes observar el dividir el código en módulos con responsabilidad limitada,
permite que el código sea mucho más legible y mantenible. Si quisiéramos añadir la
posibilidad de encender o apagar un motor, únicamente tendrı́amos que crear otra
función que podrı́amos llamar perform action motor() y hacer las comprobaciones
pertinentes.

4.2.3. Ejemplo 3: Comunicación entre Arduinos


En los ejemplos anteriores hemos visto como manejar actuadores y como recibir
información de los sensores. Además se ha practicado con la comunicación serie con
el ordenador. En este ejemplo veremos como comunicar dos Arduinos entre si.
Normalmente en un diseño 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 éxito donde se puede apreciar esta jerarquı́a de controladores es
el BMW X5. En BMW decidieron añadir un controlador y sensores en cada amorti-
guador con el objetivo de recibir información de manera inmediata de las condiciones
de las ruedas y del sistema de amortiguación. En función del valor de los sensores,
el ordenador central del vehı́culo modifica diferentes parámetros de modo que la con-
ducción sea más suave y cómoda.
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 actuación.
En este ejemplo vamos a captar información en un Arduino y mediante las técnicas
aprendidas en los ejemplos anteriores informaremos a otro Arduino de dichos valores
siendo este último el responsable de actuar en función de los valores recibidos.

Tabla de entrada/salida

Para simplificar el montaje en este ejemplo vamos a trabajar con sensores utiliza-
dos en otros ejemplos. Utilizaremos un botón, un sensor de temperatura como sensores
y dos LEDs como actuadores. Un primer Arduino (esclavo) tendrá conectados los sen-
sores. Leerá los datos del sensor de temperatura y del botón y los enviará a un segundo
Arduino (maestro) cuando éste lo solicite. Este segundo Arduino tendrá conectados
dos LEDs. Un led se encenderá si el boton ha sido apretado, mientras que el segundo
led lo hará de acuerdo al valor de temperatura recibido.

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

Entrada/Salida Descripción Nombre variable Pin


Entrada Botón button 8
Entrada Sensor de tempera- temperature sensor A1
tura
Entrada Rx0 RX 0
Salida Tx0 Tx 1
4.2. COMUNICACIÓN SERIE MEDIANTE USART 51

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 }

Código 4.2: Ejemplo 2 - Comunicaciones


4.2. COMUNICACIÓN SERIE MEDIANTE USART 52

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

Entrada/Salida Descripción 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
botón

Comunicaciones
El protocolo para este ejemplo es muy sencillo. El maestro (Arduino actuador)
manda una petición de información. Esta petición únicamente contiene el id del sensor
del cual desea obtener la información. Para simplificar los id se definen mediante
la directiva de preprocesador ((#DEFINE)). Ambos Arduinos tendrán que tener los
mismos valores. Una vez que el esclavo detecta la petición envı́a un mensaje con
el identificador del sensor y el valor, separado por una coma. El maestro entonces
deberá capturar la trama y analizar de que sensor ha recibido el valor, si el sensor es
un botón, esperará un int, si el sensor, por el contrario, es el de temperatura, entonces
esperará 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 carácter \t y como finalizador de trama
\r.

Diagrama de flujo
El diagrama de flujo está dividido en 3 partes. La primera de ellas es común a
todos los diseños y es el setup() del microcontrolador. Una vez se ha realizado el setup
correctamente, el siguiente paso es obtener la información de los sensores (en nuestro
caso el botón y el sensor de temperatura). Por último comprobaremos si tenemos
alguna comunicación que atender. Si hay una comunicación por atender haremos una
decodificación de la misma (mediante el parser ) y mandaremos un mensaje u otro en
función de la petición.
En este esquema el Arduino que va a actuar es el que pide los datos. Podrı́a
plantearse otra situación en el que el Arduino con los sensores fuera el que envı́a 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 creación del diagrama para el Arduino Maestro.

Código
El código es muy parecido al realizado en la Subsección 4.2.2. En Cod. 4.4 puedes
ver el código del Arduino que actúa como esclavo. En Cod. 4.3 se encuentra todo el
código del Arduino que realiza las peticiones de información, es decir, el maestro. Ten
en cuenta que se han omitido partes de código repetidas como por ejemplo la lectura
de valores de temperatura con el objetivo de no hacer muy extenso el código.
4.2. COMUNICACIÓN SERIE MEDIANTE USART 53

Figura 4.6: Ejemplo 3 - Comunicaciones: Diagrama de flujo

El código del esclavo te deberı́a resultar familiar ya que es una recopilación de los
ejemplos anteriores. La parte más importante ahora es la decodificación del protocolo
que es prácticamente igual a la realizada en Subsección 4.2.2. Se comprueba si hay
alguna trama sin procesar preguntado por el carácter \n, si hay alguna se obtienen
todos los bytes del puerto serie hasta llegar al carácter \n que indica el fin de lı́nea.
Por otro lado, el maestro solicita los valores enviando una cadena por el puerto
serie. Cuando recibe la información realiza un parser y en función del tipo de infor-
mación (temperatura o botón) realiza una acción u otra.
Otra parte importante del código es la utilización de la librerı́a SoftwareSerial.
Mediante esta librerı́a podemos implementar un emulador de UART en software (co-
mo una UART fı́sica) dándole un nombre a la instancia creada y asignándoles los
pines de RX y TX correspondientes (lı́nea 13 del código). 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 comunicación entre
Arduinos, tendremos que desconectar dichos cables cada vez que queramos programar
alguno de los Arduinos ya que la programación tal y como se explicó en el Sección A.1
se realiza mediante el puerto serie.
Este tipo de comunicación aunque muy sencilla puede llevar a problemas muy
difı́ciles de depurar. Uno de los problemas podrı́a ser que uno de los Arduinos desbor-
dara el buffer del otro. Para evitar esta condición en la función check petition() del
Arduino maestro se queda bloqueado hasta recibir el carácter fin de trama. Este sim-
ple bucle actúa como un mecanismo de control de flujo no permitiendo enviar ningún
mensaje hasta que no se reciba la contestación. Aunque esta solución es muy sencilla
tiene muchos problemas dado que si el esclavo no contestara el Arduino maestro se
quedarı́a constantemente en espera. Para evitar estos problemas existen soluciones
que utilizan ((ACK’s)) o mensajes de verificación para mantener un control de flujo.
Se deja como ejercicio al lector la implementación del mecanismo de ((ACK’s)) a la
solución.
1 #d e f i n e BUTTON 0
4.2. COMUNICACIÓN SERIE MEDIANTE USART 54

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. COMUNICACIÓN SERIE MEDIANTE USART 55

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 }

Código 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. COMUNICACIÓN I2C 56

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 }

Código 4.4: Ejemplo 3 - Comunicaciones: esclavo

4.3. Comunicación I2C


En la Sección 4.2 vimos un mecanismo de comunicación muy sencillo (Comuni-
cación mediante UART). En esta sección veremos otro protocolo de comunicación
llamado I2C.
I2C fue diseñado por la empresa Philips en 1992. Actualmente muchos periféricos
implementan este bus de comunicación por su sencillez, fiabilidad y el bajo número
de cables requerido (únicamente 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 múltiples esclavos
cada uno de ellos identificados por una dirección de 7 bits o 10 bits en función del
estándar utilizado.

4.3.1. Hardware
Una de las ventajas de I2C frente a otros buses como SPI (Serial Peripheral Inter-
face) es el bajo número de cables requeridos para la comunicación. I2C únicamente
4.3. COMUNICACIÓN I2C 57

requiere 2 cables dentro de un mismo circuito y tres cables para la comunicación entre
circuitos. La 3 señales principales son:

SCL (Serial CLock): La señal de reloj siempre la genera el dispositivo que


actúa como maestro. Los esclavos pueden forzar al maestro a que el reloj deje
de oscilar. En la Subsección 4.3.2 veremos este caso.

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

GND (Ground): Esta señal no es obligatoria en comunicaciones dentro de un


mismo circuito pero si la comunicación es entre circuitos, entonces se necesi-
tará con el objetivo de proporcionar un mismo nivel de referencia.

4.3.2. Protocolo I2C

Figura 4.7: Esquema de comunicación I2C

El valor de las señales SCL y SDA está 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 señal de reloj por la lı́nea SCL, determinando
la tasa de transferencia de datos, y controla o deja controlar por el esclavo la lı́nea de
direcciones SDA.
La comunicación entre un maestro y un esclavo consiste en una secuencia de
transacciones de datos a través de SDA controladas por el maestro o por el esclavo,
sincronizadas por la señal 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 comunicación a través de I2C está formada por cuatro pasos:

Condición de Inicio

Trama de Direccionamiento

Trama de Datos

Condición de Parada

Cada transacción comienza con una condición de inicio (S) y termina con una
condición de Parada (P). Estas condiciones son controladas por el maestro. La
condición de inicio la realiza el maestro con una transición de la señal SDA de 1
a 0 en el semiciclo positivo del reloj SCL, mientras que la condición de parada se
realiza con una transición de la señal SDA de 0 a 1 en el semiciclo positivo de la señal
SCL. En I2C la transición en la lı́nea SDA durante las transacciones se hace en la
4.3. COMUNICACIÓN I2C 58

Figura 4.8: Trama de dirección I2C

parte baja del ciclo reloj. Únicamente en la condición de inicio y parada se realiza la
transición en la parte alta del ciclo de reloj.
Una vez que se ha indicado la condición de inicio el siguiente paso es enviar una
trama con la dirección del dispositivo que se desea seleccionar. Existen dos modos
de direccionamiento: de 7 bits o de 10 bits. El uso de 10 bits de comunicación no
es frecuente y no está cubierto en este libro. Al final de los 7 bits de dirección se
agrega un octavo bit que indicará el tipo de transacción que se hace (0 = escritura,
1 = lectura). Hay que tener en cuenta que la dirección del esclavo se colocará 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 transición del reloj, teniendo en cuenta
que el primer bit es el más significativo de la dirección y que el último bit corresponde
al tipo de transferencia. Para comprobar que el esclavo ha detectado la petición de
comunicación del maestro, el esclavo debe poner la lı́nea SDA a nivel bajo antes del
décimo pulso de reloj, de lo contrario el maestro puede interpretar que la comunicación
no se ha podido establecer. Una vez que se ha establecido la comunicación el siguiente
paso es enviar la trama de datos. La trama de datos únicamente se trasmite bit por bit
mediante codificación MSB. En función de si la transferencia es de lectura o escritura,
será 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 envı́a un NACK después de recibir un byte, indicará al receptor que quiere
cerrar la comunicación.
El último paso consiste en la transición 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 generará la condición de parada.
El protocolo I2C admite la comunicación continuada entre un maestro y un esclavo.
Para ello, una vez terminado el primer flujo de información (Condición de inicio +
dirección + ack + datos + ack) en vez de enviar la condición de parada, el maestro
mantiene ocupado el bus poniendo la señal 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 dirección del esclavo (cualquiera) y
se procede al envı́o de datos.
En la Figura 4.8 se puede ver una ilustración de la trama de dirección I2C y en
Figura 4.7 un esquema donde se ilustra la comunicación anteriormente explicada. Es
importante tener en cuenta que el ACK lo envı́a el receptor y no el emisor. En la
Figura 4.9 se puede observar la estructura que presenta una trama de datos, donde
únicamente se mandan los datos y un bit de ACK que envı́a 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 librerı́a llamada Wire que implementa el protocolo I2C. Me-
diante esta librerı́a la utilización de protocolo se reduce a unas sencillas funciones que
4.3. COMUNICACIÓN I2C 59

Figura 4.9: Trama de datos I2C

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

Entrada/Salida Descripción Nombre variable Pin


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

nos abstraerán por completo de detalles de más bajo nivel.


Los pines I2C variarán en función del microcontrolador que estemos utilizando,
recuerda que siempre puedes recurrir a la página oficial de Arduino o al datasheet
del microcontrolador para más información. En el caso de utilizar el bootloader del
Atmega328 los pines correspondientes a SDA y SCL son A4 y A5 respectivamente.
Algunas tarjetas de expansión poseen varios conectores I2C que permite expandir el
bus para varios esclavos.
En este ejemplo utilizaremos dos placas Arduino de modo que una envı́e un men-
saje y el otro reciba el mensaje y en función 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), únicamente tenemos un
LED en el nodo esclavo que servirá para comprobar que el mensaje ha sido recibido
con éxito.

Diagrama de flujo

Los diagramas de flujo que contienen eventos y comunicaciones suelen ser más
complejos dado que tenemos que atendar a eventos que no son sı́ncronos. Se reco-
mienda que el lector cree el diagrama de flujo correspondiente basándose en el código
mostrado en Tabla 4.3.3.

Código

Para evitar tratar con detalles de implementación del protocolo I2C utilizaremos
la librerı́a Wire cuyas funciones mas utilizadas se explicarán a continuación. La docu-
mentación oficial de la librerı́a Wire puede ser consultada desde la siguiente dirección:
http://playground.arduino.cc/Main/WireLibraryDetailedReference.

begin(): Esta función nos permite iniciar la librerı́a. Esta función es recurrente
en la mayorı́a de las librerı́as. La implementación suele reiniciar o poner en el
estado inicial la máquina de estados y los registros de trabajo.
4.3. COMUNICACIÓN I2C 60

requestFrom(dirección, tamaño): Mediante esta función ponemos al maes-


tro a la escucha del esclavo hasta que reciba el tamaño fijado en el segundo
parámetro. La dirección tiene que ser de 7 bits.

beginTransmission(dirección): Si queremos enviar datos desde el maestro al


esclavo, después de llamar a la función begin, tenemos que utilizar la función
beginTransmission. Mediante esta función indicamos a la librerı́a que pase al
estado preparado y configure el buffer de direccionamiento. Ten en cuenta que
esta función no enviará ningún bit, únicamente 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 dirección la función send nos


permite configurar el buffer de datos de la interfaz TWI2 .

endTransmission(): La última función en cada comunicación es endTransmis-


sion() que mandará todos los datos del buffer a la dirección indicada mediante
beginTransmission() y por último enviará el bit de parada.

onReceive((void*)func(int)): Cuando la librerı́a detecta que existe un dato


llama a la función handler pasada como parámetro. Esta función tiene que tener
la ((firma)) void func(int), el parámetro que recibe contiene el número de bytes
capturados. El bus no será liberado hasta que se retorne de la función handler.

receive(): Devuelve el siguiente byte en el buffer de recepción.

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


la dirección del dispositivo y la opción de lectura, la librerı́a Wire llama a esta
función handler donde se deberá insertar los datos en el buffer de escritura con
el objetivo de iniciar el envı́o.

available(): Devuelve el número de bytes del buffer de recepción.

Una vez que se tiene claro cada una de las funciones de la librerı́a, el código
mostrado en Cod. 4.5 y Cod. 4.6 no debe suponer ningún problema. En primer lugar
el Arduino maestro envı́a un byte de datos con el carácter ascii ((h)) (recuerda que un
carácter ascii extendido es igual a un byte) y espera medio segundo. Por otro lado,
el Arduino esclavo únicamente espera los datos mediante la función handler. Esta
función handler se inscribe al evento del bus mediante la función onRequest(). Ten en
cuenta que aunque en este código y a modo de ejemplo se ha realizado la operación
de encender el led dentro de la función, esto no es recomendable dado que mientras
permanezcamos en dicha función el bus se mantendrá ocupado.
Por último, se recomienda que el lector modifique el código del esclavo y del
maestro con el objetivo de apagar y encender el led mediante algún evento, este
evento puede ser la inserción de una cadena en el puerto serie o un evento temporal.
Además, se recomienda que se realice la modificación del pin de salida fuera de la
función handler.
2 TWI (Two Wire Interface) es otra forma de nombrar al protocolo I2C
4.3. COMUNICACIÓN I2C 61

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 }

Código 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 }

Código 4.6: Ejemplo 1 - Comunicaciones I2C: esclavo


4.3. COMUNICACIÓN I2C 62

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 medición inercial) mediante el protocolo I2C para obtener los ángulos de rotación
con respecto al eje ((X)) y el eje ((Y)).
El uso de estos sensores requiere de práctica y de conocimientos trigonométricos
para tratar los valores ((crudos))3 . En este ejemplo, no nos centraremos en las funciones
matemáticas, la parte más interesante para este capitulo es la comunicación 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 únicamente veremos como ob-
tener el valor del giroscopio, aunque también obtengamos el del acelerómetro para
utilizarlo en el filtro complementario4 .
Debido a la sencillez del ejemplo y al hecho de que únicamente utilizaremos un
sensor, en este caso no realizaremos diagrama de flujo ni tabla de comunicaciones,
únicamente 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
únicamente estamos utilizando un sensor y no estamos haciendo ningún tipo actuación
en función de los valores obtenidos. En la Figura 4.11 se puede observar la conexión
de este sensor. Únicamente 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 unión de un filtro de paso bajo con un filtro de paso alto
4.3. COMUNICACIÓN I2C 63

Figura 4.11: Conexiones MPU6050

mediante I2C incorporan esta resistencia.

Código
Se puede revisar el código en Cod. 4.7. A continuación, describiremos las partes
más 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. COMUNICACIÓN I2C 64

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 }

Código 4.7: Ejemplo 2 - Comunicaciones I2C: Sensor MPU-6050

En la función de setup() iniciamos el dispositivo. La inicialización del sensor de-


penderá 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 envı́a
un byte con el registro donde se quiere leer o escribir (en este caos el 0x6B) y segui-
damente se envı́a o recibe el dato del registro (en este caso enviamos el comando 0
que inicia el dispositivo). Una vez que hemos enviado la petición de inicio cerramos
la comunicación mediante la llamada a la función endTransmission() de la librerı́a
Wire.
Por otro lado, en cada ciclo de ((scan)) (función loop()) obtenemos los valores de
cada uno de los registros del sensor5 . Para obtener el valor del registro que contiene los
valores del acelerómetro tenemos que enviar la petición a la dirección 0x3B. Este paso
es muy importante y te ayudará a entender cualquier comunicación 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 explicó en la Tabla 4.3.3 los datos no se envı́an hasta que se llama a la función
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 65

endTransmission. Al contrario que en el caso del comando de inicialización, en este


caso vamos a recibir datos del sensor. Cuando se envı́a la petición 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 transmisión de datos mediante endTransmission(false). Después indicamos
que queremos obtener datos del esclavo y esperamos hasta tener 6 bytes, todo ello
mediante la función requestFrom(). Los datos se envı́an byte a byte. Como los datos
son de 16 bits tendremos que utilizar operaciones de manipulación de bits para formar
una palabra de 16 bits. En las lı́neas 24,25,26 se pueden ver dichas operaciones.
Para obtener el valor del giroscopio se sigue el mismo procedimiento, la única
diferencia está en el número de bytes que recibimos del esclavo y la dirección del
registro.
En las lı́neas 41 y 46 se aplica un filtro complementario que no será explicado
debido a que no es el objetivo de este libro.
Si abres el monitor serial podrás ver los valores obtenidos cada 10ms.

4.4. Protocolo SPI (Serial Peripheral Interface


En esta sección veremos otro protocolo de comunicación serial SPI. SPI es un
protocolo de comunicación serie sı́ncrono para comunicación entre microcontroladores
y uno o mas periféricos a corta distancia como displays, tarjetas de memoria y sensores.
Cada instancia de SPI tiene un maestro y uno o varios esclavos.
Posee cuatro señales básicas: Tres desde el maestro hacia el esclavo

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

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

SCK (Serial Clock): Señal que sincroniza la comunicación Y una señal desde
el esclavo hacia el maestro

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

El procesador ATmega328 tiene una interfaz SPI para comunicación. Las señales
SS, MOSI, MISO y SCK corresponden a los pines 16 al 19 respectivamente. En el
módulo Arduino estos pines están conectados a los pines 10 al 13 respectivamente.
(Figura 4.12).
El mecanismo de transferencia se realiza de la siguiente manera: Toda la comunica-
ción es controlada por el máster que selecciona el esclavo poniendo en bajo la señal SS
correspondiente. Entonces envı́a en modo serie una palabra (byte) por la lı́nea MOSI
y simultáneamente acepta un byte proveniente desde el esclavo por la lı́nea MISO.
Esta transferencia se realiza generando 8 pulsos sobre la lı́nea de sincronización SCK
En Arduino existe una librerı́a SPI.h que nos proporciona una serie de funciones
para controlar el proceso de comunicación.

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 66

Figura 4.12: Pines SPI en el proc. ATmega328

SPISetting: Esta función permite configurar algunos parámetros de comuni-


cación tales como los siguientes, los cuales se deben poner en el orden que se
describen:

ˆ Velocidad de comunicación (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 comunicación SPI, que nos indica en que flanco del reloj los
datos son enviados o recibidos (Fase del reloj) y si el reloj está inactivo
cuando está 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 comunicación SPI son los siguientes,

1. SPI.begin(): Iniciar la librerı́a SPI (normalmente en el setup() del programa)

2. SPI.beginTransaction(SPISetting()): La librerı́a 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 67

Cuadro 4.8: Modos de comunicación 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 señal sinusoidal en el procesador y
enviarla a un conversor DAC (Conversor Digital/Analógico) a través del puerto SPI.
El código del ejemplo está en la figura Cod. 4.8. En la primera parte del código
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 través del bus SPI, al conversor DAC externo
conectado al Arduino para generar la sinusoide. Para este ejemplo se utilizó un módu-
lo de conversı́on D/A de 8 canales PMODDA4 de DIGILENT que usa el conversor
AD5628 de ANALOG DEVICES, 6
La comunicación se realiza enviando transacciones de 32 bits a través del puerto
MOSI al esclavo (ver figura Figura 4.137 Sin entrar en detalles técnicos 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 dirección 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 comunicación SPI. Para ello hemos creado la fun-
ción init spi que pone en alto la señal slave select y llama a la función begin de la
librerı́a SPI (SPI.begin). El siguiente paso es configurar el módulo esclavo, según se
indica en la lı́nea 27 del código, donde se indica la frecuencia del reloj, si el primer
bit es el más significativo, y el modo de comunicación. 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 señal slave selecta ’0’ durante la
transacción. En este ejemplo se realizan cuatro transacciones de 8 bits cada una (tanto
en la función config dac como en la función Loop), pero se puede utilizar la función
6 Información 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 68

Figura 4.14: Forma de onda obtenida conversión D/A

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

Una vez configurado se entra en la función 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 69

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 }

Código 4.8: Ejemplo - Bus SPI: Código


4.4. PROTOCOLO SPI (SERIAL PERIPHERAL INTERFACE 70
CAPÍTULO 5
INTERRUPCIONES

Hasta ahora toda la obtención de información se ha realizado de manera unidirec-


cional y siempre iniciando la petición desde el Arduino. En este capitulo veremos cómo
trabajar con las interrupciones en el microcontrolador Atmega328. Las interrupciones
son señales recibidas por el procesador que indican que se ha producido un evento en
alguna de sus entradas o que un periférico quiere comunicarse con el procesador.
Las peticiones de interrupción se pueden implementar de dos maneras diferentes.
La primera es utilizando la técnica de polling que consiste en mirar cada cierto tiempo,
de manera regular, si un periférico o el procesador requiere de atención. Como puedes
imaginar este procedimiento es altamente ineficiente dado que el procesador pierde
mucho tiempo de procesamiento en comprobaciones, por lo que hoy en dı́a no se suele
utilizar. El segundo método es a través de pedidos de interrupción enviados por el
periférico o componente que quiere ser atendido. Al recibir el pedido de interrupción
el procesador deja de ejecutar su tarea actual y atiende a la interrupción mediante la
ejecución de la rutina de interrupción correspondiente al tipo de interrupción que se
produjo..
La rutina de interrupción es la función que se ejecuta cada vez que ocurre una
interrupción en el procesador y es una pieza de código que depende del elemento
que interrumpe. Por ejemplo cuando apretamos una tecla el controlador del teclado
envı́a un pedido de interrupción que hace que el contador de programa en logar de
continuar lo que estaba ejecutando salta a una dirección de memoria donde está a su
vez la dirección de la rutina de atención al teclado.
Existen diversas maneras de configurar la dirección de salto cuando ocurre una
interrupción. A continuación, se nombran las más importantes:
Dirección fija: Las direcciones se encuentran ((cableadas)) al procesador por lo
que no se puede modificar la posición de las interrupciones.
Dirección variable: Las direcciones se pueden encontrar en cualquier lugar
y pueden variar de un procesador a otro del mismo tipo (configuración del
procesador).
El método que nos interesa a nosotros es el de las direcciones variables, dentro de
este grupo, existen diversas implementaciones. Una de las más utilizadas y que además

71
5.1. INTERRUPCIONES EN EL ATMEGA328 72

Figura 5.1: Ejemplo de indirección

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


esta técnica una lı́nea de interrupción tiene asociada una posición en el vector de
interrupciones donde se encuentra la dirección de la función que finalmente se lanzará.
En la Figura 5.1 se puede ver una ilustración del procedimiento.
En este capı́tulo veremos cómo trabajar con las interrupciones del microcontrola-
dor ATmega328 y realizaremos algunos ejemplos con el objetivo de asentar todos los
conceptos teóricos.

5.1. Interrupciones en el ATmega328


Cuando afrontamos la tarea de desarrollar una solución basada en microcontro-
ladores, lo primero que debemos realizar es un análisis de caracterı́sticas del mismo.
Algunas de las caracterı́sticas son el número 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
interrupción 1 (reset) tiene la máxima prioridad y la número 26 (spm ready) tiene la
mı́nima 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 interrupción asociada al puerto de interrupción INT0 moverá el
contador de programa a la dirección 0x0002 y será en ese lugar en el cual pondremos
el código para tratar dicha interrupción.

5.2. Manipulación software


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

Librerı́a AVR: Utilizar la librerı́a avr que implementa todas las interrupciones.

Librerı́a Arduino: Gestionar las interrupciones mediante las herramientas pro-


porcionadas por Arduino, aunque muchas interrupciones no se han implemen-
tado aun en esta librerı́a.
5.2. MANIPULACIÓN SOFTWARE 73

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 Sección B.1 puedes manipular los registros de interrupciones
y gestionar al nivel más bajo posible las interrupciones. Hay que tener en cuenta
que esta manera es la menos portable, además de ser la más complicada.

5.2.1. Librerı́a avr


Como ya se ha comentado a lo largo del libro, el entorno Arduino realmente es un
conjunto de librerı́as de abstracción que hacen las cosas más sencillas aunque puede
ocurrir que si queremos realizar tareas especı́ficas, es probable que en algunos casos
esta librerı́a no nos proporcione todas las comodidades que necesitemos, dado que ha
sido pensado para prototipos sencillos principalmente. En esta sección, veremos cómo
utilizar la librerı́a avr-libc para el manejo de las interrupciones. Esta librerı́a es la que
utiliza Arduino y por lo tanto no tendremos que instalar ningún 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 simplificarán la tarea de gestionar las interrupciones.
El ATmega, al igual que la mayorı́a de los microcontroladores, utiliza un bit de
habilitación global de interrupciones. Este bit es el bit 7 del registro de estado y control
SREGdel ATmega. Además cada periférico tiene su registro de interrupciones con su
bit de habilitación correspondiente. Luego para que una habilitación sea atendida
deberá tener en 1 el bit de habilitación global y a 1 el bit de habilitación especı́fico.
Para habilitar o deshabilitar el bit de habilitación global utilizaremos las macros
sei() o cli() respectivamente. Estas macros escriben un 1 o un 0 en el bit 7 de la
dirección de memoria 0x3F, que corresponde al registro de estado y control SREG.
Cuando una interrupción ocurre, se pone a 0 el bit de habilitación global y se
deshabilitan todas las interrupciones. El software de usuario puede poner a 1 otra
5.2. MANIPULACIÓN SOFTWARE 74

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 }

Código 5.1: Ejemplo de definición de ISR

1 # include < avr / interrupt .h >


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

Código 5.2: Captura de interrupción no esperada

vez el bit de habilitación para poder atender interrupciones anidadas, de manera que
cualquier interrupción habilitada pueda interrumpir la rutina de interrupción actual.
Luego, cuando se retorna de la interrupción, el bit de habilitación global se pone
automáticamente a 1.
Para habilitar las interrupciones correspondientes a cada periférico debemos po-
ner a 1 el bit correspondiente en el registro de máscara de Interrupción. Si el bit de
máscara esta a 1 y a su vez el bit de habilitación global está a 1 entonces el procesa-
dor atenderá la interrupción cuando ocurra. Para el caso de interrupciones externas
existen registros que permiten configurar que tipo de eventos se atenderán. Como se
detallará 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 interrupción.
Para definir la rutina debemos utilizar la macro ISR. Esta macro tiene como
parámetro una constante que define la interrupción 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 quisiéramos definir una rutina de interrupción para INT0
el código serı́a similar al mostrado en Cod. 5.1
Otra práctica que es recomendable sobre todo en diseños que hacen uso intensivo
de las interrupciones, es capturar todas aquellas interrupciones para las cuales no se
ha definido ninguna rutina de interrupción (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 solución, es cuando se desea utilizar
una misma rutina de interrupción 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 interrupción de
la interrupción PCINT1 pasa a ser igual a la de PCINT0.
5.2. MANIPULACIÓN SOFTWARE 75

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 ) ) ;

Código 5.3: Captura de interrupción no esperada

5.2.2. Librerı́a Arduino


Arduino provee de un conjunto de funciones para el tratamiento de interrupciones
en sus placas de desarrollo. La librerı́a de Arduino solo da soporte a un número
limitado de interrupciones, a continuación explicaremos cada una de las funciones de
la librerı́a:

noInterrupts(): Esta función 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 interrupción para una


interrupción determinada. En función de la placa de desarrollo el número de pin
variará, se recomienda ver el datasheet, ası́ como el esquemático para averiguar el
pin correspondiente a la interrupción. Las interrupciones que están soportadas
por esta función son las siguientes: INT0, INT1, INT2, INT3, INT4, INT5.
Además, mediante un parámetro podemos indicar en qué momento queremos
que se active la rutina de interrupción pudiendo ser:

ˆ LOW: Cuando el pin está 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 función podemos eliminar la rutina de in-


terrupción que se indique como parámetro.

En general se recomienda usar la librerı́a de AVR dado que aparte de ser más
eficiente es mucho más flexible.

5.2.3. Consideraciones importantes


A la hora de utilizar las interrupciones tenemos que tener algunas consideraciones
en cuenta.
Las interrupciones por defecto están 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 INTERRUPCIÓN 76

Cuadro 5.2: Ejemplo 1: Tabla de entrada/salida

Entrada/Salida Descripción Nombre variable Pin


Entrada Botón button 2
Salida Led notification led 13

las interrupciones ya que por ejemplo, para usar una función delay() dentro de una
rutina de interrupción debemos habilitar otra vez el bit de habilitación global, dado
que por defecto al entrar en una rutina de interrupción se llama de forma implı́cita
a la macro cli.

5.3. Ejemplo 1: Primera rutina de interrupción


En este primer ejemplo veremos un ejemplo de utilización de las interrupciones en
el procesador ATmega328 usando las librerı́as avr. El ejemplo consistirá en encender
y apagar un led pulsando un botón. Seguramente al lector le resultará familiar pues
en la Sección 3.2 se estudió, sin embargo la principal diferencia es que en este caso no
leeremos el valor de un pin, sino que el propio evento será el encargado de realizar la
acción.
A partir de esta sección 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 diseños.

5.3.1. Tabla de entrada/salida


Lo más importante de la Tabla 5.2 es la elección del pin para el botón. 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, están conectados a los pines fı́sicos 2 y 3. El pin para el led puede ser
cualquiera de los de salida digitales.

5.3.2. Código
El código 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 interrupción especı́fica: Cada interrupción tiene asociado un bit
de máscara de activación individual. Mediante este bit podemos permitir in-
terrumpir al procesador mediante dicha interrupción. 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 (lı́nea 13).
Configurar la interrupción: Algunas interrupciones tienen registros que per-
miten configurar cuando lanzar la misma. En nuestro caso, para la interrupción
INT0 seleccionaremos el flanco descendente en el pin 2 poniendo un ’1’ en el
bit ISC01.
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 77

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 }

Código 5.4: Ejemplo 1 - Interrupciones

Definir la rutina de interrupción: Mediante la macro ISR podemos indicar


las acciones que se deberán tomar al atender una interrupción.

En Cod. 5.4 se puede ver la instanciación de los pasos anteriormente nombrados.


Un procesamiento intensivo dentro de la rutina de interrupción no es una buena idea
dado que durante el tiempo en que el procesador se encuentre en esta función no
realizará ninguna otra acción. En este ejemplo se ha modificado el estado del pin 13
dentro de la función únicamente con el objetivo de simplificar el código, sin embargo
se deja como ejercicio al lector modificar el ejercicio para que la modificación no se
haga dentro de dicha rutina de interrupción1 .
Como puedes observar, el bucle principal está vacı́o, sin embargo si pulsas al botón
verás como el led se enciende y apaga. Este comportamiento se llama asincronı́a, dado
que no hay ningún lugar donde se llame a la función que enciende y apaga el led, es
el propio suceso el que llama a dicha función.

5.4. Ejemplo 2: Midiendo distancias


En este ejemplo vamos a utilizar las interrupciones con la ayuda de la librerı́a de
Arduino para medir las distancias a la que se encuentran los objetos haciendo uso
también 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
continuación se explicará cada uno de ellos:
1 Como pista se podrı́a utilizar algún tipo de ((bandera)) o ((flag))
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 78

Figura 5.2: Sensor emisor/receptor ultrasonidos

Cuadro 5.3: Ejemplo 2: Tabla de entrada/salida

Entrada/Salida Descripción Nombre variable Pin


Entrada Pin de recepción echo pin 2
del sensor HC-
SR04
Salida Pin gatillo del sen- tigger pin 3
sor HC-SR04
Salida Transmisión puerto Tx 1
serie

VCC: Este sensor trabaja a 5V por lo que podrá ser conectado directamente
al Arduino a través del pin de 5V del mismo.

GND: Común.

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


hacer una medición. Para que el sensor sepa que queremos realizar la medición
y que no es un falso positivo debemos activar este pin como mı́nimo durante 10
microsegundos.

Echo: Este pin n0s indicará la distancia a la que se encuentra el objeto. ¿Cómo?
muy sencillo. Una vez que se recibe la señal del trigger el sensor manda una
señal 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 explicará nada más al respecto.
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 79

5.4.2. Código
El código se puede ver en Cod. 5.5 a continuación veremos las partes más impor-
tantes del mismo.
Cuando el Arduino entra en la fase de setup configuramos la comunicación serie
para poder mostrar la distancia por pantalla y además 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 transmisión Tx del puerto serie. El pin de entrada está conectado a la señal
echo del sensor. Es importante en este punto recordar que tenemos lı́neas limitadas de
interrupción en nuestra placa y que estas están conectadas a unos pines determinados.
En este caso, la lı́nea de interrupción que hemos utilizado es la conectada al pin 2 del
Arduino uno, que corresponde a la interrupción INT0 (ver en el datasheet).
El siguiente paso y seguramente el más importante en este ejemplo es la habilita-
ción de la interrupción del pin 2. Para ello utilizamos la función attachInterrupt de
la librerı́a de Arduino. Esta función tiene tres parámetros: Nº de interrupción, rutina
de interrupción que se ejecutará, y el tipo de evento que disparará la interrupción
(cambio de estado, flanco, etc). Como puedes observar esto es lo mismo que hemos
hecho en las lı́neas 13 y 14 del ejemplo 1, solo que en lugar de escribir en los registros
utilizando las librerı́as avr, lo que hacemos es llamar a esta función que se encarga de
eso. Por lo tanto no incluimos el archivo de cabecera avr/interrupt en este ejemplo.
En este ejemplo el primer parámetro es un 0. Este parámetro no se refiere al pin al
que está conectado el sensor, sino a la interrupción del microcontrolador. En este caso
el pin 2 está conectado como ya hemos dicho a la interrupción 0 por lo que este será el
número que tendremos que utilizar. En las últimas versiones del entorno Arduino se
ha añadido una macro llamada digitalPinToInterrupt(pin) que permite despreocu-
parnos de este parámetro. Comprueba si tu entorno ha añadido este macro y si es
ası́ puedes utilizarla. El otro parámetro importante es la función que será llamada
cuando recibamos la interrupción (rutina de Interrupción). La función no deberá re-
tornar ningún tipo (void). Por último el parámetro CHANGE indica que nos avise
en cualquier cambio en el pin, esto es, alto a bajo o bajo a alto. (en Subsección 5.2.2
puedes ver las opciones que se pueden pasar a la función).
Como ya comentamos en Sección 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 función interrupt echo() se llama cada vez que hay una variación en el pin echo lo
único que tenemos que registrar es cuando empieza (flanco de subida) y cuando termi-
na (flanco de bajada). Esta comprobación se hace mediante la función digitalRead()
que permite saber en que estado estamos y en función del mismo registra el tiempo
en las variables correspondientes. El tiempo se registra con la función micros(). Se
podrı́a usar la función millis() para medir tiempos en milisegundos pero esto no es
posible dentro de una rutina de interrupción.
En el datasheet del sensor se indica que el tiempo se debe dividir entre 58 para
obtener la distancia en centı́metros.
Otro punto a resaltar es la activación del pin trigger que debe estar en estado
alto durante al menos 10 microsegundos. Aunque esto se podrı́a realizar mediante un
timer e interrupciones, en este caso hemos optado por un delay en microsegundos.
El retardo final de 100 milisegundos (linea 21) únicamente tiene la función de
mejorar la visualización de la distancia.
5.4. EJEMPLO 2: MIDIENDO DISTANCIAS 80

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 }

Código 5.5: Ejemplo 2 - Midiendo distancias


CAPÍTULO 6
MULTITASKING Y TIMERS

Hasta ahora cada vez que buscamos repetir una acción en un tiempo determinado
hemos utilizado la función delay() que nos provoca un retardo en la ejecución duran-
te el tiempo especificado en la misma, en milisegundos. Como se explicará en este
capı́tulo la utilización de dicha función no debe realizarse en todos los casos, es más,
por regla general deberı́a evitarse su uso.
Cuando se utiliza la función delay() el Arduino se queda en un bucle activo. Por
lo tanto, durante ese tiempo el microcontrolador no realizará ninguna otra función,
esto es, estará consumiendo energı́a pero no estará haciendo ningún trabajo útil como
por ejemplo podrı́a ser registrar diferentes valores de los sensores.
En este capı́tulo veremos de manera resumida como utilizar otras funciones como
por ejemplo millis() para evitar los problemas que trae consigo la función delay().

6.1. Timers
Los microcontroladores como ya sabrás están compuestos de un conjunto de pe-
riféricos como por ejemplo los TIMER, USARTS, WDG, etc que nos permiten expan-
dir su funcionalidad. En este caso hablaremos de un periférico muy importante y que
sin duda alguna es la base para todas las funciones relacionadas con el tiempo. Este
periférico 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 esté basado nuestro Arduino tendremos
más o menos timers, en el caso del microcontrolador ATmega328 podemos encontrar
tres timers: dos de 8 bits y uno más de 16 bits.
En la Figura 6.1 puedes encontrar un diagrama en bloques del timer de 8 bits del
ATmega328. A continuación pasaremos a explicar cada uno de los componentes que
forman este módulo de tal modo que podamos tener una visión amplia a la hora de
utilizar cualquier función relacionada con el tiempo en nuestros diseños.
Básicamente 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

81
6.1. TIMERS 82

Figura 6.1: Diagrama TIMER 8 bits ATmega328

a su entrada, guardando la cuenta en un registro especı́fico (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
interrupción 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 generación de factor de escala es
configurable desde el registro Clock prescaler del ATMega328, ubicado en la dirección
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 número de timer que puede ser 0 o 1). Este
valor se incrementa o decrementa, según 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 máximo
que se podrá 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
activará el bit OCF0A. El resultado de la comparación se puede utilizar para
generar distintas formas de onda o PWM como se verá más adelante.

OCR0b: El mismo comportamiento que OCR0a.


6.1. TIMERS 83

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 último únicamente


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

6.1.2. Modos de funcionamiento


A continuación veremos los distintos modos de funcionamiento que posee el timer.
Estos modos se verán de manera general, lo suficiente como para abordar los ejemplos
de este capı́tulo, y se deja como tarea para el lector una lectura mas detallada de los
mismos.

Modo normal: Es el modo más simple de utilización. 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 último 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 señal
de overflow (TOV0). Esta señal no volverá a 0 de forma automática, será el
programador el encargado de realizar dicho cambio. Si está habilitado el bit de
interrupción por desbordamiento, cuando se produzca el mismo se generará una
interrupción.

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 resolución 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 comparación). 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 sabrá si se ha alcanzado
el valor de comparación. La ejecución 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 señal OC0 en el caso de que el timer esté configurado para
permutar dicho pin en cada acierto de comparación (esto se configura en el
registro TCCR0A, TCCR0B ). En el diagrama de la Figura 6.2 se muestran
diferentes valores del registro de comparación OCR0a, variando el periodo de la
señal. Según el datasheet para generar una señal cuadrada con una frecuencia
determinada podemos utilizar la formula mostrada en la Ecuación 6.1. Más
adelante, en los ejemplos, veremos cómo utilizar esta formula y cómo seleccionar
los valores para cada uno de los parámetros.
6.1. TIMERS 84

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 activación
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 precisión. 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 comparación no-invertida o
invertida. En el modo no-invertida la salida se pone a 0 cuando se alcanza el
valor de comparación y a 1 cuando se llega al valor BOTTOM. En los ejemplos
veremos cómo se materializa este modo pero para que el lector se haga una idea,
este modo nos permite ((indicar)) al microcontrolador qué porcentaje de tiempo
queremos que el pin esté en alto y cuanto tiempo queremos que esté en bajo,
pudiendo emular valores analógicos variando el ancho del semiciclo positivo de
la señal 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 energı́a al motor. En la Figura 6.3 se puede ver un ejemplo de
uso del modo PWM, a continuación pasaremos a explicar el mismo aunque en
los ejemplos se verá de manera más detallada.
6.1. TIMERS 85

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 señal normal y se pone en alto
en el caso de la señal invertida. El contador continua su incremento hasta que
llega al valor top. Será cuando alcance este valor cuando el pin vuelva a cambiar
su estado.
Como puedes observar, en función del valor del registro OCRnx podremos man-
tener más tiempo la señal en alto, o lo que es lo mismo para nuestro ejemplo
podremos entregar mas energı́a al motor.
La ecuación para el cálculo de la frecuencia se puede consultar en Ecuación 6.2.

Modo Fast PWM con corrección de fase: Este modo es muy parecido
al Fast PWM con la única diferencia que la salida está en fase y además la
frecuencia máxima que se puede alcanzar es la mitad con respecto al modo
PWM ya que ahora el contador trabaja en modo ascendente y descendente.
La fórmula para calcular el valor de TOP y del prescaler es la mostrada en
Ecuación 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 están implicados en su funcionamiento es el momento de programar el ATmega
para poner en práctica lo aprendido.
Como el Arduino utiliza el timer 0 para diferentes funciones como por ejemplo
la función 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 únicamente la cantidad de bits de
ancho ası́ como el número de funciones que poseen cada uno. También se podrı́a haber
utilizad el timer 2 el cual es de 8 bits.
6.1. TIMERS 86

Ejemplo de uso básico

En este primer ejemplo veremos cómo podemos configurar el timer únicamente


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 lógico, 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 continuación,
explicaremos cómo configurar cada registro. Es importante tener en cuenta que todas
esta información está extraı́da 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 comparación y dos de los bits que configuran el com-
portamiento del propio timer (WGM11 y WGM10 ). La razón por la cual solo se
pusieron dos bits de los cuatro necesarios para configurar el timer en este registro es
una decisión de diseño 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 comparación pero todavı́a nos falta un dato más, 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 será igual a 1/16000000 o lo que es lo mis-
mo: 62.5 ns. Teniendo en cuenta esto, el tiempo que tardará el timer en desbordarse
será igual a: 216 ∗ 62,5−9 = 4ms. Como puedes observar este valor es muy pequeño
y no nos sirve para alcanzar esos 4 segundos que buscamos. La solución podrı́a ser
aumentar el número de bits del timer o por otro lado aumentar el tiempo que tarda en
incrementar el registro TCNT. Como la primera opción (aumentar el número de bits)
no depende de nosotros y solo podrı́amos tenerla en cuenta si tuviéramos un timer
con más bits (no lo tenemos), abordaremos la segunda solución, es decir, aumentar el
tiempo que tarda el timer en incrementar el registro TCNT. Si utilizamos un pres-
caler de 1024 entonces significará que por cada 1024 ciclos de reloj incrementaremos
en uno el valor del registro TCNT1 por lo que volviendo a los cálculos anteriores el
tiempo que tardarı́a en desbordarse el timer serı́a igual a: 1024 ∗ 216 ∗ 62,5−9 = 4,194s.
¡Lo conseguimos! ya tenemos el timer configurado para que tarde 4 segundos. . . En
realidad esto no es del todo cierto dado que según nuestros cálculos el tiempo es de
4.194 s por lo que intentaremos ajustar más el valor, recuerda, 1 décima de segundo
puede significar ganar o perder una carrera en formula uno pero para una primera
aproximación no está nada mal. Si queremos un tiempo exactamente igual a 4 segun-
dos únicamente 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 número de incrementos que se realizarán en 4 segundos será igual a 62500.
Con todos estos cálculos ya sabemos que el valor del prescaler es de 1024 y que nos
gustarı́a que en vez de a los 62536 incrementos el TIMER se desbordará a los 62500.
Para esto último únicamente tenemos que modificar el valor de TOP o modificar el
valor en el que empieza el registro TCNT.
En el código mostrado en Cod. 6.1 puedes ver como se instancian los pasos ante-
riormente mencionados. A continuación explicaremos las partes más importantes del
6.1. TIMERS 87

código.
En el setup() se llama a la macro cli(), como ya se explicó en la Capı́tulo 5 esta
macro únicamente deshabilita las interrupciones de manera global, de manera que
podamos realizar toda la configuración 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 definición del registro. El registro
TCCR1B sin embargo sı́ que tiene un valor ((útil)) asignado. En un primer momento
el lector podrı́a preguntarse por qué se asigna dicho valor. La razón se encuentra en
la configuración 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 demás bits deben estár a cero (ver explicación
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 asignación el cual se explica en Sección B.1. El bit que hay
que habilitar es el TOIE1 que nos permitirá indicar al microcontrolador que queremos
interrumpir nuestro programa cuando se produzca un overflow.
Por último, 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 después 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 notación binaria con el objetivo de que se cubran
todas las posibilidades, aunque la más portable es la notación utilizada en el registro
TIMSK1.
Lo que resta del código ya ha sido explicado en capı́tulos anteriores y por lo tanto
no se volverá a explicar.

Ejemplo de uso modo CTC


En este ejemplo cubriremos el modo de uso CTC. Este modo es el más utilizado
cuando queremos medir tiempos dado que es más sencillo que el modo normal y
permite actuar sobre un pin de manera inmediata sin necesidad de ninguna instrucción
software, además de contar con una precisión mayor al poder modificar el valor TOP.
Se configurará 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 cómo sin necesidad de ningún
código extra podemos realizar acciones cada X segundos y con una precisión bastante
buena. Si sustituyes el led por un transistor que sustituya el botón de la cámara de
fotos tendrás un temporizador de obturación. (Sólo realizar esta modificación si se
tienen los conocimientos suficientes sobre electrónica)
El modo de proceder es muy parecido al de Subsección 6.1.3 con la diferencia que
en este caso tendremos que configurar el pin de salida y además modificaremos el
valor de ((overflow)) del timer (aunque como ya veremos, realmente no es el valor de
((overflow))).
Los cálculos para el tiempo en este caso se dejarán como tarea para el lector
de modo que pueda comprobar si ha comprendido los conceptos explicados en la
Subsección 6.1.3
En Figura 6.7 podemos ver los diferentes modos que posee el timer para actuar
6.1. TIMERS 88

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 }

Código 6.1: Ejemplo de uso TIMER

Figura 6.5: Bits de configuración del presclarer

Figura 6.6: Registro TCCR1B


6.1. TIMERS 89

Figura 6.7: Bits para configuración del pin de salida

sobre la salida. En nuestro caso elegiremos el modo ((01)) que nos permite permutar
la salida en cada comparación con los registros OCR1A/B.
El siguiente paso consiste en configurar el timer para que actué en modo CTC.
En la Figura 6.8 se puede ver la tabla de configuración en función de los bits WGM.
A continuación, explicaremos los referentes al modo CTC.
Si configuramos los bits WGM en el modo ((0100)) el timer empezará a contar
hasta llegar al valor configurado en el registro OCRxA y OCRxB, en este momento se
generará una interrupción siempre y cuando el bit OCIEXa o OCIEXb del registro
TIMSKX esté habilitado, una vez que ocurra ese evento, el contador seguirá ascen-
diendo hasta llegar al valor TOP donde se generará la interrupción de overflow y
finalmente se volverá a contar desde 0.
El lector puede observar que lo que estamos realizando en este ejercicio es generar
dos frecuencias a partir de un único timer, esto no siempre será posible, en este
caso al ser una frecuencia múltiplo de la otra no habrá ningún problema.
Para ejemplificar todo lo explicado pasaremos a analizar el código Cod. 6.2.
En primer lugar, se bloquean las interrupciones con la macro cli() esto mismo
se podrı́a haber hecho con la función noInterrupts(), sin embargo, es más eficiente
y portable la segunda opción. Seguidamente se ponen a cero los dos registros de
configuración del timer de modo que tengamos certeza de que partimos de inicialmente
de 0. Basándonos en el datasheet del ATmega328, configuramos el registro TCCR1A.
Cómo el lector puede observar el único 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 notación que se considera más portable (lı́nea 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 qué?.
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 añadido un pin más,
esto nos permitirá generar una señal de referencia para saber que nuestros cálculos
son correctos. La elección de estos pines se debe al mapeo que realiza el Arduino
de los pines del ATmega328. Si quieres saber más sobre esto se recomienda ver el
esquemático del Arduino UNO ası́ como el datasheet del ATmega328.
En la rutina de interrupción TIMER1 COMPA vect() se comprueba el valor del
6.1. TIMERS 90

Figura 6.8: Configuración modo CTC

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


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

Ejemplo de uso modo Fast PWM

El último modo que veremos en profundidad será el modo Fast PWM. Como ya
se comentó anteriormente, con este modo podemos modificar la cantidad de tiempo
que pasa una señal en alto y en bajo, es decir, podemos modificar el ciclo de trabajo.
Existen diversas aplicaciones en las que se utilizan este tipo de señales, como
ejemplos podemos nombrar los servos de los aviones radio control o los variadores de
los motores industriales, etc.
En este ejemplo veremos cómo utilizar el modo Fast PWM para mover un servo
como el de la Figura 6.10. Este servo está 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 electromecánico, que mediante
una lógica empotrada consigue decodificar una señal de entrada para generar como
resultado un desplazamiento a una posición determinada. Estos dispositivos se suelen
utilizar en aeromodelismo pero su campo de aplicación es mucho más extenso, otro
ejemplo podrı́an ser los robots.
A continuación pasaremos a explicar cada una de las partes que forman un servo-
motor:

Control de posición: El control de posición básicamente es un pequeño con-


trolador que decodifica la señal 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 mecáni-


co y únicamente consiste en un motor conectado a un ((puente en H)) y una
reductora que permite transformar la mayor parte del giro en torsión.
6.1. TIMERS 91

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 }

Código 6.2: Ejemplo de uso TIMER modo CTC


6.1. TIMERS 92

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 función de la calidad de los mismos el movimiento
será más o menos suave. Normalmente el potenciómetro de feedback va unido
al sistema de engranajes.
Para mover un servo a una posición tenemos que enviar una señal con unos paráme-
tros concretos. Normalmente los servomotores de aeromodelismo utilizan una frecuen-
cia de 20 Hz por lo que la señal viajará con un periodo de 50 ms. En la Figura 6.11
puedes ver el principio de funcionamiento.
Normalmente se confunden los términos PWM y PPM, a continuación intentare-
mos clarificar ambos conceptos. Desde el ATmega328 generaremos una señal a una
frecuencia determinada, esa señal la podemos modificar de modo que el ciclo de tra-
bajo vaya desde el 0 % hasta 100 %. El servomotor espera un pulso de una duración
concreta, por ejemplo, siguiendo los tiempos de la Figura 6.11, el servo espera una
señal de 1ms para colocarse en la posición 0. Si nuestro periodo es de 50 ms, un
pulso de 1ms será equivalente a un 2 % de la señal en alto. Si modificamos el valor
de la frecuencia por ejemplo a 200 hz, el periodo será 5 ms y un 2 % de dicha señal
será 0.1 ms. Con esto llegamos a la conclusión 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 será 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 cálculo de dicho valor) y por último añadir la lógica de
control para calcular los ciclos de trabajo en función de los grados que se desee mover.
6.1. TIMERS 93

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 }

Código 6.3: Ejemplo de uso PWM con servo

(Aquı́ únicamente añadiremos la lógica de control para modificar el ciclo de trabajo a


un valor fijo, se deja como tarea para el lector el añadir al código la lógica necesaria
para generar un ciclo de trabajo u otro en función de los grados que se desee mover).
En Cod. 6.3 se puede estudiar el código implementado. A continuación explicare-
mos como siempre las partes más 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 señal permute en cada comparación con OCR1A.
En el bucle loop() únicamente comprobamos si existen datos en el buffer de recep-
ción serial. Si existe algún dato lo convertimos a entero y lo escribimos en el registro de
comparación. Esto nunca se debe realizar dado que no hemos realizado ningún control
de entrada y el usuario podrı́a ingresar cualquier valor, incluso letras o sı́mbolos que
podrı́an causar serios problemas en un diseño en producción.
Los valores recomendados son 125,250,375,500 que corresponden a 0,45,90,135,180
6.2. MULTITASKING 94

grados, la manera de calcular estos valores es muy sencilla. Pongamos un ejemplo: 180º
son 2ms o 2.5 ms en función 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.
Además 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 está determinado por el valor TOP
almacenado en el ICR1, en este caso 5000, un 12,5 de 5000 será 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 potenciómetro no tiene tanto recorrido,
por esta razón hemos recomendado un valor de 500.

6.2. Multitasking
Hasta ahora hemos visto cómo funciona el core de tiempos de Arduino, al final
todo se resume en una palabra, TIMERs. En esta sección veremos cómo abstraernos
de todos los detalles de implementación y utilizando las librerı́as de Arduino crear
nuestros programas ((paralelos)).
La idea que reside en esta implementación del ((multitasking)) es muy sencilla,
nuestro programa principal actuará de ((scheduler)) de tal modo que en su bucle loop()
comprobará para cada elemento si es su turno y si es ası́ le concederá 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 dı́a se está 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 lı́neas de alimentación que
representan la entrada de señales 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 diseños
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 solución estructurada que ayudará bastante
a la consecución de este objetivo.
Apoyándonos en la orientación a objetos encapsularemos cada elemento en una
clase y dotaremos a la misma de un método llamado ((scan)), este método para simpli-
ficar el diseño nos devolverá un valor ((boolean)) con la información del ciclo (correcto,
defecto), aunque lo correcto serı́a encapsular el valor de retorno en otra clase que nos
brindara más información.
La orientación a objetos no es el objetivo de este libro, únicamente lo utilizaremos
como una manera de mostrar al usuario la verdadera importancia de estructurar el
código y para que pueda ver los beneficios inmediatos de su utilización. El código
que se utilizará en los ejemplos tratará utilizará los conceptos más sencillos posibles
en cuanto a orientación a objetos se refiere intentando evitar aspectos más complejos
como el polimorfismo, herencia, etc.
Este es un buen momento para que te animes a leer el Sección C.1 y prepares
tu entorno de desarrollo para estos ejemplos, aunque nosotros sigamos utilizando el
entorno Arduino te servirá para ver las bondades de Eclipse en cuanto tenemos un
programa medianamente complejo.
Como ya se comentó en la Sección 6.1, para medir tiempos podemos utilizar varias
6.2. MULTITASKING 95

Figura 6.12: Ejemplo de programa en ladder


6.2. MULTITASKING 96

funciones de Arduino, nosotros utilizaremos la función millis() que nos permite saber
cuanto tiempo ha pasado desde que se ha iniciado el programa. Para aplicaciones que
estarán activas durante mucho tiempo hay que tener en cuenta que el contador se
pondrá a 0 en 50 años, por lo que tendremos que salvar de alguna manera el tiempo
(por ejemplo guardándolo en la memoria EEPROM).

6.2.1. Encendiendo y apagando un led de manera profesional


El led ha sido nuestro compañero desde el primer capı́tulo, con él hemos realizado
la mayorı́a de ejemplos y como no puede ser de otra manera, en este primer ejemplo
sobre multitasking recurriremos de nuevo a él. En la mayorı́a de los ejemplos que
puedes encontrar por Internet verás que para encender y apagar un led se utiliza la
función delay(). En entornos industriales esta función es muy temida y la razón es
bastante sencilla: Si un programador que está encargado de mover un motor utiliza la
función 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 útil.
En Cod. 6.4 puedes encontrar el código que a continuación 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 serán los siguientes:
Led: Se encenderá y apagará cada segundo.
Comunicaciones: Se mandará un mensaje con la cadena .Estoy aquı́ ”más el
tiempo en milisegundos cada 100 milisegundos.
Empecemos por la función scan led(), se utiliza una variable de tipo estática. La
razón por la cual hemos utilizado este tipo de variable es únicamente educativa de
modo que el lector sepa que estas variables solo se inicializan una vez y luego cada vez
que se entra a la función se mantiene el valor anterior, es decir, es prácticamente igual
que una variable declarada fuera de todos los métodos tal y como habı́amos hecho
anteriormente. Mediante esta función registramos el valor de la última ejecución de la
acción a controlar. En el ((if)) es donde se encuentra el ((scheduler)). En este ((if)) com-
probamos si ha pasado el tiempo mı́nimo que habı́amos programado. Es importante
observar que solo podemos asegurar un tiempo mı́nimo y no un tiempo máximo. Si lo
que buscamos es acotar el tiempo en el cual se debe realizar una determinada acción,
en ese caso necesitaremos dotar a nuestro sistema de un ((scheduler)) apropiativo de
modo que cuando se alcance un determinado tiempo máximo cancele todas las ta-
reas y vuelva a la tarea que requiere atención. Un sistema operativo muy liviano que
permite dotar al sistema de un ((scheduler)) apropiativo es el sistema FreeRTOS. Si
ejecutas el código podrás ver que efectivamente por cada mensaje y permutación del
led, se imprimen 5 mensajes con el mensaje ((I’m here xxxx)).
En la Subsección 6.2.2 veremos una manera más elegante de ordenar el código como
se explicó anteriormente, con el objetivo de facilitar el mantenimiento del mismo.
6.2. MULTITASKING 97

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 }

Código 6.4: Ejemplo de multitasking


6.2. MULTITASKING 98

6.2.2. Encendiendo y apagando un led de manera más profe-


sional
La orientación a objetos, como ya se ha comentado, puede reducir muchı́simo los
costes de mantenimiento ası́ como posibles fallos en la implementación. Mantener un
programa en un único 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 módulo ((led)) para proyecto futuro.
La empresa en su guı́a de estilos tiene definida una interfaz clara de que méto-
dos se deben exponer. En este caso, para simplificar, supondremos que únicamente
está especificado el método scan.
Para que nuestro trabajo pueda ser reutilizado en los demás proyectos tendremos
que encapsular el mismo lo máximo posible.
En Cod. 6.5 puedes ver el ejemplo completamente implementado. A continuación
explicaremos las partes más 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
método llamado scan(), tal y como se nos indicó en la guı́a de estilos. La clase también
tiene un método privado update() que es el encargado de comprobar si ha llegado el
tiempo de permutación. Ten en cuenta que este método es privado y por lo tanto no
le afecta la guı́a de estilos. Por otro lado, el constructor recibe el número de pin y el
tiempo en el cual se quiere permutar el led (lı́nea 16).
Como puedes ver esta manera es mucho más elegante dado que si queremos crear
otro led, únicamente tenemos que añadir otra lı́nea como LED led1 (13, 500). En este
ejemplo tan sencillo puede que no se vean las virtudes de este método, pero imagina
tener que controlar 200 LEDs, ¿ves ahora la diferencia?, probablemente con el método
tradicional (copia de código) y variables globales nos quedarı́amos sin memoria en el
microcontrolador y la tarea de mantenimiento serı́a 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 apoyándose en el código Cod. 6.3 y en todo
lo aprendido hasta ahora. Una vez creada la clase añádela al proyecto y observa las
bondades del multitasking en Arduino.
6.2. MULTITASKING 99

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 }

Código 6.5: Ejemplo de multitasking orientado a objetos


6.2. MULTITASKING 100
APÉNDICE A
CONSTRUYENDO NUESTRO
PROPIO ARDUINO

A.1. Introducción
En este apéndice se guiará al lector en la construcción de su propio Arduino Uno,
el cual será completamente compatible con un Arduino comprado en la tienda oficial.
Como se comentó en el Capı́tulo 1, Arduino mantiene la filosofı́a del software libre y
nos proporciona todos los esquemas necesarios para construir o mejorar su plataforma.
El Arduino que vamos a construir en este apéndice es muy sencillo y únicamente
tendrá 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 continuación, se detallará 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 más comunes, no obstante, si el lector encuentra un equivalente
al componente no habrá problema en sustituirlo, aunque se recomienda escoger en la
medida de lo posible los citados en la siguiente lista.

1. Protoboard

2. Fuente de alimentación 5-18V CC

3. Cables Jumper

4. LM7805

5. Condensador electrolı́tico 10 uF (x2)

101
A.3. ENSAMBLADO 102

6. Reloj de cristal de 16MHz


7. ATmega328
8. Condensador electrolı́tico 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 colocación es algo meramente decorativo y
((superfluo)) es muy importante tenerlo en cuenta. La diferencia entre poner el con-
densador de desacoplo a 1 centı́metro del microcontrolador o ponerlo a 20 centı́metros
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 conexión (mismo esquema) de modo que los componentes estén más
próximos entre ellos.
El circuito se puede dividir en tres partes. A continuación, describiremos cada una
de ellas:
Etapa de alimentación: El circuito está 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 funcionará, pudiendo
llegar a dejarlo inservible. Si el lector no tuviera una fuente de corriente conti-
nua con estas caracterı́sticas, puede montarse una pequeña fuente, para lo cual
necesitará entre otras cosas un transformador (fuente con transformador) y un
puente de diodos (en Internet hay mucha información a cerca de las fuentes de
alimentación).
Lo siguiente es conectar los terminales a la protoboard. Existe un convenio de
conexión por el cual se recomienda conectar el terminal positivo a la primera
fila de la protoboard y el negativo a la segunda. El lector podrá reconocer estas
filas porque normalmente vienen separadas del resto de la protoboard.
Ahora que la placa está alimentada, solo queda situar los componentes tal cual
aparecen en el esquema Figura A.2 también puedes guiarte por la Figura A.1
aunque recuerda, ésta no es la distribución óptima. Es importante que se respete
la polaridad del condensador electrolı́tico dado que de lo contrario, si se
supera la tensión inversa umbral, podremos romper el mismo. Para saber cómo
conectar el condensador busca una lı́nea vertical de color blanco que indica el
terminal negativo. (ver Figura A.1)
A la salida del integrado LM7805 (lı́nea naranja) tendremos 5 V estables que
será la ((fuente de alimentación)) 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 podrá 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 diseños donde la

precisión es muy importante, hay que tener en cuenta la longitud de los cables entre otros factores
A.3. ENSAMBLADO 103

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 104

Figura A.3: Patillaje ATmega328

Microcontrolador: El microcontrolador ATmega328 es el corazón del Arduino


UNO, es en él donde se cargaran los programas. En esta parte del montaje nos
encargaremos de que el corazón empiece a ((latir)). Para ello, lo primero que hay
que saber es cómo están distribuidos los terminales. En la Figura A.3 se puede
observar que los pines para alimentar el microcontrolador son el 7 y 8, pero...
¿Dónde empieza la numeración? 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 numeración. En la Figura A.1 se puede observar el punto que indica el inicio.
Una vez que el los terminales de alimentación del microcontrolador están 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
analogı́a del corazón 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 precisión
y a que normalmente oscilan a una velocidad inferior, añadiremos 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 más cerca esté el reloj de los terminales de conexión
del microcontrolador mayor precisión, por lo que de nuevo se recomienda que
únicamente se siga la Figura A.2 para guiarse y se modifiqué la disposición de
los elementos.
Finalmente falta comunicar el pin 20 (AVCC) con VCC, esto se mantendrá ası́ has-
ta que el lector quiera hacer uso del ADC (Digital Analogic Converter) cuando
tendrá que conectar el pin 20 a VCC a través de un filtro de paso bajo.

Comunicación: Ahora que el microcontrolador ya es capaz de oscilar a una


frecuencia determinada (en nuestro caso 16MHz) tenemos que situar el canal de
comunicación entre el computador y el microcontrolador. Si el microcontrolador
tuviera integrado un chip para USB únicamente tendrı́amos que conectar los
cables del USB al mismo, pero como el ATmega328 solo posee de comunicación
serial (para la programación) 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 precisión menor frente al reloj de

cuarzo.
A.4. PROGRAMACIÓN DEL BOOTLOADER 105

no hay ningún problema, únicamente tendrás 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 únicamente dispone de puertos USB,
tendrás 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. Programación del Bootloader


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

El ATmega328 tiene una memoria Flash de 32 KB en la que se podrı́a 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 decidió añadir una interfaz USB de modo que realice la conversión de
los datos que llegan por USB a un protocolo serial y los multiplexe en el tiempo del
mismo modo en que lo harı́a 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
petición para programar el Arduino y si es ası́ seleccionar la dirección de memoria
donde ((situar)) dicho programa. Si el bootloader no encuentra ningún petición de pro-
gramación entonces salta a la dirección de memoria del bucle de setup del programa
y a continuación empieza el bucle de operación.
Como el lector habrá podido apreciar, no es necesario ((quemar)) este bootloader
pero si no se quema el lector tendrá que contar con algún programador AVR para
((quemar)) el programa y además tendrá que realizar la programación por ISP mediante
el conector ICSP4 lo cual es mucho más incomodo.
A continuación vamos a proceder a ((quemar)) el bootloader, para ello lo prime-
ro que necesitamos es un programador de AVR, en este libro, como ya se ha dicho
anteriormente se utilizará el programador libre USBasp. La instalación de los drivers
del mismo se deja como trabajo para el lector. Una vez que el programador está co-
rrectamente 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 útil del
microcontrolador
A.4. PROGRAMACIÓN DEL BOOTLOADER 106

(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 ubicación de cada uno de los pines se puede utilizar un multı́metro
y medir la diferencia de potencial (voltaje) entre un pin y tierra de tal manera que
el pin que devuelva el voltaje de programación (3.3-5V) será el VTG o VCC y el
pin común será tierra. El lector también se puede guiar por el cable rojo, de modo
que éste será el pin 1. Existen alternativas para facilitar la conexión del programador
al Arduino como por ejemplo el conversor ((ICSP Header)) cuya única funcionalidad
es proporcionar una interfaz ((inline)) que se podrá conectar a la protoboard siendo
mucho más sencilla la conexión final al ATmega.
Una vez que el lector haya localizado cada uno de los pines del conector o haya
adquirido un conversor, ya está en disposición de realizar la conexión entre el ICSP y
el microcontrolador. Los pines son los proporcionados por la interfaz de comunicación
ISP, es decir, GND, VCC, MISO (Master Input Slave Output), MOSI(Master Output
Slave Input), SCK (Clock), RESET. La conexión ISP es muy sencilla gracias a lo
descriptivo de los nombres de sus pines, sin embargo, a continuación 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. Después conectar
el programador al computador, es importante que los drivers estén completamente
configurados, de lo contrario el proceso no se completará con éxito. 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,
después seleccionamos “Herramientas → programador → USBasp”, por último hay
A.4. PROGRAMACIÓN DEL BOOTLOADER 107

Figura A.5: Captura de pantalla del Arduino IDE

que pulsar sobre “Herramientas → Grabar secuencia de inicio”. Si todo ha ido bien
el lector podrá quemar sus propios sketch.
A.4. PROGRAMACIÓN DEL BOOTLOADER 108
APÉNDICE B
MANIPULACIÓN DE REGISTROS

B.1. Introducción
Cuando un diseñador se enfrenta a la tarea de diseñar una solución informática en
un microcontrolador, debe manejar una serie de conceptos que normalmente no son
necesarios o indispensables cuando se programa una solución en un computador de
propósito 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 apéndice se va a guiar al lector en el aprendizaje de las operaciones lógicas
con bits. Es importante tener en cuenta que la información 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 posición inicial,
iniciar operación) y otro registro para los datos como por ejemplo la hoja a imprimir.
Cuando el diseñador programa la lógica de la impresora, deberá obtener y validar la
información contenida en estos registros. En este apéndice se mostrarán las diferentes
técnicas que se utilizan para obtener y tratar los datos de los registros.

B.2. ¿Qué es un registro?


Antes de tratar de realizar operaciones con los registros, debemos tener claro qué es
realmente un registro. Se puede explicar qué es un registro desde dos perspectivas:
la perspectiva lógica y la perspectiva fı́sica. En este apéndice nos centraremos en la
primera dado que buscamos el tratar con la información almacenada en los registros
y no el implementar fı́sicamente el mismo.
El registro es la unidad de almacenamiento que se encuentra en el nivel superior
dentro de la jerarquı́a de memorias (ver Figura B.1), es decir, es la memoria más
rápida y más 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 introducción, existen registros de muchos tipos como por ejemplo los registros de
datos que tienen la función principal de almacenar datos. Un ejemplo de este tipo de

109
B.2. ¿QUÉ ES UN REGISTRO? 110

registros es el registro acumulador de la ALU de los microcontroladores PIC. También


existen registros especı́ficos como el registro SP (stack pointer) de la arquitectura
ARM el cual únicamente se utiliza para almacenar la dirección 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: Jerarquı́a de memoria

Todos estos registros tienen unas propiedades comunes como son:

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


derá del propósito para el que se haya diseñado. Por ejemplo, un registro que
mantenga la dirección de la siguiente instrucción a ejecutar IR (instruction regis-
ter) deberá 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 deberá diseñar un registro con un ancho de 8 bits.1

2. Tecnologı́a: Los registros fı́sicamente se diseñan con una tecnologı́a determina-


da. Esta tecnologı́a será determinante a la hora de comparar otras caracterı́sticas
como la latencia, capacidad de integración. . .

3. Dirección: Un registro queda identificado univocamente por su dirección de


memoria como por ejemplo podrı́a ser la dirección 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 interesará toda la ((caja)) (registro) o únicamente 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 número almacenado, esto no implica que no se pueda
acceder a un único bit, por ejemplo para saber si el número es positivo o negativo en
un número representado en signo magnitud.
1 Con 8 bits se pueden codificar 28 instrucciones
B.3. OPERACIONES CON REGISTROS 111

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 qué es un registro, estamos en disposición de realizar dife-
rentes operaciones con ellos. En esta sección y con el objetivo de que el lector pueda
poner sus conocimientos en práctica vamos a utilizar registros reales del microcontro-
lador ATmega328, a continuación se realizará una pequeña descripción de la utilidad
de cada uno de estos registros:
DDRC (Data Direction Register C ): Este registro se encuentra en la direc-
ción 0x07 y permite configurar los pines del puerto2 como entrada(0) o salida(1),
en función del bit que se situé en cada uno de las posiciones del registro, por
ejemplo la configuración 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 está configurado como salida (DDRX(n)
== 1) este registro determinará el voltaje ((alto)) o ((bajo)) a la salida del pin.
PINC (Pin Input Register C ): Este registro normalmente formará 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 significará que el pin
que representa tiene VCC como entrada, sin embargo, si el valor es 0 entonces
significará 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 especı́fico como es el registro DDRC. Sabemos
también que la dirección de memoria del registro es la 0x07 y que podemos acceder
a él mediante el puntero DDRC que apunta a dicha dirección5 . Si queremos activar
el led 0 como salida podrı́amos 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 número de pines.
3 Es importante tener en cuenta que los bits se empiezan a numerar por 0.
4 Toda esta información se puede obtener del Datasheet del microcontrolador ATmega328.
5 Estos punteros son mapeados normalmente por la librerı́a del fabricante.
B.3. OPERACIONES CON REGISTROS 112

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 está 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 dı́gito hexadecimal equivale a cuatro bits (el conjunto de 4 bits se le conoce como
nibble). La última representación es la más común pero no será explicada hasta la
Subsección B.3.3
Ahora imaginemos que pasa si ocurrirı́a si quisiéramos configurar el pin 1 del
puerto ((c)) como salida. En principio actuarı́amos de la misma manera, es decir,
configurarı́amos el valor 0b0000010, pero como el lector habrá podido observar, el
valor del pin 0 cambiará y se configurará como entrada (0). La manera más sencilla
de solventar este comportamiento es asignar el valor 0b00000011 pero ¿si no se conoce
que pines están como salida en un estado anterior, cómo se calcula el valor a escribir?
esta pregunta será contestada en la siguiente sección.

B.3.2. ORing, activación de un bit


Para evitar el comportamiento observado en la sección anterior, se hace uso de la
operación OR. Esta operación permite activar un bit sin ((afectar)) a los demás bits
del registro.

Imaginemos el mismo ejemplo que el de la Subsección B.3.1, es decir, deseamos


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

De este modo conseguiremos el siguiente valor:

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

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


el 2 están configurados como salida. La operación ORing únicamente afecta a los
valores que poseen un 1 lógico, esto es debido a que el 0 actúa como operador neutro
(A OR 0 = A). El operando que utilizamos para realizar la operación OR sobre el
registro se llama ((mascara)).

B.3.3. Bit Shifting, movimiento de bits


La aritmética booleana proporciona herramientas extremadamente útiles al pro-
gramador. Por ejemplo, una de las mejoras que un compilador puede realizar es la
transformación de una multiplicación de potencia 2 que normalmente lleva varios ci-
clos (las unidades de multiplicación suelen ser lentas) en una única operación lógica
llamada ((Bit Shifting)). En este apartado vamos a explotar una de sus utilidades
B.3. OPERACIONES CON REGISTROS 113

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: Operación: 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: Activación de pin 3 sobre registro previamente configurado

que nos permitirá hacer nuestro código más legible y nos ahorrará mucho código in-
necesario.

Continuando con nuestro ejemplo de la configuración del pin 0 y 1 como salida,


en este apartado se verá como construir o hallar el valor que debe ser utilizado como
operando extra en la operación OR.
En la Subsección B.3.2 se utilizó el operando ((extra)) 0b00000010, a este operando
como ya hemos dicho anteriormente, se le llama máscara. Para obtener este valor
se recurre a la operación Bit Shifting. Si queremos crear una mascara para el pin 1
únicamente tenemos que utilizar la siguiente operación (1 << 1) (en la Figura B.3
está representada gráficamente la operación).
Si quisiéramos configurar el pin 3 como salida procederı́amos del mismo modo,
es decir, calcular la máscara y aplicarla mediante la operación OR. Para calcular la
máscara utilizamos la técnica del Bit Shifting (1 << 3). En la Figura B.4 se puede
ver de forma gráfica el procedimiento.
En el Cod. B.1 se puede ver un extracto de código donde se configura el pin PC6
y PC7 como salida. Esta es la forma más común para la configuración de registros. Es
importante observar que la operación OR se realiza sobre el valor actual del registro
(| =) y que se pueden realizar varias operaciones ORing en una única asignación.
B.3. OPERACIONES CON REGISTROS 114

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 }

Código B.1: Ejemplo de asignación 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: Configuración del pin 3 como entrada sobre registro previamente con-
figurado (procedimiento erróneo)

B.3.4. ANDing, desactivando bits


Hasta ahora únicamente 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
situación es mediante un ejemplo. Imaginemos que sobre el último 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 único bit por lo que para crear la máscara
podrı́amos pensar en utilizar el mismo método 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 está a 1. A continuación, explicaremos el modo
de realizar este tipo de operaciones.
Si utilizamos la máscara normal, (la utilizada para configurar un bit a 1) y negamos
la misma, podemos observar que el resultado tras realizar la operación AND es el
esperado. Se recurre al elemento neutro de la operación AND, es decir, al 1, de modo
que todos los bit que eran 0 al negarse se convierten a 1 consiguiendo que la operación
AND no tengan efecto (A AND 1 = A) sobre los bits que ya está configurados y
sı́ tenga efecto (configurar con el valor 0) sobre los bits que están en 1 en el operando
B.3. OPERACIONES CON REGISTROS 115

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: Configuración 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 116
APÉNDICE C
ENTORNO ECLIPSE CON
ARDUINO

C.1. Introducción
En este apéndice veremos cómo 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 qué utilizar otro IDE si ya ha utilizado el oficial
de Arduino y no ha tenido ningún problema.
Arduino fue pensado como una plataforma con una curva de aprendizaje muy
baja, la ventaja obvia es la rápida adaptación de cualquier persona con mı́nimos
conocimientos de informática al entorno. El problema, no tan obvio en un primer
momento, es la carencia de herramientas tan importantes como: control de versiones,
gestión de proyectos, ((tasks list)), etc.
Con el objetivo de mostrar al lector el potencial de Arduino, en este apéndice con-
figuraremos un entorno con todas las herramientas necesarias en cualquier ambiente
profesional.

C.2. Qué es Eclipse


Eclipse es un entorno de desarrollo integrado de código libre y multiplataforma.
Eclipse ofrece un ((core)) sobre el que otros desarrolladores realizan sus modificaciones
para crear entornos de desarrollo especı́ficos para cada tecnologı́a. Un ejemplo podrı́a
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 apéndice trabajaremos sobre Eclipse para C++ con el objetivo de
poder tener control sobre cada una de las partes que conforman la construcción de
un binario para Arduino.

117
C.3. INSTALACIÓN DEL ENTORNO 118

Figura C.1: Pantalla inicial de Eclipse

La página oficial de Eclipse se puede visitar desde el siguiente enlace: https:


//eclipse.org, en ella podrás encontrar toda la información de la fundación Eclipse,
además, desde el apartado ((Download)) podrás descargar cada una de las versiones
oficiales del entorno.

C.3. Instalación del entorno


El primer paso será 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 opción de ((Eclipse IDE for C/C++ Developers)). Esta
versión 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 será la
descompresión del mismo. Eclipse es un programa portable, esto quiere decir que no
requiere instalación por lo que una vez descomprimido estaremos en disposición de
utilizar el mismo. El lugar donde se descomprima no es relevante.
El siguiente paso será ejecutar el programa, en teorı́a si tenemos Java en nuestro
computador el entorno se ejecutará sin ningún tipo de fallo, en caso de no tener Java
el entorno nos avisará y tendremos que instalarlo.
La pantalla inicial tendrá la apariencia de la Figura C.1.
Ahora deberemos instalar el entorno Arduino tal y como lo hicimos en Sección 2.1.
Si ya lo tienes instalado no hace falta que vuelvas a hacerlo.
Con la instalación del entorno Arduino tendremos todas las herramientas necesa-
rias, como son:

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


C.4. CONFIGURACIÓN DEL ENTORNO 119

nuestros proyectos.

Conjunto de librerı́as Arduino: Las librerı́as de Arduino, forman parte del


llamado ((ArduinoCore)). Estas librerı́as nos simplifican tareas repetitivas como
la configuración del modulo USART, configuración de pines, etc.

Librerı́as AVR: Como ya sabrás la librerı́a de Arduino utiliza las librerı́as de


AVR por lo que también tendremos estas últimas en nuestra instalación.

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 será ((avrdude)) que
forma parte del conjunto de herramientas de AVR.

Mapeado de pines: Como ya sabrás, el entorno Arduino utiliza su propia


numeración de pines, de tal modo que si pones pin 13 en una función, el Arduino
((sabe)) qué pin es y a qué puerto le corresponde (en realidad el encargado de
hacer esta conversión como es lógico, no es Arduino, es el compilador). Esto se
realiza mediante el archivo de variants que veremos más adelante.

Es importante que anotemos la dirección de instalación de Arduino para los pasos


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

C.4. Configuración del entorno


Ahora que tenemos todo descargado es el momento de realizar la configuración 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 menú ((Help, Install new
software)) o ((Ayuda, Instalar nuevo software)) en Español. Una vez en esa pantalla
deberemos pulsar al botón ((Add)) y luego en el apartado de ((Name)) poner el nombre
que se desee, por ejemplo avr-descargas, y en location la siguiente dirección: http://
avr-eclipse.sourceforge.net/updatesite, deberá quedar como en la Figura C.2.
Una vez rellenado el formulario aparecerá ante nosotros un plugin llamado AVR
Eclipse Plugin con un ((checkbox)), deberemos seleccionar dicho ((checkbox)), seguida-
mente pulsaremos ((Next)) y ((Finish)) según las instrucciones, hasta finalizar la insta-
lación.
Una vez instalado, el siguiente paso consiste en la configuración de las variables
que permiten al plugin saber dónde se encuentran los binarios del compilador, etc.
Para configurar el plugin hay que ir al menú ((Window, Preferences)) o ((Ventana,
Preferencias)). Una vez en el menú 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 también


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

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 aparecerá 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 podrı́a quedar la configura-


ción, ten en cuenta que en función de donde se realice la instalación, los path y en
consecuencia la configuración variará.

C.5. Creando el proyecto: ArduinoCore


Como ya hemos comentado, Arduino nos proporciona un conjunto de librerı́as
que hacen que sea mucho más sencillo el utilizar algunos de los módulos ((hardware))
del microcontrolador. Para poder contar con todas estas comodidades tendremos que
compilar las librerı́as en un proyecto a parte (esto nos permitirá reutilizar las librerı́a
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 más
rápido para el usuario pero a la vez, hace poco didáctico el proceso.
Para compilar las librerı́as en primer lugar crearemos un proyecto, para ello va-
mos al menú ((New, C++ Project)) y en tipo de proyecto ((AVR Cross Target Static
Library)) el nombre que pondremos al proyecto será ((ArduinoCore)), el nombre no
es determinante, sin embargo este nombre simplifica la labor de comprensión de los
pasos que estamos realizando.
En configuraciones únicamente seleccionaremos la de ((Release)) esto es debido a
que esta librerı́a no la modificaremos y por lo tanto no necesitamos toda la configu-
ración de ((Debug)). En la siguiente pantalla de configuración se nos preguntará por
C.5. CREANDO EL PROYECTO: ARDUINOCORE 121

Figura C.3: Configuración de ejemplo

el microcontrolador y por la frecuencia. Ambos parámetros deberán ser configurados


en función del Arduino sobre el que se vaya a realizar el proyecto. En el caso del
Arduino construido en este libro en la Sección A.1 configuraremos estos parámetros
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 añadir el código fuen-
te de las librerı́as, y configurar los includes. Para esto último daremos ((click)) de-
recho sobre el proyecto y buscaremos el menú de propiedades. Una vez dentro del
mismo deberemos buscar el apartado ((C/C++ Build, Settings)) y en el apartado
((AVR Compiler)) ir a la pestaña de ((Directories)) y añadir el directorio: <directo-
rioArduino>/hardware/arduino/avr/cores/arduino. Este directorio contiene todos los
((headers)) de las librerı́as. Por otro lado tenemos que añadir el ((header)) que utiliza
el entorno Arduino para referirse a sus pines. Este fichero como habrás podido adi-
vinar varı́a en función del microcontrolador. El fichero se puede encontrar dentro de
<directorioSDK>/hardware/arduino/variants luego selecciona el que necesites, por
ejemplo, para el microcontrolador ATmega328 deberı́amos 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 código fuente de las librerı́as, para ello damos botón derecho sobre el
proyecto y seleccionamos la opción de ((Import, File System)). En el cuadro de búsque-
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 último ya solo queda compilar el proyecto, para ello cruzamos los dedos y
damos botón derecho ((Build Project)). Si todo va bien ya tendremos las librerı́as de
Arduino compiladas.
C.6. CREANDO EL PROYECTO FINAL 122

Figura C.4: Configuración de las librerı́as

C.6. Creando el proyecto final


Ahora que tenemos compilado el conjunto de librerı́as de Arduino para el mi-
crocontrolador que estamos utilizando, ya podemos crear un proyecto tal y como
lo harı́amos en el IDE oficial de Arduino.
Para crear un proyecto en primer lugar accedemos al menú: ((New, C++ Project))
y en tipo de proyecto ponemos: ((AVR Cross Target Application)) tal y como en la
Sección C.5. El nombre del proyecto en este caso no es relevante.
Como este proyecto sı́ que pasará por la fase de ((debug)) y de ((release)) dejaremos
habilitadas ambas configuraciones.
El siguiente paso consiste en añadir los mismos directorios que en la Sección C.5
con el objetivo que se pueda referenciar a los ficheros de cabecera del ((core)). Además,
hay que añadir al propio proyecto en la lista de directorios. Para ello añadir 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 código objeto, el primer paso es ir al
menú 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 último en la apartado ((libraries))
tenemos que indicar dónde se encuentra el fichero con la librerı́a, 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 deberı́a ser
parecido al mostrado en la Figura C.4.
C.7. SUBIENDO EL PROYECTO A NUESTRO ARDUINO 123

Una vez configuradas las librerı́as 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 opción
((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 será el programador software encargado de realizar la comunicación con el Arduino
para subir el ((.hex)) al mismo.
((avrdude)) utiliza un fichero de configuración 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 menú ((AVR, AVRdude)). Una vez en el menú, 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 qué 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 menú ((AVR,
AVRDude)). Ahora crearemos una nueva configuración dando al botón ((new)) de la
pestaña ((Programmer)). El nombre de la configuración puede ser el que desees, se
recomienda utilizar el nombre de tu placa ası́ te será más 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 deberá escribirse de la siguiente manera ((//./COMX)).
En cuanto a la velocidad en baudios variará en función del ((bootloader)) y de la placa,
los valores tı́picos son 57600 y 115200. El aspecto de esta ventana de configuración
deberı́a 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 botón
derecho sobre el proyecto y sobre la opción ((Build Project)). Si todo está bien se
creará una carpeta llamada ((Debug)) donde podremos encontrar un fichero llamado
((nombreDelProyecto.hex)). Ahora solo nos queda subirlo a nuestra placa, para ello
pulsamos botón derecho sobre el proyecto y buscamos ((AVR, Upload Project to Target
Device)).
C.7. SUBIENDO EL PROYECTO A NUESTRO ARDUINO 124

Figura C.5: Configuración 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 }

Código 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.

125

También podría gustarte