Arduino
Descripción de Arduino
Arduino es una placa de circuitos electrónicos que incorpora dos elementos interesantes:
● Un circuito integrado (chip) que incluye microprocesador, memoria ram y otra
memoria permanente (a esto se le llama generalmente "microcontrolador")
● Unas conexiones muy simples donde se pueden conectar :
○ botones, interruptores, sensores y otros dispositivos de medidas
○ luces, motores, emisores y otros circuitos que realizan algún trabajo
La utilidad de arduino es realizar montajes con circuitos simples que realicen funciones de
automatización y robótica de una forma fácil y rápida. Para ello, el usuario escribe un
programa, código, o conjunto de instrucciones que se guarda en la memoria de arduino y
que se ejecuta en el microprocesador
Algunos ejemplos de uso de arduino:
● Controlar la iluminación de un local.
● Controlar un coche teledirigido.
● Controlar un coche que se mueve evitando obstáculos
● controlar una cinta transportadora en algún proceso industrial.
● Controlar un Ascensor,
● etc.
Ejemplo de un circuito (simulado) con múltiples elementos de entrada y salida.
Una característica flexible de arduino es que las conexiones de la parte superior (etiquetadas
con la palabra "DIGITAL"), son entradas o salidas a elección del usuario que realiza el
montaje y la programación. Si necesitas una salida o una entrada, puedes elegir un pin
cualquiera y configurarlo como desees -entrada o salida-. El problema es que no hay
muchos pines disponibles, pero la mayoría los que hay son entradas o salidas a elegir.
Un aspecto cómodo de arduino es que incorpora una conexión USB con la que se
puede conectar a un ordenador normal. Así, resulta sencillo programarlo y verificar su
funcionamiento.
Simulador de arduino.
En vez de utilizar un robot real, vamos a realizar ejercicios y ejemplos con un simulador
online que se ejecuta en un navegador.
www.tinkercad.com
En este simulador, se puede colocar componentes en un tablero virtual y además realizar la
programación en un editor que incorpora la propia página. La compilación, descarga y
ejecución del código es realizado por la misma página del simulador.
Primer ejemplo con arduino
Para aprender arduino, vamos a realizar un primer circuito de ejemplo. Con el tinkercad,
obtén este circuito (usa exactamente el pin 13)
El simulador, a modo de ejemplo, genera automáticamente y sin pedírselo, el siguiente
código:
void setup()
{
pinMode(13, OUTPUT);
}
void loop()
{
digitalWrite(13, HIGH);
delay(1000); // Wait for 1000 millisecond(s)
digitalWrite(13, LOW);
delay(1000 ); // Wait for 1000 millisecond(s)
}
Analizaremos el código anterior para descubrir cómo se programa.
Lo primero que hay que observar es que aparecen dos funciones llamadas "setup" y "loop".
Cada una de ellas encierra entre llaves { } unas instrucciones que son el programa que
ejecutará arduino. El siguiente diagrama te ayudará a identificar las funciones y sus partes
En el ejemplo, estas instrucciones aparecen porque las ha puesto el simulador por su
cuenta, pero la misión del programador es escribir aquí las instrucciones de lo que se desee
ejecutar. Tú tendrás que escribir las instrucciones adecuadas para solucionar el problema
que se te plantee en el futuro.
setup()
La función setup() es una función que se ejecuta en el momento en el que se
enciende o inicia el arduino. Las instrucciones o código que escribas allí, se ejecutarán sólo
una vez, y lo harán al principio. Esta función sirve para preparar lo necesario en el momento
inicial, para establecer unas condiciones de funcionamiento concretas en el primer
momento.
Por ejemplo, supón que arduino está controlando un sensor infrarrojo que detecta
personas que pasan por un pasillo y contándolas. Al principio deberemos establecer en 0 las
personas que han pasado, y después durante el funcionamiento normal, cada persona
detectada incrementará esa cuenta.
loop()
La función loop es la función principal. Allí se escribe el programa que deberá
ejecutar arduino. Esta función se ejecuta una y otra vez indefinidamente. Cuando termina su
ejecución, vuelve a iniciarse otra vez, y así hasta que se desconecte arduino. Si el programa
va a controlar algo durante mucho tiempo (por ejemplo una puerta automática que se abre
cuando detecta la presencia de un objeto) no hace falta hacer un bucle infinito, ya que la
función loop se ejecuta una y otra vez
Funciones utilizadas
Ahora analizaremos las líneas de código escritas, explicando qué ocurre en ellas.
delay();
setup y loop, son dos funciones que debes hacer tú. Sin embargo, delay() es una
función que está completamente hecha y que se "invoca", "llama" o "ejecuta" en tu
programa. Puedes usarla escribiéndola como en el ejemplo anterior. Tan sólo has de elegir
un número y ponerlo entre paréntesis. Cuando el programa se ejecute, y llegue a la línea
donde está escrita esta función, arduino se parará por un tiempo. Todo se quedará tal como
esté en ese momento y nada cambiará ni se ejecutará la siguiente instrucción hasta que
transcurra el tiempo especificado. Después de ese tiempo, el programa seguirá
ejecutándose en la siguiente instrucción al delay. Entre paréntesis escribes el tiempo (en
milisegundos) que el programa ha de estar parado.
Así, por ejemplo:
delay(1000);
provoca una detención de 1000 milisegundos.
pinMode()
pinMode() es una función que también es llamada por el programa y que el
programador escribe cuando la necesita. Su tarea es configurar un pin para indicar que es
de entrada o de salida. Hay que pasarle dos datos a la función: el número de pin y una
indicación de si es una entrada o una salida. En nuestro caso:
pinMode(13, OUTPUT);
Estamos configurando el pin 3 como salida "OUTPUT". Normalmente esto debe hacerse
durante setup() y dejar el pin configurado para el resto del funcionamiento normal.
digitalWrite()
Esta función también es utilizada por el programador para especificar una acción a
realizar. La tarea de ella es cambiar el estado de una salida, de ahí, que se llame "Write". Y
la palabra "digital" indica que se trata de una salida digital, que puede tomar un valor de 0 o
1, verdadero o falso, si o no, alto o bajo, 0 voltios o 5 voltios…. como quieras entenderlo.
Esta función debe saber el número de pin que se va a cambiar y el valor a establecer,
Los pines son números y los valores posibles: "HIGH" o "LOW".
Ejercicio
Vamos a modificar el ejemplo anterior, haciendo que el parpadeo sea cada vez más lento.
Solución:
Algunas cosas deben cambiarse:
● No se puede hacer un delay fijo, de otra forma nada cambiará y todo seguirá igual
○ Por ello, h
ay que poner una variable y usarla en el delay
○ La variable hay que inicializarla obligatoriamente antes de usarla
● Hay que incrementar la variable cada vez
○ No hay que hacer un while… El "loop" ya es un while
○ En este momento tenemos lo siguiente:
Prueba el siguiente ejemplo y sigue con el tutorial a
unque no funcione.
void loop()
{
int retraso;
retraso = 10;
digitalWrite(13, HIGH);
delay(100+ retraso);
digitalWrite(13, LOW);
delay(100 + retraso );
retraso = retraso + 10 ;
}
La líneas
1 int retraso;
2 retraso = 10;
son :
1. la declaración de una variable llamada "retraso" que es de tipo "número entero" (se
indica con "int")
2. a asignación de un valor concreto a la variable (en este caso 100)
En la línea
delay(100+ retraso);
Se indica a arduino que debe dejar todo quieto durante un tiempo. Ese tiempo está
especificado entre paréntesis, y en esa instrucción aparece una suma. Por tanto el tiempo a
a esperar puede no ser siempre el mismo, cuanto más valga la variable "retraso" , más
tiempo estará todo detenido…. En principio pinta bien
La siguiente línea intenta provocar ese retraso cada vez mayor.
retraso = retraso + 10 ;
En la expresión anterior ocurre lo siguiente:
● Primero se suma 10 al valor de la variable retraso
● El resultado de la suma se guarda en la variable retraso
Lo que se consigue es aumentar en 10 el valor de la variable retraso, y como esa variable se
usa para establecer el retraso, ocurrirá que cada vez habrá más retraso
… pruébalo y … verás como no funciona
Depuración. Serial.println()
En el desarrollo anterior el programa funciona mal. El Led parpadea siempre con la misma
frecuencia. Tenemos una variable que usamos para indicar el retardo pero parece no
funcionar. Necesitamos saber mejor qué está pasando en el programa, m ientras el programa
está en marcha.
Arduino se conecta al computador para ser programado. Normalmente, se realiza la
programación y después se desconecta. Sin embargo, s i se mantiene conectado, es posible
enviar información al computador y verla en la pantalla de éste durante el funcionamiento.
Esto se puede simular en tinkerCad también.
Lo que pretendemos conseguir es poder d epurar el programa. Depurar es
poder ver datos internos sobre el funcionamiento del programa mientras está
en ejecución para facilitar al programador ideas de por qué está funcionando
mal.
Debemos hacer lo siguiente:
1. Inicializar la comunicación serie con el Computador en la función setup()
2. Llamar a Serial.println() cada vez que queramos mostrar algún valor
Observa las líneas resaltadas. la primera es la inicialización, la segunda es la escritura del
valor.
void setup()
{
pinMode(13, OUTPUT);
Serial.begin(9600);
}
void loop()
{
int retraso;
retraso = 10;
digitalWrite(13, HIGH);
delay(100+ retraso); // Wait for 1000 millisecond(s)
digitalWrite(13, LOW);
delay(100 + retraso ); // Wait for 1000 millisecond(s)
retraso = retraso + 10 ;
Serial.println(retraso,DEC);
}
Para realizar la simulación y ver el resultado, hay que activar el botón de "Serial Monitor" en
la parte inferior de la ventana de código.
Practica bien la depuración porque es una herramienta muy poderosa y
necesaria en el trabajo de un programador y en el arduino, además, es el único
recurso para ver información compleja.
Si pruebas este código verás que la variable siempre vale 20.
● Hay que inicializar la variable
○ Pero no en la función loop, porque en ese caso, se inicializa siempre
● Hay que declarar la variable fuera e inicializarla en setup()
○ Así sólo se inicializa la primera vez
int retraso;
void setup()
{
pinMode(13, OUTPUT);
Serial.begin(9600);
retraso = 10;
void loop()
{
digitalWrite(13, HIGH);
delay(100+ retraso); // Wait for 1000 millisecond(s)
digitalWrite(13, LOW);
delay(100 + retraso ); // Wait for 1000 millisecond(s)
retraso = retraso + 10 ;
Serial.println(retraso,DEC);
}
Ampliación con un pulsador de reset
Para completar y probar cuanto antes, un circuito que tenga entradas y salidas, vamos
ampliar el montaje anterior añadiendo un botón. Al pulsar el botón, se efectuará una
reinicialización de la frecuencia de parpadeo. El botón es un componente más complejo de lo
que parece en primer momento. Tiene dos conexiones a cada lado que están unidas
internamente entre sí. En la imagen se observa que A y B están siempre unidas entre sí. C y
D también están siempre unidos.
Sin embargo, A y B están desconectados de C y B cuando el botón no está pulsado.
Al pulsar el botón, se establece un puente entre A-B y C-D.
En Arduino el botón debe estar conectado a un pin de entrada que va a leer 0 o 5 voltios
dependiendo de si está pulsado el botón. Hemos de hacer un pequeño montaje y circuito
para alcanzar este objetivo. Observa el montaje a realizar:
Observa que el botón está conectado por una línea roja a la alimentación de 5 Voltios y por
una línea negra a masa (ground). Si quitásemos la resistencia que aparece en la línea negra,
al pulsar el botón ocurriría un cortocircuito. Esa resistencia limita la corriente cuando el
botón se pulsa.
Por otra parte, cuando el botón está pulsado, la línea verde -de entrada- está
conectado a la línea roja que está a 5 voltios. Por lo que en el pin 7 de entrada se lee un
HIGH, 1 o 5 Voltios. Sin embargo, cuando el botón está sin pulsar, la línea negra está a
0Voltios y ello se transmite a la línea verde por lo que en el pin 7 se leen 0 Voltios o LOW
Lectura de valores de entrada
Una vez montado el circuito, vamos a programar la lectura de la pulsación en el
programa. El primer paso es configurar el pin 7 de entrada. Esto se realiza en la función
setup, al igual que se ha hecho para el pin de salida.
void setup()
{
pinMode(13, OUTPUT);
pinMode(7, INPUT);
Serial.begin(9600);
retraso = 0;
}
La lectura de datos se realiza mediante una función llamada "digitalRead". Ésta debe saber
el pin del que se desea leer, por ello se le indica -como argumento- el número del pin. La
función al ejecutarse obtiene un valor que puede ser 1 o 0, HIGH o LOW. El valor debe
almacenarse en algún lugar, para lo cual necesitamos otra variable
void loop()
{
...
int botonPulsado;
botonPulsado = digitalRead(13);
...
}
El dato leído ya puede ser analizado en el programa para determinar si hay que reinicializar
el valor del retardo.
void loop()
{
int botonPulsado;
botonPulsado = digitalRead(13);
if ( botonPulsado == 1 ) reset = 10;
retraso = retraso + 20 ;
}
Ejercicio. Controlador de ascensor.
Para continuar aprendido, vamos a realizar un controlador simplificado de un
ascensor
Se trata de realizar el control de un motor de ascensor de tres plantas. El ascensor
dispone de una botonera con un botón por cada planta. Los botones exteriores de cada
puerta son los mismos que los interiores del ascensor (es decir, el botón de la tercera planta
es el mismo dentro del ascensor como el de la puerta de la tercera planta). Por lo tanto, sólo
pondremos una botonera para llamar o activar el ascensor y que suba o baje a algún piso.
Los ascensores disponen de unos sensores que se activan cuando el cajón del
ascensor llega a una planta. En nuestro montaje estos sensores serán también botones.
Pulsar un botón de estos, equivaldrá a indicar que el ascensor ya ha llegado a una planta
La misión de la programación del arduino es decidir si el motor gira para levantar el
ascensor o para hacerlo descender. En vez de poner un motor y rotarlo en una y otra
dirección, vamos a colocar dos leds para indicar el sentido de giro del motor y facilitarnos la
visualización del prototipo. El circuito es el siguiente
Con las conexiones ya hechas:
Declaramos e inicializamos los pines de sensores y leds:
const int pinBoton1 = 3;
const int pinBoton2 = 4;
const int pinBoton3 = 5;
const int pinSensor1 = 6 ;
const int pinSensor2 = 7;
const int pinSensor3 = 8;
nt pinLedSubiendo = 13;
const i
const i nt pinLedBajando = 12;
Y hago la inicialización!
La función setup se queda así:
void setup()
{
pinMode(pinBoton1,INPUT);
pinMode(pinBoton2,INPUT);
pinMode(pinBoton3,INPUT);
pinMode(pinSensor1,INPUT);
pinMode(pinSensor2,INPUT);
pinMode(pinSensor3,INPUT);
pinMode(pinLedSubiendo,OUTPUT);
pinMode(pinLedBajando,OUTPUT);
Serial.begin(9600);
}
Programación por Estados
Este problema tiene una característica especial que necesitamos describir detalladamente.
Vamos a utilizar un ejemplo simple durante la explicación. Supón que el ascensor está en
reposo en el piso 1 y ningún botón ha sido pulsado todavía.
Cuando se pulse el botón de la planta dos, el ascensor deberá empezar a subir. El botón de
llamar al ascensor, no estará pulsado todo el tiempo mientras el ascensor se mueve. Por eso
se llama "llamar al ascensor", el botón se pulsa una vez mediante una pulsación corta y el
ascensor se mueve durante un tiempo hasta llegar a la planta.
Los ascensores reales tienen unos sensores que indican que el cajón del ascensor ha
llegado a la planta. Cuando así ocurre y el sensor se activa, el control del ascensor detiene
inmediatamente el motor. En nuestro programa, nosotros hemos de reproducir esta
situación.
Recuerda otra circunstancia importante en este ejemplo: El ascensor tiene 6 entradas
(tres pulsadores y 3 sensores). Cuando estaba en reposo, los valores de cada entrada eran
los mismos que durante el tiempo en el que el ascensor ha estado moviéndose. Es decir,
para decidir si el motor debe moverse, no basta con leer los valores de las entradas, porque
como hemos visto, hay ocasiones en las que el ascensor se está moviendo y las entradas
son iguales que cuando está en reposo.
En este problema, el movimiento del ascensor no depende de los datos leídos de las
entradas, sino del estado en el que se encuentra el ascensor. Es decir, si el ascensor está
subiendo, da igual que los botones estén pulsados o no, el motor debe seguir tirando el
ascensor hacia arriba. Esa idea de que "el ascensor está subiendo" hay que programarla.
Evidentemente, el ascensor no estará subiendo eternamente, sino que en algún
momento deberá dejar de subir. Para ello están los sensores de llegada a cada planta.
Podemos pensar que "el ascensor -mientras intenta alcanzar el piso2 estará subiendo hasta
que se active el sensor de llegada al piso 2".
Estas ideas reflejan la necesidad de proporcionar una solución basada en los e
stados y
cambios de estado.
Para entender esto inicialmente, vamos a mantenernos en el escenario del ejemplo, en el
cual, podemos ver tres situaciones o estados:
1. El ascensor está en reposo en el piso 1, nadie ha pulsado todavía el botón y el
ascensor no se mueve
2. El ascensor se mueve hacia arriba porque alguien ha pulsado el botón de subir al
piso 2
3. El ascensor está ya en el piso 2 y por tanto debe volver a permanecer parado y en
reposo
Estas tres situaciones posibles reflejan tres estados que podemos dibujar de la siguiente
manera
Las flechas indican las transiciones o cambios de estado y marcan las posibilidades
existentes. Así, es posible pasar de estar en reposo en el piso 1 a estar subiendo. Pero no es
posible pasar de estar en el piso 1 a estar en el piso 2.
(normalmente hay que añadir un estado intermedio para controlar el tiempo en el
que el boton1 está pulsado, pero en este ejercicio podemos seguir sin prestarle
atención a este hecho y la solución es casi igualmente válida)
Implementación de estados
En la programación, necesitamos primero que nada indicar qué estados posibles existen.
Hay varias formas de hacer esto (por ejemplo asignar un número o código numérico a cada
estado "1": en piso1, "2": subiendo a piso 2, "3": en piso 2, etc). Nosotros vamos a utilizar
una característica del lenguaje C que simplificará muchísimo la comprensión posterior del
programa. Utilizaremos "enum" para declarar tipos de datos que pueden contener ciertos
valores indicados libremente. Observa la declaración del enum siguiente
enum estado { enPiso1,subiendoA2,enPiso2};
La línea anterior define un tipo de dato llamado "estado" que puede tener sólo los valores
"enPiso1", "subiendoA2" y "enPiso2". Ahora, es posible declarar variables con ese tipo de
datos. A continuación se declara una variable llamada "estadoActual" cuyo contenido sólo
pueden ser los valores anteriores
estado estadoActual;
Por ejemplo, podemos inicializar la variable "estado" en la función setup() para indicar que
inicialmente se está en el piso 1 de la siguiente manera:
estadoActual = enPiso1;
Modificamos la función setup con esta instrucción para inicializar el Ascensor (suponemos
por comodidad que está en la planta 1 al principio)
void setup()
{
...
estadoActual = enPiso1;
...
}
Vamos a preparar el terreno para poder ver qué hace el ascensor en cada momento. En la
función loop añadimos la comunicación serie con el simulador que nos permitirá ver el
estado del ascensor. Aquí volvemos a ver las ventajas de utilizar enum como forma de
codificar los estados del ascensor. Observa cómo se utiliza un switch para comparar el valor
de la variable "estadoActual" con los posibles valores asignables:
void loop()
{
Serial.print("Estado actual: ");
switch (estadoActual) {
case enPiso1: Serial.print("enPiso1");break;
case subiendoA2: Serial.print("subiendoA2");break;
case enPiso2: Serial.print("enPiso2");break;
}
Serial.println("");
delay(100);
}
Cambios de estado
La programación por estados consiste básicamente en determinar dos cosas:
● ¿ Cuándo se cambia de estado?
● ¿ Qué salidas se activan según el estado actual?
Generalmente, un cambio de estado ocurre puntualmente. Es fácil que durante muchos
milisegundos no ocurra nada especial y el estado de arduino se mantenga sin cambiar. Pero
en algún momento, debido a una combinación de ciertos valores de entradas y de variables
internas, se produzca un cambio de estado.
Estos cambios se programan estado por estado de forma independiente. Y siempre se
realiza en dos fases.
1. Leemos los datos de entrada que son relevantes para un estado concreto (por
ejemplo, si estamos en el estado "subiendoA2" sólo nos interesa saber si el sensor
de llegada al piso 2 está activo o no. Porque ese estado sólo se cambia cuando se
llega a ese piso, no importa si se pulsa mientras otro botón.
2. Verificamos que se está en cierto estado y que los datos de entrada indican que
debemos cambiar
Para empezar, Vamos a controlar la subida del primer piso, -que es donde nos encontramos
al principio- al segundo piso
Primero deberemos leer la entrada de los pines que nos interesan:
int boton2 = digitalRead(pinBoton2);
int sensor2 = digitalRead(pinSensor2);
Si estamos en el primer piso y el botón ha sido pulsado, entonces hay que cambiar de
estado
if (estadoActual == enPiso1 && boton2 == 1 )
estadoActual = subiendoA2;
Ya está. Hemos implementado el cambio de estado para el estado "enPiso1". Seguimos con
el resto de estados
Igualmente, si estamos subiendo y detectamos que hemos llegado a la segunda
planta, hay que cambiar de estado
if (estadoActual == subiendoA2 && sensor2 == 1 )
estadoActual = enPiso2;
Activación de salidas según estados
Como se ha dicho antes, cada estado determina unas salidas. Cuando el ascensor está en el
estado "enPiso1", todo está en reposo y el motor (o led) no debe activarse. En este ejercicio,
de momennto, el estado importante es subiendoA2. Es en ese estado cuando debe estar el
motor (o led) en marcha.
Esta parte se programa al final, después de todos los cambios de estado. La programación
suele ser muy sencilla:
if (estadoActual == subiendoA2)
digitalWrite(pinLedSubiendo,1);
El problema es que el led no se apagará después de haberse activado. Podríamos complicar
la programación con "elses" para "if" como ese, pero vamos a adoptar una estrategia astuta:
Apagaremos los led al principio y sólo los encenderemos si hace falta
digitalWrite(pinLedSubiendo,0);
if (estadoActual == subiendoA2)
digitalWrite(pinLedSubiendo,1);
Para seguir, reproducimos lo razonado para estos dos pisos en el otro piso y en los sentidos
de bajada:
enum estado { enPiso1,subiendoA2,
enPiso2,subiendoA3,
enPiso3,bajandoA2,
bajandoA1};
El código:
int boton1 = digitalRead(pinBoton1);
int sensor1 = digitalRead(pinSensor1);
int boton2 = digitalRead(pinBoton2);
int sensor2 = digitalRead(pinSensor2);
int boton3 = digitalRead(pinBoton3);
int sensor3 = digitalRead(pinSensor3);
if (estadoActual == enPiso1 && boton2 == 1 )
estadoActual = subiendoA2;
if (estadoActual == enPiso2 && boton3 =
= 1 )
estadoActual = subiendoA3;
if (estadoActual == subiendoA2 & & sensor2 == 1 )
estadoActual = enPiso2;
if (estadoActual == subiendoA3 & & sensor3 =
= 1 )
estadoActual = enPiso3;
if (estadoActual == enPiso3 && boton2 == 1 )
estadoActual = bajandoA2;
if (estadoActual == enPiso2 && boton1 =
= 1 )
estadoActual = bajandoA1;
if (estadoActual == bajandoA2 && sensor2 == 1 )
estadoActual = enPiso2;
if (estadoActual == bajandoA1 && sensor1 == 1 )
estadoActual = enPiso1;
La activación/desactivación de los led:
digitalWrite(pinLedSubiendo,0);
digitalWrite(pinLedBajando,0);
if (estadoActual == subiendoA2 ||
estadoActual == subiendoA3)
digitalWrite(pinLedSubiendo,1);
if (estadoActual == bajandoA2 ||
estadoActual == bajandoA1)
digitalWrite(pinLedBajando,1);
Fin del ejercicio.