La explicación es un poco compleja. Hay que tener en cuenta varias cosas.
El tipo del segundo parámetro de la función digitalWrtite() es un entero sin signo de 8 bits. Así que recibirá un valor entre 0 y 255. La función digitalWrtite() pone el pin a nivel bajo si el valor del segundo parámetro es cero, en caso contrario lo pone a nivel alto.
Si ejecutamos el siguiente programa:
void setup () {
pinMode(LED_BUILTIN, OUTPUT); // LED_BUILTIN es una constante definida en Arduno, para el pin del LED de la placa
}
void loop() {
digitalWrite(LED_BUILTIN, 1); // Encendemos el LED de la placa del Arduino
delay(500);
digitalWrite(LED_BUILTIN, 0); // Apagamos el LED de la placa del Arduino
delay(500);
}
Podremos ver cómo parpadea el led de la placa del Arduino. El valor 1 lo enciende y el 0 lo apaga.
¿Qué pasa si cambiamos el 1 por un 128? Si lo prueban, verán que continúa parpadeando.
¿Y si el 1 lo cambiamos por 256? Si lo prueban, verán que el LED no parpadea.
¿Y con el 257? El LED parpadea.
¿Qué está pasando? ¿Porqué con los valores 1, 128 y 257 el LED parpadea, pero con los valores 256, 512 y 768; entre otros; no parpadea? Simplemente porque con esos valore no se pone el pin a nivel alto.
¿Pero no dije al principio que cualquier valor distinto a cero pondría a nivel alto la salida? Sí, lo dije, pero también dije que el parámetro es un entero sin signo de 8 bits. Y que eso significaba que el valor iba entre 0 y 255. Bien, cuando se le pasa un valor mayor que 255, en realidad estamos tratando de pasarle un valor que necesita más de 8 bits para representarlo, así que hay una parte que “sobra” porque sólo disponemos de 8 bits. Bien, en ese caso se queda con el valor de los 8 bits menos significativos, el resto “desaparecen”. Los 8 bits menos significativos del valor 256 están todos a cero, con lo que la función recibe un cero. De hecho la función recibirá un cero si le ponemos como segundo parámetro un valor múltiplo de 256. Por eso los valores 256, 512 y 768 no encienden el LED. Y ese es “el problema” que se quiere resolver con la doble exclamación !!
Una vez entendido el problema, pasemos a explicar la solución. Debemos saber que en C/C++ el tipo boolean se representa en 8 bits y que adquiere únicamente los valores 0 para falso y 1 para verdadero. Así que si le asignamos el valor 256 a una variable de tipo boolean, esta variable tendrá el valor 1. No el valor del bit menos significativo de 256, sino 1, porque 256 es distinto de cero.
El operador ! retorna negado el valor lógico de lo que tenga a su derecha y retorna un valor de tipo boolean. Así que el valor de !256 es 0 porque el valor lógico de 256 es “verdadero” por ser distinto de cero y la negación de “verdadero” es “falso”, cuyo valor es 0. ¿Y qué pasa ahora si volvemos a negar el valor devuelto? Pues que tenemos el valor lógico “original”: así que el valor de !!256 es por tanto 1. De esta forma convertimos en un 1 cualquier valor distinto de 0, sin importar el “tamaño” del valor. Mientras que !!0 vale 0 igualmente.
Esa es la utilidad de la doble exclamación !!. Curiosamente, en el ejemplo puesto en la consulta, no hace falta utilizar este “truco” porque los valores nunca serán mayores que 2. Supongo que quien lo puso originalmente lo haría por costumbre. Más vale prevenir que curar. De hecho yo he tenido este problema, aún siendo conocedor de ello, por no haber tenido en cuenta que el valor con el que trabajaba “desbordaba” a la variable que lo recibía. Esta no es la única solución, también se podría utilizar el operador ternario ? : que puede resultar un poco más claro (al menos para los que conocen este operador). Utilizando el ejemplo de la consulta:
digitalWrite(AP, (row & B00000001) ? HIGH : LOW);
digitalWrite(BP, (row & B00000010) ? HIGH : LOW);
Si no se sabe cómo funciona el operador ternario, no hay más que buscar en Google “operador ternario c”.
Espero haber aclarado la duda.