|
|
View previous topic :: View next topic |
Author |
Message |
kd5uzz
Joined: 28 May 2006 Posts: 56
|
Reading multi-bytes from an i2c slave - Wii Nunchuck |
Posted: Tue Nov 20, 2007 2:45 am |
|
|
Hello,
I'm new to i2c, and I'm a little confused as to the correct command sequence. First off, my goal is to read data from the Wii Nunchuck. The i2c address is 0x52. There is an init sequence, (0x40 then 0x00) to set the mode of the nunchuck. After that you send 0x00 to read a 6byte block of data with 3 axis accelerometer, joystick and button data. My basis for this is http://www.windmeadow.com/node/42 . The code listed on the website is for an Arduino board, and the i2c code seems to be encapsulated, so I can't see how it actually reads.
So on to the question(s):
The address of the nunchuck is 0x52. Some other sites say that to read from the nunchuck you use address of 0x53. They seem to imply that you change the last bit of the address based on if you want to read or write to the device.
As I understand it this is what I need to do:
Code: |
include "16f876.h"
#fuses HS,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7)
#use I2C(master, sda=PIN_C4, scl=PIN_C3, FORCE_HW)
#define SLAVEADDRESS 0x52
void Main(){
int JoyX = 0, JoyY = 0;
int AccelX = 0, AccelY = 0, AccelZ = 0;
int Extra = 0;
printf("\n\rI2C Test for Wii Nunchuck.");
//init the nunchuck
i2c_start();
i2c_write(SLAVEADDRESS);
i2c_write(0x40);
i2c_write(0x00);
i2c_stop();
// done.
//read data
i2c_start();
i2c_write(SLAVEADDRESS);
JoyX = i2c_read(1);
JoyY = i2c_read(1);
AccelX = i2c_read(1);
AccelY = i2c_read(1);
AccelZ = i2c_read(1);
Extra = i2c_read(0);
i2c_stop();
}
|
But some sites show re-starts when reading from a slave, while most don't show multi bytes.
Is there a tutorial I can look at? I havn't found any good explanations of i2c conventions (when to ACK, when to use re-start, etc).
-Daniel |
|
|
rnielsen
Joined: 23 Sep 2003 Posts: 852 Location: Utah
|
|
Posted: Tue Nov 20, 2007 9:47 am |
|
|
Ok, here is where I can really confuse somebody so here goes...
Assuming you have the correct command sequence to write to/read from the nunchuck here is what's happening.
Code: |
// writing data to a slave
i2c_start();// tells the i2c bus that a Master is wanting to take control
i2c_write(address);// send the address of the Slave that the Master wants to talk to
i2c_write(data);// send whatever data, to the slave, that is appropriate
i2c_write(data1);// this can be continued as many times as the slave is expecting it
i2c_stop();// releases the bus from the current Master's control
// reading data from a slave
i2c_start();// ATTENTION Slaves! :twisted:
i2c_write(address + 1);// adding 1 to the address tells the slave that you will be reading data from it
variable1 = i2c_read(1);// read data from the slave (a '1' is default and sends and ACK(acknowledge) to the slave upon successful reception
variable2 = i2c_read();// remember, '1' is default
.
.
.
.
extra = i2c_read(0);// a '0' tells the slave that reading time is over and the Master doesn't want any more data
i2c_stop();// realease the bus |
IF, and I say IF, this is the proper sequence to use then you should be able to retrieve the data from the nunchuck. Some devices require a slightly different sequence, like eeproms, in order to communicate. Simply because a device, like the nunchuck, uses the I2C protocol it doesn't mean that it doesn't have a special sequence of commands that it wants to use.
It is advised to insert code that will handle things in case the slave does not send an ACK back. Things can get 'hung' if you don't handle problems that can happen, like being at a distance where the signal starts to fade and a portion of the data stream is dropped.
Clear as mud now? Good luck!
Ronald |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Tue Nov 20, 2007 1:18 pm |
|
|
So now I've hit another problem. Either my code is incorrect (possible, but its so simple I doubt it), or I've fried the nunchuck. I got confused when trying to figure out the nunchuck's pinout, and may have swapped a data line with a +3.3v. My code locks up when I send the address, almost like the nunchuck won't release whatever line it holds to tell the PIC it's busy. Unfortunatly the only way to know for sure is to get another nunchuck...[/b] |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Tue Nov 20, 2007 3:24 pm |
|
|
I went and got another nunchuck. Same problem. Locks up when I send the address. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Nov 20, 2007 3:45 pm |
|
|
Did you change the address in your 2nd routine so it looks like this ?
Quote: |
//read data
i2c_start();
i2c_write(SLAVEADDRESS + 1);
JoyX = i2c_read(1);
JoyY = i2c_read(1);
AccelX = i2c_read(1);
AccelY = i2c_read(1);
AccelZ = i2c_read(1);
Extra = i2c_read(0);
i2c_stop(); |
|
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Tue Nov 20, 2007 4:43 pm |
|
|
The Arduino example I found runs the nunchuck at 5v. I was trying to run it at 3.3v. When I run it at 5v it gets past the sending address code, but it doesn't return any data. I see the PIC's SCL and SDA lines sending a pulse via an o-scope.
My Code:
Code: |
#include "16f876.h"
#fuses HS,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7)
#use I2C(master,slow, sda=PIN_C4, scl=PIN_C3, FORCE_HW)
#define SLAVEADDRESS 0x52
void Main(){
int JoyX = 0, JoyY = 0;
int AccelX = 0, AccelY = 0, AccelZ = 0;
int Extra = 0;
int address = SLAVEADDRESS;
int result;
printf("\n\rI2C Test for Wii Nunchuck.");
output_high(PIN_B5);
printf("\n\rStarting I2C Transaction...");
//init the nunchuck
i2c_start();
printf("\n\rSending nunchuck address (%X)...",address);
i2c_write(address);
printf("\n\rSending init string...");
i2c_write(0x40);
i2c_write(0x00);
printf("\n\rStopping I2C bus...");
i2c_stop();
// done.
While(TRUE){
// printf("\n\rReading from Slave...");
// reading data from a slave
i2c_start(); // ATTENTION Slaves!
// printf("\n\rSending Address...%X",address +1);
i2c_write(address + 1); // adding 1 to the address tells the slave that you will be reading data from it
// printf("\n\rReading byte...");
JoyX = i2c_read(); // read data from the slave (a '1' is default and sends and ACK(acknowledge) to the slave upon successful reception
// printf("\n\rReading byte...");
JoyY = i2c_read(); // remember, '1' is default
// printf("\n\rReading byte...");
AccelX = i2c_read(); // remember, '1' is default
// printf("\n\rReading byte...");
AccelY = i2c_read(); // remember, '1' is default
// printf("\n\rReading byte...");
AccelZ = i2c_read(); // remember, '1' is default
// printf("\n\rReading last byte...");
extra = i2c_read(0); // a '0' tells the slave that reading time is over and the Master doesn't want any more data
// printf("\n\rStopping the I2C bus...");
i2c_stop(); // realease the bus
i2c_start();
i2c_write(address);
i2c_write(0x00);
i2c_stop();
delay_us(100);
// printf("\n\rRead complete!");
printf("\n\rJoyX: %D JoyY: %D AccelX: %D AccelY: %D AccelZ: %D Extra: $D",JoyX,JoyY,AccelX,AccelY,AccelZ,Extra);
}
}
|
output via rs232:
Code: |
I2C Test for Wii Nunchuck.\0A
Starting I2C Transaction...\0A0D
Sending nunchuck address (52)...\0A
Sending init string...\0A \0D
Stopping I2C bus...\0A \0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
JoyX: -1 JoyY: -1 AccelX: -1 AccelY: -1 AccelZ: -1 Extra: $D\0A\0D
|
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Nov 20, 2007 5:14 pm |
|
|
But that website says the following:
Quote: |
For the Arduino to communicate with the nunchuck, it must send
a handshake. So first send 2 bytes "0x40,0x00". Then send one
byte "0x00" each time you request data from the nunchuck. The
data from the nunchuck will come back in 6 byte chunks. |
He has this routine:
Code: | void
send_zero ()
{
Wire.beginTransmission (0x52); // transmit to device 0x52
Wire.send (0x00); // sends one byte
Wire.endTransmission (); // stop transmitting
} |
I believe that translates to this CCS code:
Code: | i2c_start();
i2c_write(0x52);
i2c_write(0x00);
i2c_stop(); |
This routine should be placed just after the init routine, but before
the read routine.
Also, it's best if you don't put printf statements after each call to an
i2c function. |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Tue Nov 20, 2007 6:44 pm |
|
|
I do that near the bottom of my code:
Code: |
i2c_start();
i2c_write(address);
i2c_write(0x00);
i2c_stop();
delay_us(100);
|
Granted, I do this AFTER the read, but if you look at his code he has a function called 'loop' that does the read (just after the init I guess) then calls 'send_zero' to send what I guess is the 'I want more data' command'. Then he delays for '100', although i don't know what unit (ms, us?). Maybe if I look at the 'Wire.requestFrom (0x52, 6);' code I'll be able to see what I'm doing wrong. |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Wed Nov 21, 2007 11:17 am |
|
|
Below is the code from the 'twi_readFrom' function, which is called from wire.requestfrom(). Seems rather straight forward, except for the bit where it sends the start, I'm not sure what all it is or-ing together. TWI_MRX must be master receive mode.
Code: |
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 1;
}
// wait until twi is ready, become master receiver
while(TWI_READY != twi_state){
continue;
}
twi_state = TWI_MRX;
// 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;
// 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;
}
// copy twi buffer to data
for(i = 0; i < length; ++i){
data[i] = twi_masterBuffer[i];
}
return 0;
}
|
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Nov 21, 2007 11:51 am |
|
|
I think the "wire." functions expect a 7-bit i2c address. In CCS, the
convention is to use an 8-bit address constant, in which the 7-bit address
is pre-shifted to the left by 1 bit.
So I think it's possible that the address of 0x52 should be changed
to be 0xA4. Then for an i2c read operation, this would be 0xA5.
Try that and see if it works. |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Wed Nov 21, 2007 4:42 pm |
|
|
No go. I've tried to mimic the functions they used on the Arduino board to see if I could spot any logic errors. New Code:
Code: |
#include "16f876.h"
#fuses HS,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7)
#use I2C(master,slow, sda=PIN_C4, scl=PIN_C3, FORCE_HW)
#define SLAVEADDRESS 0x52
void nunchuck_init();
void send_zero();
void Main(){
int JoyX = 0, JoyY = 0;
int AccelX = 0, AccelY = 0, AccelZ = 0;
int Extra = 0;
int address = SLAVEADDRESS;
int result;
printf("\n\rI2C Test for Wii Nunchuck.");
output_high(PIN_B5);
nunchuck_init();
printf("End INIT");
While(TRUE){
i2c_start(); // ATTENTION Slaves!
i2c_write(0xA4 + 1); // adding 1 to the address tells the slave that you will be reading data from it
JoyX = i2c_read(1); // read data from the slave (a '1' is default and sends and ACK(acknowledge) to the slave upon successful reception
JoyY = i2c_read(1); // remember, '1' is default
AccelX = i2c_read(1); // remember, '1' is default
AccelY = i2c_read(1); // remember, '1' is default
AccelZ = i2c_read(1); // remember, '1' is default
extra = i2c_read(0); // a '0' tells the slave that reading time is over and the Master doesn't want any more data
i2c_stop(); // realease the bus
send_zero();
delay_ms(100);
printf("\n\rJoyX: %U JoyY: %U AccelX: %U AccelY: %U AccelZ: %U Extra: %U",JoyX,JoyY,AccelX,AccelY,AccelZ,Extra);
}
}
void nunchuck_init(){
i2c_start();
i2c_write(0xA4);
i2c_write(0x40);
i2c_write(0x00);
i2c_stop();
}
void send_zero(){
i2c_start();
i2c_write(0xA4);
i2c_write(0x00);
i2c_stop();
}
|
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Nov 22, 2007 2:11 am |
|
|
Try the test program shown below. You should get output like this:
Quote: |
7b 7c 8a 67 69 eb
7b 7c 8a 67 69 eb
7b 7c 8a 67 69 fb
7b 7c 8a 67 69 eb
7b 7c 8a 67 69 eb
7b 7c 8a 67 69 eb
|
The code below uses software i2c on pins B4 and B5. Change the
pins to in the #use i2c() statement to match your own hardware.
Code: |
#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)
#use i2c(Master, sda=PIN_B5, scl=PIN_B4)
#define NUNCHUCK_I2C_WRITE_ADDR 0xA4
#define NUNCHUCK_I2C_READ_ADDR 0xA5
void nunchuck_init(void)
{
i2c_start();
i2c_write(NUNCHUCK_I2C_WRITE_ADDR);
i2c_write(0x40);
i2c_write(0x00);
i2c_stop();
}
//---------------------------------
void nunchuck_read_data(int8 *buffer)
{
i2c_start();
i2c_write(NUNCHUCK_I2C_WRITE_ADDR);
i2c_write(0);
i2c_stop();
delay_ms(1); // This delay is necessary.
i2c_start();
i2c_write(NUNCHUCK_I2C_READ_ADDR);
buffer[0] = i2c_read();
buffer[1] = i2c_read();
buffer[2] = i2c_read();
buffer[3] = i2c_read();
buffer[4] = i2c_read();
buffer[5] = i2c_read(0);
i2c_stop();
}
// This routine was only used to find the i2c address
// of the Nunchuck.
int8 get_ack_status(int8 address)
{
int8 status;
i2c_start();
status = i2c_write(address);
i2c_stop();
return(status);
}
//===================================
void main()
{
int8 i;
int8 buffer[6] = {0};
// This code is only used to find the i2c address of
// the Nunchuck. It is 0xA4 (in CCS "byte" format).
//for(i=0x50; i < 0xF0; i+=2)
// {
// printf("%x %x \n\r", i, get_ack_status(i));
// delay_ms(100);
// }
nunchuck_init();
delay_ms(100);
while(1)
{
nunchuck_read_data(buffer);
for(i = 0; i < sizeof(buffer); i++)
printf("%x ", buffer[i]);
printf("\n\r");
delay_ms(500);
}
} |
|
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Thu Nov 22, 2007 9:24 pm |
|
|
All I'm getting is 'ff ff ff ff ff ff'. I'm guessing you are running this with a wii nunchuck and it works? Maybe I'm hooking mine up wrong. I'm trying to avoid cutting the connector off, and am not sure I've got the connections correct. I'm not at the lab right now, I'm access the PC via VNC (and turning the hardware on/off via software, makes remote dev easy.) so I'm not able to look at it right now. Happy Turkey Day! |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Thu Nov 22, 2007 9:26 pm |
|
|
I should mention I changed your code just a bit, I changed the baud and clock, as well as the i2c pins. I could have the i2c pins wrong, I changed them the other day just to see if I had them wrong and I may have left them that way. I won't be able to make it to the lab to check until Sat. Thanks for the help! |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Nov 22, 2007 9:38 pm |
|
|
Yes, I cut the connector off. There are four wires and one ground
sheath. I connected the wires per the instructions on the link that
you posted. I didn't connect the ground braid to anything.
I am also running the unit at +3.1 volts. The reason for that voltage
is because I don't have a 3.3v regulator at my current location. So
I'm using three 1N914A diodes in series with the +5v to drop the
Nunchuck's voltage down to the 3v range. On the website, they say
that the unit is supposed to run at 3.3v and they wonder if running
it at +5v will shorten it's life. Yes, I sure think it would.
The unit apparently has internal pullups for SCL and SDA. You
don't need to add external pullups. Because it's running at 3.1v,
I'm using software i2c on Port B pins. Those pins have TTL input
levels, so they'll work fine with a 3v i2c device. The i2c pins on
Port C have Schmitt Trigger inputs, so they require 4v input levels.
They can't work with 3v i2c. It has to be 5v i2c. That's what they're
using on the website.
You've already gone through two units. I suggest you cut off the
connector and get it running. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|