Wire library OnRecieve function behavior

I've been working on a couple of master/slave demos that communicate between 2 MCUs over I2C. I have it working and put in a lot of Serial.print statements to observe the behavior. I was puzzled by the fact that my OnReceive event function got called with the howMany integer = 0. It should contain the number of bytes that can be read.
In the code below I had to add the test for homany == 0 to keep from falling through the while loop the first time.

The master is only sending one byte and my console will print twice, once with 0 and once with the value sent, which is an incremented byte.

Any ideas? I'm hoping I missed something stupid.

void receiveEvent(int howmany) {
  byte x = 0;
  if (howmany == 0) {
    return;
    }
 
  // Read while data received
  while (Wire.available()) {
     x = Wire.read();
  }
  
  // Print to Serial Monitor
  SerialUSB.print("\nReceive event - data receviced = ");
  SerialUSB.println(x,HEX);
}

You missed to mention which Arduino board you use :wink:

The Arduino Due had this problem in the past. I hope you don't use the Due.

When the Master is sending one byte, then check if there is one byte:

void receiveEvent( int howMany) 
{
  if( howMany == 1)
  {
    int x = Wire.read();
  }
}

When 'howMany' is one, then there is no need to call Wire.available(), because you already know that there is one byte.

However, there is something that you should never do: Use Serial in a interrupt routine. The SerialUSB is even worse !
Store the data in an array, set a flag (volatile bool), and process it in the loop().
The onReceive is called from a interrupt routine, so it is really executed inside an interrupt routine.

Whatever you do, don't call SerialUSB functions from an interrupt routine.

I put in another print statement and see that there is 1 triggering of the OnRecieve function with howmany = 0 for every message the slave function receives. I have no idea why this is happening. The MCU is a Seeeduio Xiao.
I’ll add the complete code below:

/*
 Modified from the demo that Dronebotworkshop created.
  I2C Slave Demo
  i2c-slave-demo.ino
  Demonstrate use of I2C bus
  Slave receives character from Master and responds
  DroneBot Workshop 2019
  https://dronebotworkshop.com
*/

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

// Define Slave answer size
#define ANSWERSIZE 5

// Define string with response to Master
String answer = "Hello";

void setup() {

  // Initialize I2C communications as Slave
  Wire.begin(SLAVE_ADDR);
  
  // Function to run when data requested from master
  Wire.onRequest(*requestEvent); 
  
  // Function to run when data received from master
  Wire.onReceive(*receiveEvent);
  
  // Setup Serial Monitor 
  SerialUSB.begin(9600);
  SerialUSB.println("I2C Slave Demonstration");
}

void receiveEvent(int howmany) {
  byte x = 0;
  if (howmany == 0) {
    SerialUSB.println("receiveEvent howmany = 0");
    return;
    }
 
  // Read while data received
  while (Wire.available()) {
     x = Wire.read();
  }
  
  // Print to Serial Monitor
  SerialUSB.print("\nReceive event - data receviced = ");
  SerialUSB.println(x,HEX);
}

void requestEvent() {

  // Setup byte variable in the correct size
  byte response[ANSWERSIZE];
  
  // Format answer as array
  for (byte i=0;i<ANSWERSIZE;i++) {
    response[i] = (byte)answer.charAt(i);
  }
  
  // Send response back to Master
  Wire.write(response,sizeof(response));
  
  // Print to Serial Monitor
  SerialUSB.println("Request event - data sent");
}

void loop() {

  // Time delay in loop
  delay(50);
}

Koepel:
You missed to mention which Arduino board you use :wink:

The Arduino Due had this problem in the past. I hope you don’t use the Due.

When the Master is sending one byte, then check if there is one byte:

void receiveEvent( int howMany) 

{
 if( howMany == 1)
 {
   int x = Wire.read();
 }
}



When 'howMany' is one, then there is no need to call Wire.available(), because you already know that there is one byte.

However, there is something that you should never do: Use Serial in a interrupt routine. The SerialUSB is even worse !
Store the data in an array, set a flag (volatile bool), and process it in the loop().
The onReceive is called from a interrupt routine, so it is really executed inside an interrupt routine.

Whatever you do, don't call SerialUSB functions from an interrupt routine.

I missed your post while posting my latest. Thanks for the pointers. I’ll modify and play with this some more. I totally space on the print in the ISR. That was dumb. :confused:

So I removed the SerialUSB.println calls in the ISR and totaled the howmany =0 and how many requests I’m getting and they are still the same. So my skipping the read when howmany is zero solved the problem but it sounds like this is a bug that people see with certain boards. Should I report this to someone who works the Seeeduino Xiao libraries?
My new code is below:

/*
 Modified from the demo that Dronebotworkshop created.
  I2C Slave Demo
  i2c-slave-demo.ino
  Demonstrate use of I2C bus
  Slave receives character from Master and responds
  DroneBot Workshop 2019
  https://dronebotworkshop.com
*/

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

// Define Slave answer size
#define ANSWERSIZE 5

// Define string with response to Master
String answer = "Hello";

int num_howmany_zero = 0;
int num_request_received = 0;

void setup() {

  // Initialize I2C communications as Slave
  Wire.begin(SLAVE_ADDR);
  
  // Function to run when data requested from master
  Wire.onRequest(*requestEvent); 
  
  // Function to run when data received from master
  Wire.onReceive(*receiveEvent);
}

void receiveEvent(int howmany) {
  byte x = 0;
  if (howmany == 0) {
    num_howmany_zero += 1;
    return;
    }
 
  // Read while data received
  while (Wire.available()) {
     x = Wire.read();
  }
  

}

void requestEvent() {

  // Setup byte variable in the correct size
  byte response[ANSWERSIZE];
  
  // Format answer as array
  for (byte i=0;i<ANSWERSIZE;i++) {
    response[i] = (byte)answer.charAt(i);
  }
  
  // Send response back to Master
  Wire.write(response,sizeof(response));
  num_request_received += 1;
}

void loop() {

  // Time delay in loop
  delay(500);
  SerialUSB.println("\n----------");
  SerialUSB.print("num_request_received = ");
  SerialUSB.println(num_request_received);
  SerialUSB.print("num_howmany_zero = ");
  SerialUSB.println(num_howmany_zero);
  
}

It is a bug. On the other hand, your test is not completely correct :o

Seeeduino XIAO:

The XIAO board: https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html.
There is also a wiki.
That is a SAMD21 processor as used in the Arduino Zero and most MKR boards.
It is very small and cheap.

To use it, Seeedstudio wants to add their own development environment to the Arduino IDE.
That is terrible :stuck_out_tongue_closed_eyes:
That means you don't have the latest bug fixes of the Arduino team. Not the many years of Arduino support for that board. They could stop updating it any day.
It is like sitting in a sinking boat, you found the first hole that was eaten into the boat by bugs and you will never know when the boat will sink.

I don't know if they use their own bootloader.
Can you toss it away and buy a MKR board ?
The next best solution is put the Arduino bootloader on it and use it as a Arduino MKR board. You have to make a list of the pins that can be used and some pins might have other numbers.

About your sketch:

I don't understand why the compiler accepts the "Wire.onRequest(*requestEvent) ;", it should be "Wire.onRequest(requestEvent) ;".

Variables that are changed in a interrupt routine and used in the loop() should be "volatile". Without it, it is not certain that you see the actual value of 'num_howmany_zero'. That is the theory. If you see a number increasing then it can only be the interrupt code that increases it.

Creating a buffer runtime and using a String function in a interrupt routine should also be avoided. Therefor, your requestEvent() function can be improved. Using a global buffer that has the data ready is better.

Conclusion:

You could report a bug. Would it do any good ? If you continue to use it, then just add a test for 'howMany' and ignore any false call to onReceive.
Maybe an old version of the Arduino had this problem and maybe it was already fixed years ago. Please stop using this board.

P.S.: After writing this, I started to doubt what I wrote. Perhaps it was not the Due that had this problem, perhaps it was with the SAMD21 processors (MKR boards). It could even be possible that the current Arduino IDE still has this same bug. So I downloaded the Seeedstudio development environment and I think it is indeed a older version.
Looking at Issues · arduino/ArduinoCore-samd · GitHub, there seems to be a number of I2C issues with the official Arduino as well for the SAMD21 processors.

I appreciate your thoughts on this issue. I'm a retired BSEE, but I'm a beginner at playing with micro controllers. So I'm using the Xiao as a learning tool. For the price of a good Arduino board I could buy 10 Xiao board. So they are really throw-away's. I've got most of my demo code working now and I've learned a lot about using I2C as a media to get my Raspberry Pi's talking to a group of sensors. I have some ESP32-s2 wifi boards on order and I'll play with getting them talking to my network in the house.

Basically I'm killing time until the Covid stuff lifts and I can got back on road RVing around the USA.

Thanks, again.

I wrote: "add a test for 'howMany' and ignore any false call to onReceive", that is what I always do. Just an extra check to be sure.
In my opinion a often used and official Arduino board is better because of the many years of support. I even think that a clone is therefor better than a "almost"-compatible board. The exception is the ESP8266 and ESP32 because there is a lot of work done for those and there is a lot of support for those.

Can you explain more about the project ?

Is the Raspberry Pi communicating with the XIAO via I2C ?
Are the sensors connected to that same I2C bus ? Are both the Raspberry Pi and the XIAO a Master on the bus ?

The I2C bus is not that kind of bus. Use it for the sensors. It is better to connect the XIAO via USB to the Raspberry Pi and use serial/uart communication. As a bonus, you will be able to upload a sketch to the XIAO.
I have a slow Raspberry Pi Zero W and I use it without desktop and can upload a sketch with the command line.

At least everything (Raspberry Pi, XIAO, sensors) is running at 3.3V ! Others connect things with a 5V I2C bus to a 3.3V I2C bus, the I2C bus is not that kind of bus either.

At this point I don't really have a project per say. I have bags of sensors and MCUs so I'm seeing what they are capable of. I have 4 or 5 raspberry pi's and they have been used for pi.hole DNS servers, media backends and frontends, etc.
I've moved on to testing what can be done on a Raspberry Pi vs. a MCU. Obviously, analog is the domain of MCUs.

In the last 9 months, I've finished Netflix, tired of reading books. I do find interesting projects people do on Youtube. Some of those I will use as starting points.

I'm going to clean up the ISR part of the slave routine for the i2c just in case I need it. I had a little demo where my RPI host a local webpage that lets the use control LEDs on the RPI. I even pushed the LED control to the MCU and the RPI just told it what to turn on.

Again, thanks for your help.