I2C slave protocol

I have a Nano acting as an I2C slave for a master system. This Nano looks after the positioning and reporting back of four motorised slider pots. It can receive either a request to send the sliders position or a command to move them. When they are moving a flag is set and if their position is requested then the I2C sends a -1 for that slider position.

So far so good and the system works, nearly as expected. However, whenever I pole the I2C slave for the sliders position I always get back the position number, I never see -1 even though the position request is sent immediately after the move command. I know the immediate request should return a -1 because their is no way that the slider can move in that time.

So my question is when is the I2C slave function invoked? Is it on a function's return or is it when the loop function exits? I would have thought that it would interrupt the function moving the motor but does not appear to do so.

Is it really -1 or a 0xFF byte ? (I suppose you mean the call back Wire.onReceive(handler) how many bytes do you expect? Are you getting all of them?)

Yes I am getting all the bytes back I expect. I am sending two bytes of 0xff and combining them into an int, just like the two bytes of the A/D conversion of the slider position.

It is all working as expected but it appears not to get the request until after the motors have stopped.

Are you using interrupts on the Nano to watch the positions? Could there be a timing conflict there? (too many high priority interrupts)

I am using a simple analogRead to watch for the position, so no none system interrupts are being used.

Ok weird

the handler is called without any reference to the loop AFAIK

are you answering from the handler or just setting a flag and capturing that in the loop?

Just answering from the handler.

So is the same true as well for the first type of command? ie you send an initial I2C command to ask to move the motor, this is received by the handler. Does the handler return then and let the loop handle the motor movement or issue the command for the motor right there? (The I2C bus is not released until the handler returns)

In TWI Bus (aka I2C Bus) Protocol --
1. Master sends executive command (s) to the Slave by executing Wire.write() instruction. The Salve is interrupted, and from the ISR it goes to Wire.onReceive() subroutine (SUR). In the SUR, the received command is interpreted and necessary actions are taken, after that, the control goes back to the loop() function.

2. Master sends data read command (s) to the Slave by executing Wire.requestFrom() instruction. The Slave is interrupted, and from the ISR it goes to Wire.onRequest() subroutine. In the SUR, the Slave finishes sending data to the Master; after that, the control goes back to loop() function.

3. For the Master, to receive flag status information/data from the Wire.onReceive() SUR of the Slave, it (Master) has to be configured as Slave as well (my understanding).

4. Just after sending the Wire.onRequest() command to the Slave, the Master will start polling the FIFO buffer to grasp the data (slider position) coming from the Wire.onRequest() SUB of the Slave.

Hope that the above information will become helpful to debug the program of the OP.

BTW: -1 is required to be defined in terms of a numerical value. What is about -- to assign a Control Character to it like ACK(06)?

UNO-1: Master/Slave Codes

#include <Wire.h>

volatile bool flag3 = false;    //this flag synchronizes the timing of the events between UNO-1 and UNO-2

void setup() 
{
  Serial.begin(9600);
  Wire.begin(0x50);

  Wire.onReceive(receiveEvent);
  
  Wire.beginTransmission(0x051);
  Wire.write(0x01);   //Command for Motor1
  Wire.endTransmission();

  while(flag3 == false)
   ;

  Wire.requestFrom(0x51, 1);   //slider position
  while (Wire.available() ==0)
    ;
  byte x = Wire.read();
  Serial.print("Slider value received: ");
  Serial.println(x, HEX);  //show slider position

}

void loop() 
{
  
}

void receiveEvent()
{
  sei();
  if(Wire.read() == 0x06)
  {
    Serial.println("Motor-1 Moved Command ACK Received from Slave!");
    flag3 = true;
  }
}

UNO-2: Master/Slave Codes

#include<Wire.h>

void setup()
{
  Wire.begin(0x51);    //address as a slave
  Serial.begin(9600);
 pinMode(13, OUTPUT);
 analogReference(DEFAULT);

 Wire.onReceive(receiveEvent);
  Wire.onRequest(sendEvent);
  
}

void loop()
{
 
}

void receiveEvent(int howmany)
{
    sei();
    if(Wire.read() == 0x01)  //Command to Move motor-1
    {
      Serial.println("Motor-1 Move Command Received");
      //--SEND ack (0X06)--
      Wire.beginTransmission(0x50);
      Wire.write(0x06);       //ACK Send 
    //  Wire.endTransmission();

      //---Motor-1 move command
      digitalWrite(13, HIGH);
      delay(5000);   //5-sec delay to move slider
      digitalWrite(13, LOW);
       Serial.println("Slider Moved!");
        Wire.endTransmission();
          
   }
}

void sendEvent(int howmany)
{
 
   Serial.println("Request Received to send Slider Position");
   Wire.write(analogRead(A0)); // slider position is sent from Ch-0 of ADC (3.3V = 0XA6)
  Serial.println("Slider-1 position is sent to Master");  
}

Operating procedures:
1. Upload UNO-1 codes.
2. Bring in UNO-1 Serial Monitor

3. Upload UNO-2 codes.
4. Bring in UNO-2 Serial Monitor

5. Press and Hold down Reset Switch of UNO-1
6. Activate the Reset Switch of UNO-2
7. Release the Reset Swich of UNO-1
8. Let us observe that the following dialog message has appeared on the Serial Monitors of UNO-1 (Top SM) and UNO-2 (Bottom SM).

Thanks for your interest.

I am under the impression that I am do all that.
It is a simple master / slave setup not a mulimaster setup like you put.

This is what happens in the slave
The receiveRequest function takes in the required slider positions and sets a flag. In the loop function this flag is tested and if set a move slider function is called and when it returns the flag is cleared.

The requestData function reports back the position of the sliders as two bytes, substituting two bytes of 0xff if the motor moving flag is set.

By the way:-

-1 is required to be defined in terms of a numerical value.

Not sure what that means but to a hardware man two bytes of 0xff is the two's complement of 1 and hence -1, by not clearing the moving flag in the move sliders function I always receive -1 from any request for the sliders position, so that bit works.

It looks to me like the function that switches on the motors and regulates the speed and monitors the position, is not being interrupted and running the requestData until after that function is finished.

This is what I see, and this is not what I thought happens. From the replies here, I think that is what everyone else imagens happens.

Data that comes from the Slave to the Master in response to Master's Wire.requestFrom() instruction is taken care of by the Master just after the request() command.

But if the Master expects data outside of Wire.requstFrom() command, the Master may (must) turn itself into a Slave and jump into the Wire.onReceive() routine to grasp the data. This is my understanding (program written on this understanding works well -- Post#8 ) from which the multi--master setup has emerged.

One idea: are you using volatile variables for the flag and position and having critical section in the loop when you reset the flag or update position (given Your handler is called from an interrupt) ?

the Master may (must) turn itself into a Slave and jump into the Wire.onReceive() routine to grasp the data.

No that is not how it works. A slave can send data but it can not initiate a data transfer. So a slave does not turn into a master to respond to a request for data.

One idea: are you using volatile variables for the flag and position and having critical section in the loop when you reset the flag

Not so sure about this but I will give it a try thanks.

Mike--

To gain a clue to the timing, perhaps you can turn on a led in the .onRequest() handler and see when it happens in relationship to the slider movement.

Can you please post the movement function which appears to not get interrupted?

No that is not how it works. A slave can send data but it can not initiate a data transfer. So a slave does not turn into a master to respond to a request for data.

A Slave like UNO-2 of Post#8 can initiate data transfer by turning itself into a Master. In the example of Post#8, the Slave (UNO-2) sends the ACK (0x06) in respone to Motor-1 command by executing the following instructions in the Wire.onReceive(receiveEvent) subroutine.

void receiveEvent(int howmany)
{
    sei();
    if(Wire.read() == 0x01)  //Command to Move motor-1
    {
      Serial.println("Motor-1 Move Command Received");
      //--SEND ack (0X06)--
      Wire.beginTransmission(0x50);
      Wire.write(0x06);       //ACK Send 
    //  Wire.endTransmission();

      //---Motor-1 move command
      digitalWrite(13, HIGH);
      delay(5000);   //5-sec delay to move slider
      digitalWrite(13, LOW);
       Serial.println("Slider Moved!");
        Wire.endTransmission();               //UNO-2 is generating the SCL pulses
          
   }
}

@golam

There is no need to turn into a master. The master does send a request for data from the slave and the slave answers (Master Reader/Slave Sender)

Sometimes when the slave is slow it might be useful to have some sort of a delay on the master because the master is in charge of generating the clock but here seems that the data back makes sense so ...

When I have used two Arduinos and I2c comms, I use a digital out pin on the slave to indicate to the master that I have a message and that I wasn it to send a message request.
I use short messages and a message queue to quickly hande messages and it seams to be very reliable.
I also put an led on the “I have a message pin” which helps with visual debugging.

A Slave like UNO-2 of Post#8 can initiate data transfer by turning itself into a Master.

That makes it a multi master system, it does not have to do that and I have had enough trouble with multi master systems professionally to put me off using one especially where there is no need to.

cattledog:
To gain a clue to the timing, perhaps you can turn on a led in the .onRequest() handler and see when it happens in relationship to the slider movement.

The problem is that I don't have the hardware at the moment. It is a joint project and I have passed it on to the person writing the master code.

Can you please post the movement function which appears to not get interrupted?

OK I will post the whole of the code. Hardware notes, a port expander controls the motors and a AT85 has the job of updating and driving the LEDs.
Too big to post so I will use an attachment.

Four_Sliders_I2C.zip (4.27 KB)

And a photo of the system just for context:-

can you check the behavior by replacing your requestData() handler with

void requestData() { // send the position of all 4 pots
  int data[4]; // buffer for the TWI answer

  for (int i = 0; i < 4; i++) {
    if (moveing[i]) {
      // return -1 if the slider is moving
      data[i] = -1;
    } else {
      // respond with message of 2 bytes MSB first
      int value = analogRead(analogueChannel[i]);
      uint8_t * dataPtr = (uint8_t *) & (data[i]);
      uint8_t * valuePtr = (uint8_t *) &value;
      *dataPtr = *(valuePtr + 1);
      *(dataPtr + 1) = *(valuePtr);
    }
  }
  Wire.write((uint8_t *) data, sizeof(data));
}

Reading your code I assume the master expects MSB first whereas your arduino is little endian and thus has LSB first in memory, so I'm swapping bytes to build the answer when the slider is not moving. (if you have also an arduino as the master, I'm not sure why you would swap bytes on both ends - receiving a buffer would already then be in the right order)

The reason I offer this idea is that I believe (long time I've not used it) that when you use the Wire library in slave mode you should issue only one Wire.write() command inside the ISR in response to a request from a master.

My understanding is that's because the master is the one responsible for the clock to get the data back and the write() call is basically encapsulating a call to twi_transmit() which fills the slave tx buffer with your data. If you call write() multiple times in a row at a fast pace your are not guaranteed that the twi_txBuffer has been emptied and you'll overwrite existing data there (or something like this :)) )

give it a try

PS: nice looking piece of hardware !