Buscar en Google

domingo, 27 de marzo de 2011

Desplazamientos


Desplazamientos


En el lenguaje C, nos está quedando una operación que se puede realizar con las variables y que es muy interesante e importante conocer. La operación que está faltando es el desplazamiento. Esta operación, junto con las operaciones aritméticas y lógicas; componen la familia básica de herramientas que posee el C; y que además también en la mayoría de los microcontroladores tienen implementadas por hardware. Con el uso de estas 3 operaciones básicas, me atrevo a decir que se resuelven el 90% de los casos de programas.

La operación que nos toca en éste apartado es el desplazamiento. El desplazamiento consiste en cambiar de posición los bits de una variable de algún tipo. Por ejemplo, un desplazamiento hacia la izquierda significa hacer que el bit 7 cargue lo que tiene el bit 6, luego el bit 6 carga el valor que tiene el bit 5, y así sucesivamente hasta llegar al bit 1. El bit 0, por definición del C tomará el valor 0.

El signo que se utiliza para realizar desplazamientos es el << cuando se indica que se debe realizar un desplazamiento hacia la izquierda. Y el >> cuando se quiere desplazar hacia la derecha. La variable que queda a la izquierda del doble signo “<<” o “>>” es la variable que se va a desplazar. Mientras que el valor o variable que quedan del lado derecho del signo, debe indicar la cantidad de desplazamientos que se deben realizar. El valor que está colocado a la derecha, nunca debe ser mayor a la cantidad de bits que tiene la variable o el valor que se está desplazando, puesto que si se intenta desplazar más veces que la cantidad de bit que tiene el valor original, el resultado será obligatoriamente 0, ya que se van completando los bits agregados siempre con el valor 0.

Podemos ver en forma de gráfico como se produce el desplazamiento:

Supongamos que hacemos el siguiente programita:

char A;
char B;

A = 0x55;
B = A << 1;

Este programita, indica que la variable B tiene que tener como resultado el valor de la variable A, desplazado una vez hacia la izquierda.


Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1

A << 1 = 0xAA
1
0
1
0
1
0
1
0
Se agrega un cero


Podemos observar que al producir un desplazamiento, se agrega un cero en el bit 0 del resultado del desplazamiento. Y el bit 7 de la variable A original se descarta.

Lo interesante de ésta operación, es que cuando es un único desplazamiento. Se resuelve en un sólo paso dentro del microcontrolador. En algunos modelos de microcontroladores, y muchos DSP, tienen la posibilidad de realizar más de un desplazamiento al valor, y aún así tomar solo un ciclo del procesador.

Si lo que deseamos es realizar una mayor cantidad de desplazamientos, podemos por ejemplo escribir la línea como A << 4. Donde lo que significa que se rotará 4 veces hacia la izquierda el valor de la variable A
Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1

A << 4 = 0x50
0
1
0
1
0
0
0
0
Se agregan 4 ceros

Este último caso, si lo que se está utilizando es un microcontrolador PIC o un Texas MSP430xxx. Necesariamente ocupará como mínimo 4 ciclos de trabajo. Ya que ninguno de los dos posee un desplazamiento mayor a una vez, implementada por hardware.

De la misma manera que se realizan los desplazamientos hacia la izquierda, se pueden realizar desplazamientos hacia la derecha. Por ejemplo:

A >> 1

Significa que se tome el valor de la variable A y se le aplique un desplazamiento de sus valores hacia los bits de la derecha:

Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1

A >> 1 = 0x2A
0
0
1
0
1
0
1
0

El funcionamiento consiste en hacer que el bit 0 del resultado cargue el valor del bit 1, luego el bit 1 toma el valor del bit 2, y así sucesivamente hasta llegar al bit 6. El bit 7 siempre se completa con 0.

De la misma manera que los desplazamientos a la izquierda, es posible realizar mayor cantidad de desplazamientos a la derecha. Por ejemplo:

A >> 3

Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1
A >> 1 = 0x0A
0
0
0
0
1
0
1
0


Ahora bien. ¿Para qué querría alguien usar estas operaciones?

Respondiendo a esa comprensible pregunta, volvamos a las bases, cuando hablamos de números binarios y números decimales.

Veamos que sucede en nuestro sistema decimal cuando multiplicamos un número por 10, o por 100, o por 1000.

Supongamos que tenemos un número 123 en decimal y lo multiplicamos por 10. En forma mental ¿qué hacemos?¿como realizamos una multiplicación de un número por 10?

Simplemente agregamos un cero a la derecha y el número se multiplicó por 10:

123 x 10 = 1230

Cuando multiplicamos un número por 100, le agregamos mentalmente dos ceros a la derecha. Por ejemplo:

123 x 100 = 12300
Ahora pasemos al mundo binario, el mundo de nuestros microcontroladores. No voy a multiplicar por 10 en decimal, sino por 2, por 4, y por 8. Notemos que en binario el 2 es igual a 10. El 4 en binario se representa por 100. El 8 en binario se representa por 1000.

Supongamos que tenemos el valor 6 y lo multiplicamos por 2.

6 x 2 = 12

Ahora veamos los mismos valores en binario: 6 es 110. 2 es 10.  Y el 12 mágicamente es 1100

110 (6) x 10 (2) = 1100 (12)

O sea, al multiplicar un valor cualquiera por 2, que en binario es 10. Si representamos el valor del número que queremos multiplicar por 2 en binario, la multiplicación consiste en agregar un cero a la derecha del número, y desplazar todos los otros dígitos una posición hacia la izquierda.

Si repetimos el proceso para la multiplicación x 4, vamos a ver que se cumple la misma regla, pero ahora agregando dos ceros a la derecha, y desplazando dos veces el valor hacia la izquierda. Notemos que 2x2 = 4. Por lo tanto, es lo mismo multiplicar un valor por 4, que multiplicarlo dos veces consecutivas por 2. O sea, agregar dos veces consecutivas un cero a la derecha.

6 x 4 = 24

110 (6) x 100 (4) = 11000 (24)

Lo mismo sucede para multiplicar por 8, por 16, por 32, y por cualquier potencia de 2. O sea, si tenemos que realizar una multiplicación de una potencia  de 2 en un programa hecho en C. Se puede resolver de dos maneras diferentes.

Por ejemplo, vamos a resolver la multiplicación de dos valores por 8. Una de la soluciones es usar la operación aritmética multiplicación y la otra usando las rotaciones.

char A;
char B;
char C;

B = A * 8;
C = A << 3;

¿Cuando conviene una forma y cuando la otra?

Recordemos que una multiplicación, en un microcontrolador que no posee multiplicador por hardware, requería de muchos pasos de procesamiento. Suponiendo que el multiplicando y el multiplicador son de 8 bits, vimos que como mínimo se requerían de 16 pasos de procesamiento. Si utilizamos el desplazamiento, para la misma multiplicación de un número por 8 sólo se usan 3 pasos. O sea, si nuestro microcontrolador trabaja a 5 MIPS, cuando se utiliza la multiplicación normal, podremos como máximo lograr 312500 operaciones por segundo. En el otro caso, podremos  llegar a las 1666666 operaciones por segundo.

No se puede utilizar el desplazamiento cuando el valor es negativo ya que la información con el signo no se respeta. Para ese caso, debe hacerse un truco que consiste en preguntar previamente si el valor era positivo o negativo. Convertir el valor a positivo, desplazarlo y luego convertirlo nuevamente en negativo.

De la misma manera que desplazar hacia la izquierda implica una multiplicación por una potencia de 2. El desplazamiento hacia la derecha implica la división en una potencia de 2. De nuevo la gran ventaja de usar desplazamientos en vez de una división, cuando el divisor es una potencia de 2 es que la cantidad de ciclos de procesamiento que se utilizan son mucho menores. Así, con esta herramienta se puede lograr que un microcontrolador de los más humildes, realice tareas que son impensadas si lo intentamos programar en la forma más natural que se nos ocurriría normalmente que es usando multiplicaciones y divisiones.

Para poder usar estas herramientas como ayudas matemáticas, tenemos que acostumbrarnos al uso de las potencias de 2 como base para toda nuestra programación. Tenemos que sacarnos de la cabeza el sistema decimal y adoptar el binario y/o hexadecimal. Así, cuando hagamos por ejemplo un acumulador para tomar un valor promedio, no pensemos en usar 10 muestras y luego dividir por 10 para tener el promedio. Pensemos siempre en usar un número potencia de 2 para tomar las muestras, por ejemplo 8 o 16, y después el valor promedio se saca dividiendo por 8 o por 16, que es lo mismo que hacer una rotación del acumulador 3 veces hacia la derecha para el caso de la división por 8, o 4 veces a la derecha para el caso de la división por 16.

Incluso los compiladores mas avanzados, tienen opciones de optimización. Cuando colocamos la operación A = B / 8. Ya conoce el truco, y automáticamente en vez de hacer una operación división estándar, aplica desplazamientos hacia la derecha. Lo único que complica también en éste caso es cuando la variable B es del tipo signed. El compilador tiene que respetarlo, y eso implica mayor cantidad de código al momento de compilarlo. Por ello, es importante conocer que tipo de variable estamos utilizando, si es con signo o nó, si los valores que vamos a utilizar son siempre positivos o pueden aparecer valores negativos.

Si lo que les tira más en su carrera de la programación van a ser los DSP, o van a apuntar a tener equipos donde estén ingresando datos a una tasa de velocidad relativamente importante para un microcontrolador (que puede ser del orden de las 10000 muestras por segundo), y a esas muestras hay que aplicarle algún filtro digital, donde hay que acumular valores y efectuar multiplicaciones, van a tomarle mucho cariño al uso de los desplazamientos que le van a ayudar enormemente a encontrar la solución. En mi experiencia, he hecho cosas con PIC que para muchos eruditos en la programación y manejo de microcontroladores es totalmente imposible. Sin embargo, usando las técnicas y trucos que les estoy dando se pueden implementar. Conclusión, a lo mejor con un microcontrolador económico se pueden realizar tareas que a simple vista parecen muy difíciles o imposibles. Dichas tareas sin dudas que con un microcontrolador más grande o DSP se realizan sin dificultad. Pero, cuando hay que hacer producciones en masa, el precio del microcontrolador que elijan será determinante en el precio del producto final. Por lo tanto, hay que analizar muy bien como implementar la solución para que el resultado sea el óptimo. Si no hay restricciones de microcontrolador y pueden elegir alguno bien potente elijan ese. Ahora, si existe una imposición que se deben reducir los costos al máximo, van a tener que recurrir a herramientas como éstas para poder meter en lugares mas chicos las soluciones. Tengamos en cuenta que cuando trabajamos en el mundo de los microcontroladores es totalmente diferente al mundo de las PC. En las PC cada vez se hacen programas más pesados total cada vez las computadoras son más potentes. En los microcontroladores lo que se busca principalmente es la reducción de costos. Si bien cada día aparece un microcontrolador nuevo más potente, debemos estar atentos a lo que nos ofrece la tecnología y cual es nuestro objetivo de costo.



lunes, 21 de marzo de 2011

Operaciones lógicas bit a bit

Operaciones lógicas a bit a bit: bitwise


Ya vimos como funcionan las operaciones relacionales AND, OR, y NOT.  Estas operaciones se usan principalmente cuando lo que se desea realizar son comparaciones de valores. El resultado comparado puede servir para bifurcar la ejecución del programa hacia otro lado. También se usan para generar funciones matemáticas o aritméticas que cambien según que valores vayan tomando sus variables.

Sin embargo, existe otro uso para las operaciones lógicas, AND, OR, NOT y una nueva que es la XOR. Y es que éstas se pueden aplicar sobre las variables, pero afectando bit por bit en cada comparación, y a su vez, el resultado también afectado a nivel de bit.

Estas operaciones lógicas, aplicadas a nivel bit a bit, son muy útiles en el mundo de los microcontroladores. Estos chips, tienen registros que son los manejan los diferentes periféricos. Dichos periféricos colocan sus resultados, y necesitan ser configurados, encendiendo y apagando bits específicos dentro de uno o varios bytes que componen los registros de configuración.

Entonces, podemos con la ayuda de estas funciones lógicas, modificar, comparar, y realizar muchas combinaciones de tareas que afectan los bits de un valor en forma particular.

Cuando se realizan operaciones del tipo bitwise, o bit a bit. El microcontrolador toma siempre dos valores para ejecutar. Estos valores pueden ser de cualquier tipo (char, int, long) y o longitud. Y va evaluando uno a uno bit por bit. Siempre toma para la operación los bits que están en la misma posición en cada uno de los valores utilizados como datos o entradas. Esto es, ejecuta la operación entre el bit 0 de un valor con el bit 0 del otro valor. Luego continúa con el bit 1 de ambos valores, y así sucesivamente hasta llegar al último bit. La ventaja es que el microcontrolador ejecuta la acción en sólo un ciclo de trabajo. No ocupa tantos ciclos de trabajo como bits tengan las variables. Por lo tanto, la acción es muy rápida.

Es importante que cuando se ejecuten operaciones lógicas a nivel de bit, los dos valores que se utilizan sean por lo menos del mismo tamaño en bits. De ser de diferentes tamaños, el compilador realiza un “casting” automático y lleva toda la operación de manera que los valores utilizados sean de la longitud del dato con mayor cantidad de bits.

 Repasemos como son las funciones lógicas que conocemos hasta ahora, y veamos la función lógica XOR.

Función AND: Su salida vale uno, cuando en ambas entradas en forma simultanea hay un uno colocado.  De otra manera el resultado es cero. El símbolo en C para realizar una operación lógica AND bit a bit es el &. Se diferencia de la operación lógica AND que vimos antes, que cuando se quiere aplicar bit a bit sólo lleva un símbolo & y no dos como era el otro caso.

C = A & B;

Función OR: Su salida vale uno, cuando en cualquiera de sus entradas hay un uno colocado. Sólo cuando en ambas entradas hay un cero, a su salida pone un cero. El símbolo de la operación lógica OR bitwise o bit a bit es el |. De la misma manera que la función AND se diferencia de la operación OR que vimos antes, puesto que lleva sólo un caracter | y no dos.

C = A | B ;

Función NOT: Su salida es el inverso al valor que haya en la entrada. Si hay un uno a la salida coloca un cero, y viceversa. El símbolo para indicar la operación inversión que trabaja a nivel bit a bit es ~. Acá es diferente al usado en el caso anterior que era el símbolo de exclamación ¨!¨

C = ~ A;

Función XOR: Su salida vale uno, cuando en sus entradas sólo una está en 1. Si ambas entradas están en uno, o ambas entradas están en cero, su salida es cero.  El símbolo utilizado para indicar la operación XOR bit a bit es el ^. No existe la operación XOR a nivel relacional.

La tabla de verdad de la función XOR es como la siguiente:

A
B
Salida
0
0
0
1
0
1
0
1
1
1
1
0

Veremos ahora como se aplican las funciones lógicas bitwise en algunos ejemplos.




Ejemplos de uso de la función And bitwise


Comienzo con la función AND bit a bit. La forma más común de pensar en la función AND cuando se hacen las operaciones bitwise, es que tengamos en la cabeza que esta función trabaja como si fuera un filtro. Imaginemos que tenemos una especie de  filtro o colador al cual solo dejamos pasar por sus huecos parte de lo que pongamos del otro lado. Por ejemplo, tengo una variable A con un valor cualquiera. Esta variable A es de 8 bits. Y quiero que en una variable B se almacenen sólo los bits 1,2 y 3 de la variable A. El resto de los bits quiero que siempre permanezcan en cero. Esta operación se resuelve muy fácilmente con la operación AND &:

char A;
char B;

A = cualquier valor

B = A & 0x0E;

Veamos ahora como funciona este programita ejemplo que les acabo de pasar. Para poder entenderlo bien, debemos convertir las variables a binario. Cosa que debemos hacer siempre cuando trabajemos con operaciones lógicas bit a bit. Que si a ésta altura ya están bien familiarizados con el sistema hexadecimal y se conocen de memoria la tabla de conversión deberían ya haber entendido lo que acabo de hacer.

Si convertimos el valor 0x0E a binario, tenemos

Numero de bit
7
6
5
4
3
2
1
0
0x0E
0
0
0
0
1
1
1
0

Ahora veamos, cualquier valor que tenga la variable A. Por ejemplo el valor 0x55 que en binario es 01010101. Coloco en la misma tabla la variable A

Numero de bit
7
6
5
4
3
2
1
0
0x0E
0
0
0
0
1
1
1
0
A
0
1
0
1
0
1
0
1

Calculemos el resultado de hacer A & 0x0E, que es el valor que se carga en la variable B. Notemos que la operación And tiene como resultado 1 sólo cuando ambos bits a comparar están en 1. El resultado de la operación va a ser:

Numero de bit
7
6
5
4
3
2
1
0
0x0E
0
0
0
0
1
1
1
0
A
0
1
0
1
0
1
0
1
B = A & 0x0E
0
0
0
0
0
1
0
0

Notemos, que en el resultado los bits 1 a 3 de la variable A se reflejan en el resultado. Los bits restantes quedan en cero.

Si coloco en forma genérica valores en A, podemos tener una operación como se ve:

Numero de bit
7
6
5
4
3
2
1
0
0x0E
0
0
0
0
1
1
1
0
A
b7
b6
b5
b4
b3
b2
b1
b0
B = A & 0x0E
0
0
0
0
b3
b2
b1
0

Seguramente, alguno de ustedes se preguntará: ¿Para que sirve hacer algo como ésto?

Cuando se trabaja con microcontroladores, muchas veces hay que configurar diferentes periféricos dentro del mismo. También, tenemos los puertos que son las entradas y salidas. Estos puertos son bits dentro de algún registro que significan que alguna de las entradas o salidas están con una señal en 0v o 5v. O que tienen una de sus patitas con un 1 lógico o con un cero lógico. Supongamos que tenemos un microcontrolador, que tiene conectado un botón en una de sus patitas. Esa patita será un bit dentro de un registro que se pone en cero o uno, según si está apretado o soltado el botón. Supongamos que además, en las otras patitas hay otras cosas,  y que esas otras cosas van conmutando entre diferentes valores que no tiene sentido conocer ahora. Si nuestro programa tiene que saber si está apretado o nó el botoncito, puede hacer uso de la función And para descubrir si el botón está pulsado o nó.

Por ejemplo, si nuestro botoncito está conectado al bit 5 de uno de los puertos del microcontrolador. Y si tenemos que tomar alguna decisión cuando se pulsa el botón, nuestro programita sería algo como lo siguiente:

char A;
char B;

A = lee el puerto;

B = A & 0x20;

si B es diferente de cero el boton estaba apretado.

Notemos que la operación B = A & 0x20 es lo mismo que hacer B = A & 00100000. Eso es dejar pasar sólo el bit 5. No importa que valores tengan los otros bits en la variable A, si el bit 5 de A está en uno, el resultado siempre será 0x20, si el bit 5 de A está en cero, el resultado será siempre 0x00.

Otra operación donde muy útil la función AND, es cuando tenemos que hacer lo que se llaman bufferes rotativos. No importa todavía que es lo que significa. Pero si es interesante lo que podemos hacer con la función And para limitar un contador.  Paso a explicarles.

Supongamos que tenemos una variable A. Esta variable A significa un valor o algo dentro de otra parte del programa que no interesa por ahora. Lo importante, es que esa variable A nunca puede salirse del rango. Por ejemplo, necesitamos que nuestra variable A siempre esté dentro del rango 0 a 7. Independientemente de cualquier operación que se  haga con la variable A, debe estar en el rango 0 a 7. Cuando los rangos de valores que tenemos que limitar, son siempre potencias de 2. Recordemos que las potencias de 2 son 1, 2, 4, 8, 16, 32, 64, 128, etcétera. Veamos que dije que queríamos que la variable A esté en el rango 0 a 7, o sea que tome 8 valores. Por lo tanto es un rango que es potencia de 2.

Si realizo al final de cualquier operación aritmética sobre la variable A, la operación A = A & 0x7, veamos que lo que hacemos es limitar el rango de la variable A en los valores 0 a 7.

Numero de bit
7
6
5
4
3
2
1
0
0x07
0
0
0
0
0
1
1
1
A
b7
b6
b5
b4
b3
b2
b1
b0
A & 0x07
0
0
0
0
0
b2
b1
b0

Notemos que siempre quedan almacenados en A los bits 0, 1 y 2. El resto de los bits siempre quedarán en cero. Notemos que si tenemos los 3 bits menos significativos, sólo podrá tomar la variable A los valores 0 a 7. Lo que acabamos de hacer, es como si fuera crear un tipo de datos nuevo, donde los valores de este tipo de datos, pueden estar entre 0 y 7.

Recordemos que si queremos utilizar este truco, siempre debemos utilizar rangos que sean potencia de 2. Esto nos permite en un sólo ciclo de microcontrolador crear o limitar los valores de una variable. Si pensamos realizarlo de otra manera, por ejemplo, preguntando si el valor de la variable A superó el límite máximo para después descontarle el sobre-pasamiento seguramente vamos a ocupar mucho mayor cantidad de ciclos de trabajo, cuando utilizando la operación & (and) se toma sólo uno. Es muy importante recordar que los microcontroladores, especialmente los más pequeños, son limitados en su velocidad. Por lo tanto necesitamos optimizar al máximo las operaciones para que ocupen el menor tiempo posible.

Por supuesto, que a esta altura, si me está siguiendo alguien que tiene conocimientos en C ya se habrá dado cuenta que los programas que les he ido pasando no están para nada optimizados en la sintaxis. Por ejemplo, el último programita que les pasé se podría haber resumido todo en una sola línea. Más adelante vamos a ver como mejorar aun más las líneas de código. Por ahora, prefiero seguir escribiendo los programas de esta manera ya que es mas clara su lectura. Sepamos que a medida que voy avanzando, vamos a ir adentrándonos más y más en lo profundo del C.

Ejemplos de uso de la función OR bitwise


Veamos ahora algunos casos donde tenemos que actuar sobre bits, y que se resuelven fácilmente con la función OR.

La función OR podemos pensarla como que es el opuesto de la función And. En la función Or, el resultado será cero, sólo cuando ambos bits a su entrada son ceros. ¿Notamos la inversión?, en la función And el resultado era uno cuando ambos bits estaban en uno. Para la función Or, en los demás casos el resultado es siempre uno.

La función Or se puede utilizar cuando necesitamos forzar un valor de un bit a 1. Por ejemplo, si queremos colocar en uno el bit 4 de una variable A, la operación sería:

char A;
char B;

B = A | 0x10;

De nuevo, para entender el programita, pensemos en binario. Pongamos la tablita para entenderlo:

Numero de bit
7
6
5
4
3
2
1
0
0x10
0
0
0
1
0
0
0
0
A
b7
b6
b5
b4
b3
b2
b1
b0
B = A | 0x10
b7
b6
b5
1
b3
b2
b1
b0

Notemos que la función Or dejó pasar todos los bits de la variable A, excepto el bit 4 que lo dejó forzado en 1. Observemos comparando con la tabla de la operación And, vemos que es exactamente la operación inversa. Mientras la operación And dejaba pasar todos los bits que se colocaban en la operación con su valor en 1, y los restantes los forzaba a cero. En la operación Or, deja pasar todos los bits que se comparan con el valor 0, y los restantes bits los fuerza a uno.

Supongamos que tenemos un puerto, donde tenemos conectado un LED. Un led es un diodo emisor de luz, pensémoslo como si fuera una lamparita. Suponiendo que dicha luz está conectada en el bit 4 del puerto. Entonces, si queremos encender y apagar el led, tenemos dos programitas que sirven para realizar dicha tarea.

Para encender el led:

char A;
char B;

A = leer puerto;

B = A  | 0x10;

sacar por el puerto el valor de B


Para apagar el led:

A = leer puerto;

B = A & 0xEF;

sacar por el puerto el valor de B

Notemos que para encender el led utilicé la operación Or, mientras que para apagarlo utilicé la operación And. Notemos que utilicé primero la variable A para leer el valor del puerto y luego le apliqué una operación lógica. Esto lo hice de ésta manera, puesto que no quiero modificar los valores que hayan previamente cargados en los restantes bits del puerto que manejan nuestro led.

Ejemplo de uso de la función Not bitwise


Me tomo del último ejemplo que acabo de pasarles en el programita anterior, ya que me interesa demostrar el potencial de la función Not bit a bit.

Veamos en el programita anterior que utilicé la operación A | 0x10 para encender el led. Y que para apagarlo utilicé la operación A & 0xEF. Así puesto de esa manera, parece un poco complicado. Ya que hay que pensar como es el inverso de 0x10 para que cuando se ejecute la operación And queden todos los bits en 1 excepto el bit 4 que es el que quiero colocar en cero.

Sin embargo, si utilizo la función Not bitwise, la lectura del código para colocar en cero el bit 4 se hace más evidente:

char A;
char B;

A = leer el puerto;

B = A & ~0x10;

Vemos que utilicé el operador ~ y luego el número 0x10. Esto significa que antes de aplicar la operación And, tome el inverso de 0x10 y le aplique la función. ¿Vemos que es más simple leer que la operación and se hace con el inverso de 0x10, que tener que leer en forma directa el valor 0xEF?

Numero de bit
7
6
5
4
3
2
1
0
0x10
0
0
0
1
0
0
0
0
~ 0x10 = 0xEF
1
1
1
0
1
1
1
1




La función lógica Xor significa O Exclusivo. Quiere decir que su salida se coloca en uno, cuando uno de sus bits de entrada esta colocado en uno, pero no los dos. Esta operación lógica tiene una cualidad importante. Veamos eso aplicado en un ejemplo.

Supongamos que queremos hacer un programa que cambie constantemente el valor de 2 bits en una variable o registro. Por ejemplo, que con cada pasada invierta los valores de sus bits. Supongamos que tenemos conectados en un puerto dos leds o lucecitas. Arrancamos el programa colocando una de las luces encendida y la otra apagada. Y queremos que vayan moviéndose las luces como si fuera un pequeño secuenciador. Supongamos que tenemos los leds conectados en los bits 3 y 7 del registro de un puerto:

char A;

 Paso 1: A = 0x80;

Paso 2: A = A ^ 0x88;

Paso 3: sacar por el puerto el valor de A

Paso 4: Volver al paso 2

Analicemos el programita. En el paso 2, se cargó la variable A con el valor 0x80 que en binario es 1000000. En el segundo paso se hace la operación Xor con el valor 0x88 que es 10001000

Numero de bit
7
6
5
4
3
2
1
0
A
1
0
0
0
0
0
0
0
0x88
1
0
0
0
1
0
0
0
A = A ^ 0x88
0
0
0
0
1
0
0
0

Observemos que en el paso 2, el bit 7 que originalmente estaba en uno, pasa a valer 0, puesto que cuando ejecuta la operación Xor los dos bits que se comparan están en uno, por lo tanto el resultado es cero. En cambio, el bit 3 en la variable A originalmente está en cero, pero el valor en el número 0x88 está en 1. El resultado para el valor final de la variable A será que el bit 3 pasó al estado lógico uno.

En la próxima pasada del programa, la variable A arranca con el valor 0x08 (00001000). Entonces la operación Xor, A = A ^ 0x88 quedará:

Numero de bit
7
6
5
4
3
2
1
0
A
0
0
0
0
1
0
0
0
0x88
1
0
0
0
1
0
0
0
A = A ^ 0x88
1
0
0
0
0
0
0
0


Como podemos ver, de nuevo se invirtieron los bits 7 y 3. De ésta manera podemos seguir indefinidamente, y en cada pasada se invertirán los bits 7 y 3.