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