Cant pass values from onRecieve event to a global array. Bug?

Ok so this is very confusing for me.
I have a setup where a Raspi 0 W is sending an Arduino Uno a series (27 to be exact) bytes every second via I2C.
My setup works perfectly hardware-wise as you will see in the following example.

This code here works fine...

#include <Wire.h>

int dataArray[27];

void receiveEvent(int howMany) {
  int x = 0;
  while(Wire.available() > 0) {
    dataArray[x] = Wire.read();
    ++x;
  }
  for(int i; i <= 27; ++i) {
    Serial.print(dataArray[i]);
  }
  Serial.println("");
}

void setup() {
    Wire.begin(4);
    Wire.onReceive(receiveEvent);
    Serial.begin(9600);
}

void loop() {

}

...because i get:

021115221736951678822016235213153421281906522820527821731213185
021115221737951678822016235213153421281906522820527821731213185
021115221738951678822016235213153421281906522820527821731213185
021115221739951678822016235213153421281906522820527821731213185
021115221740951678822016235213153421281906522820527821731213185
021115221741951678822016235213153421281906522820527821731213185
021115221742951678822016235213153421281906522820527821731213185
021115221743951678822016235213153421281906522820527821731213185

...from the Serial monitor. (Which is what i want. Its data like time, date, color values etc)

HOWEVER if i use this code here:

#include <Wire.h>

int dataArray[27];

void receiveEvent(int howMany) {
  int x = 0;
  while(Wire.available() > 0) {
    dataArray[x] = Wire.read();
    ++x;
  }
}

void setup() {
    Wire.begin(4);
    Wire.onReceive(receiveEvent);
    Serial.begin(9600);
}

void loop() {
  for(int i; i <= 27; ++i) {
    Serial.print(dataArray[i]);
  }
  Serial,println("");
}

...i get this nonsense from the serial monitor. (I of course was expecting it to print more often but it prints total nonesense)

000000000000000000000000000260

000000000000000000000000000260

000000000000000000000000000260

000000000000000000000000000260
(there were a lot more line breaks between those numbers)

Does anybody have an Idea why this might not work?

The Arduino Uno has a 5V I2C bus and the Raspberry Pi has a 3.3V I2C bus. That does not match. You should use a level converter.

You need a 'flag'. A 'bool' variable that is set in the onReceive handler and then the code in the loop() checks that flag if there is new data and reset that flag.

Variables that are written in a interrupt routine and are also used in the loop() should be made 'volatile'.

When an array has 27 elements, then it is dataArray[0] up to dataArray[26] (inclusive).
A for-loop for that array is this:

for( int i=0; i<27; i++)
{
  ...
}

You use data from outside the array when you have "i <= 27".

Do you always get 27 bytes ? Not integers ? Then I suggest to use bytes.

volatile bool newData = false;
volatile byte dataArray[27];

void receiveEvent( int howMany) 
{
  if( howMany == 27)      // it must be 27 bytes
  {
    Wire.readBytes( dataArray, 27);
    newData = true;
  }
}

void loop()
{
  if( newData)
  {
    for( int i; i<27; i++) 
    {
      Serial.print( dataArray[i]);
    }
    Serial.println();

    newData = false;   // reset the flag
  }
}

It depends on the data if you want to use the flag to block new data until the previous data has been processed.
This is possible, but most of the time not needed:

void receiveEvent( int howMany) 
{
  // it must be 27 bytes and sketch must not be busy with the previous data
  if( howMany == 27 and !newData)
  {
    Wire.readBytes( dataArray, 27);
    newData = true;
  }
}

If timing is important and the data must stay together at all times, then it is possible to make a copy of the data in the loop(). This is almost never done, but it might be needed.

void loop()
{
  if( newData)
  {
    byte copiedData[27];

    noInterrupts();
    memcpy( copiedData, dataArray, 27);
    newData = false;
    interrupts();

    for( int i; i<27; i++) 
    {
      Serial.print( copiedData[i]);
    }
    Serial.println();
  }
}

So i managed to overcome the problem of not being able to write to a global array.
But know i have a different problem that being that the arduino or at least the serial monitor only outputs the values it Recieved after its been reset and bytes where available on the I2C.

The code:

#include <Wire.h>

volatile byte dataArray[27];

void setup() {
    Wire.begin(4);
    Wire.onReceive(receiveEvent);
    Serial.begin(9600);
}
  
void loop() {
  for(int i; i < 27; ++i) {
      Serial.print(dataArray[i]);
  }
  Serial.println("");
}

void receiveEvent(int howMany) {
  for(int i; i < 27; ++i) {
    dataArray[i] = Wire.read();
  }
}

Any ideas?

Koepel:
Variables that are written in a interrupt routine and are also used in the loop() should be made 'volatile'.

And unless the operation is atomic, interrupts should be disabled when accessing them from outside the interrupt context.

NoWayOut8344, where is the 'flag' ?

Koepel:
void loop()
{
if( newData)
{
byte copiedData[27];

noInterrupts();
memcpy( copiedData, dataArray, 27);
newData = false;
interrupts();

for( int i; i<27; i++)
{
Serial.print( copiedData*);*

  • }*
  • Serial.println();*
  • }*
    }[/quote]
    I see you code here does do that. But I think it's a point worth mentioning still.

Koepel:
NoWayOut8344, where is the 'flag' ?

In programming terms a 'flag' is just a variable used to signal something. You can compare it to raising and lowering a physical flag. It allows parts of the program to influence other parts. In Koepel's example it would be the 'newData' variable.

You see the problem when doing this:

#include <Wire.h>

volatile byte dataArray[27];
volatile bool newData;
byte copiedData[27];

void setup() {
    Wire.begin(4);
    Wire.onReceive(receiveEvent);
    Serial.begin(9600);
}
  
void loop() {
  if (newData) {
    noInterrupts();
    memcpy(dataArray, copiedData, 27);
    newData = false;
    interrupts();
    for(int i; i < 27; i++) {
        Serial.print(copiedData[i]);
    }
    Serial.println("");
  }
}

void receiveEvent(int howMany) {
  if(!newData) {
    for(int i; i < 27; ++i) {
      dataArray[i] = Wire.read();
    }
    newData = true;
  }
}

I only get:

00000000000000000000

this once from the serial output.
Idk why but it seems that using a flag just somehow blocks the execution and volatile variables are only 0?

Start with the simple short example in my reply #2.
The two things after that are extras and might be needed, but first you have to make it work.

The memcpy() has this format: memcpy( pointer to destination, pointer to source, amount of bytes)
http://www.cplusplus.com/reference/cstring/memcpy/.
Every time you see a 'memcpy', say out loud: "Destination, Source".

Well if i try your example i only get an error:
no matching function for call to 'readBytes(volatile byte [27], int)'

It seems like readBytes isnt inluded in the current version of Wire

I'm sorry :-[ there are two bugs and it was not a complete example.

The setup() function and include <Wire.h> have to be added.
The readBytes should be: Wire.readBytes( (byte *) dataArray, 27);
The for-loop should be: for( int i**=0**; i<27; i++)

Np man the only thing i needet to know was : (byte*). But im not getting anything on the serial monitor.
?

(Why is i2c being so confusing :slightly_frowning_face: )

Then I assume that the Raspberry Pi is not sending 27 bytes.
You could fill the first of the array with the 'howMany' and set the flag. On the serial monitor, look only at the first number and ignore the rest:

// temporary test
void receiveEvent( int howMany)
{
  dataArray[0] = howMany;    // get the number of received bytes
  newData = true;
}

If you want my opinion, you should not use I2C between an Arduino and Raspberry Pi in the first place. Have a look at what I wrote two weeks ago: How to make a reliable I2C bus.