Go Down

Topic: I2C problems between 2 ATmega328s (Read 1 time) previous topic - next topic

Magic Ross

Hi All,

I'm having some trouble communicating via I2C between 2 ATmega328s.

Here is the setup:

I have 2 ATmega328 chips.  I assign one the address 0x0a and the other 0x0b.

I can send commands from either chip to the other without any problems.  However, what I need to do is when a command is received, the chip needs to send a response.  This is where it falls over.  Through debugging, I can see this is the flow:

0x0b sends command "requestID" to 0x0a
0x0a receives "requestID" and tries to send "id1" back to 0x0b

This is where it stops.  0x0b never receives anything back.  And it seems that 0x0a never sends it.  I can't even get a return code from Wire.endTransmission() on 0x0a - it seems to crash on the endTransmission().

I've tried reversing the roles of each chip to rule out a faulty chip, but I have the same problem.  As mentioned, I can send multiple commands and they are all received OK, but as soon as I try to send a response back after receiving a command it crashes.  I have tried adding in delays between sending and receiving, but no luck.

A4 is connected to A4, and A5 to A5, both chips share the same ground.  These are the only two devices on the bus.  I have tried with and without pullup resistors. Any help?

Here is my code on device 0x0a:

Code: [Select]

#include <Wire.h>

String i2cBuffer="";
byte buffer=0x00;

void setup() {
  Serial.begin(9600);
  Wire.begin(0x0a);
  Wire.onReceive(receiveEvent);
}

void receiveEvent(int howMany){
  i2cBuffer="";
  while (Wire.available() > 0){
    buffer = Wire.receive();
    i2cBuffer=String(i2cBuffer + buffer);
  }
  processBuffer(i2cBuffer);
}


void processBuffer(String processCommand) {
  Serial.print("Command Received: '");
  Serial.print(processCommand);
  Serial.println("'");
  if (processCommand=="requestID") {
    sendCommand(0x0b,"id1");
  }
}
 
void sendCommand(byte device, String command) {
  char sendChar[32]="";
  command.toCharArray(sendChar,32);
  Serial.print("Sending '");
  Serial.print(command);
  Serial.print("' to Device ");
  Serial.println(device,HEX); 
  Wire.beginTransmission(device);
  Wire.send(sendChar);
  int response=Wire.endTransmission();
  Serial.print("Return Code: ");
  Serial.println(response);       
}


void loop()
{

}




BenF

The receiveEvent is called from an interrupt handler and so need to be as efficient as possible in order not to block other interrupts from occuring or in this case violate I2C bus timing. Specifically, you can not use Serial.print from an interrupt handler (or any of the routines called from a handler). If you need to print or process data received, you can update a global volatile variable and act on this in your loop function.

Also you can not return data from within a receiveHandler. For this you need to read up on the requestEvent. I would also avoid using the String class. This class uses dynamic memory allocation and prone to fail with all kinds of unpredictable issues.

Magic Ross

Thanks for the advice.

I'll modify my code as per your suggestions and give it a try.

PS. I was only using the Serial monitor for debugging - it didn't work without the Serial.print commands either.

But I'll try updating a global and processing it in the loop.  I'll also try to do away with Strings.

Thanks.  Will post again if it works or doesn't.

WillR

My experience is to minimize serial port use when working with I2C sensors and UDP transmissions on Ethernet.

The serial port seems to "load" the processor. I have not really dug into the "why's".
Just another Hacker

Nick Gammon

#4
Feb 17, 2011, 08:14 am Last Edit: Feb 17, 2011, 09:05 pm by Nick Gammon Reason: 1
Here's a working example:

Master (requests the slave ID):

Code: [Select]
#include <Wire.h>

#define SLAVE_ADDRESS 0x0B

char buf [100];
byte bufpos;

void setup()
{
 Wire.begin();  
 Serial.begin(9600);  // start serial for output
 
 Wire.beginTransmission (SLAVE_ADDRESS);
 Wire.send ("requestID");
 Wire.send (0);
 Wire.endTransmission ();
 
   // initiate blocking read into internal buffer
 Wire.requestFrom (SLAVE_ADDRESS, 10);
 bufpos = 0;
 
 while (Wire.available ())
   {
   if (bufpos < sizeof (buf))
     buf [bufpos++] = Wire.receive ();
   }

 Serial.println (buf);
 
}  // end of setup

void loop()
{
}


This sends the "requestid" and then does a Wire.requestFrom to get a response.

Slave:


Code: [Select]
#include <Wire.h>

#define MY_ADDRESS 0x0B

char buf [100];
byte bufpos;

void setup() {
 bufpos = 0;
 
 Wire.begin(MY_ADDRESS);
 Wire.onReceive(receiveEvent);
 Wire.onRequest(requestEvent);
}


void loop() {
}

void receiveEvent(int howMany)
{
byte c;
 while (Wire.available() > 0)
 {
   if (bufpos < sizeof (buf))
     {
     c = Wire.receive ();
     buf [bufpos++] = c;  
     }
 }
}

void requestEvent ()
{
if (strcmp (buf, "requestID") == 0)
  {
  Wire.send ((byte *) "id1", 4);
  bufpos = 0;  // empty buffer out
  }  
 
}


We have two event handlers (that is, interrupt handlers). One gets the incoming message from the master (eg. "requestid"). It stores it into a buffer ready for testing later.

The second handler is called when the master requests a response (that is, the master has done Wire.requestFrom). The slave then checks (from its buffer) what is being requested and sends a response. Note that you don't need to do beginTransmission or endTransmission because you are responding to someone else. So you don't even need to know the master address (note I haven't got it there).

The slightly unusual way of sending the "id1" string is because of what appears to be a bug in the Wire library. I'll look into that.
http://www.gammon.com.au/electronics

Nick Gammon

I suggest you redesign your protocol a bit. Sending a string is slow and inefficient. For one thing, you have to buffer the string up at the receiving end, you have to worry about making it null-terminated, the library routines to do a strcmp will take memory, you have to worry about the string overflowing whatever size buffer you allocate, etc.

So, instead of sending the string "requestid" you might send (for example) 0x01 which means to you, "I request your ID". Then the receiver only has to store one byte (the command). Then it might reply with a single byte (eg. 0x01 for ID 1, 0x02 for ID 2). Then if you need a different command (eg. take sensor reading) you might send 0x02.

Anyway, on to the "bug". When I was testing this:

Code: [Select]
void requestEvent ()
{
if (strcmp (buf, "requestID") == 0)
  {
  Wire.send ((byte *) "id1", 4);
  bufpos = 0;  // empty buffer out
  }  
} // end of requestEvent


I found that worked, but looked a bit strange. I initially had this, which looks better:

Code: [Select]
void requestEvent ()
{
if (strcmp (buf, "requestID") == 0)
  {
  Wire.send ("id1");  // send string
  Wire.send (0);    // string terminator
  bufpos = 0;  // empty buffer out
  }  
}  // end of requestEvent


Sending a string does not send the null-terminator, so the other end doesn't know when it has fully been received. So I added the second Wire.send (0) to send the extra zero. But in fact, all that got sent was the zero, and not the string!

The code that handles this case in twi.c is here:

Code: [Select]
/*
* 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;
}


Notice how every time you call Wire.send in "slave transmitter" mode it simply replaces the existing buffer with the new data? So it would have put "id1" into the buffer, and then on the second "send" call replaced the "i" by a zero.

I'm not sure if this is by design (what design?) or simply a bug.

However a point worth nothing is that in the current library, when replying to a "requestFrom" event, you need to fully assemble your reply into a single buffer and call wire.Send once.
http://www.gammon.com.au/electronics

bubulindo

The reason why the serial port stops the processor is because it implemented as a soft UART or a polled UART instead of interrupt based UART. You can check that in the library files.
Eu não sou o teu criado. Se respondo no fórum é para ajudar todos mediante a minha disponibilidade e disposição. Responder por mensagem pessoal iria contra o propósito do fórum e por isso evito-o.
Se realmente pretendes que eu te ajude por mensagem pessoal, então podemos chegar a um acordo e contrato onde me pagas pela ajuda que eu fornecer e poderás então definir os termos de confidencialidade do meu serviço. De forma contrária toda e qualquer ajuda que eu der tem de ser visível a todos os participantes do fórum (será boa ideia, veres o significado da palavra fórum).
Nota também que eu não me responsabilizo por parvoíces escritas neste espaço pelo que se vais seguir algo dito por mim, entende que o farás por tua conta e risco.

Dito isto, mensagens pessoais só se forem pessoais, ou seja, se já interagimos de alguma forma no passado ou se me pretendes convidar para uma churrascada com cerveja (paga por ti, obviamente).

Go Up