Multiple RequestEvents Slave I2C

Hi all,

I'm quite new in the Arduino world and I bumped into a question I couldn't solve using just Google, so I've come to you guys for help.

I have set up an I2C-connection between three Arduino Uno's (1 master and 2 slaves). Now I wanted to request data (the values of some LDRs) from the slaves and that worked out pretty well. My problem, however, is the following:
I want to request more, different data from that same slave Arduino, but I couldn't work out how I could make the slave know which data it has to send back...

My code for the master is:

//MASTER

#include <Wire.h>

const int slave1 = 1;

const int slave2 = 2;

const int ldrCountSlave1 = 2;  //The number of LDRs on slave 1
const int ldrCountSlave2 = 3;  //The number of LDRs on slave 2
const int ldrCount = ldrCountSlave1 + ldrCountSlave2;
int ldrVals[ldrCount];
int minIntensity = 800;

void setup() {
  Wire.begin();
}

void loop() {
  transmit(slave1, 0);
  updateLdrArray();
  for(int i = 0; i < ldrCount; i++) {
    if(ldrVals[i] > minIntensity) {
      transmit(slave1, 1);
      transmit(slave2, 2);
      break;
    }
  }
}

void transmit(int address, int function) {
  Wire.beginTransmission(address);
  Wire.write(function);
  Wire.endTransmission();
}

void updateLdrArray() {
  Wire.requestFrom(slave1, ldrCountSlave1);
  int i = 0;
  while (Wire.available()) {
    byte byteValue = Wire.read();
    ldrVals[i] = int(byteValue);
    i++;
  }
  Wire.requestFrom(slave2, ldrCountSlave2);
  while (Wire.available()) {
    byte byteValue = Wire.read();
    ldrVals[i] = int(byteValue);
    i++;
  }
}

The code for slave 1 is:

//SLAVE 1

#include <Wire.h>

#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

const int buttonValueUp = 9;
const int buttonValueDown = 8;
const int buttonOk = 10;

int upState = 0;
int downState = 0;
int okState = 0;

boolean speedSet = false;
float wishedSpeed = 0.0;

const int ldr1 = A0;
const int ldr2 = A1;
int ldrValue = 0;

const int startRelay = 13;

void setup() {
  Wire.begin(1);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);   //This is the part I mean with my question: on a request, the arduino always goes to the function requestEvent...

  lcd.begin(16, 2);

  pinMode(buttonValueUp, INPUT);
  pinMode(buttonValueDown, INPUT);
  pinMode(buttonOk, INPUT);

  pinMode(ldr1, INPUT);
  pinMode(ldr2, INPUT);

  pinMode(startRelay, OUTPUT);
  digitalWrite(startRelay, LOW);
}

void loop() {
}

void receiveEvent(int howMany) {
  int function = Wire.read();
  switch (function) {
    case 0:
      setWishedSpeedAndLcd();
      break;
  }
}


void setWishedSpeedAndLcd() {
  digitalWrite(startRelay, LOW);
  if (okState == HIGH) {
    speedSet = true;
    digitalWrite(startRelay, HIGH);
  }
  if (speedSet == false) {
    upState = digitalRead(buttonValueUp);
    downState = digitalRead(buttonValueDown);
    okState = digitalRead(buttonOk);
    if (upState == HIGH) {
      wishedSpeed += 0.1;
    } else if (downState == HIGH) {
      wishedSpeed -= 0.1;
    }
    wishedSpeed = constrain(wishedSpeed, 0.0, 5.0);
  }
  if (speedSet == false) {
    lcdStuff(1, wishedSpeed);
  } else {
    lcdStuff(2, wishedSpeed);
  }
}


void lcdStuff(int procedure, float wishedSpeed) {
  lcd.clear();                      
  lcd.home();
  if (procedure == 1) {
    lcd.print("Set wished speed");
    lcd.setCursor(0, 1);
    lcd.print("Speed: ");
    lcd.print(wishedSpeed);
  } else if (procedure == 2) {
    lcd.print("You have set the");
    lcd.setCursor(0, 1);
    lcd.print("speed to ");
    lcd.print(wishedSpeed);
    lcd.print(" m/s");
  } else {                          //Back-up procedure
    lcd.print("Error 404: No");
    lcd.setCursor(0, 1);
    lcd.print("procedure found");
  }
}

void requestEvent() {                             //Here I want the arduino to know which data it should send back to the master, but I don't know how to do that...
  ldrValue = analogRead(ldr1);
  byte sendableValue = byte(int(ldrValue / 4));
  Wire.write(sendableValue);
  ldrValue = analogRead(ldr2);
  sendableValue = byte(int(ldrValue / 4));
  Wire.write(sendableValue);
}

The code of the second slave is not so important for this question, so I left that away.

Can you guys help me out?
Thanks a lot!
Tim

1 Like

It can be done, but the code will become big and complex very fast.

You can scroll through this: Doubts on Wire library implementation - Programming Questions - Arduino Forum.

The receiveEvent() and requestEvent() are called from a ISR (Interrupt Service Routine). To keep the Slave running without problem you should keep those two interrupt routines as short as possible.

Try to avoid a number of analogRead() and writing to the display from those functions.

It is better to use a buffer. The buffer can be an array or a struct (I use a struct) that is know by the Master and Slave.
The Slave fills the struct with data in the loop(), and the Master can read the struct over I2C.
The Master can even send a command to the Slave, but then an extra variable is needed to signal code in the loop() that a new command has arrived.

Reading a struct after a Wire.requestFrom() can be done with a single function: Wire.readBytes().

The maximum size for data over I2C is 32 bytes for the AVR Wire library. When you use a struct that is smaller than 32 bytes, and the Master reads the whole struct at once, then you avoid a lot of problems.

Every variable (or array or struct) that is used both in a interrupt routine and in the loop() must be made 'volatile'. Then the compiler knows that when optimizing the loop(), that variable can change at any moment.