September 08, 2010
by frankenclaw1
|
I'm trying to read the data from a wii nunchuck with my atmega168 nerdkit.
I've looked at a lot of guides online explaining to me how I2C works and tried reading the TWI information in the atmega168's datasheet but cannot get anything to work. I haven't been using my microcontroller very long and I'm just wondering if someone could at least show me a sample code that works with the atmega168 to check out and play around with. Anything will help! |
September 09, 2010
by Ralphxyz
|
A quick search of the Nerdkit forum for twi and I2C comes up with this plus others.
And Humberto gives this WikiPedia link
So it is not explicit but it is a start besides trying to decipher the datasheet.
I picked up a Wii Nunchuck at a yardsale for 50ยข and wanted to connect it to my Nerdkit so please keep us posted on how and what you do.
Ralph |
September 18, 2010
by Rick_S
|
Well, I played around with some code and after a days worth of research and trial and error, I finally got the nunchuk working without the use of the arduino libraries.
I've successfully run this on both my original OEM nunchuks. There are 3 files needed for my program to run.
Nunchuck.c
twimaster.c
i2cmaster.h
As well as a slightly modified make file and the NK libraries in their usual places.
Here is the code and make file.
1st Nunchuck.c
#include <avr/io.h>
#include <inttypes.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "i2cmaster.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#define Nunchk_ADR 0xA4
int main(void)
{
uint8_t nc_data[6], btnc, btnz, temp,i;
uint16_t accx, accy, accz;
// initialize data array
for(i=0;i<6;i++)
{
nc_data[i]=0;
}
i2c_init(); // init I2C interface
lcd_init(); // fire up the LCD
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// Setup the Nunchuk for first time providing address to read
uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);
if(ret==1)
{
fprintf_P(&lcd_stream, PSTR("1st start bad"));
}
i2c_write(0x40);
i2c_write(0x00);
i2c_stop();
while(1){
// Start Nunchuck read by writing initiating a start then writing 0x00
uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);
if(ret==1)
{
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("2nd start bad"));
}
i2c_write(0x00); // Send Address to read from
i2c_stop();
delay_us(500); // Wait a bit before sending read request
// begin read request
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck();
}
nc_data[5]= i2c_readNak();
i2c_stop();
// Decode data
for (i = 0; i < 6; i++)
{
nc_data[i] = (nc_data[i] ^ 0x17) + 0x17;
}
//Display results
lcd_line_one();
btnz=0;
btnc=0;
if(nc_data[5] & 1) btnz=1;
if(nc_data[5]&(1<<1))btnc=1;
// Setup the 10 bit data for accelerometer readings.
// First shift the MSB's 2 places
accx = (nc_data[2]<<2);
accy = (nc_data[3]<<2);
accz = (nc_data[4]<<2);
// Then add the least significant two bits
// for X
temp = nc_data[5];
temp = ((temp&0b00001100)>>2);
accx += temp;
// for y
temp = nc_data[5];
temp = ((temp&0b00110000)>>4);
accy += temp;
// and for z
temp = nc_data[5];
temp = ((temp&0b11000000)>>6);
accz += temp;
// send the data to the LCD
lcd_home();
fprintf_P(&lcd_stream, PSTR("JoyX: %3u JoyY: %3u"),nc_data[0],nc_data[1]);
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("Acc X:%4d Y:%4d"),accx,accy);
lcd_line_three();
fprintf_P(&lcd_stream, PSTR("Acc Z:%4d"),accz);
lcd_line_four();
fprintf_P(&lcd_stream, PSTR("Button Z:%2u C:%2u"),btnz,btnc);
}
for(;;);
return 0;
}
Next the two library files from Peter Fleury (modified a little for the NK and Nunchuck)
twimaster.c
/*************************************************************************
* Title: I2C master library using hardware TWI interface
* Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
* File: $Id: twimaster.c,v 1.3 2005/07/02 11:14:21 Peter Exp $
* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3
* Target: any AVR device with hardware TWI
* Usage: API compatible with I2C Software Library i2cmaster.h
**************************************************************************/
#include <inttypes.h>
#include <compat/twi.h>
#include "i2cmaster.h"
/* define CPU frequency in Mhz here if not defined in Makefile */
#ifndef F_CPU
#define F_CPU 14745600UL
#endif
/* I2C clock in Hz */
#define SCL_CLOCK 300000L
/*************************************************************************
Initialization of the I2C bus interface. Need to be called only once
*************************************************************************/
void i2c_init(void)
{
/* initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1 */
TWSR = (0<<TWPS1) | (0<<TWPS0); /* no prescaler */
TWBR = ((F_CPU/SCL_CLOCK)-16)/2; /* must be > 10 for stable operation */
}/* i2c_init */
/*************************************************************************
Issues a start condition and sends address and transfer direction.
return 0 = device accessible, 1= failed to access device
*************************************************************************/
unsigned char i2c_start(unsigned char address)
{
uint8_t twst;
// send START condition
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN); // wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;
// send device address
TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);
// wail until transmission completed and ACK/NACK has been received
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;
return 0;
}/* i2c_start */
/*************************************************************************
Issues a start condition and sends address and transfer direction.
If device is busy, use ack polling to wait until device is ready
Input: address and transfer direction of I2C device
*************************************************************************/
void i2c_start_wait(unsigned char address)
{
uint8_t twst;
while ( 1 )
{
// send START condition
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) continue;
// send device address
TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);
// wail until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst == TW_MT_SLA_NACK )||(twst ==TW_MR_DATA_NACK) )
{
/* device busy, send stop condition to terminate write operation */
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
// wait until stop condition is executed and bus released
while(TWCR & (1<<TWSTO));
continue;
}
//if( twst != TW_MT_SLA_ACK) return 1;
break;
}
}/* i2c_start_wait */
/*************************************************************************
Issues a repeated start condition and sends address and transfer direction
Input: address and transfer direction of I2C device
Return: 0 device accessible
1 failed to access device
*************************************************************************/
unsigned char i2c_rep_start(unsigned char address)
{
return i2c_start( address );
}/* i2c_rep_start */
/*************************************************************************
Terminates the data transfer and releases the I2C bus
*************************************************************************/
void i2c_stop(void)
{
/* send stop condition */
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
// wait until stop condition is executed and bus released
while(TWCR & (1<<TWSTO));
}/* i2c_stop */
/*************************************************************************
Send one byte to I2C device
Input: byte to be transfered
Return: 0 write successful
1 write failed
*************************************************************************/
unsigned char i2c_write( unsigned char data )
{
uint8_t twst;
// send data to the previously addressed device
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits
twst = TW_STATUS & 0xF8;
if( twst != TW_MT_DATA_ACK) return 1;
return 0;
}/* i2c_write */
/*************************************************************************
Read one byte from the I2C device, request more data from device
Return: byte read from I2C device
*************************************************************************/
unsigned char i2c_readAck(void)
{
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
while(!(TWCR & (1<<TWINT)));
return TWDR;
}/* i2c_readAck */
/*************************************************************************
Read one byte from the I2C device, read is followed by a stop condition
Return: byte read from I2C device
*************************************************************************/
unsigned char i2c_readNak(void)
{
TWCR = (1<<TWINT) | (1<<TWEN);
while(!(TWCR & (1<<TWINT)));
return TWDR;
}/* i2c_readNak */
i2cmaster.h
#ifndef _I2CMASTER_H
#define _I2CMASTER_H 1
/*************************************************************************
* Title: C include file for the I2C master interface
* (i2cmaster.S or twimaster.c)
* Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
* File: $Id: i2cmaster.h,v 1.10 2005/03/06 22:39:57 Peter Exp $
* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3
* Target: any AVR device
* Usage: see Doxygen manual
**************************************************************************/
#ifdef DOXYGEN
/**
@defgroup pfleury_ic2master I2C Master library
@code #include <i2cmaster.h> @endcode
@brief I2C (TWI) Master Software Library
Basic routines for communicating with I2C slave devices. This single master
implementation is limited to one bus master on the I2C bus.
This I2c library is implemented as a compact assembler software implementation of the I2C protocol
which runs on any AVR (i2cmaster.S) and as a TWI hardware interface for all AVR with built-in TWI hardware (twimaster.c).
Since the API for these two implementations is exactly the same, an application can be linked either against the
software I2C implementation or the hardware I2C implementation.
Use 4.7k pull-up resistor on the SDA and SCL pin.
Adapt the SCL and SDA port and pin definitions and eventually the delay routine in the module
i2cmaster.S to your target when using the software I2C implementation !
Adjust the CPU clock frequence F_CPU in twimaster.c or in the Makfile when using the TWI hardware implementaion.
@note
The module i2cmaster.S is based on the Atmel Application Note AVR300, corrected and adapted
to GNU assembler and AVR-GCC C call interface.
Replaced the incorrect quarter period delays found in AVR300 with
half period delays.
@author Peter Fleury pfleury@gmx.ch http://jump.to/fleury
@par API Usage Example
The following code shows typical usage of this library, see example test_i2cmaster.c
@code
#include <i2cmaster.h>
#define Dev24C02 0xA2 // device address of EEPROM 24C02, see datasheet
int main(void)
{
unsigned char ret;
i2c_init(); // initialize I2C library
// write 0x75 to EEPROM address 5 (Byte Write)
i2c_start_wait(Dev24C02+I2C_WRITE); // set device address and write mode
i2c_write(0x05); // write address = 5
i2c_write(0x75); // write value 0x75 to EEPROM
i2c_stop(); // set stop conditon = release bus
// read previously written value back from EEPROM address 5
i2c_start_wait(Dev24C02+I2C_WRITE); // set device address and write mode
i2c_write(0x05); // write address = 5
i2c_rep_start(Dev24C02+I2C_READ); // set device address and read mode
ret = i2c_readNak(); // read one byte from EEPROM
i2c_stop();
for(;;);
}
@endcode
*/
#endif /* DOXYGEN */
/**@{*/
#if (__GNUC__ * 100 + __GNUC_MINOR__) < 304
#error "This library requires AVR-GCC 3.4 or later, update to newer AVR-GCC compiler !"
#endif
#include <avr/io.h>
/** defines the data direction (reading from I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_READ 1
/** defines the data direction (writing to I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_WRITE 0
/**
@brief initialize the I2C master interace. Need to be called only once
@param void
@return none
*/
extern void i2c_init(void);
/**
@brief Terminates the data transfer and releases the I2C bus
@param void
@return none
*/
extern void i2c_stop(void);
/**
@brief Issues a start condition and sends address and transfer direction
@param addr address and transfer direction of I2C device
@retval 0 device accessible
@retval 1 failed to access device
*/
extern unsigned char i2c_start(unsigned char addr);
/**
@brief Issues a repeated start condition and sends address and transfer direction
@param addr address and transfer direction of I2C device
@retval 0 device accessible
@retval 1 failed to access device
*/
extern unsigned char i2c_rep_start(unsigned char addr);
/**
@brief Issues a start condition and sends address and transfer direction
If device is busy, use ack polling to wait until device ready
@param addr address and transfer direction of I2C device
@return none
*/
extern void i2c_start_wait(unsigned char addr);
/**
@brief Send one byte to I2C device
@param data byte to be transfered
@retval 0 write successful
@retval 1 write failed
*/
extern unsigned char i2c_write(unsigned char data);
/**
@brief read one byte from the I2C device, request more data from device
@return byte read from I2C device
*/
extern unsigned char i2c_readAck(void);
/**
@brief read one byte from the I2C device, read is followed by a stop condition
@return byte read from I2C device
*/
extern unsigned char i2c_readNak(void);
/**
@brief read one byte from the I2C device
Implemented as a macro, which calls either i2c_readAck or i2c_readNak
@param ack 1 send ack, request more data from device<br>
0 send nak, read is followed by a stop condition
@return byte read from I2C device
*/
extern unsigned char i2c_read(unsigned char ack);
#define i2c_read(ack) (ack) ? i2c_readAck() : i2c_readNak();
/**@}*/
#endif
And Lastly the makeile
GCCFLAGS=-g -Os -Wall -mmcu=atmega168
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
AVRDUDEFLAGS=-c avr109 -p m168 -b 115200 -P com9
LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o twimaster.o
all: Nunchuck-upload
Nunchuck.hex: Nunchuck.c
make -C ../libnerdkits
avr-gcc ${GCCFLAGS} -o twimaster.o -c twimaster.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o Nunchuck.o Nunchuck.c ${LINKOBJECTS}
avr-objcopy -j .text -O ihex Nunchuck.o Nunchuck.hex
Nunchuck.ass: Nunchuck.hex
avr-objdump -S -d Nunchuck.o > Nunchuck.ass
Nunchuck-upload: Nunchuck.hex
avrdude ${AVRDUDEFLAGS} -U flash:w:Nunchuck.hex:a
This code will just display the data from the nunchuck on the LCD wired as per the standard NK guide.
This is the end result
Have fun...
Rick
|
September 19, 2010
by Rick_S
|
One other thing, I did try the code on a Non-Nintendo Nunchuk I have and it just gave me max values for all the data.
I have read there are minor differences that prevent some aftermarket hardware from working the same. My guess is it has something to do with either the I2C speed or the delay between certain stages of communication. For the OEM Nunchuks I changed the I2C speed from 400kHz to 300kHz and had to add a 500us delay at one spot to get them to work reliably. So if you have a non-OEM nunchuk that doesn't work with the code, try poking in some delays or changing the I2C speed. That may or may not get it working, but that's the fun part... figuring it all out! :D
Rick |
September 20, 2010
by Rick_S
|
If anyone else tries this, please let me know... If it does work for you, what are you planning for it? I'm thinking about trying 2 axis servo control. I saw a cool video on youtube with a guy controlling a web cam and it's mimicing his hand movements with the nunchuck.
I'm curious more than anything :D
Rick |
September 21, 2010
by jbremnant
|
Rick_S
I just came across this thread and it's now motivating me to complete my unfinished project. Thanks for posting the code. =)
I had gotten as far as reliably reading the first 3 bytes, but the last 3 bytes were all 0xFF's for some reason. I also suspected timing issues, but didn't dig in further.
If I can reproduce the same result, I'll you know.
I'll be also using OEM nunchuck. |
September 21, 2010
by Rick_S
|
Cool, let me know if it works out for you. I'd begun to think no-one was interested. The code performed flawlessly for me on both my OEM nunchucks. If you have any problems just let me know.
Rick |
September 21, 2010
by Ralphxyz
|
Well I also am very interested and hopefully will be doing this project, thank you.
I have not thought about this but my first question is wiring could you post a picture or schematic of how you have the nunchuk wired to the Nerdkit?
It probable will be this winter before I get around to doing this unless I can come up with a compelling need, maybe some other post about what others have done with the nunchuk will get me motivated.
Ralph |
September 21, 2010
by Rick_S
|
The Nunchuck was wired to the chip via the twi/i2c interface these are pins 27 & 28 it also connects to power and ground.
I found the wiring from this little adapter that is sold at a couple of electronics venues such as sparkfun.
Looking at those photos, you can see which connection is which from the indented side of the connector and figure what is what on the back side. I made my own connector like this so I didn't have to cut the plug off my nunchuck.
The clk line goes to pin 28, the dat line to pin 27, the gnd to ground and the pwr to (5v).
Keep in mind, the nunchuck is designed to run on 3.3v and while mine works fine at 5v, yours may not. I'm not responsible if something goes bad. Also, I've read the new black nunchucks will not work at 5v.
One other thing, be very careful if using an adapter like this as it would be very easy to get it upside down and reverse the voltage going into the nunchuck.
The only other wiring on the chip is the standard NK configuration for the initial setup with the LCD.
Try it... you'll like it :)
Rick |
September 25, 2010
by Rick_S
|
I thought I'd post some photo's of the wiring in case my descrptions aren't too good.
First, both sides of my home brew (pretty ugly but functional) connector to the Nunchuck plug.
Next Showing the connections from the Nunchuck to the Nerdkit ATMEGA168.
A Close Overhead View
A view from the pin 1 side of the chip
And lastly a view from overhead showing the LCD wiring
Hope that helps clarify if there were any questions.
Rick |
September 25, 2010
by Ralphxyz
|
Thanks Rick, I ordered a dual magnetic field device the other day that uses I2C I hope I can use your code.
It seems as if I should be able to after all input is input, I'll probable have to make it more specific to the device but it seems as if your code should be a good start. I might only have to change the LCD readings.
Where did you get your wires from they look neat.
Ralph |
September 25, 2010
by Rick_S
|
Got them on e-bay. They are flexable with solid tips and make breadboarding much more user friendly... They are a bit more money than standard solid jumpers though. |
September 26, 2010
by Rick_S
|
Well, I have the Nunchuck controlling two servo's now. Here is the modified code. The Servo outputs are connected to Pins 15 and 16 respectively (PB1 and PB2). Thanks go to the NK guys for the servo squirter code and Ted (Phrank916) for the two servo mod part. The two made the additions to this code relatively painless :D
Nunchuck.c
#include <avr/io.h>
#include <inttypes.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "i2cmaster.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#define Nunchk_ADR 0xA4
#define PWM_MIN 1300
#define PWM_MAX 4450
#define PWM_START 2765
// Modified from servo squirter program
void pwm_set(uint16_t x, uint16_t y) {
OCR1B = x;
OCR1A = y;
}
// Modified from servo squirter program
void pwm_init() {
// setup Timer1 for Fast PWM mode, 16-bit
ICR1 = 36864; // sets PWM to repeat pulse every 20.0ms
pwm_set(PWM_START,PWM_START);
TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<COM1A1);
TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);
// each count is 8/14745600 = 0.5425us.
// so 1.0ms = 1843.2
// 1.5ms = 2764.8
// 2.0ms = 3686.4
// 20.0ms = 36864
}
int main(void)
{
uint8_t nc_data[6], btnc, btnz, temp,i;
uint16_t accx, accy, accz;
uint16_t posx = PWM_START;
uint16_t posy = PWM_START;
// initialize data array
for(i=0;i<6;i++)
{
nc_data[i]=0;
}
// set PB1,PB2 as output
DDRB |= (1<<PB1) | (1<<PB2);
i2c_init(); // init I2C interface
lcd_init(); // fire up the LCD
// init PWM
pwm_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// Setup the Nunchuk for first time providing address to read
uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);
if(ret==1)
{
fprintf_P(&lcd_stream, PSTR("1st start bad"));
}
i2c_write(0x40); // Write the Address to the Nunchuck
i2c_write(0x00);
i2c_stop(); // Issue a stop on I2C bus
while(1){
// Start Nunchuck read by writing initiating a start then writing 0x00
uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);
if(ret==1)
{
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("2nd start bad"));
}
i2c_write(0x00); // Send Address to read from
i2c_stop(); // Issue a stop on I2C bus
delay_us(500); // Wait a bit before sending read request
// begin read request
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck();
}
nc_data[5]= i2c_readNak();
i2c_stop();
// Decode data
for (i = 0; i < 6; i++)
{
nc_data[i] = (nc_data[i] ^ 0x17) + 0x17;
}
// Determine Button Positions
btnz=0;
btnc=0;
if(nc_data[5] & 1) btnz=1;
if(nc_data[5]&(1<<1))btnc=1;
// Setup the 10 bit data for accelerometer readings.
// First shift the MSB's 2 places
accx = (nc_data[2]<<2);
accy = (nc_data[3]<<2);
accz = (nc_data[4]<<2);
// Then add the least significant two bits
// for X
temp = nc_data[5];
temp = ((temp&0b00001100)>>2);
accx += temp;
// for y
temp = nc_data[5];
temp = ((temp&0b00110000)>>4);
accy += temp;
// and for z
temp = nc_data[5];
temp = ((temp&0b11000000)>>6);
accz += temp;
//Display results
lcd_home();
fprintf_P(&lcd_stream, PSTR("JoyX: %3u JoyY: %3u"),nc_data[0],nc_data[1]);
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("Acc X:%4d Y:%4d"),accx,accy);
lcd_line_three();
fprintf_P(&lcd_stream, PSTR("Acc Z:%4d"),accz);
lcd_line_four();
fprintf_P(&lcd_stream, PSTR("Button Z:%2u C:%2u"),btnz,btnc);
if(btnz==0) // Only move if the button is pressed
{
posx=(accx*3)+PWM_MIN;
posy=(accy*3)+PWM_MIN;
pwm_set(posx,posy);
}
}
for(;;);
return 0;
}
I setup a short 2-1/2 minute video on youtube if you want to see the results...
Follow this link
Rick |
September 26, 2010
by Ralphxyz
|
Rick, that is great. Nice jump of combining the different projects.
Now what is the little board on the left with the capacitors?
Details please.
Ralph |
September 26, 2010
by Rick_S
|
That's just a 5V breadboard power supply. They come in very handy to power the projects.
Rick |
September 28, 2010
by jbremnant
|
Rick_S, the code you posted worked flawlessly. After some minor changes, it initialized the nunchuck correctly and read in 6 bytes without much trouble at every iteration.
This whole experiment spurred me to dig up my old project and try to figure out why the asynchronous (interrupt-based) TWI library didn't work. I had gotten a copy of it from arduino distribution and hacked around with it. After few hours of trial and error, I finally got it working. Here are some of the observations. (might not be entirely correct). I am not satisfied with the fix though. Putting in delays at specific places in the code to make I2C against nunchuck work seems rather hacky to me.
In any case, the details:
-
looks like asynchronous (interrupt-based) twi library is quirky to get it
working correctly with Wii Nunchuck. The library does cover the entire gamut
of I2C protocol features, but the interrupt handling routine is possibly too
expensive/slow (massive switch statement) to keep up with TWI speed greater
than 100kHz. Just a speculation though...
-
However, when twi_readFrom and twi_writeTo is invoked with the blocking "wait"
option, it didn't detect the status variable change fast enough when it came
out of the initial state. So I had to insert a bit of delay after this snippet
of code:
// wait for read operation to complete
while(TWI_MRX == twi_state){
continue;
}
delay_us(200);
-
I can't get this interrupt driven TWI to communicate reliably with TWI_FREQ
above 100kHz. Better stick with this low speed.
-
I tried using 2 different ways to initialize the nunchuck: the old way vs
the new way. Both methods seem to work fine. And decoding the bytes wasn't
necessary.
-
twi_readFrom was buggy. Actually, it was the Master Receiver section of the
interrupt handing routine that was busted. It was reading one more extra byte
than necessary:
// Master Receiver
case TW_MR_DATA_ACK: // data received, ack sent
// put byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
case TW_MR_SLA_ACK: // address sent, ack received
// ack if more bytes are expected, otherwise nack
// JB NOTE: you need -1 here because we want to send NACK _at_ the last byte, not after.
if(twi_masterBufferIndex < twi_masterBufferLength-1){
twi_reply(1);
}else{
twi_reply(0);
}
break;
case TW_MR_DATA_NACK: // data received, nack sent
// put final byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
// printf_P(PSTR("TW_MR_DATA_NACK TWDR=%d\r\n"), twi_masterBuffer[twi_masterBufferIndex-1]);
case TW_MR_SLA_NACK: // address sent, nack received
twi_stop();
break;
Here's the tarball of my rudimentary project files if you are interested. It's bare minimum to get your nunchuck working and displaying the values on LCD using interrupt-driven TWI.
And the twiasync code pasted for reference:
twiasync.h
#ifndef twi_h
#define twi_h
#include <inttypes.h>
// #define ATMEGA8
// for nerdkit
// for arduino
// #define CPU_FREQ 16000000L
#ifndef CPU_FREQ
#define CPU_FREQ 14745600L
#endif
#ifndef TWI_FREQ
// #define TWI_FREQ 400000L
#define TWI_FREQ 100000L
#endif
#ifndef TWI_BUFFER_LENGTH
#define TWI_BUFFER_LENGTH 16
#endif
#define TWI_READY 0
#define TWI_MRX 1
#define TWI_MTX 2
#define TWI_SRX 3
#define TWI_STX 4
void twi_init(void);
void twi_setAddress(uint8_t);
uint8_t twi_readFrom(uint8_t, uint8_t*, uint8_t);
uint8_t twi_writeTo(uint8_t, uint8_t*, uint8_t, uint8_t);
uint8_t twi_transmit(uint8_t*, uint8_t);
void twi_attachSlaveRxEvent( void (*)(uint8_t*, int) );
void twi_attachSlaveTxEvent( void (*)(void) );
void twi_reply(uint8_t);
void twi_stop(void);
void twi_releaseBus(void);
#endif
twiasync.c
// macro definitions are contained in : /usr/avr/include/util/twi.h
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <compat/twi.h>
#include "utils.h"
#include "twiasync.h"
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
uint8_t twi_slarw;
void (*twi_onSlaveTransmit)(void);
void (*twi_onSlaveReceive)(uint8_t*, int);
volatile uint8_t twi_masterBuffer[TWI_BUFFER_LENGTH];
volatile uint8_t twi_masterBufferIndex;
volatile uint8_t twi_masterBufferLength;
volatile uint8_t twi_txBuffer[TWI_BUFFER_LENGTH];
volatile uint8_t twi_txBufferIndex;
volatile uint8_t twi_txBufferLength;
volatile uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH];
volatile uint8_t twi_rxBufferIndex;
volatile uint8_t twi_state;
volatile uint8_t twi_error;
/*
* Function twi_init
* Desc readys twi pins and sets twi bitrate
* Input none
* Output none
*/
void twi_init(void)
{
// initialize state
twi_state = TWI_READY;
// initialize twi prescaler and bit rate
cbi(TWSR, TWPS0);
cbi(TWSR, TWPS1);
TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;
/* twi bit rate formula from atmega128 manual pg 204
SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
note: TWBR should be 10 or higher for master mode
It is 72 for a 16mhz Wiring board with 100kHz TWI */
// enable twi module, acks, and twi interrupt
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
// allocate buffers - dynamic allocation bad
/*
twi_masterBuffer = (uint8_t*) calloc(TWI_BUFFER_LENGTH, sizeof(uint8_t));
twi_txBuffer = (uint8_t*) calloc(TWI_BUFFER_LENGTH, sizeof(uint8_t));
twi_rxBuffer = (uint8_t*) calloc(TWI_BUFFER_LENGTH, sizeof(uint8_t));
*/
}
/*
* Function twi_slaveInit
* Desc sets slave address and enables interrupt
* Input none
* Output none
*/
void twi_setAddress(uint8_t address)
{
// set twi slave address (skip over TWGCE bit)
TWAR = address << 1;
}
/*
* Function twi_readFrom
* Desc attempts to become twi bus master and read a
* series of bytes from a device on the bus
* Input address: 7bit i2c device address
* data: pointer to byte array
* length: number of bytes to read into array
* Output number of bytes read
*/
uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length)
{
uint8_t i;
// ensure data will fit into buffer
if(TWI_BUFFER_LENGTH < length){
return 0;
}
// wait until twi is ready, become master receiver
while(TWI_READY != twi_state){
continue;
}
twi_state = TWI_MRX;
// reset error state (0xFF.. no error occured)
twi_error = 0xFF;
// initialize buffer iteration vars
twi_masterBufferIndex = 0;
twi_masterBufferLength = length;
// build sla+w, slave device address + w bit
twi_slarw = TW_READ;
twi_slarw |= address << 1;
// printf_P(PSTR("start read: %d\r\n"), length);
// send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
// wait for read operation to complete
while(TWI_MRX == twi_state){
continue;
}
delay_us(200);
if (twi_masterBufferIndex < length)
length = twi_masterBufferIndex;
// printf_P(PSTR("read count: %d\r\n"), twi_masterBufferIndex);
// copy twi buffer to data
for(i = 0; i < length; i++){
data[i] = twi_masterBuffer[i];
// printf_P(PSTR("buf[%d] = %d\r\n"), i, data[i]);
}
return length;
}
/*
* Function twi_writeTo
* Desc attempts to become twi bus master and write a
* series of bytes to a device on the bus
* Input address: 7bit i2c device address
* data: pointer to byte array
* length: number of bytes in array
* wait: boolean indicating to wait for write or not
* Output 0 .. success
* 1 .. length to long for buffer
* 2 .. address send, NACK received
* 3 .. data send, NACK received
* 4 .. other twi error (lost bus arbitration, bus error, ..)
*/
uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait)
{
uint8_t i;
// ensure data will fit into buffer
if(TWI_BUFFER_LENGTH < length){
return 1;
}
// wait until twi is ready, become master transmitter
while(TWI_READY != twi_state){
continue;
}
twi_state = TWI_MTX;
// reset error state (0xFF.. no error occured)
twi_error = 0xFF;
// initialize buffer iteration vars
twi_masterBufferIndex = 0;
twi_masterBufferLength = length;
// copy data to twi buffer
for(i = 0; i < length; i++){
twi_masterBuffer[i] = data[i];
}
// build sla+w, slave device address + w bit
twi_slarw = TW_WRITE;
twi_slarw |= address << 1;
// printf_P(PSTR("sending TWI start condition\r\n"));
// send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
// wait for write operation to complete
while(wait && (TWI_MTX == twi_state)){
continue;
}
// NOTE: wow, adding this delay here made nunchuck work.. wth?
delay_us(200);
// printf_P(PSTR("passed TWI start condition\r\n"));
if (twi_error == 0xFF)
return 0; // success
else if (twi_error == TW_MT_SLA_NACK)
return 2; // error: address send, nack received
else if (twi_error == TW_MT_DATA_NACK)
return 3; // error: data send, nack received
else
return 4; // other twi error
}
/*
* Function twi_transmit
* Desc fills slave tx buffer with data
* must be called in slave tx event callback
* Input data: pointer to byte array
* length: number of bytes in array
* Output 1 length too long for buffer
* 2 not slave transmitter
* 0 ok
*/
uint8_t twi_transmit(uint8_t* data, uint8_t length)
{
uint8_t i;
// ensure data will fit into buffer
if(TWI_BUFFER_LENGTH < length){
return 1;
}
// ensure we are currently a slave transmitter
if(TWI_STX != twi_state){
return 2;
}
// set length and copy data into tx buffer
twi_txBufferLength = length;
for(i = 0; i < length; ++i){
twi_txBuffer[i] = data[i];
}
return 0;
}
/*
* Function twi_attachSlaveRxEvent
* Desc sets function called before a slave read operation
* Input function: callback function to use
* Output none
*/
void twi_attachSlaveRxEvent( void (*function)(uint8_t*, int) )
{
twi_onSlaveReceive = function;
}
/*
* Function twi_attachSlaveTxEvent
* Desc sets function called before a slave write operation
* Input function: callback function to use
* Output none
*/
void twi_attachSlaveTxEvent( void (*function)(void) )
{
twi_onSlaveTransmit = function;
}
/*
* Function twi_reply
* Desc sends byte or readys receive line
* Input ack: byte indicating to ack or to nack
* Output none
*/
void twi_reply(uint8_t ack)
{
// printf_P(PSTR("TWI replying\r\n"));
// transmit master read ready signal, with or without ack
if(ack){
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
}else{
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
}
}
/*
* Function twi_stop
* Desc relinquishes bus master status
* Input none
* Output none
*/
void twi_stop(void)
{
// printf_P(PSTR("TWI stop\r\n"));
// send stop condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);
// wait for stop condition to be exectued on bus
// TWINT is not set after a stop condition!
while(TWCR & _BV(TWSTO)){
continue;
}
// update twi state
twi_state = TWI_READY;
}
/*
* Function twi_releaseBus
* Desc releases bus control
* Input none
* Output none
*/
void twi_releaseBus(void)
{
// printf_P(PSTR("TWI release bus\r\n"));
// release bus
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
// update twi state
twi_state = TWI_READY;
}
// SIGNAL(SIG_TWI)
// ISR(SIG_2WIRE_SERIAL,ISR_NAKED)
ISR(TWI_vect)
{
// printf_P(PSTR("interrupt. status is: 0x%x\r\n"), TW_STATUS);
switch(TW_STATUS) {
// All Master
case TW_START: // sent start condition
case TW_REP_START: // sent repeated start condition
// copy device address and r/w bit to output register and ack
TWDR = twi_slarw;
twi_reply(1);
break;
// Master Transmitter
case TW_MT_SLA_ACK: // slave receiver acked address
case TW_MT_DATA_ACK: // slave receiver acked data
// if there is data to send, send it, otherwise stop
if(twi_masterBufferIndex < twi_masterBufferLength){
// copy data to output register and ack
TWDR = twi_masterBuffer[twi_masterBufferIndex++];
twi_reply(1);
// printf_P(PSTR("TW_MT_DATA_ACK TWDR=%d\r\n"), twi_masterBuffer[twi_masterBufferIndex-1]);
}else{
twi_stop();
}
break;
case TW_MT_SLA_NACK: // address sent, nack received
twi_error = TW_MT_SLA_NACK;
twi_stop();
break;
case TW_MT_DATA_NACK: // data sent, nack received
twi_error = TW_MT_DATA_NACK;
twi_stop();
break;
case TW_MT_ARB_LOST: // lost bus arbitration
twi_error = TW_MT_ARB_LOST;
twi_releaseBus();
break;
// Master Receiver
case TW_MR_DATA_ACK: // data received, ack sent
// put byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
// printf_P(PSTR("TW_MR_DATA_ACK TWDR=%d\r\n"), twi_masterBuffer[twi_masterBufferIndex-1]);
case TW_MR_SLA_ACK: // address sent, ack received
// ack if more bytes are expected, otherwise nack
// printf_P(PSTR("bufferindex=%d, bufferlength=%d\r\n"), twi_masterBufferIndex, twi_masterBufferLength);
if(twi_masterBufferIndex < twi_masterBufferLength-1){
twi_reply(1);
}else{
twi_reply(0);
}
break;
case TW_MR_DATA_NACK: // data received, nack sent
// put final byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
// printf_P(PSTR("TW_MR_DATA_NACK TWDR=%d\r\n"), twi_masterBuffer[twi_masterBufferIndex-1]);
case TW_MR_SLA_NACK: // address sent, nack received
twi_stop();
break;
// TW_MR_ARB_LOST handled by TW_MT_ARB_LOST case
// Slave Receiver
case TW_SR_SLA_ACK: // addressed, returned ack
case TW_SR_GCALL_ACK: // addressed generally, returned ack
case TW_SR_ARB_LOST_SLA_ACK: // lost arbitration, returned ack
case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
// enter slave receiver mode
twi_state = TWI_SRX;
// indicate that rx buffer can be overwritten and ack
twi_rxBufferIndex = 0;
twi_reply(1);
break;
case TW_SR_DATA_ACK: // data received, returned ack
case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
// if there is still room in the rx buffer
if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
// put byte in buffer and ack
twi_rxBuffer[twi_rxBufferIndex++] = TWDR;
twi_reply(1);
}else{
// otherwise nack
twi_reply(0);
}
break;
case TW_SR_STOP: // stop or repeated start condition received
// put a null char after data if there's room
if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
twi_rxBuffer[twi_rxBufferIndex] = '\0';
}
// callback to user defined callback
twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);
// ack future responses
twi_reply(1);
// leave slave receiver state
twi_state = TWI_READY;
break;
case TW_SR_DATA_NACK: // data received, returned nack
case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
// nack back at master
twi_reply(0);
break;
// Slave Transmitter
case TW_ST_SLA_ACK: // addressed, returned ack
case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
// enter slave transmitter mode
twi_state = TWI_STX;
// ready the tx buffer index for iteration
twi_txBufferIndex = 0;
// set tx buffer length to be zero, to verify if user changes it
twi_txBufferLength = 0;
// request for txBuffer to be filled and length to be set
// note: user must call twi_transmit(bytes, length) to do this
twi_onSlaveTransmit();
// if they didn't change buffer & length, initialize it
if(0 == twi_txBufferLength){
twi_txBufferLength = 1;
twi_txBuffer[0] = 0x00;
}
// transmit first byte from buffer, fall
case TW_ST_DATA_ACK: // byte sent, ack returned
// copy data to output register
TWDR = twi_txBuffer[twi_txBufferIndex++];
// if there is more to send, ack, otherwise nack
if(twi_txBufferIndex < twi_txBufferLength){
twi_reply(1);
}else{
twi_reply(0);
}
break;
case TW_ST_DATA_NACK: // received nack, we are done
case TW_ST_LAST_DATA: // received ack, but we are done already!
// ack future responses
twi_reply(1);
// leave slave receiver state
twi_state = TWI_READY;
break;
// All
case TW_NO_INFO: // no state information
break;
case TW_BUS_ERROR: // bus error, illegal stop/start
twi_error = TW_BUS_ERROR;
twi_stop();
break;
}
}
And finally the picture of it running with twi interrupt: |
September 29, 2010
by Rick_S
|
That's funny, if I dropped my TWI speed down to 100KHz, my nunchucks would not respond. At least the OEM ones, I didn't try the non oem at that speed.
I'm glad to see someone else was able to get one going. If I'm not being too bold, what kind of project (if any) do you have in mind for it? I'm half tempted to buy one of the wireless nunchucks to see if I can get it to work. It could make for an interesting wireless control device.
Rick |
September 29, 2010
by Ralphxyz
|
Oh come on Rick you don't need to "buy" a wireless nunchuck!
Just add a ZigBee module to your existing Nerdkit and have it communicate with another Nerdkit for LCD output/readings or
maybe even send it to your PC (you can buy a Xstick for this part:-). Then you can learn Python and graphically display your movements.
Like this arduino nunchuck.
Of course you could always get a couple of the ATmel ATAVR128RFA1 MCU's and have one piece ZigBee modules.
That way I could copy what you do and I would not have to work so hard to do this.
Ralph |
September 29, 2010
by Rick_S
|
LOL Ralph I haven't looked into the zigbee stuff much. I don't really have a need. For the most part what ive been doing has been just for fun.
Rick |
September 29, 2010
by Ralphxyz
|
Rick, well you were thinking of "buying" a wireless nunchuck.
I had previously gotten a accelerometer and had asked on the forum about getting started with that. I have been interested in wireless connectivity and ZigBee has looked really attractive to me so you mentioning a wireless nunchuck got me thinking. How much would a wireless nunchuck cost? I just googled wireless nunchuck and see between $12.00 to $24.00 but they all integrate to the wii controller and we want to go to the Nerdkit. I had code that tied the wii controller to a pc using bluetooth, that was cool. I "think" I could build a wireless nunchuck one for $30 - $35.00, as a first try that would be reasonable.
It would be cool to have a actual need, right now everything I am working on is mainly feed by curiosity and my just wanting to learn.
I like the articulated camera mount and could put that together, of course that leads to more questions, like how big should the servos be to handle a actual camera? My camera weighs .5kg (1.125#). I would really want wireless control and would just have to figure out a way for my nerdkit to control my camera just servo pushing the shutter would be enough (for now).
Glad you got the humor, some people get very sensitive.
Ralph
|
September 29, 2010
by Rick_S
|
Technically, the nunchuck hooks up to the wii controller as well. That's why I thought the wireless version would be neat because I could interface the reciever with the nerdkit communicating the same way I do with the wired version and it would handle all the wireless communications between the nunchuck and itself.
Rick |
October 02, 2010
by esoderberg
|
Rick,
Thanks for the code, very helpful. Got it working as advertised in no time using an OEM nunchuck.
Eric S. |
October 03, 2010
by Rick_S
|
Glad to hear it worked out for you as well Eric. Do you have something specific in mind you want to do with it?
Rick |
October 03, 2010
by Rick_S
|
Ralph,
Like you said earlier...
"right now everything I am working on is mainly fed by curiosity and my just wanting to learn."
I have to agree I'm pretty much the same. When I saw this thread it caught my attention. I thought, Hey, I have a few Wii Nunchucks around why not try and see if I can make it work.
That's how almost all my projects have been. Something catches my eye, whether here or some other place online, and I dig into it until I can make it work. Not always the most straightforward way, not always the cleanest code, but often figured out.
Then if it's applicable I try to pass what I learned along the way to the community. I visit here nearly every day... even on vacation sometimes. I've grown to love this community and really hope to see it continue to grow.
Rick |
October 04, 2010
by Ralphxyz
|
Rick, I can not get your code to compile.
I get a error from the makefile:
miniMac:nunchuck Me$ make
makefile:9: *** missing separator. Stop.
miniMac:nunchuck Me$
I copied your makefile from this thread.
The first time I ran it the "missing separator" was on line 11 subsequent tries are on line 9.
I am able to compile other programs so I do not "think" it is a board wiring problem.
Any ideas?
Ralph |
October 04, 2010
by jbremnant
|
Ralphxyz,
Seems like Makefile error. If you copy and pasted the Makefile content, you might have problems with tabs getting translated to spaces.
Makefile needs tabs in front of the commands. For example, you will need tab in the 4 lines following Nunchuck.hex: Nunchuck.c.
Nunchuck.hex: Nunchuck.c
make -C ../libnerdkits
avr-gcc ${GCCFLAGS} -o twimaster.o -c twimaster.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o Nunchuck.o Nunchuck.c ${LINKOBJECTS}
avr-objcopy -j .text -O ihex Nunchuck.o Nunchuck.hex
Hope that helps. |
October 04, 2010
by Ralphxyz
|
Thanks jbremnant, I made sure there were tabs. now the missing separator is from line 5.
I believe others have run Rick's code without complaint, strange.
Ralph |
October 04, 2010
by Ralphxyz
|
I modified a working makefile copying Rick's modifications and using a 328p mcu:
# Nunchuck328
GCCFLAGS=-g -Os -Wall -mmcu=atmega328p
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
AVRDUDEFLAGS= -c avr109 -p m328p -F -b 115200 -P /dev/cu.PL2303-0000101D # USB1
#AVRDUDEFLAGS= -vvv -c avr910 -p m328p -F -b 115200 -P /dev/cu.PL2303-0000205D # USB2
LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o twimaster.o
all: Nunchuck-upload
Nunchuck.hex: Nunchuck.c
make -C ../libnerdkits
avr-gcc ${GCCFLAGS} -o twimaster.o -c twimaster.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o Nunchuck.o Nunchuck.c ${LINKOBJECTS}
avr-objcopy -j .text -O ihex Nunchuck.o Nunchuck.hex
Nunchuck.ass: Nunchuck.hex
avr-objdump -S -d Nunchuck.o > Nunchuck.ass
tempsensor-upload: Nunchuck.hex
avrdude ${AVRDUDEFLAGS} -U flash:w:Nunchuck.hex:a
With this makefile I get this error:
miniMac:nunchuck328 Me$ make
make: *** No rule to make target `Nunchuck-upload', needed by `all'. Stop.
miniMac:nunchuck328 Me$
Ralph |
October 04, 2010
by Ralphxyz
|
Duh I had missed renaming tempsensor to Nunchuck on the next to last line.
It looks like it compiled now to see it running.
Ralph |
October 04, 2010
by Ralphxyz
|
Yahoo!! It works, it actually works. After getting a working makefile it works out of the box!
Here is the makefile for a ATmega328p mcu working with a Mac mini:
# Nunchuck328
GCCFLAGS=-g -Os -Wall -mmcu=atmega328p
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
AVRDUDEFLAGS= -c avr109 -p m328p -F -b 115200 -P /dev/cu.PL2303-0000101D # USB1
#AVRDUDEFLAGS= -vvv -c avr910 -p m328p -F -b 115200 -P /dev/cu.PL2303-0000205D # USB2
LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o twimaster.o
all: Nunchuck-upload
Nunchuck.hex: Nunchuck.c
make -C ../libnerdkits
avr-gcc ${GCCFLAGS} -o twimaster.o -c twimaster.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o Nunchuck.o Nunchuck.c ${LINKOBJECTS}
avr-objcopy -j .text -O ihex Nunchuck.o Nunchuck.hex
Nunchuck.ass: Nunchuck.hex
avr-objdump -S -d Nunchuck.o > Nunchuck.ass
Nunchuck-upload: Nunchuck.hex
avrdude ${AVRDUDEFLAGS} -U flash:w:Nunchuck.hex:a
In studying this project and the i2c/twi protocol a lot of new projects using i2c come to mind.
First up would be a led cube with the nunchuck controlling the leds.
Of course building a wireless nunchuck would be cool, I would like to use the nunchuck connected to the Nerdkit and then connect to another Nerdkit or a pc using zigbee or any RF method for that matter.
Thanks so much Rick for posting this project. Now I will do the interrupt driven method.
What does
1st start bad
2nd start bad
mean?
I just let the program run with out moving the nunchuck while I was typing this out.
Ralph |
October 04, 2010
by Rick_S
|
I put those there as messages in case for some reason the I2C didn't initialize properly either in the 1st initial writing of the address stage, or the setup for reading stage. They were mainly put there for trouble shooting so I could better locate where the failure was.
Rick |
October 04, 2010
by Ralphxyz
|
jbremnant, I tried your interrupt code but it fails to compile.
miniMac:nunchuck328 Me$ cd ../nunchuck_async
miniMac:nunchuck_async Me$ make
avr-gcc -g -Os -Wall -mmcu=atmega168 -DCPU_FREQ=14745600 -DTWI_FREQ=100000 -DF_CPU=14745600 -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -c -o utils.o utils.c
avr-gcc -g -Os -Wall -mmcu=atmega168 -DCPU_FREQ=14745600 -DTWI_FREQ=100000 -DF_CPU=14745600 -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -c -o twiasync.o twiasync.c
twiasync.c: In function '__vector_24':
twiasync.c:429: warning: passing argument 1 of 'twi_onSlaveReceive' discards qualifiers from pointer target type
avr-gcc -g -Os -Wall -mmcu=atmega168 -DCPU_FREQ=14745600 -DTWI_FREQ=100000 -DF_CPU=14745600 -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -o wiink.o wiink.c utils.o twiasync.o
avr-objcopy -j .text -j .data -O ihex wiink.o wiink.hex
miniMac:nunchuck_async Me$
That's all I get.
Ralph |
October 06, 2010
by jbremnant
|
Hi Ralph,
Glad to know you got the original Makefile running.
My Makefile, however, is different from Rick's.
Seeing the output, looks like the code compiled correctly. What you see is just a warning. I think there's some implicit casting going on somewhere, but the code did compile. You can verify it by looking for wiink.hex in the same dir.
Now you just have to upload it to the chip by running:
make upload |
October 07, 2010
by Ralphxyz
|
jbremnant, thanks your makefile is really different than Ricks but there is a similar flow to it so I think my modifications are complete, here is the modified makefile for a Mac compile:
TARGET=wiink
PORT=/dev/cu.PL2303-0000101D
MCU = atmega328p
CPU_FREQ = 14745600
# CPU_FREQ = 16000000
# wiimote does 400kHz, but nunchuck as a slave should be ok with 100kHz
TWI_FREQ = 100000
UPLOAD_RATE = 115200 # 57600
AVRDUDE_PROGRAMMER = stk500
GCCFLAGS=-g -Os -Wall -mmcu=$(MCU) -DCPU_FREQ=$(CPU_FREQ) -DTWI_FREQ=$(TWI_FREQ) -DF_CPU=$(CPU_FREQ)
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
AVRDUDE = avrdude
AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
AVRDUDE_FLAGS=-V -F -c avr109 -p m328p -P $(PORT) -b $(UPLOAD_RATE) $(AVRDUDE_WRITE_FLASH)
AVRDUDE_FLAGS_ISP=-V -F -P usb -c avrispmkII -p m328p $(AVRDUDE_WRITE_FLASH)
# -c $(AVRDUDE_PROGRAMMER) -b $(UPLOAD_RATE)
all: $(TARGET).hex
upload: $(TARGET)-upload
uploadisp: $(TARGET)-uploadisp
utils.o: utils.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -c -o utils.o utils.c
twiasync.o: twiasync.c utils.o
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -c -o twiasync.o twiasync.c
$(TARGET).hex: $(TARGET).c twiasync.o utils.o
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o $(TARGET).o $(TARGET).c utils.o twiasync.o
avr-objcopy -j .text -j .data -O ihex $(TARGET).o $(TARGET).hex
$(TARGET).ass: $(TARGET).hex
avr-objdump -S -d $(TARGET).o > $(TARGET).ass
$(TARGET)-upload: $(TARGET).hex
./pulsedtr.py $(PORT)
$(AVRDUDE) $(AVRDUDE_FLAGS)
$(TARGET)-uploadisp: $(TARGET).hex
$(AVRDUDE) $(AVRDUDE_FLAGS_ISP)
One of these days I am going to start a thread about makefile so that I can learn what is going on and the options.
Now I have to get python running on my Mac before I can run your code.
Thanks again,
Ralph |
October 07, 2010
by jbremnant
|
Ralph,
Sorry about that python piece. I had put it there because arduino bootloader required it. Yeah, I was using the same makefile to program an arduino.
But since we are working with Nerdkit, you can simply ignore the pulsedtr.py and delete out that line. |
October 07, 2010
by Ralphxyz
|
WhooooWeeee, fantastic I have your interrupt driven code working also.
Hotdog, here is the modified makefile if one is using a Mac:
TARGET=wiink
PORT=/dev/cu.PL2303-0000101D
MCU = atmega328p
CPU_FREQ = 14745600
# CPU_FREQ = 16000000
# wiimote does 400kHz, but nunchuck as a slave should be ok with 100kHz
TWI_FREQ = 100000
UPLOAD_RATE = 115200 # 57600
AVRDUDE_PROGRAMMER = stk500
GCCFLAGS=-g -Os -Wall -mmcu=$(MCU) -DCPU_FREQ=$(CPU_FREQ) -DTWI_FREQ=$(TWI_FREQ) -DF_CPU=$(CPU_FREQ)
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
AVRDUDE = avrdude
AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
AVRDUDE_FLAGS=-V -F -c avr109 -p m328p -P $(PORT) -b $(UPLOAD_RATE) $(AVRDUDE_WRITE_FLASH)
AVRDUDE_FLAGS_ISP=-V -F -P usb -c avrispmkII -p m328p $(AVRDUDE_WRITE_FLASH)
# -c $(AVRDUDE_PROGRAMMER) -b $(UPLOAD_RATE)
all: $(TARGET).hex
upload: $(TARGET)-upload
uploadisp: $(TARGET)-uploadisp
utils.o: utils.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -c -o utils.o utils.c
twiasync.o: twiasync.c utils.o
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -c -o twiasync.o twiasync.c
$(TARGET).hex: $(TARGET).c twiasync.o utils.o
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o $(TARGET).o $(TARGET).c utils.o twiasync.o
avr-objcopy -j .text -j .data -O ihex $(TARGET).o $(TARGET).hex
$(TARGET).ass: $(TARGET).hex
avr-objdump -S -d $(TARGET).o > $(TARGET).ass
$(TARGET)-upload: $(TARGET).hex
#./pulsedtr.py $(PORT)
$(AVRDUDE) $(AVRDUDE_FLAGS)
$(TARGET)-uploadisp: $(TARGET).hex
$(AVRDUDE) $(AVRDUDE_FLAGS_ISP)
This is so interesting now I can compare Ricks code to yours in order to get some comprehension of exactly what is going on.
I have some more i2c/twi projects in mind plus a neat (at least I think so) project using the Nunchuck and a LED cube, I would like to get a visual representation of the Nunchuck's movement in 3D. Of course I'd like to strap on a gyro and maybe a compass to see how all that might work also.
This is so great, just yesterday as part of a thread on AVRfreaks I asked how one would set the crystal speed using the makefile instead of hardcoding it in your code (having a generic speed set routine) and now I see:
CPU_FREQ = 14745600
in your make file, so there ya go.
Thanks again to you and Rick.
Ralph |
October 07, 2010
by bretm
|
It's important to note that
CPU_FREQ = 14745600
in a makefile does nothing by itself. It's actually the -D flag on the avr-gcc command line that does the trick. The makefile just uses CPU_FREQ as an arbitrary variable name to make it easier to find and edit the value. It inserts it into GCCFLAGS as -DF_CPU, and then GCCFLAGS gets appended to the avr-gcc command-line options. The avr-gcc compiler sees the -DF_CPU command and assigns the value to the F_CPU pre-processor symbol. |
October 07, 2010
by Ralphxyz
|
Ah once again bretm, Thank You!! With my very limited knowledge about makefile that makes sense.
Ralph |
October 07, 2010
by Rick_S
|
jbremnant, I'm not sure about the build of avrdude for mac, but the build I'm using for PC has an arduino programmer option. If this is used for stk500 it does the dtr toggle before and after automatically. I've been playing around with this a bit on a couple of arduino boards I have.
Rick |
October 08, 2010
by jbremnant
|
@Ralphxyz,bretm
That's right. The defines are declared directly using -D flag via avr-gcc. And the source will check the existence of the define var by doing:
#ifndef F_CPU
#define F_CPU 14745600
#endif
It should come in handy when you want to override some defines and control the source behavior from your Makefile. You can create a whole system of machine dependent code compilation using this technique.
@Rick_S
I use linux almost exclusively for avr dev, and I haven't noticed the arduino programmer option. I might have been using older version of avrdude... Thanks for the note. :-) |
October 12, 2010
by esoderberg
|
Rick et al.,
Made a few minor adjustments to the code you posted, the most useful of which is the init portion.
With this init, the data is unencrypted, so the decryption loop is no longer needed.
It is posted below in case anyone wants the modification. It works
even when wired through the motion plus module with nunchuck attached, although it seems
to hang on the init occasionally.
Below that I've posted code that still needs some work. It is supposed to read data from the
wii motion plus module (3 axis gyro) interleaved with nunchuck data. My understanding of the
TWI is still a little shaky and I've tried to follow the gouge from several sites:
http://wiibrew.org/wiki/Wiimote/Extension_Controllers#Wii_Motion_Plus
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248889032/all
user name krulkip 1/4 way down first page has arduino wire code.
but either the init hangs up or I get constant data in all of the fields. Any guidance
would be appreciated.
Eric
Program for Nunchuck read only:
#define F_CPU 16000000
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "i2cmaster.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#define Nunchk_ADR 0xA4
int main(void)
{
uint8_t nc_data[6], btnc, btnz, temp, i;
int16_t accx, accy, accz, jx, jy;
// initialize data array
for(i=0;i<6;i++)
{
nc_data[i]=0;
}
// init I2C interface
i2c_init();
// start up the serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
printf_P(PSTR("\r\nSerial Comm Test "));
// Setup the Nunchuk for first time providing address to read
//The old way to initialize the extension was by writing the single encryption byte 0x00 to 0x(4)A40040,
//but that only works on Nintendo's own brand extensions
//With this method you must decrypt the extension bytes to read them.
//The new way to initialize the extension is by writing 0x55 to 0x(4)A400F0, then writing 0x00 to 0x(4)A400FB. It
//makes the extension type bytes unencrypted. This means that you no longer have to decrypt the extension bytes.
//**Above comment direct from wiibrew - code seems to work just the same with or without
// write of 0x00 to 0xFB
if(~(i2c_start(0xA4))) printf_P(PSTR("\r\nwrite 0xA4 OK")); else printf_P(PSTR("\r\ncrap, bad start"));
printf_P(PSTR("\r\n Initialising Wii Nunchuck ......."));
if(~(i2c_write(0xF0))) printf_P(PSTR("\r\nwrite 0xF0 OK")); else printf_P(PSTR("\r\ncrap"));
if(~(i2c_write(0x55))) printf_P(PSTR("\r\nwrite 0x55 OK")); else printf_P(PSTR("\r\ncrap"));
if(~(i2c_write(0xFB))) printf_P(PSTR("\r\nwrite 0xFB OK")); else printf_P(PSTR("\r\ncrap"));
if(~(i2c_write(0x00))) printf_P(PSTR("\r\nwrite 0x00 OK")); else printf_P(PSTR("\r\ncrap"));
i2c_stop();
while(1){
// Start Nunchuck read by writing initiating a start then writing 0x00
i2c_start(0xA4);
i2c_write(0x00); // Send Address to read from
i2c_stop();
delay_us(500); // Wait a bit before sending read request
// begin read request
i2c_start_wait(0xA4+1);
// read 6 bytes
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck();
}
nc_data[5]= i2c_readNak();
i2c_stop();
//Display results
lcd_line_one();
btnz=0;
btnc=0;
if(nc_data[5] & 1) btnz=1;
if(nc_data[5]&(1<<1))btnc=1;
jx = nc_data[0] -133;
jy = nc_data[1]-129;
// Setup the 10 bit data for accelerometer readings.
// First shift the MSB's 2 places
accx = (nc_data[2]<<2);
accy = (nc_data[3]<<2);
accz = (nc_data[4]<<2);
// Then add the least significant two bits
// for X
temp = nc_data[5];
temp = ((temp&0b00001100)>>2);
accx += (temp - 481);
accx = accx/2;
// for y
temp = nc_data[5];
temp = ((temp&0b00110000)>>4);
accy += (temp - 506);
accy = accy/2;
// and for z
temp = nc_data[5];
temp = ((temp&0b11000000)>>6);
accz += (temp - 505);
accz = accz/2;
// send the data to the LCD
//lcd_home();
//fprintf_P(&lcd_stream, PSTR("JoyX: %3u JoyY: %3u"),nc_data[0],nc_data[1]);
//lcd_line_two();
//fprintf_P(&lcd_stream, PSTR("Acc X:%4d Y:%4d"),accx,accy);
//lcd_line_three();
//fprintf_P(&lcd_stream, PSTR("Acc Z:%4d"),accz);
//lcd_line_four();
//fprintf_P(&lcd_stream, PSTR("Button Z:%2u C:%2u"),btnz,btnc);
// Print the current Joyx position to the serial port.
printf_P(PSTR("\r\nJoyX: %4d JoyY: %4d"), jx, jy);
printf_P(PSTR(" Acc X:%5.4d Y:%5.4d Z:%5.4d 1/100 g"),accx,accy,accz);
printf_P(PSTR(" Button Z:%2u C:%2u"),btnz,btnc);
}
for(;;);
return 0;
}
wiichuckplus program:
#define F_CPU 16000000
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "i2cmaster.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#define Nunchk_ADR 0xA4
//nn
int main(void)
{
uint8_t nc_data[6], btnc, btnz, temp, i;
int16_t accx, accy, accz, yaw, roll, pitch, yawinit, rollinit, pitchinit;
int16_t jx, jy;
// initialize data array
for(i=0;i<6;i++)
{
nc_data[i]=0;
}
pitch = 0;
roll = 0;
yaw = 0;
accx=0;
accy=0;
accz=0;
i2c_init(); // init I2C interface
// start up the serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
printf_P(PSTR("\r\nSerial Comm Test "));
// Setup the Nunchuk for first time providing address to read
if(~(i2c_start(0xA6))) printf_P(PSTR("\r\nStart OK ")); else printf_P(PSTR("\r\ncrap"));
printf_P(PSTR("\r\nPutting Wii in Motion plus/nunchuck pass through mode = 05 ........"));
delay_ms(500);
if(~(i2c_write(0xFE))) printf_P(PSTR("\r\nwrite 0xFE OK ")); else printf_P(PSTR("\r\ncrap"));
if(~(i2c_write(0x05))) printf_P(PSTR("\r\nwrite 0x05 OK ")); else printf_P(PSTR("\r\ncrap"));// 05 for pass through
delay_ms(500);
printf_P(PSTR("\r\n Initialising Wii Motion plus ......."));
if(~(i2c_write(0xF0))) printf_P(PSTR("\r\nwrite 0xF0 OK")); else printf_P(PSTR("\r\ncrap"));
if(~(i2c_write(0x55))) printf_P(PSTR("\r\nwrite 0x55 OK")); else printf_P(PSTR("\r\ncrap"));
i2c_stop();
delay_ms(500);
// Setup the Nunchuk for first time providing address to read
printf_P(PSTR("\r\n Set reading address ......."));
if(~(i2c_start(0xA4))) printf_P(PSTR("\r\n Start at 0xA4 address OK ")); else printf_P(PSTR("\r\ncrap"));
if(~(i2c_write(0x08))) printf_P(PSTR("\r\nwrite 0x08 OK ")); else printf_P(PSTR("\r\ncrap"));
i2c_stop();
// pending init routine, use these values for zero rotation rate data points
yawinit = 8063;
rollinit = 8063;
pitchinit = 8063;
while(1){
delay_us(5000);
printf_P(PSTR("\rtest"));
// Start Nunchuck read by writing initiating a start then writing 0x00
i2c_start(Nunchk_ADR);
i2c_write(0x08); // Send Address to read from
i2c_stop();
delay_us(500); // Wait a bit before sending read request
// begin read request
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck();
}
nc_data[5]= i2c_readNak();
i2c_stop();
//Display results
if (~(nc_data[5]&0b10)) { // if bit one of byte 5 = 0 then getting wiichuch pass through data
printf_P(PSTR("joystick/accell data"));
lcd_line_one();
btnz=0;
btnc=0;
if(nc_data[5] & (1<<2)) btnz=1;
if(nc_data[5]&(1<<3))btnc=1;
jx = nc_data[0] -133;
jy = nc_data[1]-129;
// Setup the 10 bit data for accelerometer readings.
// First shift the MSB's 2 places
accx = (nc_data[2]<<2);
accy = (nc_data[3]<<2);
accz = (nc_data[4]<<2);
// Then add the least significant bits
// for X
temp = nc_data[5];
temp = ((temp&0b00010000)>>3);
accx += (temp - 478);
accx = accx/2;
// for y
temp = nc_data[5];
temp = ((temp&0b00100000)>>4);
accy += (temp - 506);
accy = accy/2;
// and for z
temp = nc_data[5];
temp = ((temp&0b11000000)>>5);
accz = (accz&0b1111111110);
accz += (temp - 505);
accz = accz/2;
// end of pass through wiichuck data read
}
// send the data to the LCD
//lcd_home();
//fprintf_P(&lcd_stream, PSTR("JoyX: %3u JoyY: %3u"),nc_data[0],nc_data[1]);
//lcd_line_two();
//fprintf_P(&lcd_stream, PSTR("Acc X:%4d Y:%4d"),accx,accy);
//lcd_line_three();
//fprintf_P(&lcd_stream, PSTR("Acc Z:%4d"),accz);
//lcd_line_four();
//fprintf_P(&lcd_stream, PSTR("Button Z:%2u C:%2u"),btnz,btnc);
// Print the current Joyx position to the serial port.
printf_P(PSTR("\r\nJoyX: %3d JoyY: %3d"), jx, jy);
printf_P(PSTR(" Acc X:%4d Y:%4d Z:%4d"),accx,accy,accz);
printf_P(PSTR(" Button Z:%2u C:%2u"),btnz,btnc);
printf_P(PSTR(" Pitch:%4d Roll:%4d Yaw:%4d"), pitch, roll, yaw);
if (nc_data[5]&0b10) { // if bit one of byte 5 = 1 then getting motion plus data
printf_P(PSTR("rotation data"));
yaw = (((nc_data[3]<<6)&0b11111100) + nc_data[0]);
roll = (((nc_data[4]<<6)&0b11111100) + nc_data[1]);
pitch = (((nc_data[5]<<6)&0b11111100) + nc_data[2]);
yaw = ((yaw-yawinit)/13.768);
if (~(nc_data[3]&(1<<1))) yaw = yaw*(50/11);
roll = ((roll-rollinit)/13.768);
if (~(nc_data[4]&(1<<1))) roll = roll*(50/11);
pitch = ((pitch-pitchinit)/13.768);
if (~(nc_data[3]&1)) pitch = pitch*(50/11);
//end of motion plus data read
}
}
for(;;);
return 0;
}
|
October 13, 2010
by jbremnant
|
Nice, thanks for the post esoderberg!
It's good to see folks contributing and spreading the knowledge on a particular topic.
I checked out the arduino forum. Ironically, I found the use of Kalman filter more interesting than getting the raw data out of motion plus sensor. :-)
Perhaps the nerdkits staff can expand/elaborate on the use of Kalman filter in smoothing out the noise from sensor readings - maybe a new tutorial? |
October 13, 2010
by Ralphxyz
|
esoderberg, how are you connecting the wii_motion_plus to the nerdkit?
Ralph |
October 13, 2010
by Rick_S
|
The motion plus would connect the same as the nunchuck. I don't have one of those to play with so I couldn't experiment with code for it. I'm glad to see someone has though.
Rick |
October 13, 2010
by Ralphxyz
|
Oh that's right, I have a couple of the motion plus but have not played with them for a year or so, duh.
In thinking about connecting the nunchuck to a led cube to illustrate 3D motion I thought it would be cool to have a gyro so that I could also show direction. So apparently that part is done now I just need to build the cube and then do the code. I am going to do the led array first, I'll be able to get 2D from that.
Ralph |
October 15, 2010
by Ralphxyz
|
esoderberg, I installed your nunchukplus code but after programing all I get is the two black bars when switching back to run mode.
I am using a ATmega328 with the modified make file listed above.
I am able to load other programs.
Ralph |
October 15, 2010
by esoderberg
|
Ralph,
I still haven't been able to get the pass through mode to work either. What is complicating my trouble shooting is that the program hangs up during the init portion, but not in a reliably repeatable fashion. Even the nunchuck only code, which is basically working, seems to hang up occasionally (and not always at the same point in the code) during the init portion as well, although after a few resets it usually works fine, and once the init is complete and the rest of the program is running, all of the data comes without interrupt and is valid. One top of that, while I fully understand the protocol for pulling the data off the 6 bytes once it is loaded onto nc_data, I'm still not so clear on the TWI setup and wiimotion plus init. I'll post more code if I manage to get it working.
Eric |
October 16, 2010
by esoderberg
|
Finally got the pass through mode to work, but it is still a little fickle, ie I have to reset the chip usually a half dozen times or so for the init to work, but when it does work, all the data is good. I'm using a wiichuck to plug into the motion plus module so the connections to the chip seem like they should be pretty solid, but un-plugging and re-plugging the module in seems to change the init results so there must be an issue there somewhere. Any how, the code below does work. Any suggestions on making it more robust welcome:
#define F_CPU 16000000
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "i2cmaster.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#define Nunchk_ADR 0xA4
int main(void)
{
uint8_t nc_data[6], btnc, btnz, temp, i, init;
int16_t accx, accy, accz, yaw, roll, pitch, yawinit, rollinit, pitchinit;
int16_t jx, jy;
// init I2C interface
i2c_init();
// initialize data array
for(i=0;i<6;i++)
{
nc_data[i]=0;
}
temp = 0;
init=0;
pitch = 0;
roll = 0;
yaw = 0;
accx=0;
accy=0;
accz=0;
jx = 0;
jy = 0;
btnc=0;
btnz=0;
DDRC = 0;
DDRC |= (1<<PC2) | (1<<PC3);// set up PC2 and 3 as output to power nunchuck
PORTC |= (1<<PC3) | (1<<PC4) | (1<<PC5);// turn on PC3 for + and set pull up for PC4,5 for clk and data input
PORTC &= ~(1<<PC2);// turn off PC2 for ground
delay_ms(100);
// start up the serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
printf_P(PSTR("\r\nSerial Comm Test "));
i2c_start_wait(0xA6);
printf_P(PSTR("\r\nPutting Wii in Motion plus/nunchuck pass through mode = 05 ........"));
if(i2c_write(0xFE)) printf_P(PSTR("\r\ncrap 0xFE")); else printf_P(PSTR("\r\n0xFE OK"));
delay_ms(100);
if(i2c_write(0x05)) printf_P(PSTR("\r\ncrap 0x05")); else printf_P(PSTR("\r\n0x05 OK"));
i2c_stop();
i2c_start_wait(0xA6);
printf_P(PSTR("\r\n Initialising Wii Motion plus ......."));
if(i2c_write(0xF0)) printf_P(PSTR("\r\ncrap 0xF0")); else printf_P(PSTR("\r\nwrite 0xF0 OK"));
delay_ms(100);
if(i2c_write(0x55)) printf_P(PSTR("\r\ncrap 0x55")); else printf_P(PSTR("\r\nwrite 0x55 OK"));
i2c_stop();
//routine to see if reading nunchuck and motion plus
i2c_start_wait(0xA4);
if(i2c_write(0xFA)) printf_P(PSTR("\r\nwrite 0xFA Crap")); else printf_P(PSTR("\r\nwrite 0xFA OK"));
i2c_stop();
delay_ms(100);
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
delay_us(500);
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck(); }
nc_data[5]= i2c_readNak();
i2c_stop();
for(i = 0; i < 6; i++)
{
temp += nc_data[i]; }
printf_P(PSTR("\r\nextension controller xID = 0x %4x"), temp);
delay_ms(5000);
if (temp==0xCB) printf_P(PSTR("\r\nmotion plus connected but not activated"));
if (temp==0xCE) printf_P(PSTR("\r\nmotion plus connected and activated"));
if (temp==0x00) printf_P(PSTR("\r\nmotion plus not connected"));
// end of configuration check
delay_ms(5000);
// Setup the Nunchuk for actual data address to read
printf_P(PSTR("\r\n Set data reading address ......."));
i2c_start_wait(0xA4);
if(i2c_write(0x08)) printf_P(PSTR("\r\ncrap 0x08")); else printf_P(PSTR("\r\n0x08 OK"));
i2c_stop();
// pending init routine, use these values for zero rotation rate data points
yawinit = 0;
rollinit = 0;
pitchinit = 0;
while(1){
delay_ms(1);
// Start Nunchuck read by writing initiating a start then writing 0x00
i2c_start(Nunchk_ADR);
// Send zero to request data
i2c_write(0x00);
i2c_stop();
delay_us(500); // Wait a bit before sending read request
// begin read request
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck();
}
nc_data[5]= i2c_readNak();
i2c_stop();
//Display results
if ((nc_data[5]&0x03)==0x00) { // if bit one of byte 5 = 0 then getting wiichuck pass through data
btnz=((nc_data[5] >> 2) & 1);
btnc=((nc_data[5] >> 3) & 1);
jx = nc_data[0] -133;
jy = nc_data[1]-129;
// Setup the 10 bit data for accelerometer readings.
// First shift the MSB's 2 places, then add the least significant bits
accx = (nc_data[2]<<2) + ((nc_data[5] >> 3) & 2);
accy = (nc_data[3]<<2) + ((nc_data[5] >> 4) & 2);
accz = (nc_data[4]<<2) + ((nc_data[5] >> 5) & 6);
accx = (accx-478)/2;
accy = (accy-506)/2;
accz = (accz - 505)/2;
// end of pass through wiichuck data read
// Print the current data to the serial port.
printf_P(PSTR("\r\n JoyX: %3d JoyY: %3d"), jx, jy);
printf_P(PSTR(" Acc X:%4d Y:%4d Z:%4d"),accx,accy,accz);
printf_P(PSTR(" Button Z:%2u C:%2u"),btnz,btnc);
}
// send the data to the LCD
//lcd_home();
//fprintf_P(&lcd_stream, PSTR("JoyX: %3u JoyY: %3u"),nc_data[0],nc_data[1]);
//lcd_line_two();
//fprintf_P(&lcd_stream, PSTR("Acc X:%4d Y:%4d"),accx,accy);
//lcd_line_three();
//fprintf_P(&lcd_stream, PSTR("Acc Z:%4d"),accz);
//lcd_line_four();
//fprintf_P(&lcd_stream, PSTR("Button Z:%2u C:%2u"),btnz,btnc);
if ((nc_data[5]&3)==2) { // if bit one of byte 5 = 1 then getting motion plus data
yaw = (((nc_data[3]<<6)&0xFC) + nc_data[0]);
roll = (((nc_data[4]<<6)&0xFC) + nc_data[1]);
pitch = (((nc_data[5]<<6)&0xFC) + nc_data[2]);
if (init<10) { yawinit += yaw/10;
rollinit += roll/10;
pitchinit += pitch/10; }
if (init<10) init += 1;
yaw = ((yaw-yawinit)/13.768);
if (~(nc_data[3]&2)) yaw = yaw*(50/11);
roll = ((roll-rollinit)/13.768);
if (~(nc_data[4]&2)) roll = roll*(50/11);
pitch = ((pitch-pitchinit)/13.768);
if (~(nc_data[3]&1)) pitch = pitch*(50/11);
//end of motion plus data read
printf_P(PSTR(" Pitch:%4d Roll:%4d Yaw:%4d"), pitch, roll, yaw);
}
}
for(;;);
return 0;
}
|
October 16, 2010
by Ralphxyz
|
esoderberg, in looking at a thread over on AVRfreaks
I see mentioned in passing that twi_init() returns a error code. I have no idea how to read the error code or what the error codes are but thought I might mention the fact. The AVRfreaks thread has noting to do with twi, the twi_init() reference just was mentioned by a very knowledgeable regular on the AVRfreaks forum.
Sorry I am just learning all of this so I cannot help with the error codes I just know you have been having problems with init.
Ralph |
October 16, 2010
by esoderberg
|
I think I've finally run out of steam on this thread, but will post the latest and last version of my code for reading the wiichuck and motion plus. Its a little more robust than last posted version, as it simply repeats the init portion until the wiichuck is set up correctly, plus I started to play around with integrating and differentiating the gyro outputs to give rotational displacement and accel as well as rotation rate.
#define F_CPU 16000000
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "i2cmaster.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#define Nunchk_ADR 0xA4
volatile uint32_t time=0;
//volatile uint32_t timecounter=0;
volatile uint32_t t1=0;
volatile uint32_t t2=0;
int32_t dt=0;
// Clock set up
void realtimeclock_setup() {
// setup Timer0:
// CTC (Clear Timer on Compare Match mode)
// TOP set by OCR0A register
TCCR0A |= (1<<WGM01);
// clocked from CLK/
// which is 16000000/64, or 250000 increments per second
TCCR0B = 0;
TCCR0B |= (1<<CS01) | (1<<CS00);
TCCR0B &= ~(1<<CS02);
// set TOP to 4
// because it counts 0, 1, 2,..23,24, 0, 1, 2 ...
// so 0 through 24 equals 25 events
OCR0A = 24;
// enable interrupt on compare event
// (250000 / 25 = 10000 per second)
TIMSK0 |= (1<<OCIE0A);
}
// the_time will store the elapsed time
// (10000 = 1 second)
//
// note that this will overflow eventually
//
// This variable is marked "volatile" because it is modified
// by an interrupt handler. Without the "volatile" marking,
// the compiler might just assume that it doesn't change in
// the flow of any given function (if the compiler doesn't
// see any code in that function modifying it -- sounds
// reasonable, normally!).
//
// But with "volatile", it will always read it from memory
// instead of making that assumption.
ISR(TIMER0_COMPA_vect){ // when Timer0 gets to its Output Compare value,
// elapsed time (0.0001 seconds per count).
time++;
if (time>32000) {time=0;
t2=0;}
}
int main(void)
{
uint8_t nc_data[6], btnc, btnz, temp, i, init, initj, wii_init=1;
int16_t accx, accy, accz;
int32_t yaw[5], roll[5], pitch[5], pitchaccel, yawd, rolld, pitchd, yawinit, rollinit, pitchinit;
int16_t jx, jy, jxinit, jyinit;
// init I2C interface
i2c_init();
// start up the serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
realtimeclock_setup();
printf_P(PSTR("\r\nSerial Comm Test1 "));
// initialize data array
for(i=0;i<6;i++)
{
nc_data[i]=0;
}
temp = 0;
init=0;
initj=0;
for(i=0;i<5;i++)
{
yaw[i]=0;
roll[i]=0;
pitch[i]=0;
}
accx=0;
accy=0;
accz=0;
jx = 0;
jy = 0;
jxinit=0;
jyinit=0;
btnc=0;
btnz=0;
yawd=0;
rolld=0;
pitchd=0;
DDRC = 0;
DDRC |= (1<<PC2) | (1<<PC3);// set up PC2 and 3 as output to power nunchuck
PORTC |= (1<<PC3) | (1<<PC4) | (1<<PC5);// turn on PC3 for + and set pull up for PC4,5 for clk and data input
PORTC &= ~(1<<PC2);// turn off PC2 for ground
printf_P(PSTR("\r\nSerial Comm Test2 "));
while (wii_init) {
// Setup the Nunchuk. Keeps trying until nunchuck and motion plus indicates connected and activated
i2c_start_wait(0xA6);
printf_P(PSTR("\r\nPutting Wii in Motion plus/nunchuck pass through mode = 05 ........"));
if(i2c_write(0xFE)) printf_P(PSTR("\r\ncrap 0xFE")); else printf_P(PSTR("\r\n0xFE OK"));
if(i2c_write(0x05)) printf_P(PSTR("\r\ncrap 0x05")); else printf_P(PSTR("\r\n0x05 OK"));
i2c_stop();
i2c_start_wait(0xA6);
printf_P(PSTR("\r\n Initialising Wii Motion plus ......."));
if(i2c_write(0xF0)) printf_P(PSTR("\r\ncrap 0xF0")); else printf_P(PSTR("\r\nwrite 0xF0 OK"));
delay_ms(100);
if(i2c_write(0x55)) printf_P(PSTR("\r\ncrap 0x55")); else printf_P(PSTR("\r\nwrite 0x55 OK"));
i2c_stop();
//routine to see if reading nunchuck and motion plus
i2c_start_wait(0xA4);
if(i2c_write(0xFA)) printf_P(PSTR("\r\nwrite 0xFA Crap")); else printf_P(PSTR("\r\nwrite 0xFA OK"));
i2c_stop();
delay_ms(200);
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
delay_us(500);
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck(); }
nc_data[5]= i2c_readNak();
i2c_stop();
temp = 0;
for(i = 0; i < 6; i++)
{
temp += nc_data[i]; }
printf_P(PSTR("\r\nextension controller xID = 0x %4x"), temp);
if (temp==0xCB) printf_P(PSTR("\r\nmotion plus connected but not activated"));
if (temp==0xCE) printf_P(PSTR("\r\nYES!!!!!!!!! motion plus connected and activated"));
if (temp==0x00) printf_P(PSTR("\r\nmotion plus not connected"));
if (temp==0xCE) wii_init = 0; else wii_init = 1;
// end of configuration check
delay_ms(100);
// Setup the Nunchuk for address to read output data
printf_P(PSTR("\r\n Set data reading address ......."));
i2c_start_wait(0xA4);
if(i2c_write(0x08)) printf_P(PSTR("\r\ncrap 0x08")); else printf_P(PSTR("\r\n0x08 OK"));
i2c_stop();}
// pending init routine, use these values for zero rotation rate data points
yawinit = 0;
rollinit = 0;
pitchinit = 0;
// turn on interrupt handler
sei();
while(1){
// Start Nunchuck read by writing initiating a start then writing 0x00
i2c_start(Nunchk_ADR);
// Send zero to request data
i2c_write(0x00);
i2c_stop();
delay_us(500); // Wait a bit before sending read request
// begin read request
i2c_start_wait(Nunchk_ADR+I2C_READ);
// read 6 bytes
for(i = 0; i < 5; i++)
{
nc_data[i]=i2c_readAck();
}
nc_data[5]= i2c_readNak();
i2c_stop();
//Display results
if ((nc_data[5]&0x03)==0x00) { // if bit one of byte 5 = 0 then getting wiichuck pass through data
//init center position of joystick
if (initj<13) { if (initj>2) { jxinit += nc_data[0];
jyinit += nc_data[1]; }
initj += 1;
}
btnz=((nc_data[5] >> 2) & 1);
btnc=((nc_data[5] >> 3) & 1);
jx = nc_data[0] - (jxinit/10);
jy = nc_data[1]- (jyinit/10);
// Setup the 10 bit data for accelerometer readings.
// First shift the MSB's 2 places, then add the least significant bits
accx = (nc_data[2]<<2) + ((nc_data[5] >> 3) & 2);
accy = (nc_data[3]<<2) + ((nc_data[5] >> 4) & 2);
accz = (nc_data[4]<<2) + ((nc_data[5] >> 5) & 6);
accx = (accx-517)/2.1;
accy = (accy-506)/2;
accz = (accz - 505)/2;
// end of pass through wiichuck data read
}
// send the data to the LCD
//lcd_home();
//fprintf_P(&lcd_stream, PSTR("JoyX: %3u JoyY: %3u"),nc_data[0],nc_data[1]);
//lcd_line_two();
//fprintf_P(&lcd_stream, PSTR("Acc X:%4d Y:%4d"),accx,accy);
//lcd_line_three();
//fprintf_P(&lcd_stream, PSTR("Acc Z:%4d"),accz);
//lcd_line_four();
//fprintf_P(&lcd_stream, PSTR("Button Z:%2u C:%2u"),btnz,btnc);
if ((nc_data[5]&2)==2) { // if bit one of byte 5 = 1 then getting motion plus data
yaw[0] = (((nc_data[3]&0xFC)<<6) + nc_data[0]);
roll[0] = (((nc_data[4]&0xFC)<<6) + nc_data[1]);
pitch[0] = (((nc_data[5]&0xFC)<<6) + nc_data[2]);
//find zero values at first go or hit of c and z buttons
if (init<93) { if (init>2) { yawinit += yaw[0];
rollinit += roll[0];
pitchinit += pitch[0];}
init += 1;
}
yaw[0] = ((yaw[0]) -(yawinit/90));
if (((nc_data[3]&0x02)==2));else { yaw[0] = yaw[0]*(50/11);
printf_P(PSTR("\r\n Yaw high speed mode"));}
roll[0] = ((roll[0])-(rollinit/90));
if (((nc_data[4]&0x02)==2)); else { roll[0] = roll[0]*(50/11);
printf_P(PSTR("\r\n Roll high speed mode"));}
pitch[0] = ((pitch[0])-(pitchinit/90));
if (((nc_data[3]&0x01)==1)); else{ pitch[0] = pitch[0]*(50/11);
printf_P(PSTR("\r\n Pitch high speed mode"));}
//update and shift array to hold latest 5 readings
for(i=0;i<4;i++)
{
yaw[4-i]=yaw[3-i];
pitch[4-i]=pitch[3-i];
}
//average last 5 readings
for(i=1;i<5;i++)
{
yaw[0] += yaw[i];
roll[0] += roll[i];
pitch[0] += pitch[i];}
yaw [0] = yaw[0]/5;
roll[0] = roll[0]/5;
pitch[0] = pitch[0]/5;
// calculate displacement
if (init>90){
t1 = time;
dt=(t1-t2);//in .1 ms increments
yawd += dt*yaw[0]/10000;
rolld += dt*roll[0]/10000;
pitchd += dt*pitch[0]/10000;
t2 = t1; }
else t2=time;
pitchaccel = (pitch[0]-((pitch[4]+pitch[3]+pitch[2]+pitch[1])/4))*10/dt;
//end of motion plus data read
}
// Print the current data to the serial port.
printf_P(PSTR("\r\n JX: %3d JY: %3d"), jx, jy);
printf_P(PSTR(" AX:%4d AY:%4d"),accx,accy);
printf_P(PSTR(" Button C: %2u"), btnc);
printf_P(PSTR(" Yaw:%4d"), yaw[0]);
printf_P(PSTR(" Paccel: %4d mrad/s*s"), pitchaccel);
printf_P(PSTR(" Pitch:%4d mrad/sec"), pitch[0]);
printf_P(PSTR(" Pitch displacement:%4d mrad"), pitchd);
printf_P(PSTR(" dt: %8d sec"), time);
}
for(;;);
return 0;
}
|
May 14, 2011
by kle8309
|
Thanks you esoderberg posting and wiibrew for the decryption code!
Thanks everyone else for their contribution also.
This makes my life easier !(However, somewhat less fun) |
April 24, 2012
by theomalon
|
I was wondering if you can us this and instead of controlling the servos, can you use the Wii remote to control an RC car instead? |
April 25, 2012
by Ralphxyz
|
Short answer yes, essentially instead of directing a servo (which is what you have on your RC car) directly, you just put your transmitter and receiver in between.
That would be fun keep us posted.
Ralph |
April 25, 2012
by theomalon
|
Thank you Ralph. I would like to use the Z button to go forward and C to go backwards. I'll post a new thread if I get it working and/or need more help. |
April 25, 2012
by Ralphxyz
|
Why not tilt it forward and back for forward and reverse and then tilt left and right for turning?
The buttons will work, maybe you are doing something else with the tilt?
Of course you might find your self accelerating when you turn but you could do a override in code.
Ralph |
April 25, 2012
by theomalon
|
Actually I thought about that a few mintues after I made that post so that is what I think I will do. |
April 30, 2012
by theomalon
|
So I am having problems with this makefile
GCCFLAGS=-g -Os -Wall -mmcu=atmega168
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
AVRDUDEFLAGS=-c avr109 -p m168 -b 115200 -P COM3
LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o twimaster.o
all: Nunchuck-upload
Nunchuck.hex: Nunchuck.c
make -C ../libnerdkits
avr-gcc ${GCCFLAGS} -o twimaster.o -c twimaster.c
avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o Nunchuck.o Nunchuck.c ${LINKOBJECTS}
avr-objcopy -j .text -O ihex Nunchuck.o Nunchuck.hex
Nunchuck.ass: Nunchuck.hex
avr-objdump -S -d Nunchuck.o > Nunchuck.ass
Nunchuck-upload: Nunchuck.hex
avrdude ${AVRDUDEFLAGS} -U flash:w:Nunchuck.hex:a
I keep getting makefile:9: *** missing separator. Stop.
Don't understand why I get this error when it is the same on other makefiles. I have also tired the other ones on this thread. I am using a windows 7 computer.
Thanks |
May 01, 2012
by Rick_S
|
All the command lines in a makefile begin with tab characters not spaces. If you edited the makefile (or sometimes copied) and either used spaces instead of tabs or the editor you used did it by itself, you will get this error.
makefile:9 *** missing separartor
means the error is on line 9 which is your 1st command line. I'm not sure what you used to edit the makefile, but don't use notepad in windows. It will mess it up. I usually use programmers notepad which is part of the WINAVR install.
Just remember, any indented line should begin with tabs not spaces, any blank line should be just that... blank with no spaces or tabs.
Hope this gets you going,
Rick |
May 01, 2012
by theomalon
|
Thanks Rick. That is what it was. I am not getting anything on my LCD though. I am using the 9V battery that comes with it. Do I need a better power supply? |
May 01, 2012
by Ralphxyz
|
Redo your wiring, after the forth or fifth time you will be amazed how fast and easy that is to do.
If you still have problems post some pictures.
Ralph |
May 01, 2012
by theomalon
|
Now it is outputting 1st start bad and 2nd start bad on the LCD. What does that mean? |
May 01, 2012
by Ralphxyz
|
I think if you look at the code that is rather self explanatory, but hopefully Rick will jump in and explain the specifics:
uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);
if(ret==1)
{
fprintf_P(&lcd_stream, PSTR("1st start bad"));
}
i2c_write(0x40);
i2c_write(0x00);
i2c_stop();
while(1){
// Start Nunchuck read by writing initiating a start then writing 0x00
uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);
if(ret==1)
{
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("2nd start bad"));
}
You are not communicating with the NunChuck.
So was it re-wiring that fixed the LCD problem or something else you did?
Ralph |
May 01, 2012
by theomalon
|
Yes I rewired it. Thank you. Now I'll have to figure out why it is not communicating with the nunchuck. Thanks again |
May 01, 2012
by theomalon
|
Even though I do not have a servo connected, it should still output the data on the LCD right? |
May 01, 2012
by Rick_S
|
That means it isn't communicating with the Nunchuk like Ralph said. Either it isn't initializing correctly or there is a wiring problem preventing communication. You could try the other code posted by esoderberg to see if it works, he initializes it differently. My code works on a genuine nunchuk but hasn't been tested on aftermarket.. If your's is genuine, it should work.
Rick |
May 01, 2012
by theomalon
|
I'm using the black oem nunchuck. Does that make a difference? |
May 02, 2012
by Rick_S
|
I don't have a black one, but I wouldn't think color would matter. |
May 02, 2012
by esoderberg
|
Theomalon,
I was running the posted code on both an original Nunchuck and an after market; it worked great on both. I also used an original Motion Plus module and an after market motion plus - in this case the code still worked but the actual gyro output was significantly different. The after market gyros I got were just not very consistent and required a new re-calibration for each one.
Eric |
May 03, 2012
by theomalon
|
So I got it to display what it should on the LCD but none of the data is changing when you move the joystick, controller, or push the buttons. Thanks again for everyone's help. |