Operaciones con bits

Objetivos

 

  • Presentar la forma de rerpresentar caracteres graficos, en una matriz de puntos.
  • Como codificar estos caracteres.
  • Presentar los operaciones básicas de bits AND, OR, XOR
  • Presentar los desplazamientos de bits, a izquierda y derecha.
  •  

    Material requerido

    Kit Arduino Uno  Kit inicio UNO
    Kit Arduino MEGA Kit Inicio Mega

     

    Definiendo el grafico de un caracter

     

    Si recordáis, en la sesión 31, definimos unos arrays como estos para mostrar los números en el display, algo así:

    byte Digit[10][8] =
      {  { 1,1,1,1,1,1,0,0 },    // 0
         { 0,1,1,0,0,0,0,0 },    // 1
         { 1,1,0,1,1,0,1,0 },    // 2
         { 1,1,1,1,0,0,1,0 },    // 3
         { 0,0,1,0,0,1,1,0 },    // 4   
         { 1,0,1,1,0,1,1,0 },    // 5
         { 1,0,1,1,1,1,1,0 },    // 6
         { 1,1,1,0,0,0,0,0 },    // 7
         { 1,1,1,1,1,1,1,0 },    // 8
         { 1,1,1,0,0,1,1,0 }     // 9
       };

    Definíamos los segmentos que se tenían que iluminar, especificando un 1 o un 0, con un byte. Lo que significa que ese array ocupa 80 bytes (Podéis comprobarlo con la función sizeof(Digits)).

    Claro que esto es para unos displays BCDs que tiene 8 segmentos por cada digito. Suponed que quisiéramos utilizar unos displays un poco más sofisticados como una matriz de 8×8 puntos LED, que pudiéramos encender independientemente (Nuestra siguiente sesión). Tendríamos que definir unas matrices de 8×8 puntos, para dibujar una letra concreta.

    Si hacemos lo mismo que en el caso de la sesión 31, cada letra ocupara 8 x 8 bytes= 64 , y como si definimos el alfabeto completo, con mayúsculas y minúsculas, números y algunos símbolos como +,-,/,* símbolos de puntuación y demás, nos iremos enseguida a unos 128 caracteres. Que por 64 bytes cada uno, nos da la bonita cifra de 8.192 bytes.

    Si recordáis de cuando hablamos de la memoria para las variables, solo tenemos 2k, y resulta que necesitamos 8k para estos arrays, así que por aquí no vamos bien.

    Afortunadamente, no somos los primeros en tener este problema (Sorpresa) y más en la época heroica donde ponerle 8k de RAM al ordenador podía costar una cifra con más ceros de los que podíamos pagar. Y la solución es codificar a nivel de bits y operar luego con ellos.

    Y esto nos lleva directamente a los objetivos de esta sesión.

    Definiendo arrays compactos de caracteres 8×8

     

    Supongamos que quisiéramos dibujar las letras de PROMETEC en un display de 8×8. Podríamos jugar a dibujar en cuadriculas algo parecido a esto:

    letra P

    Para la R:

    Letra R

    Para la O:

    Letra O

    Fijaros que además de escribir el valor  en binario, he añadido una última columna en hexadecimal, porque es más cómodo de manejar.

  • Dijimos la última vez que hablamos del tema, que el hexadecimal se usaba mucho porque era muy fácil de pasar al binario y viceversa. Para los que tengáis interés, es tan fácil como coger los dígitos binarios de 4 en 4 y pasarlos directo a hexadecimal con la siguiente tabla:
  •  

    Conversion binario a HEX

    Una manera compacta de escribir esos valores es:

    byte P[] = { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40 };
    byte R[] = { 0x78, 0x44, 0x44, 0x78, 0x70, 0x58, 0x4C, 0x46 };
    byte O[] = { 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C };

    Fijaros que lo único que hemos hecho, es copiar los números de arriba en hexadecimal ( aunque es lo mismo si los ponéis en binario o decimal). De este modo, con 8 bytes codificamos los bits necesarios para dibujar cada letra.

    El problema ahora, será sacar los bits cuando los necesitemos, lo que nos lleva de cabeza al siguiente punto

    Operaciones con bits

     

    Vamos a empezar con la P y con el array que la define gráficamente.

    byte P[] = { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40 };

    Lo escribimos en hexadecimal, por comodidad. Pero la idea es que la primera fila de la P está contenida en el primer 0x78 del array. Cada uno de los bits que dibujan su primera fila está allí, contenido en un único bit, no en 8. Y lo mismo con el resto de las filas del dibujo.

    Si hacemos esto para definir un juego completo de caracteres, usaríamos

    128 caracteres x 8 bytes = 1024 bytes

    Eso sí, nos encaja y aún nos queda otro k de memoria para variables corrientes. El problema es que tendremos que desempaquetar los bits para poder iluminar cada punto del display, pero vamos a ver que C++ tiene medios para eso.

    Vamos en primer lugar con la función AND a nivel de bit. Su operador es & (no confundir con  && , que es el operador lógico). Veamos el resultado con un par de números:

    int a =  92;       // in binary: 0000000001011100
    int b = 101;       // in binary: 0000000001100101
    int c = a & b;     // result:    0000000001000100,    68 in decimal.

    AND se efectúa a nivel de bits, si los dos son 1, el resultado es 1. En caso contrario el resultado es 0. Fijaros que realiza la operación bit a bit en binario.

    Veamos la función OR. Su símbolo es |

    int a =  92;        // in binary: 0000000001011100
    int b = 101;        // in binary: 0000000001100101
    int c = a | b;      // result:    0000000001111101,  125 in decimal.

    OR se calcula a nivel de bit, del siguiente modo: El resultado es uno si cualquiera de los bits es 1 o ambos lo son, y cero si ambos son 0.

    También hay un operador XOR, que es uno si cualquiera de ellos es un 1, pero un 0 si ambos son 1 o 0. Su símbolo es ^

    int x = 12;     // binary: 1100
    int y = 10;     // binary: 1010
    int z = x ^ y;  // binary: 0110, ir decimal 6

    ¿Y para qué sirve todo esto? La pregunta del millón.

    Pues como siempre, resulta muy complicado explicar la solución a problemas que aun no has tenido, pero tranquilos que enseguida os pondré uno de estos.

     

  • Hasta ahora hemos usado las puertas de Arduino de una en una, leyendo su valor. Pero Arduino contiene registros internos que podemos leer en bloques de 8 y cada bit representa el estado de una puerta digital.
  • De hecho, el IDE de Arduino, lee los registros en bloque y después hace operaciones a nivel de bit, para mostrarnos el resultado de un digitalRead, como HIGH o LOW. Y esto queridos amigos es más lento, que hacerlo tú, y hay ocasiones en que mejorar la velocidad de tu programa puede ser crítico.
  • No tengo intención de entrar en este tema aún (aunque todo llegará), pero es importante que os vaya sonando.
  •  

    El caso de representar caracteres, mediante matrices de puntos es uno de estos problemas, en el que estas operaciones serán críticas.

    Cuando queramos encender un punto LED en una matriz e 8×8, tendremos que sacar uno, a uno los bits independientes de un byte que representan los puntos a iluminar o no. Para extraer un bit concreto de un byte de datos, tendremos que usar  este tipo de operadores y además utilizar los bitwise operators.

    Asi que, alla vamos.

    El primero es el operador  >>, que funciona desplazando tantos bits a la derecha un número. Y existe el bitshif left equivalente cuyo símbolo es <<. Veamos un ejemplo:

    int a = 5;          // binary:  00000101
    int b = a << 3;     // binary: 00101000,     // 40 en decimal
    int c = b >> 3;     // binary: 00000101,     // Vuelta al 5 inicial

    Si desplazamos 00000101, tres dígitos a la izquierda el resultado es 00101000.

    Es muy parecido a lo que pasaba con un shift register, donde los bits eran empujados. Al desplazar a la izquierda, los bits que entran por la derecha son 0s y los que salen se pierden sencillamente.

    Cuando haces un shift a la derecha, los bits salientes por la derecha se pierden, y los que entran son 0s.

    Bueno, pues armados ya con estas herramientas vamos a ver como sacamos un bit concreto de un byte concreto:

  • Estas operaciones de AND, OR, XOR , right shift y left Shift, funcionan igual con independencia del tipo de la variable, es decir funciona, también con int, long y demás.
  •  

    Obteniendo bits independientes

     

    Supongamos que tenemos un número como este,

    byte n = 0b00110110 ;       // Hex 0x36         DEC  54

    Y que queremos obtener el bit 3, empezando por la derecha ( posición 2). ¿Cómo lo haremos?

    Empecemos definiendo la posición de los bits a así, de izquierda a derecha la posición es

    pos = 7 6 5 4 3 2 1 0

    Podríamos empezar haciendo un rigt shift de 3 bits:

    byte n = 0b00110110;
    n >>3  = 0b00000110    // Los bits salen por la derecha y entran ceros por la izquierda

    Pero lo que nos interese es  solamente el bit que está a la derecha ahora y no el resto, así pues haremos:

    byte resultado = n & 0b00000001 // Decimal 1

    Al hacer el AND a nivel de bits, liquidamos todas las posiciones que llevan 0 y solo si el bit menos significante de n es 1, el resultado será 1, de lo contrario será 0.

    Podemos escribir una función de propósito general para obtener un bit concreto de un valor:

    bool GetBit( byte N, int pos)
       {                            // pos = 7 6 5 4 3 2 1 0
           int b = N >> pos ;       // Shift bits
           b = b & 1 ;              // coger solo el ultimo bit
           return b ;
       }

    Esta función nos devolverá un TRUE o FALSE según que el bit sea 1 o 0, y vamos a tener ocasión de probarla. A modo de diversión, os incluyo aquí un programita que te va listando los bits consecutivos de un número n cualquiera, que le pases en la primera línea:

        byte n = 0b01110110 ;
    
        void setup()
           {      Serial.begin(9600);  }
    
        void loop()
           {      for ( int k=7; k>=0 ; k--)
                     {   byte m = GetBit( n, k);
                         Serial.print(m);
                         Serial.print(", ");
                     }
                  Serial.println();
           }
    
        bool GetBit( byte N, int pos)
            {   // pos = 7 6 5 4 3 2 1 0
                int b = N >> pos ;         // Shift bits
                b = b & 1 ;                // coger solo el ultimo bit
                return b ;
            }

    Por cierto, que tenía pendiente comentaros(y este no parece mal momento), que nuestro Serial.print, acepta modificadores para decirle si queremos que el resultado salga en binario, decimal, hexadecimal,

    int n = 415 ;
    
    void setup()
       {
            Serial.begin(9600);
       }
    void loop()
       {
            Serial.print(n, DEC);
            Serial.print(",    ");
            Serial.print(n, BIN);
            Serial.print(",    ");
            Serial.print(n, HEX);
            Serial.print(",    ");
            Serial.println(n, OCT);
       }

    Y así espero ahorraros algún dolor de cabeza convirtiendo formatos.

     

    Resumen de la sesión

     

    En este curso arduino hemos aprendido lo siguiente:

  • Hemos visto, como rerpresentar caracteres graficos, en una matriz de puntos, y como codificar de modo compacto esta información bit a bit.
  • Presentamos los operaciones básicas de bits AND, OR, XOR
  • Hemos hecho algún ejemplo de desplazamiento de bits a derecha e izquierda.
  • Hemos preparado una función genérica GetBit, que nos permite sacar un bit independiente de un byte previo y que seria muy fácil extender otros tamaños.
  • Describimos la forma de usar Serial.print con modificadores, para que formatee la salida en decimal, hexadecimal, binario u octal.
  •  

    Deja una respuesta