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




domingo, 14 de noviembre de 2010

Controladora de servos basada en PIC16F628A

Controladora funcionando
Bueno aquí dejo esta controladora de servos que me he hecho para utilizar en futuros proyectos. La PCB es la que está publicada en una entrada anterior realizada por CNC.
En la descarga están las fuentes, esquemas y demás. El programa está realizado en CCS y los esquemas en Eagle.
Tiene algunos bugs que detallo a continuación, los cuales serán corregidos en futuras versiones.

Frontal
Trasera detalle bugs corregidos
PinOut
ServoControl r1 (13 Octubre 2010) para PIC16F628A por villamany
Contacto: villamany@msn.com

CARACTERISTICAS:
Este circuito, mediante línea de comandos, permite el control independiente de 8 servo-motores (los utilizados en RC), una salida digital, un zumbador de frecuencia variable y una entrada digital.

Está basado en el microcontrolador PIC16F628A y La comunicación se lleva a cabo mediante UART niveles TTL 5v a 9600bps con 8 bits de datos 1 bit de stop y sin control de flujo ni paridad.

Se incluyen 4 pines para futura expansión con 2 I/O auxiliares.

Se incluyen 5 pines para programación "in situ" del PIC vía ICSP.

Led de estado de funcionamiento en placa (parpadeo rápido=ok).

Atención: A la hora de reprogramar el PIC por ICSP, es recomendable desconectar la alimentación de la placa para evitar posibles errores de programación y/o movimientos involuntarios de servos/salida.

COMANDOS:
Los comandos tienen la sintaxis CMD ARG1 ARG2, es decir, un comando y 2 argumentos opcionales separados entre si por un espacio.

CMD siempre es un byte mientras que ARG1 y ARG2 pueden variar entre 1 y 5 bytes, dependiendo del comando utilizado.

Los comandos deben enviarse de uno en uno seguidos de la tecla “Enter”, tras ejecutarse el comando, el dispositivo enviara una respuesta para indicar info adicional sobre la ejecución que tuvo lugar seguido de una nueva línea de texto con "rdy>" indicando que el PIC está preparado para recibir un nuevo comando (Ready).

Listado de comandos:
Comando
Acción
R
Resetea el hardware y devuelve la versión del firmware y la posición
por defecto de los 8 servos en orden ascendente,  emitiendo un breve 
"beep" y quedando preparado ("rdy>").
i
Lee el estado de la entrada todo-nada (interno y/o externo) y devuelve "1"
si está cerrada "0" en caso contrario
o
Devuelve "1" si la salida esta activada y "0" en caso contrario.
o 1
Activa salida y devuelve "1" (salida activada).
o 0
Desactiva salida y devuelve "0" (salida desactivada).
o t           
Invierte el estado de la salida y devuelve "1" si ha sido activada o "0" si ha 
sido desactivada.
b
Devuelve la frecuencia en Hz a la que está sonando el Zumbador.
b [fffff]     
Activa el Buzzer a la frecuencia indicada y devuelve la frecuenciareal 
(frecuencia mas próxima que puede generar el oscilador).
[s]       
Obtener posición de servo. Devuelve el valor del dutty cycle aplicado a 
un servo.
[s] [tttt]    
Posicionar servo. Posiciona un servo aplicando el dutty cycle  indicado 
y devuelve el valor aplicado.
[s] + [tttt]  
Incrementar posición relativa de servo. Incrementa en el numero indicado 
el dutty cycle aplicado a un servo y devuelve el valor aplicado al mismo.
[s] - [tttt]  
Decrementar posición de servo. Decrementa en el numero indicado el 
dutty cicle aplicado a un servo y devuelve el valor aplicado al mismo.
[s] h         
Posicionar servo en su posición por defecto (valor home definible por 
otro comando), devuelve el valor del dutty cicle aplicado.
[s] U [tttt]  
Definir posición máxima admisible para un servo (dutty cycle máximo). 
Devuelve el valor introducido.
[s] L [tttt]  
Definir posición mínima admisible para un servo (dutty cicle mínimo). 
Devuelve el valor introducido.
[s] H [tttt]  
Definir posición por defecto para un servo (tras reset y comando [s] h). 
Devuelve valor introducido.
[s] u [tttt]  
Posicionar servo en su valor máximo admisible. Devuelve valor aplicado.
[s] l [tttt]  
Posicionar servo en su valor mínimo admisible. Devuelve valor aplicado.
[fffff]  :  Indica la frecuencia de funcionamiento del buzzer en Hz. Los valores posibles son los enteros comprendidos entre 250 y 62500. El valor 0 también es válido e indica buzzer desactivado.   
           
[s]      :  Indica el servo al que hace referencia el comando. Los valores posibles son 1,2,3,4,5,6,7 u 8.

[tttt]   : Indica el valor del dutty cicle en microsegundos aplicado a un servo para posicionarlo. Los valores posibles son los enteros comprendidos entre 200 y 2500. Cualquier intento de posicionar un servo por encima de su valor máximo admisible (comando[s] U [tttt]) dará como resultado el posicionado del servo a su valor máximo admisible. Cualquier intento de posicionar un servo por debajo de su valor mínimo admisible (comando [s] L [tttt]) dará como resultado el posicionado del servo a su valor mínimo admisible.

BUGS ENCONTRADOS
HARDWARE r1
-14/10/2010
Falta diodo/s de protección de entrada de alimentación (protección frente a error al alimentarlo)
-La salida RA4 (pin 3) del pic es open collector y necesita una resistencia de pull up de 1K que se ha olvidado.
29/10/2010
-El pulsador no funciona bien, se queda a 1 a veces. Es debido a que el led solo, no conduce bien a masa, esta forma de ponerlo no vale. Para salir del paso se puede poner una R de 1k entre sus patitas, así funcionara bien la entrada pero el led se enciende muy poco cuando se acciona el pulsador.

FIRMWARE r1
29/10/2010
-Cuando se le da un valor muy alto a algún servo (ejemplo 2500) empieza a no funcionar bien (van a tirones todos los servos). Parece que es porque las instrucciones de la interrupción de control de servos tardan más tiempo en ser procesadas (se pisa con la próxima ventana de servos). Se puede apreciar como el led de estado parpadea un poco mas lento  de lo habitual cuando esto ocurre. La única solución hasta su futura corrección es utilizar valores mas bajos.

Descarga Archivos del Proyecto