[SOLVED] Z Button Won't Read - Interface to Wii Nunchuck With Pic 18F4550

Hello LMR Code Masters!

   I've had moderate success interfacing a Pic 18F4550 to a Wii Nunchuck (authentic), but I have one nagging bug.  The code is written in C against Microchip's C18 compiler in MPLAB X 1.20.  The 18F4550 has a ceramic resonator valued 4 MHZ.  I use PLL to drive the chip to 48 MHz.  At four instructions per clock, the chip driver at 12 MIPS.  I use the initialization routine found here: http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers.  This ensures that the chip returns data unencrypted, and it should work for all extensions ( authentic and knock-offs ).  

   What it does:  The program initializes the Tris and Ports and SFRs as required.  Interrupts are off and pins are all digital.  It sets up USART at 19,200 baud.  It sets up the I2C for 100 KHz with slew off ( 400 KHz was a bit shaky ).  It initializes the nunchuck extension with two writes: Start, 0xA4, 0xF0, 0x55, Stop, delay 200 us, Start, 0x4A, 0xFB, 0x00, Stop.  It then reads the type from the 0xFA register: delay 200 us, Start, 0x4A, 0xFA, Stop, delay 200 us, Start, 0xA4, read 6, Stop.  This enumerates the device correctly as: 0x00, 0x00, 0xA4, 0x20, 0x00, 0x00.  It then enters an infinite loop.  The loop reads the data from 0xA4 0xFB, using 0xA5.  The data is converted and output to the PC over USART.  The PC code is a simple bash script.  The Pic waits one second and then reads and prints again.

   The data looks plausible except for the Z button.  The Z button's state is kept in the 6th byte's zero bit.  Here is the bug.  The Z button always reports as zero ( not pressed ).  I programmed the convert and print routines first.  With dummy data, everything worked great. Any help would be greatly appreciated.  The code is posted below.  I've left out the config bits and the delay defines.  they are resused code that works in many other apps, but I'll add them if they are suspect.

   Thanks in advance!

Tom

 

  1 /*
  2  * File:  nunchuck.c
  3  * Author:  Tom Hunt
  4  *
  5  * Created on February 8, 2013, 9:51 PM
  6  *
  7  *  Running Pic 18F4550 @48 MHz
  8  *  Communicate with Wii Nunchuck over I2C, convert values,
  9  *  and send them to PC over USART at 19,200 baud
 10  *
 11  *  There seem to be several schools of thought on how to talk to Wii extension
 12  *  devices.  This program eventually wants to talk to the nunchuck and the
 13  *  motion plus in pass-through mode.  This will yield a 6 DoF IMU.  To this
 14  *  end, the program will try to use the protocols as outlined in the wiiBrew.org pages:
 15  *  http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers
 16  *  http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck
 17  *  http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers/Wii_Motion_Plus
 18  *
 19  *  It will attempt to use the universal initializations and run in unencrypted mode.
 20  *
 21  *  The main routine will initialize and go into a forever loop.  The loop will take
 22  *  a reading, convert it, send it, and delay about 1 second.
 23  *
 24  *  This works pretty well with the following caveats.
 25  *  First, it works much better at 100 KHz I2C speed.
 26  *  Second, the Z acceleromter doesn't do much.
 27  *  Third, the Z button always reads as pressed ( zero ).
 28  *
 29  */
 30 
 31 #include <p18f4550.h>
 32 #include <stdio.h>
 33 #include <stdlib.h>
 34 #include <delays.h>
 35 #include <usart.h>
 36 #include <i2c.h>
 37 
 38 #include "configFuses.h"
 39 #include "mydelays.h"
 40 
 41 // For 400 KHz: ( 48MHz / 4 / 400 KHz ) - 1 = 29 or 0x1D
 42 // For 100 KHz: ( 48MHz / 4 / 100 KHz ) - 1 = 119 or 0x77
 43 #define I2C_Speed                       0x77
 44 #define Slew                                SLEW_OFF
 45 
 46 #define extensionWriteAddress   0xA4
 47 #define extensionReadAddress    0xA5
 48 
 49 #define extensionInitRegister      0xF0
 50 #define extensionInitData           0x55
 51 
 52 #define extensionTypeReadRegister   0xFA
 53 #define extensionTypeWriteRegister   0xFB
 54 #define extensionTypeData        0x00
 55 
 56 #define extensionReadRegister   0x00
 57 
 58 #pragma udata access accessRam
 59 near unsigned char values[6];
 60 near unsigned int unpackedValues[6];
 61 
 62 #pragma romdata nunchuck
 63 const far rom unsigned char fail[] = "Read data failed.\n";
 64 
 65 #pragma code
 66 
 67 /**
 68  *  Convert the 6 raw bytes of data into the 7 nunchuck values.
 69  *  Store the converted values in finalValues.
 70  *  Raw Values:
 71  *  Byte<bits>                 Value
 72  * ---------------------------------------------------------------------------
 73  *       0                              SX<7:0>
 74  *       1                              SY<7:0>
 75  *       2                              AX<9:0>
 76  *       3                              AY<9:0>
 77  *       4                              AZ<9:0>
 78  *       5                              PackedByte(see below)
 79  *
 80  *   Bits:          7:6             5:4               3:2           1         0
 81  *Value:    AZ<1:0>   AY<1:0>    AX<1:0>   BC      BZ
 82  *
 83  * @param rawValues  - the 6 bytes retrieved from the nunchuck
 84  * @param finalValues - the array to hold the 7 converted values.
 85  */
 86 void convertRawValues( unsigned char* rawValues, unsigned int* finalValues )
 87 {
 88     int i = 0;
 89     unsigned char mask = 0x0C;
 90     unsigned char shiftAmount = 0x02;
 91     unsigned char packedByte = *(rawValues+5);
 92 
 93     // SX - Joystick X value --- set the value and increment raw values pointer
 94     *finalValues++ =*rawValues++ & 0x00FF;
 95     // SY - Joystick Y value --- set the value and increment raw values pointer
 96     *finalValues++ = *rawValues++ & 0x00FF;
 97     // This loop takes care of the 3 acelerometer values.  It gets the 7 High bits from each array value in turn.
 98     // It shifts these up 2, casting to an int as it goes.  C18 doesn't follow ANSI here, so the cast is needed.
 99     // Then the LSbs are shifted in.  As we go in order, all we need to do increment the shift by 2 and shift the
100     // mask by 2.
101     for( i = 0; i < 3; i++ ) {
102         *finalValues++ = ( ((unsigned int) *rawValues++) << 2 ) + ( ( packedByte & mask ) >> shiftAmount );
103          mask = mask<<2;
104          shiftAmount += 2;
105     }
106     // Need to do the buttons
107     // if the button bit is set, the button is not pressed.
108     // N.B. The value is inverted here to be like normal C.  Pressed = 1 = true
109     // The C button is bit 1 of the 6th byte
110     *finalValues++ = ( ( packedByte >> 1 ) & 1 ) ? 0x000 : 0x0001;
111     // The Z button is bit 0 of the 6th byte
112     *finalValues++ = ( ( packedByte >> 0 ) & 1 ) ? 0x000 : 0x0001;
113     
114 }
115 
116 void printDeviceType( unsigned char* values )
117 {
118     unsigned char i;
119 
120     fprintf( _H_USART, "clear\n" );
121     fprintf( _H_USART, "Device Type:\n" );
122     for( i = 0; i < 6; i++ ) {
123         fprintf( _H_USART, "0x%X\n", *values++ );
124     }
125 }
126 
127 /*
128  * Spew out the values over USART.
129  */
130 void printValues(  unsigned int* printValues )
131 {
132     fprintf( _H_USART, "clear\n" );
133     // Joystick:
134     fprintf(  _H_USART, "     X Joystick: %d\n", *printValues++ );
135     fprintf(  _H_USART, "     Y Joystick: %d\n", *printValues++ );
136     // Accelerometer:
137     fprintf(  _H_USART, "X Accelerometer: %d\n", *printValues++ );
138     fprintf(  _H_USART, "Y Accelerometer: %d\n", *printValues++ );
139     fprintf(  _H_USART, "Z Accelerometer: %d\n", *printValues++ );
140 
141     // Now do the buttons
142     // C button
143     if( *printValues++ ) {
144         fprintf( _H_USART, "       C Button: Pressed\n" );
145     } else {
146         fprintf( _H_USART, "       C Button: Not Pressed\n" );
147     }
148 
149     // Z button
150     if( *printValues ) {
151         fprintf( _H_USART, "       Z Button: Pressed\n"  );
152     } else {
153         fprintf( _H_USART, "       Z Button: Not Pressed\n"  );
154     }
155 
156 }
157 
158 /**
159  *  initialize the extension controller with universal method.
160  *  Ensure that the type identification block will be unencrypted.
161  *
162  * @return error - 0 on success, -2 on NotAck, -3 on WCOL
163  */
164 unsigned char initExtension( void )
165 {
166     unsigned char error = 0;
167     delay100us();
168     StartI2C();
169     error = putcI2C( extensionWriteAddress );
170     if( error ) return error;
171     error = putcI2C( extensionInitRegister );
172     if( error ) return error;
173     error = putcI2C( extensionInitData );
174     if( error ) return error;
175     StopI2C();
176     // introduce an artificial wait before sending new packet
177     delay100us();
178     StartI2C();
179     error = putcI2C( extensionWriteAddress );
180     if( error ) return error;
181     error = putcI2C( extensionTypeWriteRegister );
182     if( error ) return error;
183     error = putcI2C( extensionTypeData );
184     if( error ) return error;
185     StopI2C();
186    
187     return 0;
188 }
189 
190 unsigned char readSixBytes( unsigned char reg, unsigned char* values )
191 {
192     unsigned char error = 0;
193     unsigned char i;
194 
195      // write the register from which we will read
196     delay200us();
197     StartI2C();
198     error = putcI2C( extensionWriteAddress );
199     if( error ) return error;
200     error = putcI2C( reg );
201     if( error ) return error;
202     StopI2C();
203 
204     // Read the six data bytes at the read address
205     delay200us();
206     StartI2C();
207     error = putcI2C( extensionReadAddress );
208     if( error ) return error;
209     // Now, read in the 6 bytes
210     error = getsI2C( values, 6 );
211     if( error ) return error;
212     StopI2C();
213     delay200us();
214     return error;
215 }
216 
217 // Get the type of the extension
218 unsigned char retrieveType( unsigned char* values )
219 {
220     unsigned char error = 0;
221     // Read the six data bytes
222     error = readSixBytes( extensionTypeReadRegister, values );
223     return error;
224 }
225 
226 // Get the values for the extension and pack the raw data in values array.
227 unsigned char retrieveExtensionData( unsigned char* values )
228 {
229     unsigned char error = 0;
230     // Read the six data bytes
231     error = readSixBytes( extensionReadRegister, values );
232     return error;
233 }
234 
235 void main( void )
236 {
237     unsigned char error = 0;
238     // This sillyness prevents weird stuff on the Latch from screwing up I2C
239     TRISB = 0x00;
240     LATB = 0x00;
241     TRISB = 0xFF;
242     
243     INTCON = 0x00; // turn off all interrupts
244     // make sure we are all digital
245     ADCON0 = 0x00;
246     ADCON1 |= 0x0F;
247     // initialize the USART to 19,200 baud
248     // spbrgh = ( 48,000,000 / 19200 / 16 ) - 1 = 155.25
249     OpenUSART(     USART_TX_INT_OFF & USART_RX_INT_OFF
250                            & USART_ASYNCH_MODE & USART_EIGHT_BIT
251                            & USART_CONT_RX & USART_BRGH_HIGH,
252                              155 );
253 
254     // initialize the I2C bus 
255     OpenI2C( MASTER, Slew );
256     // Now, set the speed in SSPADD.
257     SSPADD = I2C_Speed;
258     fprintf( _H_USART, "Starting.\n" );
259     // initialize the extension device
260     error = initExtension();
261     if( error == -2 ) {
262         fprintf( _H_USART, (const far rom char*) "Init Failed.  NOTACK\n" );
263     } else if( error  ) {
264         fprintf( _H_USART, "Init Failed. Write Collision\n" );
265     } else {
266         error = retrieveType( values );
267         if( ! error ) {
268             printDeviceType( values );
269             delay1sec();
270             delay1sec();
271             delay1sec();
272             delay1sec();
273             delay1sec();
274 
275             while( 1 ) {
276                 // take reading, convert, and display every second
277                 error = retrieveExtensionData( values );
278                 if( error ) {
279                     fprintf( _H_USART, "clear\n" );
280                     fprintf( _H_USART, fail );
281                 } else {
282                     convertRawValues( values, unpackedValues );
283                     printValues( unpackedValues );
284                 }
285                 delay1sec();
286             }
287         }
288     }
289 
290     fprintf( _H_USART, "I'm not doing anything right now...\n" );
291     // init error zone
292     while(1) {
293         Nop();
294     }
295     
296 }

syntax highlighted by Code2HTML, v. 0.9.1

 

 

 

 

Update: Closer

On the advice of Telefox, I’ve updated the code to capture the Z Button before the C Button.  I’ve changed the code to print that information in reverse order as well.  The Z button now works.  The C button now does not.  I need to look at how I gather the data.  More later.

 

Solved

Telefox’s advice helped me find the bug.  Somewhere along the line, I shortened the length of my unpackedBytes array to 6.  I was getting a junk value for the 7th byte in the print function.  Like most bugs, it was something stupid.

Thanks to Telefox and everyone for the help!

Grats on the fix idoneous, I

Grats on the fix idoneous, I didn’t think you were far off tracking the bug down =)