domingo, 21 de noviembre de 2010

Nunchuck BlueHack r1. Utilizar el mando Nunchuck de Wii vía puerto serie Bluetooth

*Todos los archivos de programas y fuentes  aquí indicados los puedes descargar al final de la entrada.

Aspecto final
A raíz de adquirir un “Wiimote + Nunchuck” (baratos de Ebay) para mi consola de videojuegos Wii y como consecuencia de que el Wiimote comenzó a dar problemas al poco tiempo, decidí experimentar con el Nunchuck sobrante y darle una utilidad mejor que la de de estar en el cajón de los trastos.

Tras un rápido vistazo por la Web, me lleve la agradable sorpresa de que su funcionamiento ya estaba bastante documentado.
Así que un poco de lectura “en diagonal” y manos a la obra.


Resumen de su funcionamiento:
El Nunchuck es un mando que dispone de un joystick analógico de 2 ejes, 2 botones digitales y un preciado acelerómetro de 3 ejes.

Este mando va conectado a su hermano, el Wiimote, a través de un cable por el que circulan los datos de dichos sensores, utilizando un protocolo de comunicación compatible con I2C con un direccionamiento estándar de 7 bits (por experiencia a 100Khz sin errores). Dentro de este protocolo, el Nunchuck funciona como esclavo, con lo que solo se necesitaría implementar la comunicación I2C en modo Maestro, para lo que se utilizó un micro-controlador PIC de Microchip.

Bueno, bonito y si echamos un vistazo en Ebay, su precio es bastante atractivo (incluso más barato que un simple sensor acelerómetro, 3€ a fecha de hoy).

Adaptador
Personalmente hice las primeras pruebas cortando el cable, pero existen unos pequeños adaptadores para sacar los pines fuera sin modificar el mando.

Pinout
Su patillaje es el siguiente, ten en cuenta que los colores de los cables en mi caso no coincidían (mando “pirata”), así que si vas a cortar el cable, comprueba el color de cada pin abriendo el conector previamente.

La tensión de trabajo adecuada es de 3.3v (aunque hay quienes usan 5v y afirman no tener problemas, pero no es muy recomendable).

Las resistencias de pullUp necesarias para mantener las líneas del bus a nivel alto ya las incorpora el mando internamente con lo que no es necesario añadirlas.

La dirección I2C de del mando es 0x52 (0b1010010), por lo que para realizar lecturas direccionaremos a 0xA5 (0b10100101) y para escrituras a 0xA4 (0b10100100).

El procedimiento para obtener los datos es el siguiente:

Paso 1.- Abrir comunicación, Inicializar el mando escribiendo en 0xA4 los bytes 0x40, 0x00, cerrar comunicación (solo necesario una vez mientras se mantenga alimentado).

Paso 2.-Abrir comunicación, Posicionar el puntero de lectura escribiendo en 0xA4 el byte 0x00, cerrar comunicación.

Paso 3.- Abrir comunicación, Leer 6 bytes de 0xA5, cerrar comunicación.

Paso 4.-Una vez obtenidos los 6 bytes deberemos decodificarlos realizándole la operación “XOR” 0x17 y seguidamente SUMARLE 0x17 a cada uno de ellos. Tras este tratamiento, los bytes contendrán la siguiente información (byte 1 es el primer byte recibido).

*Como se puede observar, los valores del joystick son de 8 bits de longitud (0..255), los del acelerómetro de 10 bits (0..1024) y los botones de 1 bit (0..1).

*El acelerómetro puede medir aceleraciones de hasta 2 G. Con lo que si realizamos movimientos “bruscos” obtendremos mínimos menores y máximos mayores que si realizamos el mismo movimiento a “baja velocidad”.

Byte 1: Valor del joystick X.
Byte 2: Valor del joystick Y.
Byte 3: Bits 9 a 2 del valor del acelerómetro X.
Byte 4: Bits 9 a 2 del valor del acelerómetro Y.
Byte 5: Bits 9 a 2 del valor del acelerómetro Z.
Byte 6:            
bits 7-6: Bits 1 y 0 del valor del acelerómetro Z.
bits 5-4: Bits 1 y 0 del valor del acelerómetro Y.
bits 3-2: Bits 1 y 0 del valor del acelerómetro X.
bit 1:     Botón C (0=accionado, 1=no accionado).
bit 0:    Botón Z (0=accionado, 1=no accionado).

Paso 5.-Tratar los datos como queramos y actuar en consecuencia.

Paso 6.-Volver al paso 2 para realizar otra lectura.

Pruebas BusPirate
Con la ayuda de mi BusPirate pude trastear y comprobar que todo esto era correcto.

Dejo aquí un log de ejemplo de cómo realizar la lectura del I2C utilizando BusPirate.
#
RESET

Bus Pirate v3a
Firmware v5.9 (r539)  Bootloader v4.1
DEVID:0x0447 REVID:0x3042 (24FJ64GA002 B4)
http://dangerousprototypes.com
HiZ> m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
x. exit(without change)

(1)> 4
Set speed:
 1. ~5KHz
 2. ~50KHz
 3. ~100KHz
 4. ~400KHz

(1)> 2
Ready
I2C> W
Power supplies ON
I2C> (0)
 0.Macro menu
 1.7bit address search
 2.I2C sniffer
I2C> (1)
Searching I2C address space. Found devices at:
0xA4(0x52 W) 0xA5(0x52 R)

I2C> [0xa4 0x40 0x00]
I2C START BIT
WRITE: 0xA4 ACK
WRITE: 0x40 ACK
WRITE: 0x00 ACK
I2C STOP BIT
I2C> [0xa4 0x00][0xa5 r:6]
I2C START BIT
WRITE: 0xA4 ACK
WRITE: 0x00 ACK
I2C STOP BIT
I2C START BIT
WRITE: 0xA5 ACK
READ: 0x7E  ACK 0x74  ACK 0xA4  ACK 0x6D  ACK 0x6E  ACK 0xB3
NACK
I2C STOP BIT
I2C> [0xa4 0x00][0xa5 r:6]
I2C START BIT
WRITE: 0xA4 ACK
WRITE: 0x00 ACK
I2C STOP BIT
I2C START BIT
WRITE: 0xA5 ACK
READ: 0x7E  ACK 0x74  ACK 0xA2  ACK 0x6C  ACK 0x6E  ACK 0xAB
NACK
I2C STOP BIT
I2C> [0xa4 0x00][0xa5 r:6]
I2C START BIT
WRITE: 0xA4 ACK
WRITE: 0x00 ACK
I2C STOP BIT
I2C START BIT
WRITE: 0xA5 ACK
READ: 0x1E  ACK 0x86  ACK 0x66  ACK 0x47  ACK 0x80  ACK 0xBB
NACK
I2C STOP BIT
I2C>

Una vez realizadas las pruebas pertinentes, me puse manos a la obra

Pic salida UART

Pruebas protoboard
Utilizando un PIC16F628A, que es lo que tenía a mano y basándome en que quería únicamente sacar los datos al “mundo exterior” de forma humanamente entendible y sin más complicaciones, implementé un firmware para que leyera, decodificara y finalmente expulsara por una UART dicha información en texto plano. Adicionalmente, añadí al final de cada paquete de datos una suma de verificación para poder comprobar la integridad de los mismos en caso necesario (muy útil en caso de transmitirlos inalámbricamente con posibilidad de que se corrompan).

Además de esto, el PIC también acepta que se le envié un único comando consistente en una “R” (mayúscula). Al recibirlo, el PIC se Resetea y saca por la UART Una breve explicación de los datos que, desde ese momento ira “escupiendo” por la UART de forma continua.

El circuito también dispone de un LED rojo conectado a una salida del PIC, el cual, cambia de estado cada vez que se realiza una nueva lectura, con lo que, si el PIC está alimentado y todo esta correcto veremos un ligero parpadeo del mismo.

Resumiendo: del PIC se utilizarán 2 pines para el bus I2C, otros 2 pines para la UART y 1 pin para el LED rojo, así de simple, sin ni siquiera oscilador externo, ni reset, ni mas historias.

Este es el firmware el PIC, está escrito en C# utilizando CCS (Excluyo el archivo estándar de definiciones del PIC “16F628A.h” que está incluido en CCS):

Archivo main.h:
#include <16F628A.h>

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES INTRC_IO                 //Internal RC Osc, no CLKOUT
#FUSES PUT                      //Power Up Timer
#FUSES NOPROTECT                //Code not protected from reading
#FUSES NOBROWNOUT               //No Reset when brownout detected
#FUSES NOMCLR                   //Master Clear pin desactivado
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD                    //No EE protection
#FUSES RESERVED                 //Used to set the reserved FUSE bits

#use delay(clock=4000000)

//Asiognacion de I/O. Los Aux son pines no utilizados
#define  I2CDTA   PIN_A2
#define  I2CCLK   PIN_A3
#define  AUX1     PIN_A4  //(salida open drain)
#define  ICSPVPP  PIN_A5  //(es solo entrada)
#define  AUX2     PIN_B0 
#define  UART_RX  PIN_B1
#define  UART_TX  PIN_B2
#define  AUX3     PIN_B3 
#define  AUX4     PIN_B4
#define  LED      PIN_B5
#define  ICSPCLK  PIN_B6
#define  ICSPDTA  PIN_B7
#define  AUX5     PIN_A6
#define  AUX6     PIN_A7
#define  AUX7     PIN_A0
#define  AUX8     PIN_A1
  
#use rs232(baud=9600, parity=N,xmit=PIN_B2,rcv=PIN_B1,bits=8)//UART por hadware
#use I2C(master, scl=I2CCLK, sda=I2CDTA,slow)//I2C por software

Archivo main.c:
///////////////////////////////////////////////////////////////////////////////////////////
//NunChuckBlueHack rev.1 para PIC16F628A por villamany 14/11/2010. mail: villamany@msn.com
///////////////////////////////////////////////////////////////////////////////////////////
//Hack para el mando de Wii NunChuck. Su funcion es decodificar via I2C el estado
//de los sensores del mando y enviar estos valores a traves de una UART en formato
//texto y de una forma humanamente entendible finalizados por una suma de verificacion.
//A esta UART se pueden conectar dispositivos conversores tales como:
//UART TTL a rs232, UART TTL a USB, UART TTL a bluetooth SPP...
//Leer el archivo readme`para mas info.
#include "main.h"
////////////////////
//Variables globales
////////////////////
int chksm;//para calculo de cheksum
////////////////////
//Metodos globales auxiliares
////////////////////
// lee un byte del nunchuck, lo decodifica (haciendo xor 0x17 y + 0x17) y lo
//    devuelve
int8 decodeByte();
//va sumando el caracter pasado al checksum (var chksm) y lo imprime por la UART
void PrintCheck(char c);
///////////////////
//Interrupciones
///////////////////
// Decodificacion de comandos recibidos por interrupcion UART
#int_RDA
void UART_isr(void){
   int8 chr=getc();
   switch (chr) {
      case 'R':reset_cpu();}}//comando resetear
///////////////////
//Definicion de metodos     
///////////////////
int8 decodeByte(){
   return((i2c_read()^0x17)+0x17);}
void PrintCheck(char c){
   chksm^=c;
   putc(c);}
void main(){
   delay_ms(500);//estabilizacion y evitar que arranque el programa cuando se use ICSP
                 //  (ICSP trabaja a 5v y hay partes que trabajan a  3v);

   //setear I/O todos los no usados como salidas para no dejar entradas flotantes
   output_low(AUX1);
   output_low(AUX2);
   output_low(AUX3);
   output_low(AUX4);
   output_low(AUX5);
   output_low(AUX6);
   output_low(AUX7);
   output_low(AUX8);
   output_low(ICSPCLK);
   output_low(ICSPDTA);
   //Configurar pic
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_timer_1(T1_DISABLED);
   setup_timer_2(T2_DISABLED,0,1);
   setup_comparator(NC_NC_NC_NC);
   setup_vref(FALSE);
   enable_interrupts(INT_RDA);//habilitar interrupcion por recepcion UART
   enable_interrupts(GLOBAL);// hablilitar las int. globales

   int Jx,Jy,Bz,Bc,aux;//valores joystic analogico, botones, el ultimo byte
   int16 x,y,z;//valores acelerometro;
   // Imprimir Bienvenida
   printf("\r\nReset...");
   printf("\r\nNunchuckBlueHack r1 for PIC16F628A by villamany@msn.com.");
   printf("\r\nType \"R\" for Reset");
   printf("\r\n\r\nHelp:\r\n");
   printf("\r\nx=3 Digits Joy X position (000..255).");
   printf("\r\ny=3 Digits Joy Y position (000..255).");
   printf("\r\nc=1 Digit C button status. 1=Pressed, 0=Depressed.");
   printf("\r\nz=1 Digit Z button status. 1=Pressed, 0=Depressed.");
   printf("\r\nX=4 Digits X Accelerometer value (0000..1023).");
   printf("\r\nX=4 Digits Y Accelerometer value (0000..1023).");
   printf("\r\nX=4 Digits Z Accelerometer value (0000..1023).");
   printf("\r\n*=3 Digits checkSum value (000..255)(All bytes Xor'ed ");
            printf("between first of line and prior to \"*\"");
   printf("\r\nFinally a carry return byte (0x0D) is send for return back ");
            printf("of line and perform a new sensors read.\r\n\r\n");
   // Buccle principal
   while (true) {
      output_toggle(LED);//monitorizar estado en led
      // la inicializacion del nunchuck la quengo que realizar en cada lectura
      // de lo contrario falla (no se por que)
      i2c_start();i2c_write(0xa4);i2c_write(0x40);i2c_write(0x00);i2c_stop();//inicializacion nunchuck
      delay_ms(1); //estos delays de 1ms son necesarios sin ellos falla
      i2c_start();i2c_write(0xa4);i2c_write(0x00);i2c_stop();//Posicionar puntero
      delay_ms(1);
      i2c_start();
      i2c_write(0xa5);//Leer los 6 bytes de datos     
      Jx=decodeByte();//valor joystic X
      Jy=decodeByte();//valor joystic Y
      x=decodeByte();//valor vcelerometro X (los 8 bytes mas significativos)
      y=decodeByte();//valor vcelerometro Y (los 8 bytes mas significativos)
      z=decodeByte();//valor vcelerometro Z (los 8 bytes mas significativos)
      aux=decodeByte();//Aqui estan los 2 bytes menos significativos del acelorometro y el estado de los botones
      i2c_stop();//Datos recibidos
      if ((aux & 0x01)==1)bz=0; else bz=1;//extraer estado boton z
      if ((aux & 0x02)==0x02)bc=0; else bc=1;//extraer estado boton c
      //Comentar estas 6 lineas si se quiere resolucion de 8 bits en el
      // en lugar de 10 en el acelerometro
      x=x <<2; //Extraer los 2 bytes menos significativos de los ejes del acelerometro
      x=x + ((aux >> 2) & 0x03);
      y=y <<2;
      y=y + ((aux >> 4) & 0x03);
      z=z <<2;
      z=z + ((aux >> 6) & 0x03);
      chksm=0;//Reseteo del checkSum
      //imprimir e ir calculando el checkSum
      printf(PrintCheck,"x%03uy%03uc%01uz%01uX%04LuY%04LuZ%04Lu",Jx,Jy,bc,bz,x,y,z);//
      printf("*%03u\r",chksm);
      delay_ms(1);}//Volver al bucle
}

La salida la podremos ver mediante una aplicación de consola conectada al puerto correspondiente, a través de un PC. Si lo prefieres ver gráficamente puedes utilizar una pequeña aplicación para Windows, de la que también se incluyen las fuentes en C# en el archivo de descarga, a final de la entrada.

Módulo bluetooth
Todo esto está muy bien, pero ahora quería utilizar esos datos en otros dispositivos que carecen de puertos externos como por ejemplo mi PocketPc.

Por fin encontré un motivo para comprar esos módulos UART-bluetooth que tanto tiempo llevo queriendo tener. Comentar que he adquirido 2, para tener uno de reserva, a través de Ebay por algo menos de 15€ ambos.

Estos módulos incorporan varias funciones, entre ellas una que permite configurarlos vía comandos AT, pero por motivos de no complicar el firmware del PIC e ir a lo más fácil, opte por dejarlo tal cual me llegó, esto es clave de emparejamiento “1234”, velocidad 9600, 8 bits de datos, 1 de stop y sin paridad ni control de flujo, bueno lo admito, únicamente modifique el nombre del dispositivo y la clave de acceso, por trastear un poco con él.
Probando conjunto
Podéis mirar la hoja de datos para saber cómo configurarlo vía UART física.

Este modulo trabaja a 3.3v, asimismo, el PIC acepta esta tensión de trabajo también, con lo que solo habrá que utilizar una única tensión de funcionamiento en todo el circuito: 3.3v.
Alimentacion

Entonces tenemos que se van a utilizar únicamente los 2 pines de alimentación, los 2 pines de TX y RX de la UART y una de las salidas que trae, a la que conectare un LED azul. Esta salida la mantiene a nivel alto el módulo mientras este enlazado a otro dispositivo bluetooth, poniéndola a nivel bajo en caso contrario.
Montaje

El resultado, ha sido un Nunchuck que reporta los datos de sus sensores a través de Bluetooth utilizando el servicio SPP. Como se aprecia en las imágenes, se ha introducido todo dentro del mando y se ha añadido un conector externo para darle alimentación, bien a la red eléctrica mediante fuente de alimentación o bien con baterías.
Consola PDA
Gracias al regulador que lleva integrado acepta tensiones de alimentación de entre 6-10v aproximadamente.

Consola PC
Como mejora se podría tratar de introducir dentro una pequeña Lipo con su correspondiente circuito cargador y utilizar el conector para dar carga, quedando así todo más compacto.

Cabe destacar que para abrir el mando, se necesita el destornillador habitual de Nintendo de 3 puntas, posteriormente se pueden sustituir los tornillos por otros similares de cabeza estándar.

Esquema

 



* 28/09/2012 Actualizado enlace descarga de archivos




10 comentarios:

INTI dijo...

Me gustaría saber en la aplicación para PC en la parte del acelerómetro que son los valores que marca?? Ya que oscilan entre 500 y 700…

villamany dijo...

Es el valor que da el acelerometro para cada eje en ese instante, oscila por el ruido y la mala calidad del chip, se podria alisar utilizando un filtro por soft

INTI dijo...

Bien es el valor que nos entrega el acelerometro, pero referente a que?? digamos la aceleracion se mide en m/s2, como hacemos para que nos indique dicho valor?? o mejor dicho para transformar el valor que nos entrega en algo que tenga sentido para nuestro mundo físico real? Gracias por tu respuesta....

INTI dijo...

Bueno averiguando por ahí encontré que las lecturas que estas tomando son de mV mili volt, puede ser?? pero como podes hacer para interpretar estos mV a aceleración..???

villamany dijo...

No lo he utilizado mas que para hacer la prueba que ves en el video, desconozco la relacion que guarda el valor con la aceleracion, creo recordar que media hasta fuerzas de 2Gs (segun pude leer).
La inclinacion estatica si es sencilla de medir interpolando los valores.
Habria que consultar el datasheet del chip a ver si se puede averiguar la relacion que existe entre valor-inclinacion-gravedad o algo similar.

Andres Fajardo dijo...

Muchas gracias por compartir !! me ha sido de mucha ayuda..

Americo Rojas dijo...

EL link ya no fuciona (megaupload)x fa si puedes subir en otra pagina

Americo Rojas dijo...

EL link ya no fuciona (megaupload)x fa si puedes subir en otra pagina

villamany dijo...

Actualizado el enlace. Un saludo...

Anónimo dijo...

Muy buena publicación, me fue de gran ayuda muchas gracias!
La analice y la modifique un tanto.
Dentro de lo que investigue encontré que hay dos modos de inicializar el nunchuck, el modo encriiptado y el desencriptado (que es mas simple y rapido) utilice ambos y funciono bien pero me voy por el desencriptado.
Hice algunas modificaciones al programa que pudieran ser mejoras.
Si alguien le interesa...
v_estrella@hotmail.com