I2C onRequest problems

Hi there,

I have a system where an Arduino Mega is connected to a Raspberry Pi. The necessary precautions have been done in order to keep the logic levels the same between the two boards.

On the Mega side, it’s suppost to have a set of commands that the Raspberry can use. Some of them is to read potentiometers and buttons. At first, I had the onReceive method deciphering the commands and calling wire.Write() whenever a command for a read was needed, but it wouldn’t send any data.

After checking the Master reader / Slave writer example, I had the onRequest the same handle routine in order to send the bytes. But since it started with a Wire.read() and a switch for the result, it didn’t send anything either.

Right now, I have the correct code, but it only works sometimes. Here’s the code:

Initialization:

Wire.begin(6);
  Wire.onReceive(receivedMsg);
  Wire.onRequest(requestEvent);

onReceive:

static void myClass::receivedMsg(int howMany){
  i2cRecv = true;
}

This sets a flag, in the loop() it has this:

if(board.i2cRecv){
    board.i2cRecv = false;
    Serial.println("got something");
    board.handleI2Ccommands();
  }

And finally the handleI2Ccommands:

void myClass::handleI2Ccommands(){
  byte byteArray[4];
  int i = 0;
  int aux = 0;
  byte aux2 = 0;
  if(Wire.available() > 0){
    ws2812turnRGBOn(255,180,40);
    Serial.println("I received something");
    while(Wire.available()){
      byteArray[i] = Wire.read();
      Serial.println(byteArray[i],HEX);
      i++;
    }
    switch(byteArray[0]){
      case TURN_ON_BUZZER:
        turnOnBuzzer();
        Serial.println("Turn on buzzer");
        break;
      case TURN_OFF_BUZZER:
        turnOffBuzzer();
        Serial.println("Turn off buzzer");
        break;
      case TURN_ON_WHITE_LED:
        aux2 = byteArray[1];
        turnOnWhiteLED(aux2);
        Serial.print("Turn on white led ");
        Serial.println(aux2);
        break;
      case TURN_OFF_WHITE_LED:
        turnOffWhiteLED();
        Serial.println("Turn off white led");
        break;
      case TURN_ON_RGB_STRIP:
        ws2812turnRGBOn(byteArray[1],byteArray[2],byteArray[3]);
        Serial.print("Turn on rgb strip ");
        Serial.print(byteArray[1]);
        Serial.print(" ");
        Serial.print(byteArray[2]);
        Serial.print(" ");
        Serial.println(byteArray[3]);
        break;
      case TURN_OFF_RGB_STRIP:
        ws2812turnRGBOff();
        Serial.println("Turn off rgb strip");
        break;
      case SHUTDOWN:
        startShutdown();
        Serial.println("Shutdown");
        break;
      case VERIFY_SHUTDOWN:
        if(shutdownEnabled){
          aux2 = 1; 
        }
        Wire.write(aux2);
        Serial.println("Verify shutdown");
        break;
      case READ_BATTERY: 
        aux = (int)(readBattery()) * 100;
        Wire.write(aux);
        Serial.println("Read battery");
        break;
      case READ_POT:
        aux = readPotentiometer();
        Serial.println(aux);
        readPot = true;
        break;
      case GOTO_POS:
        aux = ((byteArray[2] & 0xFF) << 8) | (byteArray[1] & 0xFF);
        Serial.println("going to pos");
        Serial.println(aux);
        slideToValue(aux);
        break;
    }
  }
  ws2812turnRGBOn(0,255,0);
}

For the onRequest, I have this:

static void myClass::requestEvent() {
  //Wire.write(readPotentiometer()); // respond with message of 6 bytes
  byte byteArray[2] = {0,0};
  int aux;
  if(readPot){
    aux = analogRead(potentiometerPin);
    byteArray[0] = lowByte(aux);
    byteArray[1] = highByte(aux);
    Wire.write(byteArray,2);
    readPot = false;
  }else if(shutdownCheck){
    shutdownCheck = false;
    if(shutdownEnabled){
      Wire.write(0x01);
    }else{
      Wire.write(0);
    }
  }else{
    byteArray[0] = 0x55;
    byteArray[1] = 0x55;
    Wire.write(byteArray,2);
  }
  readPot = false;
  shutdownCheck = false;
  //i2cReq = true;
  // as expected by master
}

Now for the problem: Most of the times, when asked for the potentiometer value, it returns the actual value. But sometimes, it returns 21845, which, in hex, is 0x5555, that means the flag wasn’t recognized as being true. This points me to one of two problems:

  1. Either the onRequest is firing first than the onReceive or

  2. the onRequest is firing after the onReceive, but before the onReceive can set the flag as true. I don’t even know if Arduino allows for nested interrupts.

Can someone help me out please?

Thanks,

Luís

I don't even know if Arduino allows for nested interrupts.

It does, but the Wire library doesn't use nested interrupts.

But sometimes, it returns 21845, which, in hex, is 0x5555, that means the flag wasn't recognized as being true.

I'm sure you know what flag you are talking about.

If you really want help, you will post ALL of your code in ONE piece.

  1. Either the onRequest is firing first than the onReceive or

Possible, depends on the code on the master.

  1. the onRequest is firing after the onReceive, but before the onReceive can set the flag as true. I don’t even know if Arduino allows for nested interrupts.

This is quite likely as you don’t handle the on Receive in the interrupt handler but you just set a flag and handle it later in the loop. This is a race condition. You should at least read the bytes from the master and set the flags according to the “command” byte. Leave lengthy tasks in the loop but the command should be handled before the interrupt handler leaves.

You shouldn’t call Wire.write() in a receiveHandler(). This interrupt routine is called if a write was received from the master. If later a read is received your requestHandler will be called and there the writes should be made.

PaulS:
It does, but the Wire library doesn't use nested interrupts.
I'm sure you know what flag you are talking about.

If you really want help, you will post ALL of your code in ONE piece.

I could post the entire code, but it's spread across multiple files and a class with alot of functions to turn on and off LED's, read buttons, etc. The part that matters is there.

The natural flow of the program should be:

The raspberry pi tries to read the value of the potentiometer. It should first do a write - slave address and the register it wants to read (in this case, it's interpreted as a command) - and then send a repeated start, with the slave address with the read bit asserted.

So, it should fire the onReceive first, set the readPot flag to true, then fire the onRequest, see that the readPot is true and write the value of potentiometer through i2c.

The code for the .ino file is this:

#include "auxiliaryLib.h"

#define DEBUG 1

// Deprecated - here for future reference
/*static volatile bool myClass::powerInterrupt;
static volatile long myClass::targetShutdown;*/

// I2C receive flag
static volatile bool myClass::i2cRecv;
static volatile bool myClass::i2cReq;
static volatile bool myClass::readPot;
static volatile bool myClass::shutdownCheck;
static volatile bool myClass::shutdownEnabled;

// Constructor
myClass board = myClass();

void setup() {
  board.initializeBoard();
  #ifdef DEBUG
  Serial.begin(115200);
  delay(1000);
  #endif
  if(digitalRead(powerSwitchPin)){
    board.enablePower();
    Serial.println("Started turned on");
  }else{
    board.disablePower();
    Serial.println("Started shut down");
  }
}

void loop() {
  
  if(board.i2cRecv){
    board.i2cRecv = false;
    Serial.println("got something");
    board.handleI2Ccommands();
  }
  if(board.i2cReq){
    
  }
  board.handleBoard();

}

The raspberry pi tries to read the value of the potentiometer. It should first do a write - slave address and the register it wants to read (in this case, it's interpreted as a command) - and then send a repeated start, with the slave address with the read bit asserted.

In this case you second presumption is correct. Fix what I wrote above and it probably will start working reliably.

For now, I'm just sending the READ_POT command and most of the times it works and some times writes the 0x5555.

Since the interrupts aren't nested, what could possibly be happening to the flag ? I can see that on the serial monitor the onReceive is doing it's thing and it's going past it, but since I can't print inside the onRequest ISR, I can't say for sure that it's always running before... I could set some pins high and low and check on the oscilloscope, but then what could possibly be the correct way to handle this? Most of the examples and codes I see here, when the arduino gets a request, it's always for the same piece of info. There's no multiple command choices like you have on every sensor, for example.

Post the code with the suggested fixed applied!

Most of the examples and codes I see here, when the arduino gets a request, it's always for the same piece of info. There's no multiple command choices like you have on every sensor, for example.

When the slave is supposed to return different kinds of data based on what the master wants, it is usually a two step process. The master tells the slave what it wants to know, then the master asks for the data.

When the master says "I want the jibbit value", the slave makes a note of that, and starts the process of getting the jibbit value. When the master says "Hey, where's the data I want?", the slave responds with the data.

It is up to the master to wait long enough for the slave to prepare the data before asking for the data.

In a well written I2C process, the slave would simply return the most current data of the type that the master wanted, without the need to spend time getting the data.

As an example, if the slave has a temperature/humidity sensor that takes 750 milliseconds to measure the temperature and humidity, it should spend most of its time twiddling its thumbs waiting for the sensor to get ready to report the temperature and humidity.

When the master says "I want to know the temperature", the slave HAS a temperature to deliver. It may be nearly a second old, but that's OK. The master shouldn't have to wait 750 milliseconds plus for the slave to get new data. It should be able to say "and I want it now" and the slave should return the latest temperature it has.

PaulS:
It is up to the master to wait long enough for the slave to prepare the data before asking for the data.

This is the philosophy behind the design of the following I2C Instruction:

Wire.requestFrom(slaveAddress, arg2);

This instruction does all these: Bus acquisition through START Command, Roll call of the Slave, Setting the data direction in read mode, Collect the data bytes from the slave, and Release the bus by asserting STOP Command.

This is the philosophy behind the design of the following I2C Instruction:

If the slave has a temperature sensor that takes 750 milliseconds to get a reading, and the master asks the slave for the temperature, and the slave THEN starts the process to get the temperature, the master using Wire.requestFrom(slaveAddress, arg2) is not going to wait around for the data from the slave.

The master needs to wait 750 milliseconds, or longer, after telling the slave that it wants the temperature before it asks for the data.

That is, of course, NOT the intent of I2C. An I2C device should have data ready as soon as the master tells the slave what kind of data it wants.

In response to Wire.requestFrom() command, the slave goes to this event handler: void sendEvent() and queues data bytes (s) into the local buffer. Having finished the write-up in the queue, the slave returns to the calling program and issues ACK signal to the Master. The Master finds the ACK signal and collects the data byte-by-byte from the slave on ACK strategy. Here, Master remains in waiting state until data get ready in the buffer of the slave.

void sendEvent(int howmany)
{
   Wire.write(dataByte1);
   ...............................
   Wire.write(dataByten);
}

This is my understanding which could be totally wrong; but, the experimental results favor, at least, my way of understanding.