CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Reading multi-bytes from an i2c slave - Wii Nunchuck
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
kd5uzz



Joined: 28 May 2006
Posts: 56

View user's profile Send private message

Reading multi-bytes from an i2c slave - Wii Nunchuck
PostPosted: Tue Nov 20, 2007 2:45 am     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 9:47 am     Reply with quote

Ok, here is where I can really confuse somebody Twisted Evil 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

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 1:18 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 3:24 pm     Reply with quote

I went and got another nunchuck. Same problem. Locks up when I send the address.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 3:45 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 4:43 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 5:14 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Nov 20, 2007 6:44 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Nov 21, 2007 11:17 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Nov 21, 2007 11:51 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Nov 21, 2007 4:42 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Nov 22, 2007 2:11 am     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Nov 22, 2007 9:24 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Nov 22, 2007 9:26 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Nov 22, 2007 9:38 pm     Reply with quote

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.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
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