2

I'm working on creating a simple ACK/NACK handshake between 2 Arduino's that will essentially go like this:

  1. Arduino 1 signals Arduino 0 it is available
  2. Arduino 0 sends a connect byte to Arduino 1 (say, 'c')
  3. Arduino 1 sends a confirmation byte to Arduino 0 (say, 's')

This seems simple enough, so what I'm doing to avoid any kind of multithreading or callback functions is to use Wire.requestFrom(). From the wiki, this function "requests" the device at the specified address to send data. From what I understand, this "request" simply entails the master device simply blocking until it receives the necessary data. It doesn't seem to send any signal to the slave device that it should send anything, just does a simple wait. As such, I created a simple demo of this handshake between the two devices as follows:

// slave
#include <Wire.h>

void setup() {
  Serial.begin(9600);
  Wire.begin(1);
  Wire.requestFrom(0, 1, false);
  Serial.println("finished");
}
// master
#include <Wire.h>

bool last = false;

void setup() {
  Serial.begin(9600);
  Wire.begin(0);
  pinMode(2, INPUT);
}

void loop() {
  if (digitalRead(2) && !last) {
    Wire.beginTransmission(1);
    Wire.write('c');
    Wire.endTransmission(1);
  } else if (!digitalRead(2)) {
    last = false;
  }
  delay(1000);
}

Here, the master code waits on a signal (pin 2) (which is given by Arduino 1) that it is available, and then on connect, will send one connect byte to Arduino 1. In Arduino 1 (slave)'s code, I've placed the requestFrom call that should block until Arduino 0 sends that connect byte.

The problem here is that it's not actually blocking. Even before I've gotten a chance to run the master's code, the slave's "finished" output has instantly triggered. Am I understanding this function incorrectly or is there something wrong in my implementation?

2 Answers 2

3

You completely misunderstand how I2C communication (that's what the Wire library does) works. I won't explain the full I2C interface here, since that is a bit too much for this answer.

Wire.requestFrom() doesn't block until it receives data. It will configure the I2C interface as master (when previously in slave mode this will be temporary). Then it enforces a start condition on the bus, sends the address of the slave and then sends a clock pulse for the acknowledgement bit. During this clock pulse the slave can pull the SDA line low and signal that way, that it is reachable. After that the master continues to generate the clock for all the bytes it wants, expecting the slave to control the SDA line. If the slave doesn't do that (for example because you didn't connect the onRequest() callback), then the received data will just be 1s (so each byte 0xFF).

So Wire.requestFrom() does only block for the time, that the transmission needs. If the slave doesn't acknowledge its address, the function will exit after that. The transaction will only need a few milliseconds either way.

avoid any kind of multithreading or callback functions

On AVR based Arduinos (like the Uno or Nano) multithreading isn't really a thing (besides using a time sharing library, but still on only one core), though callbacks from interrupts are. And as long as you use the Wire library (and with it the I2C hardware interface of the microcontroller) you have to use interrupts and thus callback functions. And honestly there is normally no reason not to do that.


Another problem with what you are currently doing it, that both Arduinos act as masters on the bus. No two masters can send on the bus at the same time. While the I2C protocol has a way to determine, which master can send first, this is often implemented rather buggy. I don't know how good the Wire library is with this currently, but you should avoid a multi-master setup if possible.


A minor problem: The I2C address space has some reserved sections for special use. You can start your slave addresses at 8 (refer to the I2C specification on the web). Address 0 is meant as a general call address, so for master-write transactions, which target every slave on the bus. You shouldn't have a slave with that address.


What to do now? Just implement the code with the Wire library as it is meant, including the onRequest() callback function. Refer to the master-read example of the Wire library. Set the second Arduino as master and the first as slave. On the second Arduino you can call Wire.requestFrom() with the slaves address. This will trigger the onReqest() callback function on the first Arduino, where you can use Wire.write() to let the library send the desired data back to the master. On the master (Arduino 2) you an then read that data via Wire.read(). And thats it.

1

Masters request data from the slave, not the other way around.

See http://gammon.com.au/i2c

Slaves set up a "receive" handler to be told that there is incoming data.

Wire.onReceive (receiveEvent);

From what I understand, this "request" simply entails the master device simply blocking until it receives the necessary data.

Yes, but you don't have this request on the master, you have it on the slave. There is your problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.