Haciendo mía la teoría de victorjam de que los interruptores están cerrados cuando la entrada está LOW
y nombrando los pines tal como él propone. Tenemos la «opción natural»:
if (digitalRead(sw1) == LOW) {
digitalWrite(led1, HIGH);
}
else {
digitalWrite(led1, LOW);
}
if (digitalRead(sw2) != LOW) {
digitalWrite(led2, HIGH);
}
else {
digitalWrite(led2, LOW);
}
if ((digitalRead(sw1) == LOW) && (digitalRead(sw2) == LOW)) {
digitalWrite(led3, HIGH);
}
else {
digitalWrite(led3, LOW);
}
Hay quien prefiere ahorrarse las llaves cuando sólo hay una sentencia en cada bloque. Yo personalmente prefiero poner las llaves por si añado posteriormente alguna línea. No sería la primera ni la última vez que añado una línea pensando que está dentro del if
o del else
y quedar fuera la añadida o la que estaba; por no estar dentro de un bloque delimitado por las llaves. Así que para quien le guste, esto es lo mismo que lo anterior, pero sin tanta llave:
if (digitalRead(sw1) == LOW)
digitalWrite(led1, HIGH);
else
digitalWrite(led1, LOW);
if (digitalRead(sw2) != LOW)
digitalWrite(led2, HIGH);
else
digitalWrite(led2, LOW);
if ((digitalRead(sw1) == LOW) && (digitalRead(sw2) == LOW))
digitalWrite(led3, HIGH);
else
digitalWrite(led3, LOW);
En este caso, como lo único que va a decidir cada una de las condiciones es el valor que queremos para el LED, podemos usar el operador ternario. Si no se sabe cómo funciona el operador ternario, no hay más que buscar en Google “operador ternario c”.
digitalWrite(led1, (digitalRead(sw1) == LOW) ? HIGH : LOW);
digitalWrite(led2, (digitalRead(sw2) != LOW) ? HIGH : LOW);
digitalWrite(led3, ((digitalRead(sw1) == LOW) && (digitalRead(sw2) == LOW)) ? HIGH : LOW);
Pero curiosamente, con el operador ternario lo único que se hace es pasar a digitalWrite()
como segundo parámetro HIGH
o LOW
; dependiendo de si la evaluación de las comparaciones dan como resultado un «verdadero» o «falso». HIGH
en el caso de ser «verdadero» y LOW
en caso de ser «falso». Tengamos en cuenta que en C/C++ un valor «distinto a cero» se considera «verdadero», mientras que el valor «cero» es considerado «falso». Además, la función digitalWrite()
usa el segundo parámetro para poner la salida a «nivel bajo» cuando se le pasa un valor igual a «cero» (LOW
está definido con el valor 0
). Si este parámetro tiene un valor distinto de «cero», digitalWrite()
pone la salida a «nivel alto» (HIGH
está definido con el valor 1
), no importa si el valor es 1
o 69
, sólo importa que sea «distinto de cero». Pues aprovechando eso, se puede utilizar directamente el valor de la comparación, sin necesidad del operador ternario. Les recuerdo que es «cero» si la condición es «falsa» y «distinto de cero» si la condición es «verdadera». Con lo que podemos poner así:
digitalWrite(led1, digitalRead(sw1) == LOW);
digitalWrite(led2, digitalRead(sw2) != LOW);
digitalWrite(led3, (digitalRead(sw1) == LOW) && (digitalRead(sw2) == LOW));
Ya llegados hasta aquí, podemos aprovechar que la función digitalRead()
devuelve «cero» cuando la entrada está a «nivel bajo» (recordemos que LOW
está definido con el valor 0
) y que devuelve un valor «distinto de cero» si la entrada está a «nivel alto» (HIGH
está definido con el valor 1
y obviamente es un valor «distinto de cero»). Así que es por eso que podemos hacerlo así:
digitalWrite(led1, !digitalRead(sw1));
digitalWrite(led2, digitalRead(sw2));
digitalWrite(led3, !digitalRead(sw1) && !digitalRead(sw2));
La exclamación !
invierte el valor «verdadero» o «falso». De tal forma que si lo que queremos es convertir el valor LOW
en un «verdadero» y el HIGH
en un «falso», nos basta con «negar el valor» de lo que tengamos anteponiendo el operador exclamación !
y listo.
Bueno, después de presentar unas cuantas formas aparentemente diferentes de hacer lo mismo, viene el momento de decir que si bien todas son válidas y que todas estas formas de implementarlo nos las podemos encontrar en cualquier momento en cualquier programa hecho por otro, yo recomiendo hacerlo de la siguiente manera (pongo todo el código):
// Definición de las constantes de los pines. Modificar según necesidad
#define PIN_LED_1 13
#define PIN_LED_2 12
#define PIN_LED_3 11
#define PIN_INTERRUPTOR_1 A0
#define PIN_INTERRUPTOR_2 A1
// Definición de las constantes de los valores. Modificar según necesidad
#define INTERRUPTOR_CERRADO LOW
#define ENCENDER_LED HIGH
// Definición de constantes que se autodefinen en función de lo definido anteriormente. No hay que modificarlas nunca
#define INTERRUPTOR_ABIERTO (!INTERRUPTOR_CERRADO)
#define APAGAR_LED (!ENCENDER_LED)
void setup () {
pinMode(PIN_INTERRUPTOR_1, INPUT_PULLUP);
pinMode(PIN_INTERRUPTOR_2, INPUT_PULLUP);
pinMode(PIN_LED_1,OUTPUT);
pinMode(PIN_LED_2,OUTPUT);
pinMode(PIN_LED_3,OUTPUT);
}
void loop () {
// Guardamos la lectura de los pines en sus correspondientes variables ya que el valor lo vamos a usar más de una vez y además hace más legible el código
bool interuptor1 = digitalRead(PIN_INTERRUPTOR_1);
bool interuptor2 = digitalRead(PIN_INTERRUPTOR_2);
// Las tres líneas que contien la lógica del programa no necesitan comentario alguno, se explican ellas solas gracias a los nombres de las variables y constantes. Así como el uso del operador ternario
digitalWrite(PIN_LED_1, (interuptor1 == INTERRUPTOR_CERRADO) ? ENCENDER_LED : APAGAR_LED);
digitalWrite(PIN_LED_2, (interuptor2 == INTERRUPTOR_ABIERTO) ? ENCENDER_LED : APAGAR_LED);
digitalWrite(PIN_LED_3, ((interuptor1 == INTERRUPTOR_CERRADO) && (interuptor2 == INTERRUPTOR_CERRADO)) ? ENCENDER_LED : APAGAR_LED);
}
No es el más compacto ni el más extenso, pero sí que creo que es el más fácil de leer y entender, eso sí, estando habituado al operador ternario. Pero el operador ternario, si no se abusa de él, una vez que se le pilla el truco, hace que el código sea más legible y cómodo de seguir en muchas ocasiones. Si el operador ternario lo usamos para decidir entre un simple valor u otro según una condición sencilla, sin buscar ofuscar el código, nos puede hacer mucho más fácil seguir el código y entenderlo. También es importante el uso de variables y constantes con nombres significativos y nada exotéricos. Observen cada uno de los ejemplos y miren a ver si en alguno de ellos se puede saber o intuir qué es lo que hace sin necesidad de comentarios o explicaciones adicionales. Tan sólo con el código que se va a compilar.
Si nos fijamos en las tres líneas de la discordia, no sólo se ve a simple vista qué es lo que hacen y el criterio que sigue (si se sabe «leer» las condiciones y el operador ternario) sino que la definición y uso de las constantes nos podría alterar el comportamiento del programa apenas tocando unas pocas líneas y ninguna de ellas tiene por qué ser una de esas tres líneas. Sin necesidad de estar buscando en todo el código para modificar la lógica del programa.
Por ejemplo, si nos dicen que el circuito se ha cambiado de tal manera que ahora se lee un HIGH
cuando los interruptores están cerrados. En mi propuesta sólo hay que cambiar la línea:
#define INTERRUPTOR_CERRADO HIGH
Sin necesidad de cambiar las líneas que controlan la lógica del programa. Mientras que si lo hubiésemos hecho con otra de las soluciones, tendríamos que cambiar en este caso tres zonas del código. Pero porque sólo son tres y no trescientas. A continuación pongo las tres líneas de uno de los ejemplos anteriores con el cambio propuesto. ¿Se tiene claro lo que hace ahora?
digitalWrite(led1, digitalRead(sw1));
digitalWrite(led2, !digitalRead(sw2));
digitalWrite(led3, digitalRead(sw1) && digitalRead(sw2));
Pues se supone que lo mismo que antes pero con la lógica de los interruptores cambiada.
Deberes para casa: ¿qué habría que cambiar en cada una de las formas de resolver el problema, si además del cambio de la lógica de los interruptores también cambia la lógica de los LED y ahora hay que poner la salida a «nivel bajo» para encender los LED?