I2C communication stacks

Hello,
I want to communicate RaspberryPi 3B+ (master) with Arduino Mega (slave). The code works fine, but only for some time.

//27.03.2021
//send buffer:
//[0] -> controll Value
//[1]-> digitalPins 38->45 //[2]-> digitalPins 46->53
//NEW//[3-18] A0-A15   //OLD//[3..6]-> A0 //[7..10]-> A1 //[11..14]-> A2 //[15..18]-> A3 //[19..22]-> A4 // [22-32] A5-A15

//receive buffer:
//[1]digitalPins 22-29 //[2]digitalPins 30-37
//[3-14] PWM pins 2-13

//18+14=32
//I2C rate: 100kbs->12500 B/s->12.5 B/ms
//32/12.5 <4ms

#include <Wire.h>

volatile byte receiveBuffer[22];
volatile byte sendBuffer[36];
byte S1; //sendBuffer [1]
byte S2; //sendBuffer [2]
int Adres=8;
int R1[8];// receiveBuffer[1]
int R2[8];// receiveBuffer[2]
void setup() {
  //Wire.setClock(400000);
  // put your setup code here, to run once:
  Wire.begin(Adres);
  Wire.onReceive(receiveEvent); 
  Wire.onRequest(requestEvent); 
  for (int pin=2; pin<38; pin++)
  {pinMode(pin, OUTPUT);}
  for (int pin=38; pin<54; pin++)
  {pinMode(pin, INPUT);}
  //Serial.begin(9600);
}

void loop() 
{
}

/////////////////////////////////////////////////////////////////////////////////////

void DigitalInputs()
{
  //Wejscia 0->7
  S1=0;
  S2=0;
  int ByteVal=1;
  for (int i=38;i<46;i++)
  {
    int j=i+8;
    if (digitalRead(i)==HIGH){S1=S1+ByteVal;}
    else{S1=S1;}
    if (digitalRead(j)==HIGH){S2=S2+ByteVal;}
    else{S2=S2;}        
    ByteVal=ByteVal*2;
  }
  sendBuffer[1]=S1;
  sendBuffer[2]=S2;
}

/////////////////////////////////////////////////////////////////////////////////////

void DigitalOutputs()
{
  int ByteVal=1;

  for (int i=0;i<8;i++)
  {
    R1[i]=0; 
    R2[i]=0;
    
    int pinNr1=i+22;
    int pinNr2=i+30;
    
    R1[i]=receiveBuffer[1] & ByteVal;
    R2[i]=receiveBuffer[2] & ByteVal;
    
    if (R1[i] == ByteVal) { digitalWrite(pinNr1, HIGH);}
    else  {digitalWrite(pinNr1, LOW);}
    
    if (R2[i] == ByteVal) { digitalWrite(pinNr2, HIGH);}
    else  {digitalWrite(pinNr2, LOW);}
    
    ByteVal=ByteVal*2;
   // if (pinNr2==33){Serial.println(receiveBuffer[2]);}
  }  

 }
 
/////////////////////////////////////////////////////////////////////////////////////

void AnalogInputs()
{
  for (int i=0;i<16;i++)
  {
    int analogPinNr=A0+i;
    int BuffPos=3+i;
    int analogValue=analogRead(analogPinNr);
    sendBuffer[BuffPos]=map(analogValue,0, 1023, 0, 250);
  }
}

/////////////////////////////////////////////////////////////////////////////////////

void PWMOutputs()
{
  for (int i=3;i<15;i++)
  {
    int pinNr=i-1;
    analogWrite(pinNr, receiveBuffer[i]); 
  }
}

/////////////////////////////////////////////////////////////////////////////////////

void receiveEvent(int Length)
{  
  int i=1;
  while( Wire.available())  {receiveBuffer[i] = Wire.read(); i++;}
  DigitalOutputs();
  PWMOutputs();
}

/////////////////////////////////////////////////////////////////////////////////////

void requestEvent()
{ 
  DigitalInputs();
  AnalogInputs();
  byte sendbuffer[18];
  sendbuffer[0]=1+4+128+16;
  for (int i=1;i<18;i++)  {sendbuffer[i]=sendBuffer[i];}
  Wire.write(sendbuffer,18); 
}

As you can see, on beginning i wanted to receive 14 bytes (position 0 in receiveBuffer is not used) and send 32 bytes ( position 0 in sendBuffer is not used). In theory during 4ms (this is interval which is used for calling a program on Raspberry) all data should be transfered (standard communication speed is 100kbs, so during 1ms there is transferred 12,5 Bytes). But in practice Communcation is stacking almost each second.

So i decided to lower Analog read resolution for each input so from Arduino i send 18Bytes and the communication is almost fine.
But I have noticed that on Raspberry sometimes there is received buffer with value 255 on each byte (sometimes it where also othere values like 64, or 96). So I decided to add control number in sendbuffer[0].
But again communication started to stack with 19 bytes sending (not each second but after a minute). So i have deleted from sending the last Analog pin value.

After 7 times checking it has stacked four times (each check was 2hours long, this is limit of software work time on raspberry).

So my questions are:

  1. Why it stacks with bigger buffer size? I can notice nice 32 value (14B received +18B send = 32B), but i have read 3 books about arduino (however only in one of them is nicely described i2c communication but not Wire library), wire library description and nowhere i could see anything about byte limits, only the communication frequency.

  2. What can i improve yet to avoid stacking?

3.If i put some code in loop(), would i need more improvement to communication?

Can I suggest that you edit your original post to break the description of what you are doing and the problems that you have encountered into paragraphs with blank lines between them in order to make it easier to read and understand

I have edit it, I hope it's better, but sadly usually I explain things chaotically ;/

Thank you. It is much easier to read than it was as a slab of text

I suggest that you re-organize your Slave codes as follows to reduce overheads of the receiveEvent() and requestEvent() interrupt service routines.

volatile flag1 = false, flag2 = false;
void loop()
{
    if(flag1 == true)
    {
        DigitalOutputs();
        PWMOutputs();
        flag1 = false;
    }
    if(flag2 == true)
   {
        DigitalInputs();
        AnalogInputs();
        flag2 = false;
   }
}

void receiveEvent(int Length)
{
      for(int i=0; i<Length; i++) //collect all the recived bytes and then process whatever you need
     {
          receiveBuffer[i] = Wire.read();
     }
     flag1 = true;
}

void requestEvent()
{
    byte sendbuffer[18];
    sendbuffer[0]=1+4+128+16;
    for (int i=1;i<18;i++)  
    {
         sendbuffer[i]=sendBuffer[i];
    }
    Wire.write(sendbuffer,18);
    flag2 = true;
}

If you know exactly how many bytes are received, then I would add a check to avoid replying to a misfire.

void receiveEvent(int howMany)
{ 
  if( howMany == 14)     // the exact number that you expect
  {
    for(int i=0; i<howMany; i++)    // read the exact number of bytes
    {
      receiveBuffer[i] = Wire.read();
    }
    DigitalOutputs();
    PWMOutputs();
  }
}

We prefer that you do code for the pins in the loop(). Can you move the I/O control of the pins to the loop() ?

The onRequest handler halts the Master to run the software, that is your requestEvent() function. When that function is finished, the Arduino has the bytes in a buffer and then the Master can request those bytes one by one. That means that your requestEvent() should be as short as possible.

Do you have extra pullup resistors ? Do you use long wires or a cable. The SDA should not be next to SCL in a flat ribbon cable.

The Arduino Mega 2560 has onboard pullup resistors of 10k to 5V. The pins of a Raspberry Pi are not 5V tolerant. That means you may not connect a Mega to a Raspberry Pi.

The buffer size is described (by me): Buffer size of the Wire library.

The sketch by GolamMostafa is how it should be done. That is how to move things into the loop() and keep the onRequest and onReceive handlers as short as possible. I decided to post this anyway to give some background information.

Thank you for your replies!!!

I have moved pin functions to loop. The reason why there was in events was that i thought the interuupts are so often that loop() is not executed.

void loop() 
{
  if (dataReceived==true)
  {
    DigitalOutputs();
    PWMOutputs();
    dataReceived=false;
  }
  if (dataSend==true)
  {
    DigitalInputs();
    AnalogInputs();
    dataSend=false;
  }
}

Now receiveEvent looks like:

void receiveEvent(int Length)
{  
  Serial.println(Length);
  if (Length==14)
  {   
    for(int i=0; i<Length; i++){receiveBuffer[i+1] = Wire.read();}
    dataReceived=true;
  }
}

And i have noticed that i receive something bigger than 14 and then it stops working. After commenting Serial.println(), it also stacks. And sadly right now it's very quick (less than minute). But at least Sending to Raspberry works properly (I'm checking yet for how long).

Between Arduino and Raspberry i have logic level converter, nothing more. For connection i use single wires.

Reading an analog input on the Mega takes slightly more than 100 microseconds, so you are going to spend around 1.6 milliseconds reading the analog data.

Do not use print in the receiveEvent or requestEvent functons, those functions are called by ISRs (interrupt service routines).

I know that using Serial in interuppts is not good but i can debug it and see what's wrong (I'm used from PLC programming that i know all the time what's happening with variables).

If reading Analogs would take 1.6ms then calling program on Raspberry Pi should be just enough (right now i'm calling program once per 20ms, it's the same time as Reading Inputs Outputs for Siemens moduls for default).

I have found on forum how to reset I2C Communication so right now receiveEvent looks like this:

void receiveEvent(int Length)
{ 
  //Serial.println(Length);
  if (Length==14)
  {   
    for(int i=0; i<Length; i++){receiveBuffer[i+1] = Wire.read();}
    dataReceived=true;
    //DigitalOutputs();
   // PWMOutputs();
  }
  else
  {
Serial.println(Length)
    TWCR=0;
    Wire.begin(8);
  }
}

And from what I noticed it works. time to time in Serial I can see that there is wrong buff size, but leds are working properly (at least my eye don't catch the stack moment). But the question is if this solution is ok? Is it safe or not too much? It will not be used in Industry but still I would like to have it reliable.

I have found on forum how to reset I2C Communication

Why do you need to do that ?

Because communication stacks

What exactly do you mean by the communications stacking? Is the "stacking" occurring on the arduino or the raspberry pi?

Have you tried increasing the I2C speed on the raspberry pi? The mega can handle up to 400KHz if the wiring is not too long.

It is apparently not causing you any problems, but the I2C bus is on pins 20 and 21, do not set those to output after calling Wire.begin.

void setup() {
  //Wire.setClock(400000);
  // put your setup code here, to run once:
  Wire.begin(Adres);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
  for (int pin = 2; pin < 38; pin++) //<<<<< do not set pins 20 and 21 to OUTPUT while being used by I2C
  {
    pinMode(pin, OUTPUT);
  }
  for (int pin = 38; pin < 54; pin++)
  {
    pinMode(pin, INPUT);
  }
  //Serial.begin(9600);
}

What exactly do you mean by the communications stacking? Is the "stacking" occurring on the arduino or the raspberry pi?

It is definitly from Arduino side. It is enough to reset arduino and it works again. And when in receiveEvent was Serial.println, re-opening the Serial monitor also restored normal functioning. By stacking I mean that communication interrupts are not executed. After checking received buffer size only receiveEvent is not executed, however i have run 3 times Raspberry pi program each time for 2 hours, and once requestEvent also stacked.

Have you tried increasing the I2C speed on the raspberry pi?

On Raspberry I use Codesys and I'm not sure if it's enough to chnge in RPi config or I have to do something in Codesys also. I haven't checked that yet. But yes, I think that it would be much better.

It is apparently not causing you any problems, but the I2C bus is on pins 20 and 21,

Minus of calling pinMode in for loop, I didn't noticed that.

Why are you using I2C? The Mega has multiple hardware UARTS. Why not use high-speed serial comms?

SVP, my google fails to uncover the meaning of “stack” or “stacking” in this context.

Anyone, simply: What exactly do you mean by the communications stacking?

a7

Why are you using I2C? The Mega has multiple hardware UARTS. Why not use high-speed serial comms?

Because of using Codesys on RaspberryPi i Can use:
-Ethernet
-I2C
-SPI
-OneWire
I think One Wire is definitly wrong choice, SPI from what i know require additional pin for saying I'm using you so I don't want it also. I2C seems to be working right now. According to Ethernet I have tried with Ethernet library:
-2 different Modbus TCP/IP libraries
-simple TCP/IP communication
-UDP
None of them has worked and i have completly no idea why (month ago i have posted a topic but no replies both here and on Codesys forum). From Arduino side i think I have done everything properly, from RPi also, I can ping arduino From RPi. But in Codesys I don't see Arduino node. And of course the best for me would be using ethernet. It would be much more similar to industrial solution, and I wouldn't be limited with short wires for I2C.
but as i wrote, right now I2c at least works for a short time.

SVP, my google fails to uncover the meaning of "stack" or "stacking" in this context.

Anyone, simply: What exactly do you mean by the communications stacking

Values in buffer should be changing. when it stacks i receive, wrong buffer size and every values are 255 and no code is executed anymore

when it stacks i receive, wrong buffer size and every values are 255 and no code is executed anymore

You still haven't explained what "stacks" or "stacking" means.

I have exactly explained. Communication doesn't work anymore. It is freezed. No response from Arduino side. I don't know how to explain it easier

EDIT:
I have found translation from my language as suspended but I think that it is according to job not to communication. On plant I have always used 'stacked' for description of such a situation and it was understandable.

sorry my bad with misspeling it should be stuck not stack.

I actually considered using google translate to toss your writing around in a few languages!

To see if it would unstack this terminology. :wink:

I think most would casually refer to this has "hanging".

THX

a7