Arduino III: Resolución de un sistema complejo y lenguaje de programación

Continuamos con la serie de tutoriales sobre Arduino, que vendrán con el hashtag #Arduino, adentrándonos un poco más en la construcción y desarrollo de sistemas complejos, aprovechando la presentación de la parte técnica de un proyecto artístico que estoy llevando acabo: Atracción Forzada.

PabloYglesias-Atracción Forzada

Como ya he dicho anteriormente, aquí veremos la parte técnica de un proyecto artístico asistido por Arduino. Al final de esta entrada, tenéis un vídeo en funcionamiento de una versión inicial (que ya he mejorado y que seguramente acabe puliendo de aquí a unos meses).

Atracción forzada es una instalación artística. Todo lo que véis aquí es el corazón de la instalación, faltando acoplar la carcasa, que recordará a un bebé sentado. La idea de Atracción forzada es generar sentimientos de unión y desprecio hacia un cuerpo robótico que pide auxilio, y únicamente se calma cuando hay personas cerca de él, volviendo a necesitar el cobijo humano conforme las personas se alejan.

Como ya hemos visto en la anterior entrada, cualquier sistema precisa de unos datos de entrada y otros de salida. En este caso, usaremos como datos de entrada un sensor de ultrasonidos, y como datos de salida, varios sistemas distintos: LED, servo y altavoces.

Trataremos cada uno de los sistemas por separado en las futuras entradas, aunque veía prudente mostraros el funcionamiento final de un proyecto de este tipo, en el que intervienen varios sistemas distintos, y que en definitiva, resulta muy sencillo de implementar, gracias en buena medida a la gran cantidad de información que las comunidades de Arduino nos brindan.

Dicho esto, vamos a entrar al lío con el tutorial que nos compete, y en el que hablaremos un poco del lenguaje (o lenguajes) de programación que usaremos.

Programar para Arduino

Arduino es un proyecto de hardware libre, y como es de esperar, y ya vimos en la entrada sobre Arduino, el proyecto acepta de buena gana la programación en cualquier lenguaje, siempre y cuando sea compatible con la placa, o se cree una compatibilidad para que ello sea posible.

Por tanto, en teoría podríamos programar en cualquier lenguaje, aunque primero tendríamos que asegurarnos que es compatible. Esto reduce las posibilidades a dos grandes lenguajes, Processing o C++.

Del primero hablaré poco esta vez, ya que tengo previsto dedicarle una entrada entera en la que veremos parte de la historia del lenguaje, y el porqué se ha hecho tan conocido en el mundo open source y la educación. Para que nos hagamos una idea, Processing está basado en Java, y está diseñado para ser usado por personas sin conocimientos previos técnicos, ya que existe una interfaz gráfica que simplifica las cosas.

Como no soy muy de Java, y en mi caso me desenvuelvo relativamente agusto con lenguaje C, un servidor optó por desarrollar en el lenguaje nativo de Arduino, C++, que no deja de ser una extensión del lenguaje estructurado C para permitir el manejo de objetos.

Código común en cualquier programa para Arduino

En la anterior entrada usamos un archivo llamado Blink para asegurarnos que la placa estaba correctamente instalada y el compilador tenía acceso al puerto y el dispositivo conectado.

El código de Blink nos servirá de ejemplo para ver las pautas que os encontraréis en cualquier proyecto de Arduino (y casi en cualquier programa de C++). Si ya estáis familiarizados con la programación C++, podéis saltaros este apartado sin problema alguno.

/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.

This example code is in the public domain.
*/

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
delay(1000);               // wait for a second
digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
delay(1000);               // wait for a second
}

Comentarios en C++

Los comentarios son líneas de código que el compilador (la herramienta encargada de repasar el código, ver si contiene errores, y mandárselo en caso contrario a la placa) no leerá. No son necesarias, pero sí muy recomendables, ya que permiten a uno mismo saber para qué se hizo X código en un futuro, o a cualquier otro desarrollador comprenderlo de forma sencillo y poder modificarlo con conocimiento de causa. Además, no afectan al rendimiento, así que no hay excusa posible.

Hay dos maneras de colocar un comentario:

  • Si el comentario va a ocupar una sola línea, podemos usar las dos barras //.
  • En caso de que el comentario ocupe varias líneas, usaremos la secuencia /* para comenzar el comentario, y */ para terminarlo. Todo lo que se encuentre entre estos dos objetos, no se tendrá en cuenta en el código.

Los comentarios también se usan en la fase de resolución de bugs (o errores), comentando parte del código para encontrar qué es lo que falla y qué no.

En el caso de Blink, tenemos un comentario de varias líneas al empezar (muy usado para informar de datos del desarrollador o empresa, así como de la función del código), y varios comentarios de línea para conocer el significado del código que acompañan.

Declaración de librerías y variables

Una vez hemos informado de la función del código, lo siguiente suele ser declarar los includes y las variables a usar. Podríamos hablar largo y tendido sobre el uso de variables globales o locales, funciones y clases, pero lo cierto es que si en algún momento necesitáis implementar alguna de estas particularidades, San Google ofrece respuestas de forma clara y sencilla, por lo que nos atenderemos a lo común.

En el caso de Blink, no hay ninguna llamada a librerías ni a otros programas o funciones. Esto es debido a que Blink funciona de forma autómata con el resto de herramientas que nos proporciona el IDE de desarrollo Arduino. Sin embargo, en futuras entradas, veremos cómo la mayoría de scripts hacen llamada a funciones que no están declaradas en el propio código, y que para ello usan la nomeclatura:

#include; //Arduino.h es la librería más usada para desarrollo en Arduino,
// pero podríamos usar cualquier otra, o varias

Una vez hacemos llamada a una librería, podremos usar en el código cualquier función, que así se haya declarado en dicha librería, de tal forma que el código se reduce y se hace más manejable (y entendible). El IDE de desarrollo Arduino cuenta con varias librerías ya creadas, y hay muchas más por internet, además de poder crearos vosotros vuestras propias librerías sin problema alguno.

El segundo punto después de hacer llamada a las librerías, es la declaración de variables globales. Hablamos de variables globales cuando dicha variable podrá ser usada en todo el código, y variables locales a aquellas que definiremos (si es necesario), dentro de una función.

La nomeclatura de una variable siempre es igual, y cuenta con la declaración del tipo de variable (int significa entero, void significa tipo vacío, float flotante,…). El tipo de variable define lo que guardaremos (y retornaremos) dentro de ella, de tal forma que si una variable es de tipo int, significa que dentro contendrá números enteros (1,2,3,4,…).

Después del tipo de variable, viene el nombre de dicha variable, que nos permitirá usarla de forma cómoda en cualquier otra parte del código, y por último, podemos inicializarla a un valor, o no.

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

Funciones

Una vez hemos terminado con la declaración de librerías y variables, toca crear las funciones del código. Una función no es más que un conjunto de órdenes que se interpretarán juntas. Podemos entender una función como una variable hipervitaminada, ya que a su vez contará con el tipo de función (int, entero; void, nulo;…), el nombre de la función (para hacer uso de la misma donde queramos), y podremos o no declarar su función, usando en este caso los brackets { }, en vez del igual.

Hay sin embargo una diferencia a tener en cuenta, y es que las variables, como cualquier sistema, reciben entradas y salidas. La salida, por supuesto, es del tipo de función que hemos escrito (si una función es de tipo int, devolverá un entero…), y las entradas, vienen definidas entre los paréntesis justo después del nombre de la función, y contendrán (o no) otras variables.

void miFuncion (int miVariable, int miOtraVariable){
// acciones a completar
}

En Arduino, usaremos básicamente dos funciones (podemos crear cuantas más queramos). La función setup, y la función loop.

Función setup

Después de conocer qué es una función, estos dos apartados se hacen sencillos de comprender. setup es una función de tipo void, lo que quiere decir que no retorna nada. En ella, declaramos las acciones que han de hacerse tan pronto se resetee la placa (algunas placas cuentan con un botón de reset en una de las esquinas), o tan pronto se envíe la información a la misma. Esto quiere decir que todas las acciones que escriban dentro de la función setup, se harán una vez y solo una vez, pasando luego al resto del código.

// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}

Dentro de la función setup de Blink, tenemos una sola acción, pinMode(led, OUTPUT) (si os fijáis no deja de ser otra función con dos variables de entrada), que nos permite avisar a la placa que el pin llamado led (que antes inicializamos a entero 13), será un pin de salida (devolverá algo, en este caso, luz).

Función loop

El nombre de esta función lo dice todo: bucle. Quiere decir que estamos ante una función sin retorno (es void), sin parámetros de entrada (no tiene nada dentro de los paréntesis), y que repetirá una y otra vez el código que coloquemos dentro, hasta que la placa se quede sin conexión eléctrica.

// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
delay(1000);               // wait for a second
digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
delay(1000);               // wait for a second
}

Dentro del loop de Blink, tenemos cuatro acciones:

  • digitalWrite(led, HIGH): Cambia el estado del pin led (recordad que es la salida 13 digital de la placa) a HIGH, lo que significa que por ahí pasará corriente (y al tratarse de un diodo led, se encenderá).
  • delay(1000): Mantendrá este estado 1000 milisegundos (1 segundo).
  • digitalWrite(led, LOW): Pasado el segundo, cambiará el estado de ese pin a LOW, lo que significa que no pasará corriente (y por tanto el led se apagará).
  • delay(1000): Mantendrá este estado 1000 milisegundos (1 segundo).

Con esto, y puesto que loop se repite indefinidamente, tenemos que el led estará encendido 1 segundo, y apagado otro segundo. Por supuesto, podéis cambiar el delay para ver que efectivamente hay un cambio en este proceso.

LedOnPin13

Queda decir que si os fijáis en la placa, entre la entrada digital 13 y GND (ground o base) hay una resistencia de 1 kohm, y un diodo led (están dentro de la placa). Si en vez de usar el led de la placa, usáis un diodo led externo, colocándolo entre el pin 13 y ground, veréis que funciona igualmente (la resistencia permite que la corriente pase entre estos dos puertos, formando el siguiente esquema):

Blink_schem2

Y visto esto, os dejo con el vídeo inicial de Atracción Forzada. Una vez tenga el proyecto terminado, seréis los primeros en disfrutarlo.