I2C Transfers between Arduinos - Handshaking

I have been experimenting with transferring data from an Arduino running as a slave. There are examples of the master and slave programs below. The master is requesting, in turn, a byte from a character array that is defined in the slave. The master prints out each byte as it is received. The program works.

However, the slave has to respond immediately to the Wire.requestFrom() issued by the master. What I want to be able to do is have the slave pause before it sends the response. The slave on receipt of the request from the master may need to go off and perform some task such as read or write to some other bit of hardware it has which could be SPI or serial based for instance.

If in the slaves requestEvent() I set a flag and detect it in loop() and then do the Wire.write(), then it does not work, the message is not returned to the master.

I have had it a type of ‘handshaking’ working on ATMega328Ps, with both Arduinos swapping packets as masters, but that does not work on an XIAO SAMD21, one of the microcontrollers I might want to use.

So, is it possible for the slave to delay returning data to the master ?

//master program

#include <Wire.h>
#define SLAVE_ADDRESS 0x30                      // Define Slave I2C Address

void loop()
{
  uint8_t index;
  uint8_t character;

  for (index = 0; index <= 17; index++)
  {
    character = readMessage(index);
    Serial.write(character);
  }
  Serial.println();
  delay(5000);
}

uint8_t readMessage(int16_t addr)
{
  uint8_t messagedata;

  Wire.beginTransmission(SLAVE_ADDRESS);
  Wire.write(1);                                //command value for register read
  Wire.write(addr);                             //character in message to read
  Wire.endTransmission();
  delayMicroseconds(250);                       //delay needed here to prevent read fail
  Wire.requestFrom(SLAVE_ADDRESS, 1);           //Read one byte response from Slave
  messagedata = Wire.read();
  return messagedata;
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("2_I2C_Master"));
  Wire.begin();                                 //Initialize I2C communications as Slave
}
//Slave program

#include <Wire.h>
#define SLAVE_ADDRESS 0x30                      // Define Slave I2C Address

volatile uint8_t rxbuff[33];                    //buffer for data recieved over I2C
uint8_t message[] = "This is a message";        //buffer for data to be read by master
volatile bool received = false;
volatile bool requested = false;
volatile uint8_t returndata;

void loop()
{
  if (received)
  {
    received = false;

    switch (rxbuff[0])
    {
      case 1:
        returndata = message[rxbuff[1]];
        break;

      default:
        break;
    }
  }

  if (requested)
  {
    requested = false;
    Wire.write(returndata);
  }
}

void requestEvent()
{
  //requested = true;                //setting this flag and dealing with the read in loop() does not work
  Wire.write(returndata);
}


void receiveEvent(int howmany)
{
  //this saves the bytes received to a globally accessible buffer
  uint8_t index;

  for (index = 0; index < howmany; index++)
  {
    rxbuff[index] = Wire.read();
  }
  received = true;                    //set flag to indicate data has been received
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("1_I2C_Slave");
  Serial.println();
  Wire.begin(SLAVE_ADDRESS);          // Initialize I2C communications as Slave
  Wire.onRequest(requestEvent);       // Function to run when data requested by master
  Wire.onReceive(receiveEvent);       // Function to run when data received from master
}

Yes! It does not work and the same thing happened to me few weeks ago. I got the understanding that the access to the I2C Buffer through write() function was only available in the requestEvent()/sendEvent. This is the feature of the I2C protocol.

Clock stretching is just such a delay option with I2C communications, but as I understand it, not all controllers implement it.

And of course, there should be timeouts, or a device could lock up the bus.

I did some searching on 'clock stretching' and found this discussion on the topic, which is the same as my problem really;

So sounds like its not possible.

Maybe not with the Wire library, but using the I2C hardware directly?

Then the 'solution' would be specific to the Microcontroller, so it could be a fair bit of work covering multiple systems.

You can make handshaking work with Pro Minis etc, they dont seem to mind swapping between Master\Slave all the time.

Since I2C communications aren't completely standardized and not all MCU implementations are equal, I think MCU-specific approaches to a given problem will usually be required.

There is no "timeout" from the Wire.h Library. It has to be implemented by the user.

How about this:

  1. The master writes a return address (and more) to the slave, as usual.
  2. The slave collects the data and transmits it as a master when ready, to the return address.

Done that, it works on Atmega328P.

But it appears the SAMD21 version of wire does not like swapping between slave and master and the same code does not work.

If it allows for clock stretching then use that on the SAMD21.
Or can you use two I2C ports as distinct master and slave on one such a controller?

How do you implement clck stretching ..................

Not enough pins for two i2C ports.

you need to spend time on the datasheet of your microcontroller.

As said before, every MCU may have its own implementation of I2C protocol.
33 IOT and 33 BLE for example have dedicated and native peripheral that allows fine (and really easy, especially in the 33BLE) tuning of the I2C protocol.

Looks that way, but I aint interested in that as a solution, too much effort supporting all the different microcontrolers.

If there is no generic solution, I will leave it as it is.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.