crash when calling code from wire.onreceive

I am using an arduino nano as an I2C slave on a Raspberry Pi.
I declared a function to act on an incomming I2C message (wire.onreceive(I2CrecieveEvent) ), but i get a crash when calling code that otherwise works perfect...:

The function i am calling (bus_out) does some things with the input and calls another function (bus_send_byte). This function uses another function (bus_Pulsout) to actially set a pin high/low.

When i call the function bus_out from the setup() or loop() it works perfect. But when i call it form I2CrecieveEvent it crashes the arduino.
It seems that any code in the last function (bus_Pulsout) makes it crash. Even Serial.Println("i am here"); makes it crash.

Is this a known thing and is there a workaround?

post your code, also read the 'Read this before posting' message.

Presumably the problem is something to do with your software.

Which we can't see.

My guess (and it is just a guess as I don't know much about how the wire library works) is that the onreceive() sets up an interrupt handler. If so, then what you do in your onreceive() function must conform to specific criteria:

  1. It must be short. Short enough that it is finished before another message has a chance of arriving and triggering another interrupt.
  2. Must not - repeat NOT - use any functions that make any use of anything that is interrupt driven. This includes delay(), millis(), Serial.print/println/write etc.

The typical thing to have in an ISR like this is simply add the incoming message to a queue (an array for example) and leave it at that. Your main loop then looks to see if there are any messages in the incoming queue and processes them accordingly. That processing can then take as long as is required, and can use all the functions you like to use.

Hmm, if so that it creates an interrupt, it could explain why it crashes…

This is the relevant part of the code i am using:

#define I2C_ADDR    0x05 
// could have several ardex devices with different i2c addresses! use this adress in Python script!

#define ArdEx5VoltIsOK false
// Only set this true if the ArdEx arduino connects to rPi via a hardware 3.3V - 5V level convertor
// This option permits 5V operation on the arduino. (Meduino device 3v/5v switchable was assumed.)     

// standard arduino libraries:
#include <Wire.h>

// custom classes, part of ArdEx:
#include "Analog.h"
//#include "ibuf.h"

// Global/static variables/buffers, class objects:

Analog analog;                     // background analog read system hebben we nodig voor 


int NB_Bus_in = 2;                                                     //was vroeger pin 10 maar deze heeft geen interupt...
int NB_Bus_out = 11;
int NB_Bus_start = 12;
unsigned long knop;
byte toest = 0;
unsigned long Nbus;
String Nbus2;                                                      
String I2CResultaat;                                                  // wat we gaan terugsturen naar de I2C Master (Raspberry Pi) 
byte Verbruikweten = 0;                                               //als we het verbruik willen weten dit op 1 zetten
//de index voor het terugsturen van de i2c commando's in i2crequest
int index = 0;

void setup ()
{
    pinMode(NB_Bus_in, INPUT);       // in basis is dit pin 2
    pinMode(NB_Bus_out, OUTPUT);     // in basis is dit pin 11
    pinMode(NB_Bus_start, OUTPUT);   // in basis is dit pin 12
    pinMode(13, OUTPUT);             //de standaard LED op de Meduino

    // de seriële berichten ivm het starten
    Serial.begin (115200);
    analog.begin(4);         // dit is blijkbaar nodig om dat voltage uit te lezen. De 4 verwijst naar pin nr 4 welke de spanning is
    if ((analog.Vcc < 3500) || ArdEx5VoltIsOK)          // switched to 3.3 volt?  (3300 mV)
    { 
        Wire.begin(I2C_ADDR);         // join i2c bus
        Wire.onRequest(I2CrequestEvent); // register i2c TX request event ISR
        Wire.onReceive(I2CreceiveEvent); // register i2c RX event ISR
        Serial.print("Vcc = ");
        Serial.print(((float)analog.Vcc) / 1000); 
        Serial.println (" Volts.");
        Serial.println ("I2C is ready for rPi."); 
        digitalWrite (13, HIGH);    
    }
    else
    {
        Serial.print("Incoming i2c pin volts = "); 
        Serial.println ((float)(analog.Vcc * analog.read(4)) / 1023000L); 
        Serial.print("But Arduino is on "); 
        Serial.print(((float)analog.Vcc) / 1000); 
        Serial.println (" Volts. Please switch to 3.3"); 
        while (true)  //  loops forever
        {
            // This flashing LED means VCC switch is on 5V. rPi does not like 5v! DANGER.
            digitalWrite (13, HIGH);    
            delay(100);
            digitalWrite (13, LOW);  
            delay(100);        
        }       
    }
 
  }


// event service function that executes when i2c data packet was received from master (rPi).  
void I2CreceiveEvent(int howMany)
{
   int ichr;
   int k=-1;   
   String Bericht;
   char opdrachttype;

   while(Wire.available()>0) //blijven lezen zolang er iets beschikbaar is. 
     {
       ichr = Wire.read(); 
       if (k<0) {
         opdrachttype = char(ichr);       
       }
       else {         
         Bericht = Bericht + char(ichr);
       }
       k++; 
     }
   Bericht = Bericht.substring(1);
   char Bericht2[k];
   Bericht.toCharArray(Bericht2,k);
   Serial.println(k); //comment out in final
  
  switch (opdrachttype)
    {
    case 'C':                                                      
          if (k == 7){  
           I2CResultaat = bus_out(Bericht2);  
           Serial.println(I2CResultaat); //comment out in final
          } ;         
        break;
        
    }
}

void I2CrequestEvent()
{
   // rPi has requested a byte of data 
  
      Wire.write(I2CResultaat[index]); 
     Serial.println(I2CResultaat[index]); //comment out in final
     ++index ;
     if (index >= I2CResultaat.length()){  
       index = 0;
     }

}    


void bus_send_byte(int NB_aantal,...)
{ int NB_Byte;
  va_list NB_Bytes;                                    // A place to store the list of arguments
  va_start(NB_Bytes, NB_aantal);                       // Initializing arguments to store all values after num
  detachInterrupt(0);
  if (NB_aantal == 3) {bus_Pulsout(0);}            
  if (NB_aantal == 3) {bus_Pulsout(0);}            
  if (NB_aantal == 3) {bus_Pulsout(3);}
  for (int Nb_k = NB_aantal; Nb_k>0; Nb_k--)
  { NB_Byte=va_arg(NB_Bytes, int);                    
    for (int Nb_k = 7; Nb_k>=0; Nb_k--)
    {  bus_Pulsout(bitRead(NB_Byte,Nb_k));         
    }
  }
  if (NB_aantal == 3) {bus_Pulsout(5);}
}


String bus_out(char *tagStg9)  
{
byte byte1, byte2, byte3;
    
    int aLen = 3;
    byte val1 = 0;          
    byte val2 = 0;          
    byte hexVal[3];        
    char dig1;              
    char dig2;              
    int i =0;                    // Master index
    int j =0;                    // Sub index
    for (i= aLen - 1; i >=  0; i--)               // Convert function from String HEX value to ascii 
          {
            dig1 = tagStg9[j++];
            //Serial.println(dig1); 
            dig2 = tagStg9[j++];
            //Serial.println(dig2);
           if(dig1 >= '0' && dig1 <= '9')
            val1 = dig1 - '0';
           else
            val1 = dig1 - 'A' + 10;
           if(dig2 >= '0' && dig2 <= '9')
            val2 = dig2 - '0';
           else
            val2 = dig2 - 'A' + 10;
           hexVal[i] = val1 * 16 + val2; // The ascii value
           //Serial.println(hexVal[i]);   //comment out in final
            }

  byte1 = byte(hexVal[2]);
  byte2 = byte(hexVal[1]);
  byte3 = byte(hexVal[0]);

for (int Nb_i = 1; Nb_i<=3; Nb_i++) bus_send_byte(3,byte1, byte2, byte3);                                  
return "OK";
}



void bus_Pulsout(int Tijd)
{  
   digitalWrite(NB_Bus_out, HIGH);   // sets the pin on
   delayMicroseconds(160);        // pauses for 150 microseconds    
   digitalWrite(NB_Bus_out, LOW);
   Serial.println(Tijd); //comment out in final
   Tijd=640+(Tijd*800);
   delayMicroseconds(Tijd); 
   
}


void loop ()
{ 
}

I have just looked at the source for the Wire library, and yes that routine is called from within the I2C receive interrupt.

So you will have to shift most of your code to loop() and use a simple queueing mechanism.

just tried it and...it works!