Hardware Interrupt and I2C interference - how to resolve

I have an issue and not quite getting my way around it, its part of a project so I will try be brief and concise.

Essentially I have couple Arduinos communicating via I2C, thats working fine.
One Arduino is a master and requests data from all the slaves around 20 times a second.

One of the Slave arduinos has 2 interrupts attached to it INT0 and INT1, INT0 is reading a pulse signal up to 13 Khz, INT1 is also reading a pulse up to 200 Hz (0.2KHz) along with other Analog and Digital pins.

When the I2C is operational, there are some missed pulses not read by the Slave Arduino. To troubleshoot I added a switch to the master, so only when pressed the master requestes the data from the slaves via I2C and not 10 - 20 times a second , when doing that, the pulses are all accounted for in the slave arduino and counted properly and transmitted to the master. Which Leads me to believe that the I2C and interrupts are interfering on the slave.

I need to keep the I2C reading the data continuously 10 20 times a second, and not loose any pulse counts. Is there a solution?

I have been reading about interrupts and I2C, seems to me that I2C is also linked to some sort of interrupt, and when the Slave is in the I2C routine which as I understand is an ISR then other interrupts are discarded. am I correct?

Thanks in advance for any assistance.

Hi, welcome to the forum.
You are an advanced user of Arduino then ? and this is your first post ?

Tutorial about interrupts : Gammon Forum : Electronics : Microprocessors : Interrupts

When an interrupt service routine is busy, no other interrupts are handled. After the interrupt service routine has finished, there might be an other interrupt pending, which will be executed.

The data coming in and going out with I2C is with interrupts, those interrupts will delay other interrupts.

Calculations: 13kHz is 77us between interrupts. According to the tutorial an interrupt could be 5us.

Can you show your complete sketch (between code tags) ?
Perhaps you do too many things in the I2C receiveEvent() and requestEvent() or in the other interrupts.

Which Arduino boards are you using ?
Do you use edge trigger interrupts ?

Thanks for the quick reply and insight on interrupts.

Well i am just a hobbyest, 1 year ago didn't even know what an Arduino is! Forums and the internet gives you a wealth of knowledge.

Just to give a brief of the Project, maybe to understand the picture better.

I have a CNC mill which I would like to add some protection to and display vital info on a TFT screen; Temps, Spindle Speeds, etc...

How I divided my Project:

A teensy 3.1 is connected to a TFT touch screen, controls the Safety swith and relay (on/off) via relays. The Teensy reads all the sensor values from 5 arduinos in bare format via I2C (Teensy is the Master in the I2C setup). 2 Arduinos are for the spindle, still work in progress till now no issues there. the other 3 Arduinos are hooked each to a stepper motor of the CNC mill (one on each axis X,Y and Z)

Each axis Arduino reads the pulses and direction signal from the Break out board that controls the stepper motor. I have added a DIY encoder on the back of the stepper motor to monitor and read the rotation of the stepper motor, its essentially a perforated disc (10 notches) and a H21A photointerrupter that reads the rotation so as to say.

So the pulse signal is connected to 1 interrupt, and the photointerrupter is connected to the other, the direction signal is connected to a digital pin.

Below are the 3 ISR's extracted from the sketch. I'll post the sketch in the next post as it will exceed the max no of chars in the post.

Thanks in advance.

//===========================================================
//=========  Pulpresses_cntr interrupt routine    ========
//===========================================================
void Pul_void()
{     
    dir_pin_stat = digitalRead (dir_pin);
    if (dir_pin_stat == HIGH) dir_pin_val = +1; else dir_pin_val = -1;
        Pul_count_net = Pul_count_net + (long) dir_pin_val;
        pul_total     = pul_total     + (long) dir_pin_val;
        pul_odo++;
        if (pul_odo > 16000) {  pul_odo=0L;  pul_odo_cm++;  }
}

//===========================================================
//=========  H21A_total interrupt routine    ========
//===========================================================
void H21A_void()
{  
    dir_pin_stat = digitalRead (dir_pin);
    if (dir_pin_stat == HIGH) dir_pin_val = +1; else dir_pin_val = -1;
    if ( dir_pin_prev != dir_pin_val) {
        Pul_count_net = 0L;
                                      }  // end if
    else                              { 
      Pul_counter_diff = (long) (  (dir_pin_val) * ( H21A_pul_width - abs  (Pul_count_net) )  );
      Pul_counter_error = Pul_counter_error + Pul_counter_diff ;
      Pul_count_net = 0L;
                                      }  // end else ..........    
    dir_pin_prev = dir_pin_val;
//    H21A_total = H21A_total + dir_pin_val;
}


//===========================================================
//=========  I2C subroutine to send to Master    ============
//===========================================================
void send_i2c_data()
{
//  st_timer3 = micros(); 
      String str1="" ;
      char qqq [50] ;
      zzcounteri2c++;
  int i_vl = 0;
  int i_dg = 0;
    for (i_vl = 0; i_vl < no_val; i_vl ++)                      {
      for (i_dg = 0;   i_dg  < no_dgt;   i_dg ++)               {
      str1 = str1 + (String) dgt_arr [arr_rdy] [i_vl] [i_dg];   
      
                                                                } // end for loop
                                                                } // end for loop
          //Wire.requestFrom(5, 32) on master should be 1 no less than write
          //only worked to 32 bytes to send
        str1.toCharArray(qqq,31);
        Wire.write(qqq);
}    // end void send_i2c_data()...............

This is the whole code for reference.

Had to remove the ISR's posted in the previous post to have less than 9000 chars.

// this worked on boaeduino as a second i2c slave
//worked with teensy as "master"  with MasterUNO also sending i2c



#include <Wire.h>
#include <SPI.h>

/***********************************************************************
************* Declarations *********************************************
************************************************************************/

//-----------add here pin numbers for analog values--------

  int dir_pin =          4;                // pin number where dir pin is connected
  int t_stmotor_pin =   15;                // pin number where dir pin is connected
  int t_stdriver_pin =  17;                // pin number where dir pin is connected

  int temp_st_motor = 0;
  int temp_st_driver = 0;
  int coded_value = 0;
  
  int test_led =13;
  boolean test_led_stat=LOW;
  int test_led_counter=0;
 
  volatile int dir_pin_val =  1;                // based on dir_pin_stat LOW HGH, value is +1 or -1
  volatile int dir_pin_prev = 1;                // previous val for checking
  volatile boolean dir_pin_stat = LOW;          // value read from dir_pin


  volatile long  pul_odo = 0L;             // Total Pulses Just adds mileage
  volatile long  pul_odo_cm = 0L;          // Total mileage in cm's
  volatile long  pul_total = 0L;           // Total Pulses adjusted for dir +/- dir
  volatile long  Pul_count_net = 0L;       // no of pulses after h21a interrupt +/- dir, if increase a lot > stalled!!!!!!!GOES VIA I2C
  volatile long  Pul_counter_error = 0L;   // no of pulses over or under run !!!!!!!GOES VIA I2C
  volatile long  Pul_counter_diff = 0L;    // temp clc'd value, 
  
  long H21A_pul_width = 80L;             // 1600 stps/mm / encoder slots = 1600/10/2
  volatile long  H21A_total = 0L;     // H21A counts adjusted for dir +/- dir
  
  volatile int  no_val    = 6 ;
  volatile int  no_dgt    = 5 ;
  volatile int  arr_rdy   = 0;  // this value is 0 or 1 for which array is ready for i2c
  volatile int  arr_fil   = 1;  // this value is 0 or 1 for which array is being filled in loop
  volatile int  dgt_arr  [2] [6] [5];  //  no_val , no_dgt; converted and i2c sent from here
  long val_arr  [6];
  long temp_val_prev =  0L;
  long temp_val =       0L;
  int factor_10 [5] = {10000,1000,100,10,1};  //same as no_dgt
  
  long zzcounter = 0L;
  volatile long zzcounteri2c = 0L;
  int zztestcount = 0 ;
  volatile int ewqewq=3;    

/***********************************************************************
************* SETUP ****************************************************
************************************************************************/

void setup()   {
  
  Serial.begin(9600);
  Wire.begin(15);                   // join i2c bus with address #2
  Wire.onRequest(send_i2c_data);    // register event
  
    pinMode (test_led, OUTPUT);   // status led; blinking = working
    pinMode (dir_pin,   INPUT);   // pin mode set for dir pin of C11
    pinMode (t_stmotor_pin,   INPUT);   // pin mode set for dir pin of C11
    pinMode (t_stdriver_pin,   INPUT);   // pin mode set for dir pin of C11

//setup*************** attach INTERRUPTS **************    

    attachInterrupt(0,Pul_void,RISING);  //this is pin 2 --> C11 Pulse
    attachInterrupt(1,H21A_void,CHANGE);  //this is pin 3 --> Stepper H21A1
    
  int i_vl = 0;
  int i_dg = 0;
  
    for (i_vl = 0; i_vl < no_val; i_vl ++)                    {
      val_arr [i_vl] = 1L;
      for (i_dg = 0;   i_dg  < no_dgt;   i_dg ++)               {
          dgt_arr [0] [i_vl] [i_dg]=1;   }  } // end for loop
    for (i_vl = 0; i_vl < no_val; i_vl ++)                    {
      for (i_dg = 0;   i_dg  < no_dgt;   i_dg ++)               {
          dgt_arr [1] [i_vl] [i_dg]=1;   }  } // end for loop


}

//--------------------ISR's were here----------------


/***********************************************************************
***************************** LOOP *************************************
************************************************************************/
void loop() {

  
//zzzzzzzzzz read the analog values, add here


    temp_st_motor  = analogRead (t_stmotor_pin);
    temp_st_driver = analogRead (t_stdriver_pin);
    
    coded_value=0;
    if(Pul_counter_error < 0) coded_value = coded_value + 100;
        else                  coded_value = coded_value + 300;
    if(Pul_count_net < 0)     coded_value = coded_value + 10;
        else                  coded_value = coded_value + 30;
    if(dir_pin_val < 0)       coded_value = coded_value + 1;
        else                  coded_value = coded_value + 3;
    if(pul_total < 0)         coded_value = coded_value + 1000;
        else                  coded_value = coded_value + 3000;


//    coded_value = coded_value + 10000 * (int) (Pul_counter_error/abs(Pul_counter_error) + 2);
//    coded_value = coded_value + 1000  * (int) (Pul_count_net/abs(Pul_count_net) +2 );
//    coded_value = coded_value + 100   * (int) (dir_pin_val/abs(dir_pin_val) + 2);

    val_arr [0] = abs (Pul_counter_error)  ;       // uses all 5 digits
    val_arr [1] = abs (Pul_count_net)  ;           // uses all 5 digits
    val_arr [2] = pul_odo_cm  ;                    // uses all 5 digits
//    val_arr [3] = (long)temp_st_motor  ;    
    val_arr [3] = abs (pul_total)  ;  //xxxxxxxxxxxxxxxxxx  
    val_arr [4] = (long)temp_st_driver  ; 
    val_arr [5] = (long)coded_value  ;     //dgt 0:+/- Pul_counter_error 3= +1 / 1= -1
                                           //dgt 1:+/- Pul_count_net     3= +1 / 1= -1
                                           //dgt 2:+/- dir               3= +1 / 1= -1
                                           //dgt 3:
                                           //dgt 4:
                                              
      zzcounter++;
      if(zzcounter>32765) zzcounter=0;
      
//    val_arr [3] = (long) ( zzcounter ) ; 
//    val_arr [4] = (long) ( zzcounteri2c) ; 
//    val_arr [5] = (long) ( zzcounter ) ; 
                                              
    
//loop... split val_arr into dgt_arr..................    
  int i_vl = 0;
  int i_dg = 0;
    for (i_vl = 0; i_vl < no_val; i_vl ++)                {
      
      
//    Serial.print("val  ");
//    Serial.print(val_arr [i_vl]);
//    Serial.print(" > dgt ");
//    Serial.print(i_vl);
//    Serial.print("  ");
    
    
    temp_val_prev = (long)val_arr [i_vl];
    temp_val =      (long)val_arr [i_vl];
      for (i_dg = 0;   i_dg  < no_dgt;   i_dg ++)         {
          dgt_arr [arr_fil] [i_vl] [i_dg]=(int) temp_val_prev/factor_10 [i_dg];
          temp_val_prev = (long)(temp_val_prev - 
                      dgt_arr [arr_fil] [i_vl] [i_dg] * (long)(factor_10 [i_dg] ));
//                          Serial.print(dgt_arr [arr_fil] [i_vl] [i_dg]);

                                                          }
//    Serial.println("...");
                                                      } // end for loops
    if (arr_fil == 0)   {  arr_fil=1; arr_rdy =0;  }
    else                {  arr_fil=0; arr_rdy =1;  }




 
//loop-----------------------------------------------
// ----------------- status led blink  --------------
    
    test_led_counter++;
    if (test_led_counter > 150)                 {
      test_led_counter = 0;    
      digitalWrite(test_led,test_led_stat);
      if (test_led_stat==HIGH)    {   test_led_stat=LOW;  }
        else                      {   test_led_stat=HIGH; }         
    
                                              }
//    Serial.println(test_led_counter);
//    Serial.println(".....");


}  // .....END LOOP ..........

A large file can be attached (click the REPLY button and look just below the text input field).

Do you use Arduino Uno for all the Slaves ?
Do they work well with the 3.3V I2C bus of the Teensy 3.1 ? Do you use a level shifter ? Do you check if the I2C transfer was succesfull.

What about the photo interruptor ? How do you use it ? Do you filter the output ?

The H21A_void() is large for a 13KHz interrupt handler.
The Arduino can do bytes fast, but longs are slow. Could you do a part of the calculation in the loop() ?
Do you have an oscilloscope ? It is possible to change an output pin in 2 clock cycli, which can be used to measure the time of the interrupt.

Some variables of the interrupt handlers are use in the loop(). That is no problem for a byte, but it is problem for an integer and long. They are read and written byte by byte.
While the code is busy with an integer or long, an interrupt could occur right in between that reading or writing of the bytes.
You not only have to use 'volatile', but you also have to disable interrupts in the loop() when you do something with those integers and longs.

We use the name "requestEvent()" for the I2C interrupt handler ::slight_smile:
Your "send_i2c_data()" is definitely too slow.
Please don't use a String object there. Allocating a new object and using it is slow.
If possible transmit binary data, not a readable string. It can be an array of floats, or a struct.

I don't understand which part of the 'dgt_arr' array is transferred via I2C. The complete array is 120 bytes, that is too much. The buffer inside the Wire library is only 32 byte.

When the loop() is optimized, the "requestEvent()" only needs one code line: Wire.write( (byte *) myDataArray , sizeof ( myDataArray )) ;

Peter_n:
A large file can be attached (click the REPLY button and look just below the text input field).

Do you use Arduino Uno for all the Slaves ?
Do they work well with the 3.3V I2C bus of the Teensy 3.1 ? Do you use a level shifter ? Do you check if the I2C transfer was succesfull.

What about the photo interruptor ? How do you use it ? Do you filter the output ?

Oops will use attach next time.

Yes Arduino is used for all the Slaves 5 total. I don't use a level shifter, same power supply (+5v) used to power up the Arduinos and Teensy; and the I2C are connected together for all Arduinos and Teensy with 4.7 Kohm resistors to +5V on the power rail.
Had no issues with the I2C transfers and verified that the data was correct by having a counter on each Uno and the data via I2C was correct on the Teensy.

I'll sketch how I connected the Pulse, Data and Direction in another post. Pulse and Photointerrupter go thru an OpAmp then a schmitt trigger. The Direction goes thru a 4N35 then also thru a schmitt trigger.
Is this what you mean as filtering?

Peter_n:
The H21A_void() is large for a 13KHz interrupt handler.
The Arduino can do bytes fast, but longs are slow. Could you do a part of the calculation in the loop() ?
Do you have an oscilloscope ? It is possible to change an output pin in 2 clock cycli, which can be used to measure the time of the interrupt.

can't compress the ISR's anymore, the only way is if I have an additional interrupt for the Direction Pin, if I poll it in the loop part, you may loose data as direction could change anytime.
Can't call it an oscilloscope its the Xprotolab plain :relaxed:
I don't quite understand the last sentence (change an output pin in 2 clock cycli) really sorry didn't get that.

Peter_n:
Some variables of the interrupt handlers are use in the loop(). That is no problem for a byte, but it is problem for an integer and long. They are read and written byte by byte.
While the code is busy with an integer or long, an interrupt could occur right in between that reading or writing of the bytes.
You not only have to use 'volatile', but you also have to disable interrupts in the loop() when you do something with those integers and longs.

thanks on the advise, will try to do some re-coding to avoid the longs. But if I disable interrupts in the loop, I will loose pulses even more.

Peter_n:
We use the name "requestEvent()" for the I2C interrupt handler ::slight_smile:
Your "send_i2c_data()" is definitely too slow.
Please don't use a String object there. Allocating a new object and using it is slow.
If possible transmit binary data, not a readable string. It can be an array of floats, or a struct.
.
.
When the loop() is optimized, the "requestEvent()" only needs one code line: Wire.write( (byte *) myDataArray , sizeof ( myDataArray )) ;

:grin: Ok noted.
After many previous trials this was the only way I could successfully transmit the data via I2C in string format. Seems I need to do more reading and researching, I understand what you are getting to, If I manage to get the ISR's faster and the I2C ISR also more efficient, I will loose less if not any pulses. But the issue still remains if one ISR is running, the other interrupts are disabled, meaning I might loose less data, but will not ensure all the data is gathered by the Uno.

Peter_n:
I don't understand which part of the 'dgt_arr' array is transferred via I2C. The complete array is 120 bytes, that is too much. The buffer inside the Wire library is only 32 byte.

Ok, data I am sending is in the val_arr [0-5] thats 6 integers; 6 integers x 5 digits each is 30 bytes.
I am converting the above array (val_arr) to (dgt_arr) in the loop. this is why I have an additional index [arr_rdy]; this is only "0" or "1". Hence, in the loop if I am filling the arr_rdy = 0 if I2C is called it sends the other one thats the "1" and when i am filling the "1" the "0" is sent. Its just to make sure the data is not sent half way it is being filled then the numbers become jargon. better send the same data couple of times while the other set of data is being readied.

Hey man thanks a lot for the asisistance, will try to do some re-coding and changing the values to integer and bytes. I;ll post my update as I am getting it done.

The Teensy 3.1 is a 3.3V microcontroller as far as I know.

With a filter I mean filtering short spikes or prevent an opamp to oscillate.
An OpAmp + Schmitt-trigger ? It should be a comparator :wink:

Xprotolab Plain : 404 - Page not Found
That's a funny little thing.
The output pin 13 of the Arduino Uno can be inverted with: bitSet ( PINB, PORTB5 ) ;
That code costs 2 clock cycli. At 16MHz that is 125ns :stuck_out_tongue:
If you use that at the start and at the end of the interrupt, you can measure it at pin 13.
Be sure to make pin 13 output in the setup() : pinMode ( 13, OUTPUT ) ;

You have to shorten the interrupts somehow. Disabling interrupts is normal, and doesn't take long.
I often make a copy.
Suppose you really really really (3x) have to use a long inside an interrupt.

volatile unsigned long counter;

void interrupt()
{
  counter++;
}

void loop()
{
  unsigned long counter_copy;

  noInterrupts();
  counter_copy = counter;
  interrupts();

  if( couner_copy > 100000)
    ...
}

The other interrupts will be delayed. You can't avoid that.
With 13kHz, I think it is possible to make that work, even with I2C interrupts running.
If it is not possible, perhaps a hardware timer of the ATmega328P can be used. It is possible to use an input pin as clock for a hardware timer.

The main goal is to make the interrupts as short as possible. If the code in loop() can't keep up, a buffer can be used.
The very best interrupt to count something is this:

volatile byte cnt;

void interrupthandler()
{
  cnt++;
}

Everything else (rollover, missed interrupts, and so on) can be taken care of in the loop().

You use some kind of flag or handshake with the arr_rdy, I didn't check if that would work all the time.

Thanks Peter_n for your insight and assistance, I know its overdue, but just recently got everything working together :o

I have shortened the interrupt routines as much as possible as you have advised and did away with the string in the I2C code.

Even if its late, thought I would post where I am at now, that its all working together.

Below is the code as it stands now;

The Interrupt code of the Arduino Slave for the two interrupts and an additional interrupt with the I2C code:

//===========================================================
//=========  Direction Pin Chg interrupt routine    =========
//===========================================================
ISR (PCINT0_vect)                            {
    dir_pin_stat = digitalRead (dir_pin);
    if (dir_pin_stat == dir_XYZ) dir_pin_val = +1; else dir_pin_val = -1;     
                                             }
//===========================================================
//=========  Pulpresses_cntr interrupt routine    ========
//===========================================================
void Pul_void()                                                          {
        Pul_count_net = Pul_count_net + (long) dir_pin_val;
        pul_total     = pul_total     + (long) dir_pin_val;
        pul_odo++;
        if (pul_odo > 16000L) {  pul_odo=0L;  pul_odo_cm++;}
                                                                         }            
//===========================================================
//=========  H21A_total interrupt routine    ========
//===========================================================
void H21A_void()                                        {    
      if ( dir_pin_prev != dir_pin_val)                           {
      Pul_counter_error = Pul_counter_error + Pul_count_net;      }  // end if
    else                              { 
      Pul_counter_error = Pul_counter_error + Pul_count_net + (long)((dir_pin_val) * (-1L) * H21A_pul_width);
                                      }  // end else ..........   
      Pul_count_net = 0L;
      dir_pin_prev = dir_pin_val;
      H21A_total = H21A_total + (long) (dir_pin_val);     }

//===========================================================
//=========  I2C subroutine to send to Master    ============
//===========================================================
void send_i2c_data()                              {
    st_timer3 = micros(); 
  uint8_t buffer[16];  //uint8_t is a byte declaration
  int iij = 0;
  for (int ii = 0; ii < 8; ii++)         {
      buffer[iij] = val_arr [ii] >> 8;    //shifts the higer bits 8 places to right
      iij++;
      buffer[iij] = val_arr [ii] & 0xff;  //removes the left most 8 bits
      iij++;                             }
  Wire.write(buffer,16);
        st_timer3 = micros()-st_timer3;
                                                   }    // end void send_i2c_data()...............

Also the Master Teensy I2C code:

  uint16_t buffers  ;
  uint16_t buffers_inv  ;

  int available = Wire.requestFrom (5, (uint8_t)16);
  while(Wire.available())       {
          for (vl_cnt =0; vl_cnt < no_val-2; vl_cnt ++)               {  //no_val=10 in declaration part
              buffers = Wire.read() << 8 | Wire.read();
            if (buffers >= 32767)                         {
                buffers_inv = (~ buffers + 1);
                val_mstr [vl_cnt]  = -1 * buffers_inv;    }
            else val_mstr [vl_cnt]  = buffers;
                                                                      }
                                }  //....end... while(Wire.available())

Just getting the diy encoder cut on the CNC, and up and running soon, hopefully less crashes now :slight_smile: