ARD-SYN 01 – Sintaxis (I). Estructura básica de sketches

Facebooktwittergoogle_pluslinkedinmailFacebooktwittergoogle_pluslinkedinmail

En este artículo vamos a recopilar la sintaxis de la programación de Arduino. Este artículo en sí mismo no es, de ningún modo, un manual de programación. La forma de aprender a programar Arduino es programando Arduino. Sigue los tutoriales numerados, realiza los montajes que se describen, analiza los sketches y haz tus propios montajes y escribe tus propios sketches. Sólo así aprenderás. Podrías leer un millón de libros, y si no practicas y adquieres experiencia, nunca sabrás programar.

Y si no es un manual de programación, ¿qué es, entonces este artículo?. La respuesta es fácil. Es un manual de referencia, donde aclarar las dudas sobre sintaxis de instrucciones y funciones, tipos de datos, estructuras de control, etc. Todo aquello que necesites consultar durante tus desarrollos. EN TODO CASO, ES COMPLEMENTARIO DE LOS TUTORIALES PRÁCTICOS. SI NO SIGUES LOS TUTORIALES, Y ANALIZAS LOS SKETCHES, ESTE MANUAL TE VA A SERVIR DE BIEN POCO.

Un buen programador, con experiencia, vocación y las ideas claras no pierde tiempo, a la hora de conocer un nuevo lenguaje o entorno de desarrollo, en aprender, de memoria, la sintaxis de las instrucciones, ni la lista de estas. En vez de eso, se dedica a comprender el lenguaje que está aprendiendo: lo que hace, lo que puede hacer, como responde a la programación… La sintaxis se consulta cuando hace falta. Solo se aprende “de memoria” cuando se te queda grabada por haber usado las instrucciones del lenguaje una vez, y otra, y otra, hasta la saciedad.

Yo llevo tres décadas programando ordenadores, y sigo recurriendo a manuales de consulta para comprobar la sintaxis exacta de tal o cual instrucción. Aquí encontrarás la referencia que necesites para tus consultas, pero ya sabes: programa, programa y sigue programando. Solo cometiendo errores se logran éxitos.

LA SINTAXIS DE ARDUINO

Vale. Después de la charla de arriba, vamos a ello. Vamos a ir desgranando, en distintos apartados, todo lo que necesitamos conocer del núcleo (también llamado kernel) del lenguaje de Arduino. En esta hoja de referencia hablaremos de todas las instrucciones, funciones, estructuras, etc. que forman parte del lenguaje Arduino, tal como viene cuando descargas el IDE. Aquellas instrucciones, métodos, etc que pertenecen a librerías externas (ya sean oficiales de Arduino o de otros desarrolladores) son objeto de estudio en los tutoriales donde se detallan en los artículos que hacen uso de tales librerías. También se detallarán en uno o más artículos de referencia relativos a la obtención y uso de cada una de esas librerías. Por supuesto, no cubriremos todas las que existen (son miles), sino sólo las más empleadas y difundidas.

En cuanto a la sintaxis del lenguaje Arduino, si has programado antes con C, C++ o, incluso, Java, verás muchas similitudes. Si no, acepta mi palabra al respecto. Hay bastante parecido.

ESTRUCTURA BÁSICA

La estructura básica de un sketch Arduino es siempre la misma:

// Zona de inclusión de librerías
// Zona de declaración de variables y constantes
// Zona de creación de objetos

// Zona de definición de funciones de usuario

void setup() {
    // Instrucciones de inicialización del sketch
    // Definición de los pines digitales de Arduino (entrada / salida)
    // Puesta a cero de contadores
    // Apertura de canales de comunicación (Serial y otros)
}

void loop() {
    // Todos los proceso que lleve a cabo el sketch de forma reiterativa,
    // con el fin de obtener el resultado deseado.

}

Si bien no todos los sketches cuentan con los elementos aquí descritos, sí es cierto que todos presentan la misma estructura básica.

INCLUSIÓN DE LIBRERÍAS

Las librerías externas son módulos diseñados para permitir que Arduino se comunique de forma adecuada con determinados dispositivos. En los tutoriales de esta serie tenemos varios ejemplos (servomotores, displays de cristal líquido, etc.).

Las librerías son, por lo tanto, módulos que resuelven determinadas necesidades, o aportan capacidades que el núcleo de Arduino no implementa. Están escritas e C y cualquiera que conozca este lenguaje (en su momento publicaremos una serie de temarios y tutoriales) puede leer su código y modificarlas, o crear las suyas propias.

Para incluir una librería debemos disponer del correspondiente fichero, que suele tener el nombre de la librería, con la extensión .h. Normalmente se suele descargar comprimido en formato zip. Algunas librerías ya las tenemos cuando instalamos el IDE de Arduino. Otras hay que obtenerlas en Internet. Se pueden localizar en la página oficial de Arduino, si son librerías oficiales del fabricante (https://www.arduino.cc/en/Reference/Libraries), o en páginas de terceros desarrolladores.

Usar una librería en nuestros sketches consta de dos pasos:

El primero es agregarla al IDE de Arduino si no viene “de fábrica”. Para ello, en nuestro IDE, pulsamos sobre el menú Programa, y la opción Incluir librería. Se nos abrirá un desplegable que nos muestra la lista de las librerías que nuestro IDE tiene agregadas, bien porque sean parte de la instalación, o porque las hayamos agregado en algún momento. Si la librería que queremos aparece en la lista, podemos pasar al siguiente paso. Si no, pulsamos sobre la opción Añadir librería .ZIP... y se nos abre una ventana para que indiquemos la ubicación en nuestro ordenador del archivo de la librería que nos hayamos descargado previamente. Lo abrimos y ya queda, automáticamente, agregada a nuestro IDE. Una vez agregada, ya está disponible cuando la necesitemos. Ya podemos borrar el archivo zip que nos habíamos descargado. Este paso sólo hay que hacerlo una vez para cada librería que queramos tener disponible.

El segundo paso es incluir la librería en el sketch que la necesitemos. Para ello, el el área de edición del sketch escribimos, las principio del código, la instrucción #include, seguida del nombre de la librería que queremos usar, incluyendo la extensión .h y entre < y >. Pondremos una línea como la detallada por cada librería que necesitemos para el sketch (un mismo sketch pede incluir tantas librerías como necesitemos. Por ejemplo, supongamos que vamos a escribir un sketch que haga uso de las librerías para sevomotores y para displays LCD. Lo iniciaremos con las siguientes líneas:

#include <Servo.h>
#include <LiquidCrystal.h>

También podemos incluir las librerías pulsando sobre el menú Programa, opción Incluir librería y, en la lista que se despliega, pulsaremos sobre el nombre de la librería que deseamos.

Las librerías se deben incluir al principio de todo el sketch, para que estén disponibles en cualquier momento que las necesitemos.

TIPOS DE DATOS

En Arduino (cómo en cualquier lenguaje de programación) se manejan dos grandes categorías de datos: variables y constantes. Un dato en un programa siempre es un par nombre-valor. Es decir, es una etiqueta que contiene un valor. Las variables son aquellos pares nombre-valor cuyo valor (o contenido) puede cambiar durante la ejecución del sketch. Las constantes, por contra, son pares nombre-valor cuyo contenido no puede variar. Los nombres genéricos de “variables” y “constantes” son, por tanto, bastante descriptivos respecto a la naturaleza de cada dato.

  • El nombre de una variable o constante debe seguir unas reglas específicas:
  • Deberá empezar con una letra.
  • Dentro del nombre podrá haber letras, números o guiones bajos.
  • Se aconseja que sea descriptivo respecto al uso del dato, para facilitar el desarrollo y la depuración. Es decir. Es mejor llamar a una variable contador_de_pulsaciones que a_2321_xj o mi_variable_17.
  • Los nombres de variables y constantes son sensibles a mayúsculas y minúsculas. Por lo tanto, contador no es el mismo nombre de variable que Contador o CONTADOR. Algunos programadores toman como norma escribir los nombres de variables en minúsculas o en CamelCased (mayúscula la primera letra de cada palabra y minúsculas las demás letras); escriben con mayúsculas los nombres de CONSTANTES, separando cada PALABRA_DE_LA_SIGUIENTE con guiones bajos. En todo caso, es criterio personal de cada uno.
  • No se puede usar una palabra reservada del lenguaje como nombre de una variable. En ese sentido lo tenemos fácil los hispano parlantes, ya que las palabras reservadas de cualquier lenguaje de programación son en inglés, o derivadas de este idioma.

 En Arduino se contemplan los siguientes tipos de datos:

  • DATOS NUMÉRICOS
    • byte. Admite valores de 0 a 255, sin decimales.
    • int. Admite números de -32768 a 32767, sin decimales.
    • unsigned int. Admite valores de 0 a 65535.
    • word. Mismo rango que unsigned int. Carece de uso práctico.
    • long. Admite números de -2147483648 a 2147483647, sin decimales.
    • unsigned long. Admite números de 0 a 4294967295
    • float. Admite números reales (con decimales) desde -3.4028235E38 hasta 3.4028235E38.
    • double. En otros lenguajes es un flotante con más rango que el float, pero en Arduino es un alias de este.

Los números se representan, normalmente, en decimal. Si queremos representar un número en binario, lo precederemos de la secuencia 0b (en minúscula y, por supuesto, sólo podrá estar formado por los dígitos 0 y 1, así; 0b01110101. La secuencia 0b se usa para indicar a Arduino que es un número binario pero, a efectos de procesamiento, no forma parte del número en sí.

Siguiendo el mismo principio, los números octales se preceden del carácter 0 y solo pueden estar formados por los dígitos del 0 al 7, así: 0452032.

Los hexadecimales se preceden con la secuencia 0x (la x es minúscula) y pueden estar formados por los dígitos del 0 al 9 y las letras de la A a la F, así: 0x45F3D2.

  • DATOS DE CADENA
    • string. Una cadena de texto, encerrada entre comillas dobles. Puede ser una cadena vacía (p.e. “Este soy yo” o “”).
    • char. Un carácter, encerrado entre comillas simples. Internamente se procesa como un número de -128 a 127, aunque, de cara al usuario, se representa como carácter.
    • Un dato de tipo String puede construirse como una matriz de datos de tipo char (ver más abajo matrices. Por ejemplo char cadena[] = {'t', 'u', 't', 'o', 'r', 'i', 'a', 'l', '\0'};. El carácter de terminación \0 se usa para evitar algunos errores.
  • VALORES LÓGICOS (BOOLEANOS)
    • true o false. Si a una variable booleana se le asigna un valor numérico, interpretará como false el 0 y como true cualquier otro valor.
  • CONSTANTES DEL LENGUAJE

Existen ciertas constantes que son propias del lenguaje Arduino:

  • INPUT
  • OUTPUT
  • HIGH
  • LOW

Las variables y las constantes se declaran como podemos ver en cualquiera de los ejemplos de los tutoriales:

int const pinPot = A0;
int valorMedido;
int angulo = 0;

Todas las variables y constantes que vayamos a usar en nuestro sketck deben ser declaradas, con indicación expresa del tipo de dato que van a contener, con la palabra const antes del nombre si se trata de constantes, y asignándoles o no un valor inicial en el momento de declararlas (si se trata de constantes, esta asignación en la declaración es obligatoria porque no se podrá cambiar su valor posteriormente). Es una buena práctica declarar juntas, en la primera sección del sketch, todas las variables y constantes que necesitemos.

CREACIÓN DE OBJETOS

Cuando incluimos una librería para gestionar un dispositivo, la programación de la librería (en la inmensa mayoría de los casos, con poquísimas excepciones) está encapsulada en un patrón llamado, genéricamente, clase. Para poder implementar las funciones de una clase es necesario, como norma general, crear un conjunto de datos y funciones (que, en este contexto se llaman propiedades y métodos) englobados en un objeto. Un objeto es, por lo tanto, la instanciación en nuestro programa de una clase, y tiene las propiedades y métodos que se hallan definidos en dicha clase.
 
Para ponerte un ejemplo, en el tutorial 012, que habla de los displays LCD, vemos que, tras incluir la librería correspondiente, usamos la siguiente instrucción:
 
LiquidCrystal LCD (12, 11, 5, 4, 3, 2);
 
Vemos el nombre de la clase (LiquidCrystal), el nombre del objeto que creamos (LCD) y, entre paréntesis, algunos argumentos, necesarios en este ejemplo. Una función de una clase, que tiene el mismo nombre que dicha clase y se usa para instanciarla en un objeto se llama, genéricamente, constructor.
 
El concepto de clases y objetos da mucho más de sí, (herencia, polimorfismo, sobreescritura…), pero no es este el lugar ni el momento de hablar de todo esto. En su momento dedicaremos el material necesario a la programación orientada a objetos. Por ahora, esto nos basta.

LAS FUNCIONES DE USUARIO

El programador de un sketch puede definir sus propias funciones que, por defecto no existen en el lenguaje, pero que necesitaremos para que nuestro sketch haga lo que queremos.

Una función de usuario es un fragmento de código que se carga en memoria pero no se ejecuta, sino que permanece “en el limbo” de la memoria de Arduino, hasta que es invocado. A una función podemos pasarle determinados valores, conocidos, genéricamente, como argumentos, que pueden variar su resultado. Una función puede “hacer algo” o, según como la definamos, “devolvernos” un resultado. Podemos ver un ejemplo de funciones de usuario en el artículo 14 (la función mostrar() es un buen ejemplo).

Las funciones de usuario se declaran con la del tipo de dato que devuelve, seguida por el nombre de la función y, entre paréntesis, los argumentos que deba recibir. Si no necesita argumentos, los paréntesis hay que ponerlos igualmente. A continuación se escriben las instrucciones que llevará a cabo la función (lo que se conoce como cuerpo de la función) entre { y }. El formato general obedece, por lo tanto, al siguiente esquema:

tipo function nombreDeLaFuncion (arg1, arg2, arg3, ..., argN) {
    // Cuerpo de la función
}

Si la función no va a devolver ningún resultado, el tipo se declarará como void (vacío), pero no puede omitirse. Si, por el contrario, la función va a devolver un resultado, el tipo de la función corresponderá con el tipo del dato que devuelve, según la lista que hemos visto en el apartado anterior. Además, si se espera que la función devuelva un resultado, la última instrucción del cuerpo de la función deberá ser, obligatoriamente, la palabra return, seguida del nombre de la variable que debe devolverse.

// Ejemplo de una función que "hace algo" pero sin devolver nada.
void function MiPrimeraFuncion (arg1, arg2) {
    // Cuerpo de la función
}

// Ejemplo de una función que hace "algo" y devuelve un resultado de tipo int.
int MiFuncionDeEntero (arg1, arg2, arg3) {
    //Cuerpo de la función
    return variableTipoInt;
}

En los tutoriales de Arduino hay algunos ejemplos que incluyen funciones de usuario, para que veas como operan. Como hemos dicho que las funciones se cargan en memoria, pero no se ejecutan hasta que son invocadas, deberemos declararlas en la primera sección del sketch. Lo ideal es a continuación de la declaración de variables, constantes y objetos.

COMENTARIOS

Cuando se escribe un código (para Arduino, o en cualquier otro entorno) es una buena práctica incluir, donde sea necesario, comentarios para recordarnos que han o como operan tal o cual conjunto de líneas del sketch, o tal función, etc. Si tenemos que depurar o modificar el sketch pasado un tiempo, y este es lo suficientemente largo y complicado, tener los comentarios adecuados puede ahorrarnos muchísimas horas de trabajo.

Un comentario puede constar de una sola línea. En ese caso lo podemos preceder con dos barras inclinadas, así:

// Esto es una línea de comentario.

También puede ser un bloque de varias líneas. En ese caso lo iniciamos con /* y lo terminamos con */, así:

/*
Esto es un conjunto de líneas de comentarios.
La función que se define a continuación, hace esto, y aquello, y 
no sé que más.
Recibe muchos datos y los procesa como puede.
Evidentemente, los comentarios no deberían tener este aspecto :)
*/

ÁMBITO DE LAS VARIABLES

Según donde declaremos nuestras variables, podremos o no acceder a ellas para cambiar su valor o leerlo. Hemos dicho que, como norma general, las variables se declaran en la primera sección del sketch. Si intentamos leer o cambiar el valor de una variable dentro de una función de usuario, no será posible: parecerá que la variable no exista. Si la necesitamos dentro de la función, deberemos pasarla como argumento (caso aparte son las funciones setup y loop, propias del sketch).

Si la declaramos dentro del cuerpo de una función, no será posible leerla o escribirla desde fuera de dicha función. Si la necesitamos fuera deberemos “sacarla” con return.

Es importante que recuerdes el ámbito donde se crea una variable (donde “vive” esa variable) porque si esperas localizarla en otro ámbito, obtendrás errores de compilación y tu sketch no funcionará.

No te preocupes ahora de si todo esto te suena un poco arcano. Los ejemplos de los tutoriales te lo aclararán. De momento, sólo quédate con la idea.

MATRICES

Una matriz es una colección de variables que se engloban bajo el mismo nombre y, dentro de ese englobamiento, se identifican mediante un índice. Se declaran con indicación expresa del tipo de datos que van a contener (lo que indica que todos los datos serán del mismo tipo. La forma general de declararlas es así:

tipo NombreDeMatriz[] = {1, 2, 3, 4, ... , n};

Como ejemplo, usamos varias en los tutoriales. Como ejemplo vemos esta línea sacada del tutorial 13, que habla de los teclados:

byte Pins_Filas[] = {13, 12, 11, 10};

Los valores se identifican, dentro de la matriz, con un índice que empieza a contar desde 0. Así pues, para localizar el primer valor (en el ejemplo, 13) lo haremos con Pins_Filas[0] y podemos leerlo o reasignarlo. Para cambiar el valor de ese índice, escribiríamos Pins_Filas[0] = 26;

Recuerda que las matrices pueden ser como filas (una dimensión), tablas (dos dimensiones), etc. Pueden tener todas las dimensiones que nos permita gestionar la memoria de nuestro Arduino. En la práctica, yo llevo tres décadas programando y no he necesitado nunca matrices de más de tres dimensiones (y, aún estas, sólo en contadísimas ocasiones).

Una matriz de dos dimensiones se declara con dos pares de corchetes, como en

int miMatriz [][] ={{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

Los índices podemos recorrerlos mediante bucles. En el artículo de sintaxis que hablemos de estructuras de control entraremos más en detalle sobre la operativa de las matrices.

CIERRE

Con esto damos por finalizado este artículo, para no hacerlo muy pesado. En sucesivos artículos sobre sintaxis continuaremos viendo como funciona el lenguaje Arduino. Sólo una cosa más. Recuerda siempre terminar cada instrucción con un punto y coma. Es algo fácil de olvidar y causa de errores “tontos” pero de difícil localización a veces.

     

2 comentarios:

  1. Pingback: LA LIBRERÍA LedControl » eldesvandejose.com

  2. Pingback: ARD-SYN 05 – Sintaxis (V). Tipos de datos » eldesvandejose.com

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *