Tomando decisiones: las estructuras de control

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 a el 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.

Ejemplos de tests lógicos:

60 < 120; true
30 > 28; true
45<= 32; false
20 == 21; false

La siguiente tabla muestra los operadores lógicos y su descripción:

Operador Descripción Operador Descripción
> Mayor que < Menor que
>= Mayor o igual que <= Menor o igual que
== Igual que != Diferente

Es importante notar que == no es lo mismo que =. == es el operador lógico de igualdad y devuelve el valor true o false mientras que = se usa para asignar valores a variables o arrays.

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.

 

3.1       If, else, else if

La primera estructura de control que vamos a considerar es el operador “if” (“si” en español). Este operador verifica simplemente si un test lógico es cierto o falso (es decir, si devuelve el valor true o false) y en función de esto realiza (o no) una serie de acciones.

En términos prácticos el “if” actúa de la siguiente manera:

if (test lógico) {
   // realiza una serie de acciones si el test lógico resulta ser verdadero (“true”)
} 

Por ejemplo:

if (varA < varB) {
   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 sinejecutar 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 funcionaría de la siguiente manera:

if (test lógico) {
   // realiza una serie de acciones si el test lógico devuelve verdadero (“true”)
}
else {
    // realiza una serie de acciones si el test lógico devuelve falso (“true”)
}

Veamos el siguiente ejemplo práctico:

if (varA < varB) {
    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);
}
else if (varA == varB) {
    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.

Es importante notar que solamente un bloque de acciones es ejecutado dentro de un “if”. De hecho podemos incluir varios “else if” dentro de un “if” para realizar un control mucho más fino dentro del sketch. Sin embargo, esto no sería una solución muy elegante (ni de fácil lectura dentro del sketch). Por eso, para este tipo de tomas de decisión donde nos enfrentamos a muchas opciones diferentes, la estructura de control ideal es el “switch case” que veremos a continuación.

 

3.2 Eligiendo entre múltiples opciones: Switch case

La estrutura “switch case” es la opción ideal cuando tenemos que elegir entre un número más o menos elevado de opciones. De algún modo es como el panel de botones en un ascensor dentro de unos grandes almacenes (si pulsas 1 vas a la planta deportes, 2 a la de moda, 3 a la de electrodomésticos, etc…).

Esta estructura funciona de la siguiente manera:

switch (variable){
   case valor1:
   // instrucciones ejecutadas cuando el valor de la variable == valor1
   break;
case valor2:
   // instrucciones ejecutadas cuando el valor de la variable == valor2
   break;
case valor3:
   // instrucciones ejecutadas cuando el valor de la variable == valor1
   break;
.....

default:
   // instrucciones ejecutadas en cualquier otro caso 
   break;
}

El “break” al final de cada bloque de instrucciones dentro de cada “case” es opcional. De no ponerlo, tras haber ejecutado ese bloque se seguirían analizando los demás casos hasta el final. Por tanto, es conveniente incluir la instrucción “break” al final de cada bloque si queremos impedir que el sketch se siga ejecutando dentro del “switch” (esto hará nuestro sketch más eficiente).

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 se representa como &&
  • OR se representa como ||
  • NOT como !
 

Veamos cómo funcionan:

 

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

Si el botón no está pulsado (NOT botonPulsado == true) encender el led azul.

 

3.4 Operadores lógicos binarios

A medida que progreses en tus sketches para Arduino verás que necesitas acceder al contenido de algunas variables a nivel binario (bit a bit). En algunos casos tendrás que leer información de sensores que sólo se puede encontrar a este  nivel, en otros se tratará de programar un dispositivo o una acción.

La operación lógica más sencilla a nivel de bit es el NOT. Como sin duda sabes, un NOT aplicado sobre un bit simplemente cambia su polaridad (es decir, su valor pasa de ser el contrario del que tenía antes: convierte un 0 en un 1 y viceversa).

Un ejemplillo: (recuerda que en Arduino el NOT a nivel binario se escribe ~)

int x = 42; // En binario esto es 101010
int y = ~x; // y == 010101 

Por otra parte los operadores lógicos AND, OR y  XOR,  (&, | y ^ en el lenguaje Arduino) funcionan del siguiente modo a nivel de bit:

a b a AND b (a&b) a OR b (a | b) a XOR b(a^b)
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 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
int y = x & 0x03; // y == 2 == B10

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
int y = x | 0x10; // y == 58 == B111010

“<<“ 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
int y = x << 1; // y == 84 == B1010100 
int z = x >> 2; // z == 10 == B1010

La segunda instrucción (int y = x << 1;) copia el valor de x en y para luego desplazar sus bits (los de y) una (1) posición hacia la izquierda rellenado con un cero a la derecha. La tercera instrucción copia el valor de x en z para luego desplazar sus bits (los de z) dos (2) posiciones a la derecha rellenando con ceros a la izquierda.

Ha de notarse que la variable x original permanece inalterada. Estos shifts (desplazamientos) tienen lugar sobre las variables y, z.

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’).