How do I make an I2C-slave Arduino just wait? [Kinda solved, hack job]

Hi all,

For my project, I use two Arduinos rigged together via I2C. Both boards are reading sensors, with the master board being responsible for datalogging. I'm using Wire.RequestFrom() and Wire.onRequest() to handle data transfer. Easy enough.

Currently, the slave board does nothing until it gets the request from the master, at which point it takes 0.02 seconds worth of data and transmits is back to the master. This isn't the way I want to handle it, since the master board only requests information every x seconds.

What I'd like to do is have the slave board gather 1 second of integrated analog sensor data and then just "hang out" until it gets the request from the master board to transfer the time average. I guess what I'm really asking is how does Wire.onRequest() behave after it executes the function it calls? Does it go back to the beginning of the loop() function or does it return to the point in loop() where it was called?

Wire.onRequest() is, to the best of my knowledge, simply a hook into the I2C interrupt, so upon returning it should go back to whatever you had going on in loop() before the request came through.

Since you don't know when the request will come, you could implement a five-second rolling buffer (this can be implemented fairly easily with an array and modulus), and just send that back whenever it is requested.

Then is there a command to start loop() from the top without running the whole way through? Then I can just put a delay() at the end so it basically does nothing until it gets the interrupt request, and then restart loop() after the interrupt function is executed?

bool valuerequested = false; //global variable

void onRequest() {//not sure how this syntax is...
   valuerequested = true;
}

void setup() {
  //Stuff
}

void loop() {
  //Read sensors
  while (!valuerequested); /*Busywait - don't do anything until valuerequested is true*/
  valuerequested = false;
}

Do what WizenedEE suggested (or make a rolling buffer). Then the moment the request is serviced the loop is resumed.

But I think the rolling buffer would be better, gather readings into a circular buffer and discard old readings, then the ISR can always return the latest reading.

Otherwise there is a danger that the interrupt occurs half-way through the 5 seconds, and everything is out of whack.

Then is there a command to start loop() from the top without running the whole way through?

No. Don’t even try to invent one.

Wire.onRequest() is, to the best of my knowledge, simply a hook into the I2C interrupt, so upon returning it should go back to whatever you had going on in loop() before the request came through.

Wire.onRequest() is called once in the program to register an event handler, for a specific event. That event handler is then called whenever the event occurs. Wire.onRequest() is not called when the event occurs.

When the event occurs, the registered handler is evoked. When that handler is done, execution resumes with the next machine instruction after the point where the interrupt occurred.

PaulS:

Then is there a command to start loop() from the top without running the whole way through?

No. Don’t even try to invent one.

Wire.onRequest() is, to the best of my knowledge, simply a hook into the I2C interrupt, so upon returning it should go back to whatever you had going on in loop() before the request came through.

Wire.onRequest() is called once in the program to register an event handler, for a specific event. That event handler is then called whenever the event occurs. Wire.onRequest() is not called when the event occurs.

When the event occurs, the registered handler is evoked. When that handler is done, execution resumes with the next machine instruction after the point where the interrupt occurred.

I meant the handler invoked by onRequest–but that’s besides the point, I think.

Wouldn’t returning from loop() cause it to exit to the overarching while(true) loop and restart at the beginning? Not sure why there’d ever be a good reason to do this, though.

Wouldn't returning from loop() cause it to exit to the overarching while(true) loop and restart at the beginning?

It would.

But, where do you put the return statement?

Like I said, I'm not sure why there would ever be a good reason to do this. It does, rather specifically, answer the OP's question though.

Returning from loop does cause loop() to start again from the beginning, and that would address OPs "need".

It is not possible to do though. You have no idea whether a particular instruction is being executed before or after an interrupt occurred. So, you would need to do something in the interrupt that could be checked before every instruction in loop. If that flag was true, return.

The problem with this, of course, is that a single C/C++ instruction often maps to several machine instructions, so you do not have the granularity of control that you need. Not to mention the overhead needed to check after every C/C++ statement.

Aeturnalus:
Like I said, I'm not sure why there would ever be a good reason to do this. It does, rather specifically, answer the OP's question though.

The reason is because it takes the slave Arduino 1 full second to log its sensor data, while the time the master takes to log data is quite variable. I need to find a way for the slave to hang out while the master finishes its reading cycle, then send over the collected data and start collection over again.

WizenedEE:

bool valuerequested = false; //global variable

void onRequest() {//not sure how this syntax is…
  valuerequested = true;
}

void setup() {
 //Stuff
}

void loop() {
 //Read sensors
 while (!valuerequested); /Busywait - don’t do anything until valuerequested is true/
 valuerequested = false;
}

This worked perfectly, however, instead of while(!valuerequested);, I had to use while(valuerequested = false);. Apparently there’s some distinction. For those interested, here’s the current prototype codes:

For the Master:

#include <Wire.h>
int received;

void setup()
{
  //Serial.begin(115200);
  Wire.begin();
}

void loop()
{
  delay(2000);
  Wire.beginTransmission(2);
  Wire.requestFrom(2, 1);
  received = Wire.receive();
  Wire.endTransmission();
}

For the Slave:

#include <Wire.h>

//hardware defines, libraries, global variables, etc. go here

boolean toggle = false;

void setup()
{
  Serial.begin(115200);
  Wire.begin(2);
  Wire.onRequest(Event);
  
  //pin initializations, etc. go here
}

void loop()
{
  catalogtime = millis();

  //Sensor reading algorithm goes here
  
  ReadingEnd = millis(); 
  ReadingInterval = ReadingEnd - catalogtime; //Move timestamp function to master board
  
  while(toggle = false);
  {
    // haha empty loop
  }
  toggle = false;
}

void Event()
{
  Wire.send("3");
  //Serial.print(data) goes here

  //SDcard datalogging goes here
  
  toggle = true;
  
  return;
}

Obviously I didn’t include ALL the code the master is running, since the analysis of several sensors plus GPS data isn’t relevant here, but that’s the basic structure. It’s working, but it feels like I hacked it together without any sense of elegance. Does anyone have any ideas to refine things?

You might want to be careful with that

while( toggle = false )

michinyon:
You might want to be careful with that

while( toggle = false )

No, I'm pretty happy with it. I want the board to trap itself in a loop until it gets interrupted by Event(), and once Event() sets toggle=true then the board can escape the loop and resume measurements.

I think michinyon is suggesting that you'll want to take a closer look at that statement before you run it. There might be an error in it as written.

There might be is an error in it as written.

I wrote a tutorial that may help you out a little if you want to check it out dsscircuits.com is for sale | HugeDomains

PaulS:
There might be is an error in it as written.

I think a good idea, then, would be to tell me what the error is, especially since it seems to be working.

You've got the wrong operator in it. The single equal sign ('=') is the assignment operator; what you want is the equality operator, which is a double equal sign ('=='). Your loop, as currently written, sets the toggle variable to false with each iteration rather than testing whether or not it already equals false.

ryschwith:
You’ve got the wrong operator in it. The single equal sign (’=’) is the assignment operator; what you want is the equality operator, which is a double equal sign (’==’). Your loop, as currently written, sets the toggle variable to false with each iteration rather than testing whether or not it already equals false.

Excellent, thank you. I’m new to boolean stuff.

I didn’t get a chance to test this today, but I’ll look in to it more tomorrow. A new problem has arisen with the SD logging, but that’s a matter for a different thread.