UD 5 - Programación de Procesos Por Código
UD 5 - Programación de Procesos Por Código
Si no has programado nunca no te preocupes: formas parte del 99.5% de la humanidad que
nunca ha escrito una línea de código. El propósito de este tutorial es iniciarte en los rudimentos
de la programación del Arduino de una forma sencilla y asequible. Recuerda que, como dijo
Steve Jobs, “todo el mundo debería saber programar porque programar te enseña a pensar”.
1. Un poco de historia
El lenguaje del Arduino está basado en el mítico lenguaje C. Si ya has trabajado en C este
tutorial te parecerá un paseo. Si no, te basta con saber que C es el lenguaje en el que se ha
desarrollado los sistemas operativos UNIX, Linux, y cientos de sistemas, programas y
aplicaciones de ordenador. El lenguaje del Arduino es una versión reducida y mucho más
sencilla de manejar que el lenguaje C. El objetivo de este lenguaje es que puedas programar de
una manera intuitiva concentrándote en lo que quieres hacer más que en la manera de hacerlo.
Trabajar con un Arduino consiste fundamentalmente en interactuar con los diferentes puertos
de entrada y salida del Arduino. A fin de evitar al programador el engorro y la complejidad de
programar estos puertos (ya sean analógicos, digitales o de cualquier otro tipo) el lenguaje de
Arduino usa una serie de librerías (de las que no te tienes que preocupar ya que forman parte
del lenguaje, ya las iremos viendo con detenimiento más adelante). Estas librerías te permiten
programar los pins digitales como puertos de entrada o salida, leer entradas analógicas,
controlar servos o encender y apagar motores de continua. La mayor parte de estas librerías de
base (“core libraries”) forman parte de una macro librería llamada Wiring desarrollada por
Hernando Barragán.
Cada vez que un nuevo puerto de entrada o salida es añadido al Arduino, un nuevo conjunto de
librerías especializadas en ese puerto es suministrada y mantenida por los desarrolladores del
nuevo puerto.
2. Variables
Programar consiste básicamente en decirle a tu Arduino y a los actuadores que este controla
desde sus puertos (o “shields”) lo que tiene que hacer (o esperamos que haga, todos los
programadores saben que estas son cosas frecuentemente diferentes).
Desde un punto de vista práctico, podemos considerar las variables como los cajones de un
escritorio, cada uno tiene una etiqueta describiendo el contenido y dentro de él se encuentra el
valor de la variable (el contenido del cajón). Hay tantos tipos de variables como de datos:
números de todo tipo representados de diferentes maneras (enteros, reales, binarios, decimales,
hexadecimales, etc.), textos (de un solo o varios caracteres o líneas), matrices (arrays),
constantes, etc.
En el lenguaje de Arduino cuando queremos utilizar una variable primero hay que declarar el
tipo de variable de la que se trata (por ejemplo ‘int’ y luego el nombre que le queremos dar a
esa variable (‘testVariable’ en los ejemplos de la tabla anterior).
Podemos dejar la variable sin inicializar (es decir, sin asignarle un valor de partida):
int comienzo;
unsigned char Este tipo de datos es idéntico al tipo byte unsigned char testUnCh = 36;
explicado arriba. Se utiliza para codificar
números de 0 hasta 255. Ocupa 1 byte de
memoria.
Para ver un resumen completo de las Funciones, Variables y Estructura, mira este link:
https://docs.arduino.cc/language-reference/#functions
Ejemplo de uso de variables en un sketch:
Veamos el típico sketch que hace parpadear un LED activado a través de un pin:
int LEDpin = 6; //la variable LEDpin se inicializa a 6,es decir vamos a activar el pin 6
void setup(){
pinMode(LEDpin,OUTPUT);
void loop(){
digitalWrite(LEDpin,HIGH);
delay (1000);
digitalWrite(pinLED,LOW);
delay (1000);
El uso de variables nos permite reutilizar este código para otro pin con tan sólo cambiar la
asignación inicial de la variable LEDpin.
No te asustes si no entiendes todo en este código. De momento basta con que comprendas el
uso de las variables. Todo lo demás lo iremos viendo en detalle más adelante. Nota que cada
instrucción debe de terminarse con un punto y coma (;) de lo contrario el compilador no
entendería que la instrucción ha acabado y nos daría un error.
2.2 Arrays
Un ‘array’ es una colección indexada (como un diccionario) de variables del mismo tipo. En el
caso de un array el índice no es una palabra como en el diccionario, sino simplemente un
número (que corresponde al número de orden de la variable concreta dentro del array). Puedes
declarar un array de la siguiente manera:
int miLista[6];
● Nota importante 1: ten en cuenta que el primer índice de un array es siempre 0 (no 1).
● Nota importante 2: ten especial cuidado de no tratar de acceder datos fuera del array
(por ejemplo en el caso anterior miLista [7] ya que esto nos devolvería datos sin sentido
de la memoria.)
Ejemplos de operaciones con arrays:
int minuevaLista[4] ={1,2,3,4} ;
nuevaVariable = minuevaLista[2];
minuevaLista[0] = 986;
nuevaVariable = minuevaLista[0];
La primera instrucción crea e inicializa el array con 4 valores. La segunda crea una nueva
variable y le asigna el valor de la tercera variable del array que habíamos acabado de crear (el
int 3). La tercera asigna a la primera variable del array el valor entero (int) 986. Por último la
cuarta instrucción asigna a la variable que habíamos creado en la segunda línea el valor
almacenado en la primera variable del array (986).
2.3 Strings
Un string es una cadena (array) de caracteres. Los strings pueden ser declarados de dos
maneras diferentes: como variables tipo ‘char’ o como objetos pertenecientes a una clase más
compleja llamados ´Strings´ de los que hablaremos al final de este capítulo. De momento nos
ceñimos a las cadenas de caracteres definidas como variables ‘char’.
Al declarar una variable de tipo “String” podemos realizar sobre ella una serie de operaciones
complejas de una manera muy sencilla: añadir caracteres a la variable, concatenar Strings,
calcular su longitud, buscar y reemplazar substrings y mucho más.
Algunos ejemplos de cómo se declara y utiliza una variable del tipo ´String´:
String stringUno = "Hola String"; //declarando un String “constante”
String stringUno = String('a'); // convirtiendo una constante tipo char en un String
En el módulo “Funciones Arduino” veremos cómo opera la función String() y como podemos
realizar todas las operaciones con strings que hemos citado anteriormente
2.4 Constantes
Algunas variables no cambian de valor durante la ejecución del sketch. En estos casos podemos
añadir la palabra reservada ‘const’ al comienzo de la declaración de la variable. Esto se utiliza
típicamente para definir números de pin o constantes matemáticas (pi,e,etc…).
const int NoPinLed = 12;
En general, se consideran las constantes enteras se formulan en base 10 (es decir, como
números decimales). Sin embargo, se pueden utilizar notaciones especiales para expresar
números en otras bases.
Ejemplo:
B101 // equivale al decimal 5: ((1 * 2^2) + (0 * 2^1) + 1)
El formato binario (B) solo funciona con bytes (8 bits) entre 0 (B0) y 255 (B11111111). Si necesitas
utilizar un entero (int de 16 bits) en forma binaria, lo puedes hacer con un procedimiento en dos
pasos como el siguiente:
Números octales están representados en base ocho. Sólo los caracteres del 0 al 7 son válidos.
Valores octales se identifican con el prefijo «0» (cero).
Ejemplo:
0101 // Equivale al número decimal 65: ((1 * 8^2) + (0 * 8^1) + 1)
Nota:
Es posible inducir en error al compilador incluyendo por accidente un cero como prefijo de una
constante que el compilador va a interpretar como un número octal.
Hexadecimal (o hex) se refiere a números en base 16. Los caracteres válidos para estos números
son las cifras del 0 al 9 y las letras A a la F; A equivale a 10, B a 1 y así hasta la F (15). Valores
Hex values se identifican con el prefijo «0x». Las letras de la A a la F pueden ser escritas tanto
en mayúsculas como en minúsculas (a-f).
Ejemplo:
0x101 // Equivale al número decimal 257 decimal ((1 * 16^2) + (0 * 16^1) + 1)
Formateadores U y L:
Generalmente, una constant entera es tratada como un int con las consiguientes limitaciones
en cuanto a valores. Para especificar que la constant entera tiene otro tipo hay que añadirle el
sufijo:
● ‘u’ o ‘U’ para que el formato de la constante sea “unsigned”. Ejemplo: 33u
● ‘l’ o ‘L’ para que el formato de la constante sea “long”. Ejemplo: 100000L
● ‘ul’ o ‘UL’ para que el formato de la constante sea “unsigned long”. Ejemplo: 32767ul
Ejemplo:
n = .005;
Las constantes en coma flotante pueden también ser expresadas en una variedad de
notaciones científicas. Ambos caracteres ‘E’ y ‘e’ pueden ser utilizados como indicadores de
exponente.
10.0 10
Por el contrario, aquellas variables definidas dentro de una función se llaman variables locales y
tienen un ámbito (scope) local: sus valores son accesibles solamente desde dentro de la función
en la que han sido declaradas.
void Setup {
pinMode(pinNoLedGlobal, OUTPUT);
void loop {
int pinNoLedLocal =13; //pinNoLedLocal definida como variable local dentro de loop
pinMode(pinNoLedLocal, OUTPUT);
digitalWrite(pinNoLedGlobal, HIGH);
digitalWrite(pinNoLedLocal, LOW);
A medida que tus programas (sketches) crecen en tamaño y complejidad, el uso de variables
locales es aconsejado porque evita confusiones entre variables con el mismo nombre.
En este punto podéis preguntaros que sucede con el ámbito de las variables en el caso de
variables declaradas dentro de funciones anidadas (una función llamada desde otra función,
que a su vez ha sido llamada desde otra función). Lógicamente, él ámbito de aquellas variables
definidas en las funciones exteriores se extiende a las funciones interiores, pero no viceversa.
Ejemplo:
/* RandomWalk
*/
#define randomWalkHighRange 20
int stepsize;
int thisTime;
int total;
void setup()
{ Serial.begin(9600);
void loop()
stepsize = 5;
thisTime = randomWalk(stepsize);
Serial.println(thisTime);
delay(10);
static int place; // variable to store value in random walk - declared static so that it stores
// values in between function calls, but no other functions can change its value
return place;
La razón por la que hacemos esto es que, bajo ciertas condiciones, el valor de una variable
almacenada en los registros internos puede sufrir alteraciones. En Arduino el único sitio en el
que esto puede suceder es en las las partes de código asociadas con interrupciones (interrup
service routines).
Ejemplo
// conmuta un LED cuando el pin de interrupción cambia de estado
void setup()
pinMode(pin, OUTPUT);
}
void loop()
digitalWrite(pin, state);
void blink()
{ state = !state;
Esta es una convención sintáctica que conviene no olvidar si no queremos tener que vérnoslas
con un impenetrable y aparentemente ilógico error del compilador.
La llave de apertura “{” debe de ser complementada siempre por una llave de cierre “}”. El IDE
de Arduino IDE (entorno de desarrollo integrado) incluye una función que nos ayuda a
comprobar este equilibrio entre llaves de apertura y de cierre. Simplemente con seleccionar una
llave de apertura/cierre, el IDE nos indicará donde está la llave de cierre/apertura que “casa”
con la seleccionada (“logical companion”). No te fíes demasiado de esta funcionalidad del IDE
ya que a veces se puede equivocar y señalar una “compañera” equivocada.
Dado que el uso de las llaves es tan variado y extendido en C, se recomienda escribir la llave de
cierre justo después de la de apertura para ir “rellenando” el interior con código y evitar así
olvidarnos de escribir la llave de cierre al final (piensa que probablemente vas a escribir bucles,
funciones, etc. dentro de estas llaves que llevarán a su vez llaves de apertura y cierre. De esta
manera evitar olvidos que te llevarían a errores bastante impenetrables del compilador que
pueden a veces ser difíciles de localizar en un programa grande. Por otra parte, ten en cuenta
que las llaves de apertura y cierre son el marcador sintáctico más importante del lenguaje y su
uso en una posición incorrecta pueden llevarte a una ejecución del programa totalmente
diferente de la que habías previsto.
Un pequeño resumen de los usos más frecuentes de las llaves de apertura y cierre:
En funciones:
void myfunction(datatype argument){
instruccion(es)
En bucles:
instruccion(es)
do
instruccion(es)
instruccion(es)
En instrucciones condicionales:
if (boolean expression)
instruccion(es)
instruccion(es)
else
{instruccion(es)
}
2.6.3 Comentarios dentro de un programa (documentando nuestros programas)
Habrás notado que muy frecuentemente añadimos comentarios al código de un sketch o
programa con el efecto de hacerlo más legible, fácil de entender (y mantener) por otro
programador, en definitiva, mejor documentado.
Generalmente los comentarios se utilizan para informar sobre la manera en que trabaja el
programa (a otros programadores, o recordase a sí mismo cuando tienes que modificar un
programa que escribiste hace tiempo) Los comentarios son ignorados por el compilador y no se
exportan al procesador, así que no toman ningún espacio en el microprocesador ni en la
memoria de ejecución. Dependiendo de su extensión, hay dos maneras diferentes de formular
comentarios:
x = 5; // Esto es un comentario de una sola línea.
/* esto es un comentario de bloque –que ocupa varias líneas –Se suele usar para comentar un bloque de código (en
Poner entre comentarios de bloque (/*…*/) una bloque entero de código que da problemas es
una buena idea de cara a identificar el elemento que da problemas sin tener que borrar (y
reescribir) todo el código del bloque en cuestión.
Sin embargo, esto puede tener algunos efectos secundarios indeseados. Por ejemplo, el valor de
una constante que ha sido declarada con #define será incluido automáticamente en todos los
nombres de constantes o variables que incluyan este nombre como parte de su propio nombre.
Veamos un ejemplo de esto último:
#define PinLed 3
const PinLedRojo = 8;
Ejemplo:
#define ledPin 3
● Nota importante: Recuerda que NO hay punto y coma tras la instrucción #define. Si lo
pones el compilador te agrede con una serie de mensajes ininteligibles mensajes de
error.
● #define PinLed 4; // ¡erróneo!!. ¡No pongas el ; al final!
Del mismo modo incluir un signo igual tras el nombre de la constante conseguirá también irritar
igualmente al compilador.
Puedes encontrar la página principal de referencia para las bibliotecas de C del AVR (el AVR es
una referencia a los microprocesadores de Atmel en los cuales se basa el Arduino) en la
siguiente dirección:
http://www.nongnu.org/avr-libc/user-manual/modules.html
Observa que el #include, del mismo similar que el #define, no admite punto y coma al final.
El ejemplo expuesto a continuación incluye una biblioteca utilizada para poner datos en la
memoria flash en vez de usar la RAM. Esto ahorra espacio en la RAM que puede ser usado como
memoria dinámica y hace que las operaciones de búsqueda en grandes tablas sean mucho más
prácticas y rápidas.
#include <avr/pgmspace.h>
0,0,0,0,0,0,0,0,29810,8968,29762,29762,4500};
= operador de asignación
Ejemplo:
int sensVal; // declara una variable int llamada sensVal
sensVal = analogRead(0); // almacena en sensVal (tras digitalizarlo) el voltaje medido en el pin analógico 0
Esto también significa que la operación puede desbordar si el resultado es más grande que el
que se pueda almacenar en el tipo de datos (por ejemplo, añadiendo 1 a un int con el valor
32.767 da -32.768). Si los operandos son de diversos tipos, el tipo “más grande” se utiliza para el
cálculo.
Si uno de los números (operandos) es del tipo float o de tipo double, el cálculo se realizará en
coma flotante.
Ejemplos
y = y + 3;
x = x - 7;
i = j * 6;
r = r / 5;
Sintaxis
Parametros:
valor1: cualquier variable o constante
valor2: cualquier variable o constante
Notas importantes:
● Ten en cuenta que las operaciones entre constantes de tipo int se almacenan por
defecto en una variable tipo int, de manera que en algunos cálculos entre constantes
(por ejemplo 60* 1000) el resultado puede desbordar la capacidad de almacenamiento
de una variable int y devolver un resultado negativo.
● Elige variables de tamaño suficientemente grande para que puedan almacenar los
resultados de los cálculos por grandes que estos sean.
● Para cálculos con fracciones usa variables en coma flotante (float), pero sea consciente
de sus desventajas: utilización de más memoria, lentitud de cálculo.
● Utilice la función de conversión myFloat para convertir un tipo variable a otro de
manera rápida.
% (módulo):
El operador % (módulo) calcula el resto cuando un número entero es dividido por otro. Es útil
para guardar una variable dentro de una gama particular (por ejemplo, el tamaño de un array).
Su sintaxis es la siguiente:
Ejemplos:
x = 7 % 5; // x contiene 2
x = 9 % 5; // x contiene 4
x = 5 % 5; // x contiene 0
x = 4 % 5; // x contiene 4
Ejemplo en un sketch:
/* modifica posición a posición los valores de un array volviendo a empezar en la primera posición (0) cuando se
haya llegado a la última (9) */
int values[10];
int i = 0;
void setup() {}
void loop()
values[i] = analogRead(0);
}
El operador módulo no funciona con variables tipo float.
++ (incremento) / — (decremento)
Estos operadores se utilizan respectivamente para incrementar o decrementar un variable.
Sintaxis
x++; // incrementa una unidad y retorna el antiguo valor de x
Ejemplos:
x = 2;
Operadores compuestos += , -= , *= , /=
Estos operadores realizan una operación matemática en una variable con otra constante o
variable. Estas notaciones son simplemente una simplificación práctica de una sintaxis más
compleja tal y como se muestra a continuación:
Sintaxis:
x += y; // equivale a la expresión x = x + y;
x -= y; // equivale a la expresión x = x - y;
x *= y; // equivale a la expresión x = x * y;
x /= y; // equivale a la expresión x = x / y;
Parametros:
Ejemplos:
x = 2;
x += 4; // x contiene 6
x -= 3; // x contiene 3
x *= 10; // x contiene 30
x /= 2; // x contiene 15
2.9 Utilidades
Sintaxis:
sizeof(variable)
Parámetros:
Ejemplo:
Este operador es muy práctico para trabajar con arrays (por ejemplo strings de texto) cuando
queremos cambiar el tamaño del array sin alterar otras partes del programa.
El programa del ejemplo imprime carácter a carácter un string de texto. Prueba el siguiente
programa cambiando la frase:
char myStr[] = "esto es una prueba";
int i;
void setup(){
Serial.begin(9600);
void loop() {
Serial.print(i, DEC);
Serial.print(" = ");
Serial.write(myStr[i]);
Serial.println();
}
2.9.2 El operador PROGMEM
Este operador almacena datos en la memoria flash en vez de hacerlo en la RAM del sistema.
Este operador PROGMEM debe de ser usado únicamente con los tipos de datos definidos en la
librería ‘pgmspace.h’. Este operador ordena al compilador que almacene los datos
especificados en la línea del operador en la memoria flash en vez de hacerlo en la RAM del
sistema donde iría normalmente.
El operador PROGMEM forma parte de la librería ‘pgmspace.h’, por lo tanto debes incluir esta
librería al comienzo de tu sketch:
#include <avr/pgmspace.h>
Sintaxis:
● program memory dataType –cualquier tipo de variable de los incluidos abajo (program
memory data types)
● variableName – el nombre del array que quieres almacenar en memoria flash.
Hay que tener en cuenta que, dado que el operador utilidad PROGMEM es un modificador
variable, no hay una regla fija sobre dónde colocarla dentro de la línea de especificaciones. La
experiencia muestra que las dos siguientes ubicaciones de PROGMEN tienden a funcionar bien:
dataType variableName[] PROGMEM = {}; //esta ubicación de PROGMEN funciona.
PROGMEM puede ser usado con una simple variable, sin embargo, debido a la complejidad de
su uso, sólo merece la pena usarla cuando quieres almacenar en memoria flash un bloque
relativamente grande de datos (generalmente un array).
El uso del operador PROGMEM implica dos operaciones bien diferenciadas: almacenamiento en
memoria flash y lectura usando métodos (funciones) específicos definidos en la librería
‘pgmspace.h’ para devolver los datos a la memoria RAM del sistema afín de poder usarlos
dentro del sketch.
Tal y como hemos mencionado antes, es esencial usar los tipos de datos especificados en
pgmspace.h. Por alguna extraña razón, el compilador no admite los tipos de datos ordinarios.
Detallamos debajo la lista completa de variables (“program memory data types”). Parece ser
que la “program memory” no se lleva nada bien con los números en coma flotante, así que
mejor evitarlos.
prog_char - ‘char’ con signo (1 byte) -127 a 128
Ejemplo
Este ejemplo muestra cómo leer y escribir variables tipo unsigned char´ (1 byte) e ‘int’ en
PROGMEM.
#include <avr/pgmspace.h> // abrir librería ‘pgmspace.h’
prog_uchar signMessage[] PROGMEM = {"EL CHORIZO NINJA. EL NUEVO ÉXITO DEL CINE ESPAÑOL"};
char myChar;
displayInt = pgm_read_word_near(charSet + k)
Programar consiste básicamente en decirle a nuestro ordenador (el Arduino) que es lo que
queremos que haga cuando se confronte con una o varias opciones. En otras palabras,
queremos que el Arduino pueda tomar decisiones en base a los datos disponibles en ese
momento (el valor de las variables).
Estas decisiones se toman en base al resultado de uno o varios tests lógicos (“boolean tests” en
inglés). El resultado de estos tests lógicos será true o false (verdadero/falso) y determinará la
decisión elegida.
20 == 21; false
Los tests lógicos tienen lugar dentro de las llamadas “estructuras de control” (“control
statements” en inglés). Estas estructuras son las que deciden el camino seguido por el sketch en
cada momento. Por ejemplo pueden verificar el nivel de tensión (HIGH/LOW) en la entrada de
cierto pin y en base a el valor leído activar (o no) un led conectado a otro pin.
A continuación vamos a explicar las estructuras de control utilizadas en el lenguaje del Arduino.
// realiza una serie de acciones si el test lógico resulta ser verdadero (“true”)
Por ejemplo:
digitalWrite(PinLedRojo, HIGH);
En el caso de arriba el sketch compara el calor de las variables “varA” y “varB”. Si el valor de
varA es inferior al de varB, el código contenido en el bloque del if (entre las llaves “{“ y “}”) será
ejecutado a continuación. En caso contrario, este código queda sin ejecutar y se pasa a la
instrucción siguiente después del bloque if.
El operador “if” puede ser complementado con el operador “else” (sino…). El bloque de acciones
asociadas al “else” son ejecutadas si el test lógico del “if” dió “false” como resultado. Esto
funciona de la siguiente manera:
if (test lógico) {
else {
digitalWrite(PinLedRojo, HIGH);
else {
digitalWrite(PinLedRojo,LOW);
En este caso, si el valor de varA es mayor o igual que el de varB el led conectado al pin
(PinLedRojo) será apagado.
Podemos complicar esta estructura de control un poco más con la inclusión de una condición
dentro del if: el “else if”. El ejemplo siguiente muestra el uso de esta nueva condición:
if (varA < varB) {
digitalWrite(PinLedRojo, HIGH);
digitalWrite(PinLedVerde, HIGH);
else {
digitalWrite(PinLedRojo,LOW);
Como veis el “else if” nos permite afinar el control dentro del “if”. En efecto, con el “else if”
hemos introducido un test lógico dentro del “if”: si el valor de las dos variables (“varA” y “varB”)
es idéntico podemos ejecutar un bloque alternativo de acciones. En este caso activamos el led
verde.
case valor1:
break;
case valor2:
break;
case valor3:
break;
.....
default:
break;
La instrucción “default” es también opcional. Se utiliza cuando queremos que se realicen una
serie de acciones concretas en caso de que ninguno de los casos anteriores haya sido activado.
3.3 Los operadores lógicos booleanos.
Los operadores lógicos booleanos son la sal de la programación. Sin ellos no podríamos realizar
programas de una cierta complejidad. Por esto es importante que entendamos bien cómo
funcionan. Arduino usa los tres operadores lógicos más conocidos: AND, OR y NOT pero los
representa de una manera diferente:
AND
if (varA > varB && varC > varD) {
digitalWrite(pinLedRojo, HIGH);
Si el valor de la variable varA es mayor que el de varB Y el valor de la variable varC es mayor
que el de varD, ejecutar las instrucciones del bloque: encender el led rojo.
OR
if (varA > varB || varC > varD) {
digitalWrite(pinLedVerde, HIGH);
Si el valor de la variable varA es mayor que el de varB O el valor de la variable varC es mayor
que el de varD, ejecutar las instrucciones del bloque: encender el led verde.
NOT
if (!botonPulsado) {
digitalWrite(pinLedAzul, HIGH);
Por otra parte los operadores lógicos AND, OR y XOR, (&, | y ^ en el lenguaje Arduino)
funcionan del siguiente modo a nivel de bit:
0 0 0 0 0
1 0 0 1 1
0 1 0 1 1
1 1 1 1 0
Estos operadores se usan a veces para enmascarar ciertos bits en un número. Por ejemplo para
extraer ciertos bits de un número. Si quieres extraer los dos bits de menor peso de un número
puedes hacerlo así:
int x = 42; // En binario esto es 101010
Puedes también activar (set) o borrar (clear) uno o varios bits en un número usando el operador
OR. El siguiente ejemplo activa el quinto bit de x con independencia del valor que este bit
tuviera antes.
int x = 42; // En binario esto es 101010
“<<“ y “>>“
<< y >> son los operadores de desplazamiento binario (bit shift operators) que te permiten
desplazar los bits de una variable a una cierta posición antes de trabajar con ellos. El primer
operador desplaza los bits hacia la izquierda y el segundo a la derecha como muestran los
siguientes ejemplos.
int x = 42; // En binario esto es 101010
Hay que evitar las operaciones de desplazamiento con números con signo ya que pueden
producir resultados impredecibles. Por otra parte, no hay que confundir los operadores.
Mucho cuidado con confundir los operadores lógicos booleanos (&&, ||) con los binarios (&, |).
Los operadores booleanos no trabajan a nivel binario (sólo a nivel ‘true’ o ‘false’).
Arduino maneja tres estructuras en bucle diferentes: “for”, “while” y “do while”.
El ejemplo siguiente muestra en típico bucle “for” que imprime el valor del contador i de 0 hasta
99 hasta que se apaga el Arduino.
void setup()
Serial.begin(9600);
void loop {
Serial.println(i);
El código en el interior del bucle se ejecuta una vez tras otra hasta que alcanza el valor 100. En
ese punto el bucle finaliza y recomienza volviendo a poner la variable i a 0.
La primera línea del bucle es la instrucción “for”. Esta instrucción tiene siempre tres partes:
inicialización, test y el incremento o decremento de la variable de control o contador.
La inicialización sólo sucede una vez al comienzo del bucle. El test se realiza cada vez que el
bucle se ejecuta. Si el resultado del test es “verdadero” (true), el bloque de código se ejecuta y
el valor del contador se incrementa (++) o decrementa (–) tal como está especificado en la
tercera parte del “for”. El bloque de código (o rutina) continuará ejecutándose hasta que el
resultado del test sea “false” (es decir, cuando el contador i haya alcanzado el valor 100).
// do statements
El código del bucle “for” que hemos explicado antes se puede reescribir como un bucle “while”
de la siguiente manera:
void setup() {
Serial.begin(9600);
void loop(){
int i = 0;
Serial.println(i);
i++;
Puedes considerar un bucle “for” como un caso particular del bucle “while”. El bucle ”while” se
usa cuando no estamos seguros de cuántas veces se va a ejecutar el bucle. El bucle “while” se
suele usar para testear la entrada de sensores o botones.
sensorValue = analogRead(analogPin);
Cuando programes bucles “while” asegúrate de que el código del bucle cambia el valor de la
variable test (sensorValue). Si no te arriesgas a que el bucle se ejecute indefinidamente (infinite
loop).
} while (expresión);
Al igual que en el caso anterior, el bloque de código comprendido entre las llaves del bucle se
ejecutará hasta que la expresión resulte ser falsa.
Veamos un ejemplo:
for (x = 0; x < 255; x ++)
{
continue;
}
digitalWrite(PWMpin, x);
delay(50);
En este ejemplo vemos cómo conseguir que se evite la ejecución de la función digitalWrite()
cuando x esté comprendido entre 40 y 120.
La instrucción goto permite transferir el control a un punto especificado mediante una etiqueta
(por ejemplo fuera de la maraña de bucles en la nos hemos metido). Esta es la sintaxis del goto:
etiqueta:
Normalmente se desaconseja el uso del goto en programación ya que el uso del goto puede
llevarnos a la creación de programas con un flujo de ejecución indefinido, que puede dificultar
mucho la detección y eliminación errores (debugging).
}
}
escape:
En este ejemplo podemos salir “bruscamente” de ese grupo de tres bucles anidados en caso de
que la lectura de un pin analógico sea mayor que 250. En ese caso, el flujo de ejecución sería
transferido fuera de los tres bucles a la etiqueta “escape”.
5. Funciones
Hemos visto ya de pasada las dos funciones o rutinas principales de un sketch: setup() y loop().
Ambas se declaran de tipo void, lo cual significa que no retornan ningún valor. Arduino
simplifica muchas tareas proporcionando funciones que permiten controlar entradas y salidas
tanto analógicas como digitales, funciones matemáticas y trigonométricas, temporales, etc.
Tu puedes también escribir tus propias funciones cuando necesites programar tareas o cálculos
repetitivos que vas a usar varias veces en tu sketch (o en futuros sketches). La primera cosa que
tienes que hacer es declarar el tipo de función (int, si retorna un entero, float si es un número en
coma flotante, etc…).
Te recomendamos dar a la función un nombre que tenga que ver con lo que hace para que te
resulte fácil recordarla (o identificarla cuando la veas escrita en uno de tus sketches). A
continuación del nombre de la función (y sin dejar espacios) van los parámetros de la función
entre paréntesis. Y ya sólo queda lo más difícil: escribir el código de la función entre llaves:
type functionName(parameters){
Recuerda que si la función no retorna un valor hay que definirla como de tipo “void” (vacío).
Para devolver el control a sketch la función usa la instrucción return seguida por el nombre de la
variable que se quiere devolver y luego un punto y coma (;).
La siguiente rutina muestra una función que convierte una temperatura expresada en grados
Fahrenheit a Celsius (también conocidos como grados centígrados):
float calcTemp(float fahrenheit){
float celsius;
return celsius;
La function se declara de tipo float y se le pasa un parámetro (también de tipo float) que
representa los grados Fahrenheit medidos por un sensor. Dentro de la función se declara la
variable Celsius (de tipo float) que va a contener la medida de la temperatura pasada en
Fahrenheit tras su conversión a Celsius (imagino que la fórmula de conversión te resulta
familiar). La instrucción return devuelve el valor en Celsius de la temperatura pasada en
Fahrenheit. Esta función puede ser usada como parte de un sistema para registrar las
temperaturas captadas por un sensor a lo largo del tiempo. Como puedes ver, el uso de
funciones es la manera ideal de simplificar tu código y abordar tareas repetitivas.