13- Arduino, Condiciones y comparaciones

Comparaciones.

En el lenguaje C ++ (y casi en todos los lenguajes) existe un concepto como boolean, que toma dos valores: verdadero y falso, true false, 1 y 0. Como tipo de datos para trabajar con valores lógicos, tenemos boolean (sinónimo – bool), que puede tomar valores 0 (falso) o 1 (cierto). El mismo valor exacto devuelve el resultado de comparar dos números o variables, para la comparación tenemos varios operadores de comparación:

  • == identidad (a == b), a es idéntico a b.
  • ! = desigualdad (a! = b) a es distinto de b, negación.
  • = igualdad (a = b) a es igual a b
  • > = mayor o igual que (a> = b)
  • <= menor o igual (a <= b)
  • > mayor (a> b)
  • < menor (a <b)

En los ejemplos abstractos anteriores con a y b sucede lo siguiente: el paréntesis “devuelve” un valor booleano, que es el resultado de comparar números. Por ejemplo, si tenemos a = 10 y b = 20 entonces ( a > b ) devolverá el valor falso. Por ejemplo ( a! = b ) devolverá cierto ya que a realmente no es igual a b. Para vincular varios valores lógicos, se utilizan operadores lógicos:

  • NO lógico, negación. Operador analógico no
  • && Y lógico. Operador analógico y
  • || OR lógico. Operador analógico o
byte a = 10, b = 20;
(a > b);  // false
(a != b); // true
boolean flag = true;
flag;   // true
!flag;  // false 
!(a > b); // true
//flagA = true, flagB = false;
(flagA && flagB); // false
//flagA = true, flagB = true;
(flagA and flagB);  // true
//flagA = true, flagB = false;
(flagA || flagB); // true
//flagA = false, flagB = false;
(flagA or flagB);  // false

Comparaciones en Coma Flotante.

Comparación de float, los números no son tan simples debido a la peculiaridad del tipo «punto flotante» – a veces los cálculos se realizan con un pequeño error, por lo que la comparación puede no funcionar correctamente. 

float val1 = 0.1;
// val1 == 0.100000000
float val2 = 1.1 - 1.0;
// val2 == 0.100000023 !!!
// aparentemente val1 == val2
// pero la comparación devolverá falso
if (val1 == val2);  // false

Tenga cuidado al comparar números en coma flotante, especialmente con operaciones estrictas == , >= y <=: ¡el resultado puede ser incorrecto e ilógico!


El «if» condicional.

Operador condicional Si (Inglés «if«) le permite bifurcar la ejecución del programa dependiendo de valores lógicos, es decir resultados de los operadores de comparación, que consideramos anteriormente, así como directamente de las variables booleanas.

if ( valor_lógico ) {  
 // ejecutado si valor_lógico = verdadero
}

Operador else (Inglés «lo contrario») funciona en conjunto con el operador if y permite tomar medidas en caso de incumplimiento del if:

if ( valor_lógico ) {  
 // ejecutado si valor_lógico - verdadero
} else {  
 // ejecutado si valor_lógico - falso
}

También hay una tercera construcción que le permite ramificar el código aún más, se llama –else if– otra opción:

if ( valor lógico 1 ) {  
 // ejecutado si log. valor 1 - verdadero
} else if ( valor lógico 2 ) {    
 // ejecutado si log. valor 2 - verdadero
} else {  
  // hecho de manera diferente 
}

Veamos todos estos operadores en acción en un gran ejemplo:

// al realizar una acción 
// dentro de una condición, {} son opcionales
if ( a > b ) c = 10; // si a es mayor que b, entonces c = 10 
else c = 20;       // si no, entonces c = 20
// en lugar de comparar, puedes usar el log. variable
boolean myFlag, myFlag2;
// si myFlag es verdadero, entonces asigna 10 a c
if ( myFlag ) c = 10; 
// condiciones complejas
// si ambos indicadores son verdaderos, entonces establece c en 10
if ( myflag && myFlag2 ) c = 10; 
// al realizar dos o más acciones
// ¡dentro de la condición, {} son obligatorios!
if ( myFlag ) {  
  c = 10;
  b = c;
} else {  
  c = 20;
  b = a;
}
byte buttonState;
if ( buttonState == 1 ) a = 10;      // si buttonState es 1 
else if ( buttonState == 2 ) a = 20; // si no, buttonState 2  
más a = 30;           

Así es como funciona el operador condicional if, lo que le permite administrar el programa y crear acciones ramificadas en función de diferentes condiciones. Observe el último bloque en el ejemplo anterior, donde usamos else if para seleccionar una acción en función del valor de la misma variable . Hay un operador switch, para hacer el código más hermoso. Hablemos de ello un poco más abajo.

Característica Booleana

En la lección sobre tipos de datos, mencioné que boolean adquiere el significado cierto, si le asigna un número distinto de cero, es decir, el operador if, puede alimentar cualquier número y devolverá verdadero de todos modos, excepto en cero. Esto es útil en algunos casos, pero también puede dar lugar a errores que son difíciles de detectar. if ( 50 ) {} – Si, el código se ejecutará para esta condición.

Orden de condiciones

El orden de las condiciones juega un papel muy importante a la hora de optimizar su código e intentar hacerlo más rápido en algunos casos. El punto es muy simple: las expresiones / valores booleanos se verifican de izquierda a derecha, y si al menos un valor hace que toda la expresión sea incorrecta (false), se detiene la verificación adicional de las condiciones. Por ejemplo, si en la expresión:

if ( a && b && c ) {  
  // hacer algo
}

Si al inicio a tiene el valor de false, la comprobación de otras expresiones (b y c) ya no se ejecutará. Cuándo puede ser importante: por ejemplo, hay algún tipo de bandera y expresión que se evalúa directamente en la condición y se verifica de inmediato. En este caso, si se omite la bandera, el microcontrolador no perderá tiempo en cálculos innecesarios. Por ejemplo:

if ( flag && analogRead ( 0 ) > 500 ) {   
  // hacer algo
}

Si se omite la bandera, el microcontrolador no gastará 100 μs adicionales en trabajar con el ADC e inmediatamente ignorará el resto de las expresiones lógicas. Esto es, por supuesto, irrelevante ahora, pero a veces incluso 100 μs en un bucle es decisivo, solo recuerde que el orden de las condiciones es importante.


Operador ternario.

Signo de interrogación -?-, u operador ternario, es un análogo más corto para escribir la construcción:

¿condición? expresión1: expresión2

Funciona así: la condición se evalúa, si es verdadera, entonces toda la expresión devuelve el valor de 1, y si es falsa, toda la acción devuelve el valor de la expresión, 2. Ejemplo:

byte a, b;
a = 10;
// si a <9, b obtiene el valor 200
// de lo contrario b obtiene 100
b = ( a > 9 ) ? 100: 200;

A modo de comparación, aquí hay una construcción similar con if-else.

a = 10;
if ( a > 9 ) b = 100; 
else if b = 200;

Otra opción con cálculo:

byte a = 10, b = 5;
// suma el resultado de la expresión a * 10, si a> 10
// de lo contrario, sume + 10
b + = ( a > 9 ) ? ( a * 10 ) : ( a + 10 ) ;

Del mismo modo, puede utilizar el operador ? para enviar datos y texto al puerto serie (más sobre eso más adelante):

Serial.println((a > 9) "mayor que 9" : "menor que 9" ) ;

¿Es posible hacer con el operador ? construcciones más complejas, como else if

void setup() {
  Serial.begin(9600);
  // el código muestra el "tamaño" de la variable de valor
  
 	byte value = 5;
  
  // construcción if-else
 if (value > 19) Serial.println("> 19");
  else if (value > 9) Serial.println("> 9");
  else Serial.println("< 9");
  // con operador -?-
  Serial.println(( (value > 9) ? ( (value > 19) ? "> 19" : "> 9" ) : "< 9" ));
}

Operador de selección.

El operador de selección switch permite crear una construcción que ramifica las acciones según el valor de una variable. La sintaxis es:

switch (valor) {
  case 0:
     // ejecutar si valor == 0
    break;
  case 1:
    // ejecutar si valor == 1
    break;
  case 2:
  case 3:
  case 4:// ejecutar si valor == 2, 3 o 4// выполнить, если значение == 2, 3 или 4
    break;
  default:
    // ejecutar si el valor no coincide con ninguno de los casos
    break;
}

El operador default no es necesario. Eel operador break es obligatorio, de lo contrario, la comparación irá más allá, como se muestra para los casos 2, 3 y 4.

Usando operadores condicionales y de selección, se construye la lógica del programa. El operador condicional lo ayudará a comparar el valor del sensor y decidir qué hacer a continuación. El operador de selección se adaptará perfectamente a los modos cambiantes del programa o al sondear los botones presionados en el control remoto IR.


NOTICIA IMPORTANTE

Debe tener mucho cuidado al trabajar con el operador switch porque el código dentro de las llaves switch () { }, es un bloque de código para todos los casos. En consecuencia, los case son solo atajos para la transición entre secciones de este bloque. Por qué es tan importante: todos los case están en el mismo ámbito, es decir, dentro del switch. Las variables locales con el mismo nombre no se pueden declarar:

switch (mode) {
  case 0:
    long val = 100;
    break;
  case 1:
    long val = 100; // resultará en un error en la variable
    break;
  case 2:
    break;
}

Además, no se recomienda crear variables locales dentro de los case, ya que esto puede romper el código.

switch ( modo ) {  
  case 0:
    break ;
  case 1:
    // variable local
    long val = 100;
    break ;
  case 2:
    // con modo == 2, el resultado saldrá
    // ¡solo si eliminamos la variable local de arriba!
     Serial.println("hola");
    break ;
}

Directivas condicionales #if #else.

Además de la directiva #define que le dice al preprocesador que reemplace el juego de caracteres con otro juego de caracteres, también hay directivas condicionales que le permiten hacer la llamada de  compilación condicional: teniendo la misma lógica que if-else, estas construcciones le permiten hacer alguna elección antes de compilar el código en sí. Un excelente ejemplo es el «núcleo» del propio Arduino: la mayoría de las funciones están escritas con las especificaciones de cada procesador, y antes de compilar el código, se selecciona el que corresponde al microcontrolador seleccionado actualmente del conjunto de opciones para implementar la función. En pocas palabras, la compilación condicional permite, por condiciones, incluir o excluir un código particular de la compilación principal, es decir, primero, el preprocesador analiza el código, algo se incluye en él, algo no, y luego se realiza la compilación.

Además, por ejemplo, no podemos declarar ninguna constante o macro mediante #define más de una vez, esto resultará en un error. La compilación condicional permite la ramificación siempre que sea posible. Para la compilación condicional, disponemos de las directivas #if, #elif, #else, #endif #ifdef, #ifndef.

  • #if – analógico Si en construcción lógica
  • #elif – otra cosa si  en construcción lógica
  • #else- más en construcción lógica
  • #endif – una directiva que acaba con la construcción condicional
  • #ifdef – si está «definido»
  • #ifndef – si «no está definido»
  • defined – este operador regresa true si la palabra especificada se «define» mediante #define, y false– si no. Se utiliza para construcciones de compilación condicional.

Veamos cómo usarlos con un ejemplo:

#define TEST 1 // define TEST como 1
#if (TEST == 1) // si TEST 1
#define VALUE 10 // define VALUE como 10
#elif (TEST == 0) // TEST 0
#define VALUE 20 // define VALUE como 20
#else // si no
#define VALUE 30 // define VALUE como 30
#endif // fin de la construccion

Por tanto, tenemos una constante definida VALUE que depende de la «configuración» TEST. El diseño le permite incluir o excluir fragmentos de código antes de la compilación, por ejemplo, un fragmento sobre depuración:

#define DEBUG 1
void setup() {
#if (DEBUG == 1)
  Serial.begin(9600);
  Serial.println("Hello!");
#endif
}

Por lo tanto, estableciendo DEBUG puede incluir o excluir cualquier fragmento de código.

El preprocesador tiene dos directivas más: #ifdef y #ifndef, le permiten incluir o excluir secciones de código por condición: #ifdef – ¿está definido? #ifndef- ¿no está definido? Definido o no definido, por supuesto, estamos hablando de #define.

#define TEST // define TEST 
#ifdef TEST // si TEST está definido 
#define VALUE 10 // define VALUE como 10 
#else // si No. 
#define VALUE 20 // define VALUE como 20 
#endif // fin de la condición

Es en la compilación condicional donde se construye toda la versatilidad de las bibliotecas para Arduino, porque cuando seleccionas una placa, se «crea» automáticamente un valor predeterminado para el nombre del microcontrolador, se ven así:

  • __AVR_ATmega32U4__
  • __AVR_ATmega2560__
  • __AVR_ATmega328P__
  • Y más de este estilo

Esto le permite crear código genérico usando la construcción con #ifdef o #if defined ():

#if defined(__AVR_ATmega32U4__)
// código para Leonardo / Micro / Pro Micro
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// código para Mega (1280 o 2560)
#elif defined(__AVR_ATmega328P__)
// código para UNO / Nano / Pro Mini
#endif

Así, un código personal estará disponible para microcontroladores (placas Arduino) de diferentes modelos, que será transferido al compilador cuando esta placa sea seleccionada de la lista de placas.


Deja un comentario