Modificaciones previas

Una vez ya tenemos claro el funcionamiento del bus de transmisión de datos I²C trasladaremos esa información al Nunchuk.


Antes de continuar, para utilizar la librería de Wire con Arduino, deberemos de modificar una serie de parámetros en el archivo. Nuestra placa Arduino utiliza alguno de los siguientes chips:
  • ATmega328 - Arduino UNO

  • ATmega2560 - Arduino MEGA

  • ATmega168 - Algunas Arduino NANO



    Por lo tanto tenemos que descomentar la siguiente línea en el archivo "twi.h". ¿Qué donde se encuentra el archivo? Pues en la siguiente ruta:
    <Carpeta_IDE_Arduino>/libraries/Wire/utility/twi.h

    Ahora abrimos el archivo con cualquier editor de texto plano y descomentamos la línea que dice:

    //#define ATMEGA8

    Dejándola como:

    #define ATMEGA8

    Con esto tendríamos preparada la librería para utilizarla con Arduino.

    Comunicación con el Nunchuk
    - Inicialización

    Hace un tiempo, se descubrió que existía una forma para inicializar el Nunchuk. La pega era que los datos que se recibían posteriormente estaban encriptados, y se requería aplicar una serie de operaciones sobre los bits para decodificarla.

    En la actualidad, se descubrió otra manera. La cual incorporaba una nueva forma de inicializar el Nunchuk con la que conseguíamos que los datos se encontraran decodificados desde el inicio.

    Otra ventaja de este nuevo modo, es que funciona tanto en Nunchuk originales como en compatibles. Este supuesto no lo he podido comprobar puesto que el Nunchuk que utilizo para las pruebas es Original.

    En la teoría, esta nueva forma de inicializar es escribir 0x55 a la dirección 0x(4)A400F0 y luego escribir 0x00 a la dirección 0x(4)A400FB.

    ¿Cómo se implementa? Primero habría que recordar como utilizar el bus de transmisión de datos y la librería Wire.
    A continuacion se expondrá la implementación de la función que inicializa el Nunchuk para poder utilizarlo:

    void inic_Nunchuk()
    {
      Wire.beginTransmission (0x52);
      Wire.send (0xF0);
      Wire.send (0x55);
      Wire.send (0xFB);
      Wire.send (0x00);
      Wire.endTransmission ();
    }
    Con Wire.beginTransmission(0x52) comenzamos una transmisión a un dispositivo I2C esclavo con la dirección dada. Esta dirección (0x52) es propia del Nunchuk y no varía entre originales y compatibles, pero sí entre otro tipo de extensiones.

    Con Wire.send(0xF0), Wire.send(0x55), Wire.send(0xFB) y Wire.send(0x00) desactivamos la encriptación e inicializamos el Nunchuk.

    Con Wire.endTransmission() damos por finalizada la transmisión y de verdad transmitimos los datos indicados en send().


    - Petición de datos
     
    Ahora ya tendríamos finalizada la función de inicialización. El siguiente paso será crear la función de petición de datos. Para solicitar los datos, primero deberemos enviarle al Nunchuk un byte 0x00 para indicarle que queremos que nos los envie.
     
    Tendría la estructura siguiente:

    void pet_Datos ()
    {
      Wire.beginTransmission (0x52);
      Wire.send (0x00);
      Wire.endTransmission ();
    }

    Con Wire.beginTransmission(0x52) comenzamos una transmisión a un dispositivo I2C esclavo con la dirección dada. Esta dirección (0x52) es propia del Nunchuk y no varía entre originales y compatibles, pero sí entre otro tipo de extensiones.

    Con Wire.send(0x00) le decimos al Nunchuk que queremos recibir los datos de los sensores.


    Con Wire.endTransmission() damos por finalizada la transmisión y de verdad transmitimos los datos indicados en send().


    - Recepción de datos 

    En total tenemos que recibir una cantidad de 6 bytes de datos. Esos bytes recibidos los almacenamos en un array de bytes de longitud 6. El código quedaría de la siguiente forma:

     uint8_t datos[6];int cnt=0;
     Wire.requestFrom (0x52, 6);
      while (Wire.available ())                             
        {
          datos[cnt] = (Wire.receive ());                   
          cnt++;
        }

    Con uint8_t datos[6] creamos un vector de bytes de longitud 6.

    Con Wire.requestFrom(0x52, 6) solicitamos 6 bytes de información al dispositivo 0x52 (en este caso el Nunchuk).

    Con while(Wire.available()) hacemos que se repita la instrucción de dentro del bucle mientras que los bytes que quedan por transmitir sean 1 o más.

    Con datos[cnt] = (Wire.receive()) almacenamos el byte recibido en una posición del vector.
     

    - Decodificación de datos

    Desde el Nunchuk, los datos que recibimos tienen una forma especifica. Recibimos 6 bytes de información:

    B1 B2 B3 B4 B5 B6

    B1: Información sobre el stick en el eje X.
    B2: Información sobre el stick en el eje Y.
    B3: Información parcial sobre la aceleración en el eje X.
    B4: Información parcial sobre la aceleración en el eje Y.
    B5: Información parcial sobre la aceleración en el eje Z.
    B6: Este byte hay que explicarlo por separado ya que su estructura es un poco más compleja.

    El byte B6 esta compuesto por 8 bits:

    [ b8 b7 b6 b5 b4 b3 b2 b1 ]

    b1: Información sobre el botón Z.
    b2: Información sobre el botón C.
    b3 & b4: Són los dos LSB (bits menos significativos) que faltan para completar la aceleración en el eje X.
    b5 & b6: Són los dos LSB (bits menos significativos) que faltan para completar la aceleración en el eje Y.
    b7 & b8: Són los dos LSB (bits menos significativos) que faltan para completar la aceleración en el eje Z.

    Por lo tanto, el rango de valores de los sensores convirtiéndolos a enteros decimales serán los siguientes:
    • Stick: [0, 255] 8 bits.
    • Aceleración: [0, 1023] 10 bits.
    • Botón: [0, 1] 1 bit.
    El problema reside en que la aceleración esta compuesta por 10 bits, pero tenemos un byte de información completo y los dos bits que faltan se encuentran en otro. Hay que hallar la forma de extraerlos y crear un entero de 10 bytes.

    Para ello debemos crear un entero con 10 bits. Hacemos la siguiente asignación:

    uint8_t byte; int dato;
    dato = byte;

    "dato" tendrá la siguiente estructura de bits :

    [b8 b7 b6 b5 b4 b3 b2 b1]

    y necesitamos obtener la siguiente:

    [b10 b9 b8 b7 b6 b5 b4 b3 b2 b1]

    La solucion reside en multiplicar "byte" por 4 (binario). Aquí la explicación:

                     b8 b7 b6 b5 b4 b3 b2 b1
                   x                                 100
                   ---------------------------------
                       0   0   0   0   0   0   0   0
                  0   0   0   0   0   0   0   0
    +      b8 b7 b6 b5 b4 b3 b2 b1
    --------------------------------------------
            b8 b7 b6 b5 b4 b3 b2 b1  0    0




    Ahora, si queremos extraer b3' y b4' de un byte y añadirlo a los dos "huecos" creados anteriormente deberemos realizar la operación ">>" que lo que hace es desplazar los bits a la derecha.
    Realizamos la operación anterior:

    [b8' b7' b6' b5' b4' b3' b2' b1'] >> 2 = [0 0 b8' b7' b6' b5' b4' b3']

    Y acontinuación:
    • Si b3'= 1 entonces sumamos 2 (binario) a "dato" quedando: b8 b7 b6 b5 b4 b3 b2 b1  1    0
    • Si b3'= 0 entonces no hacemos nada, quedando: b8 b7 b6 b5 b4 b3 b2 b1  0    0
    • Si b4'= 1 entonces sumamos 1 (binario) a "dato" quedando: b8 b7 b6 b5 b4 b3 b2 b1  0    1
    • Si b4'= 0 entonces no hacemos nada, quedando: b8 b7 b6 b5 b4 b3 b2 b1  0    0

    Volvemos ahora para aplicarlo a nuestro proyecto. La explicación anterior la tenemos que aplicar. Tenemos uint8_t datos[6] y ahora creamos int decod[7], para almacenar estado del stick (2 ejes), la aceleración (3 ejes) y los dos botones.

    void decodificar_Datos ()
    {
      decod[0]=datos[0];
      decod[1]=datos[1];
      decod[2]=datos[2] * 4;
      decod[3]=datos[3] * 4;
      decod[4]=datos[4] * 4;
      decod[5]=1;
      decod[6]=1;
     
      if ((datos[5] >> 0) & 1)
          decod[5] = 0;
         
      if ((datos[5] >> 1) & 1)
          decod[6] = 0;

      if ((datos[5] >> 2) & 1)
          decod[2] += 2;
         
      if ((datos[5] >> 3) & 1)
          decod[2] += 1;

      if ((datos[5] >> 4) & 1)
          decod[3] += 2;
         
      if ((datos[5] >> 5) & 1)
          decod[3] += 1;

      if ((datos[5] >> 6) & 1)
          decod[4] += 2;
         
      if ((datos[5] >> 7) & 1)
          decod[4] += 1;
    }

    Y con esto dejamos definidas y explicadas las funciones básicas que haran que nuestra placa Arduino pueda comunicarse con el Nunchuk.

    Gracias a Chad Phillips por la información de su blog.

    edit post