Cant use LCD I2C whiles using interrupts

Hi, I have been trying to get this code to run but I cant seem to find a solution that I can understand. My current code is for a project related to a Dam where we have a water flow sensor that needs interrupts to determine how much water has flown through the sensor, but while trying to send that information to the LCD the code freezes. Hope that someone can help me understand better why this happens and if there is any solution. If you have any questions with the code dont mind letting me know.

#include <Servo.h> 
#include <LiquidCrystal_I2C.h>

const int Trigger = 3;   //Pin digital 2 para el Trigger del sensor
const int Echo = 7;   //Pin digital 3 para el Echo del sensor
const int pin_sensor_amarillo = 4;   //Pin digital 4 para el Flotador con cables amarillos
const int pin_sensor_negro = 5;   //Pin digital 5 para el Flotador con cables negros
const int pin_sensor_flujo = 3;    //Pin digital 6 para el sensor de flujo
const int servomotor = 9;
const int pin_alarma_ultra = 11;  //Pin digital 11 para detectar la activacion de una condicion en el Ultrasonico
const int pin_alarma_amarillo = 12; //Pin 12 de arduino para representar una alerta del sensor de flujo amarillo
const int pin_alarma_negro = 13; //Pin 13 de arduino para representar una alerta del sensor de flujo negro
int valor1 = 0;
int valor2 = 0;
Servo myservo;  //creamos un objeto servo
LiquidCrystal_I2C lcd(0x27,16,2);
volatile int NbTopsFan;

int Calc;
int hallsensor = 2;
void rpm ()
{
NbTopsFan++;
}

void setup() {
  pinMode(hallsensor, INPUT);
  myservo.attach(9);  
  Serial.begin(9600);
  attachInterrupt(0, rpm, RISING);
  pinMode(Trigger, OUTPUT); 
  pinMode(Echo, INPUT); 
  digitalWrite(Trigger, LOW);
  
  lcd.init();
  lcd.backlight();
}
 
void loop()
{
  NbTopsFan = 0;
  sei();
  delay (1000);
  cli();
  Calc = (NbTopsFan * 60 / 5.5);
  Serial.print (Calc, DEC);
  Serial.print (" Litros/min\r\n");

  digitalWrite(Trigger, HIGH);
  delayMicroseconds(10);          
  digitalWrite(Trigger, LOW);

  long t; 
  long d; 
  t = pulseIn(Echo, HIGH); 
  d = t/59;             

  if(d <= 12)
  {
    digitalWrite(pin_alarma_ultra, HIGH);
  }
  else
  {
    digitalWrite(pin_alarma_ultra, LOW);
  }

  
  Serial.print (Calc,3); 
  Serial.print (" L/m"); 
  Serial.println();
  Serial.print("Distancia: ");
  Serial.print(d);
  Serial.print("cm");
  Serial.println();

  lcd.setCursor(0, 0);
  lcd.print("Distancia: ");
  lcd.print(d);
  lcd.print(" cm");
  lcd.setCursor(0, 1);
  lcd.print("Flujo:");
  lcd.print(Calc);
  lcd.print(" L/min");
  
  if(d>=15)
  {
    myservo.write(108);
  }

  if(d<=9)
  {
     myservo.write(15);
  }
 

}

The i2c communication depends on interrupts, and they are disabled in your code after reading the pulses for one second.

There is a discussion of the NbTopsFan code here
https://forum.arduino.cc/t/delay-and-interrupts/405046/3

and several solutions are proposed, one using "blink without delay" code to read the counts, and one maintaining the delay( ) based counting but managing the sei() and cli() functions to allow communications to work.

So just to make sure that I am understanding it correctly, I need to enable the interrupts for a second in my loop so that the I2C can do its job ? Or where exactly should I enable the interrupts ?

Also, I was using some other more clean code in my opinion before implementing the NbTopsFan. Its from this page

Apologies for the late reply, but my internet was down.

If you follow the referenced thread, you would see that I would attach interrupts in setup and only disable them very briefly to transfer the isr data to a loop variable and then reenable after the transfer.

Understanding this interrupt tutorial is very important in order to use them properly.
https://gammon.com.au/interrupts

Sorry I don't understand that statement. I2C is not interrupt driven by its very nature, given that the I2C data line is an input and is not normally connected to an interrupt for the specific reason it can be interrupted by another process without data loss.

As far as interrupts go, you do of course only disable them long enough to process a small routine or to set a flag, depending on your requirements, then re-enable them immediately after.

As far as not knowing what I am talking about, I suggest you read the Phillips specification for I2C, before making such stupid comments.

I2C is not interrupt driven and if you are using routines which make it so, then it is not I2C.

gfvalvo is absolutely 100% correct. And what Phillips says is totally irrelevent. Phillips defined the electrical interface protocol, but NOT the hardware implementation of the interface. It CAN be implemented without interrupts, but most/all Arduinos have HARDWARE I2C interfaces and are absolutely interrupt-driven. If you can't accept that, then you're not going to solve your problem.

Try reading the source code. For your convenience, here's the ISR for the TWI vector from twi.c:

ISR(TWI_vect)
{
  switch(TW_STATUS){
    // All Master
    case TW_START:     // sent start condition
    case TW_REP_START: // sent repeated start condition
      // copy device address and r/w bit to output register and ack
      TWDR = twi_slarw;
      twi_reply(1);
      break;

    // Master Transmitter
    case TW_MT_SLA_ACK:  // slave receiver acked address
    case TW_MT_DATA_ACK: // slave receiver acked data
      // if there is data to send, send it, otherwise stop 
      if(twi_masterBufferIndex < twi_masterBufferLength){
        // copy data to output register and ack
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);
      }else{
        if (twi_sendStop){
          twi_stop();
       } else {
         twi_inRepStart = true;	// we're gonna send the START
         // don't enable the interrupt. We'll generate the start, but we
         // avoid handling the interrupt until we're in the next transaction,
         // at the point where we would normally issue the start.
         TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
         twi_state = TWI_READY;
        }
      }
      break;
    case TW_MT_SLA_NACK:  // address sent, nack received
      twi_error = TW_MT_SLA_NACK;
      twi_stop();
      break;
    case TW_MT_DATA_NACK: // data sent, nack received
      twi_error = TW_MT_DATA_NACK;
      twi_stop();
      break;
    case TW_MT_ARB_LOST: // lost bus arbitration
      twi_error = TW_MT_ARB_LOST;
      twi_releaseBus();
      break;

    // Master Receiver
    case TW_MR_DATA_ACK: // data received, ack sent
      // put byte into buffer
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
      __attribute__ ((fallthrough));
    case TW_MR_SLA_ACK:  // address sent, ack received
      // ack if more bytes are expected, otherwise nack
      if(twi_masterBufferIndex < twi_masterBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;
    case TW_MR_DATA_NACK: // data received, nack sent
      // put final byte into buffer
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
      if (twi_sendStop){
        twi_stop();
      } else {
        twi_inRepStart = true;	// we're gonna send the START
        // don't enable the interrupt. We'll generate the start, but we
        // avoid handling the interrupt until we're in the next transaction,
        // at the point where we would normally issue the start.
        TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
        twi_state = TWI_READY;
      }
      break;
    case TW_MR_SLA_NACK: // address sent, nack received
      twi_stop();
      break;
    // TW_MR_ARB_LOST handled by TW_MT_ARB_LOST case

    // Slave Receiver
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
      // enter slave receiver mode
      twi_state = TWI_SRX;
      // indicate that rx buffer can be overwritten and ack
      twi_rxBufferIndex = 0;
      twi_reply(1);
      break;
    case TW_SR_DATA_ACK:       // data received, returned ack
    case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
      // if there is still room in the rx buffer
      if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
        // put byte in buffer and ack
        twi_rxBuffer[twi_rxBufferIndex++] = TWDR;
        twi_reply(1);
      }else{
        // otherwise nack
        twi_reply(0);
      }
      break;
    case TW_SR_STOP: // stop or repeated start condition received
      // ack future responses and leave slave receiver state
      twi_releaseBus();
      // put a null char after data if there's room
      if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
        twi_rxBuffer[twi_rxBufferIndex] = '\0';
      }
      // callback to user defined callback
      twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);
      // since we submit rx buffer to "wire" library, we can reset it
      twi_rxBufferIndex = 0;
      break;
    case TW_SR_DATA_NACK:       // data received, returned nack
    case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
      // nack back at master
      twi_reply(0);
      break;
    
    // Slave Transmitter
    case TW_ST_SLA_ACK:          // addressed, returned ack
    case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
      // enter slave transmitter mode
      twi_state = TWI_STX;
      // ready the tx buffer index for iteration
      twi_txBufferIndex = 0;
      // set tx buffer length to be zero, to verify if user changes it
      twi_txBufferLength = 0;
      // request for txBuffer to be filled and length to be set
      // note: user must call twi_transmit(bytes, length) to do this
      twi_onSlaveTransmit();
      // if they didn't change buffer & length, initialize it
      if(0 == twi_txBufferLength){
        twi_txBufferLength = 1;
        twi_txBuffer[0] = 0x00;
      }
      __attribute__ ((fallthrough));		  
      // transmit first byte from buffer, fall
    case TW_ST_DATA_ACK: // byte sent, ack returned
      // copy data to output register
      TWDR = twi_txBuffer[twi_txBufferIndex++];
      // if there is more to send, ack, otherwise nack
      if(twi_txBufferIndex < twi_txBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;
    case TW_ST_DATA_NACK: // received nack, we are done 
    case TW_ST_LAST_DATA: // received ack, but we are done already!
      // ack future responses
      twi_reply(1);
      // leave slave receiver state
      twi_state = TWI_READY;
      break;

    // All
    case TW_NO_INFO:   // no state information
      break;
    case TW_BUS_ERROR: // bus error, illegal stop/start
      twi_error = TW_BUS_ERROR;
      twi_stop();
      break;
  }
}
1 Like

Microchip (AVR) uses a variant of i2c called TWI.

https://electronics.stackexchange.com/questions/50915/what-is-the-difference-between-i2c-and-twi

See this document
https://onlinedocs.microchip.com/pr/GUID-0EC909F9-8FB7-46B2-BF4B-05290662B5C3-en-US-12.1.1/GUID-C020CA3C-C2A7-4EDB-B3AD-B17561811C2E.html

The AVR TWI is byte-oriented and interrupt based. Interrupts are issued after all bus events, like reception of a byte or transmission of a START condition.

The data sheet for the processor you are using will also contain more information about the implementation of the twi bus.

Then why are you using code which disables them for all of the loop after the 1 second counting period?

You will find that the Servo library Servo.h uses timer interrupts and it will also not be working correctly in your posted code.

Thanks for that explanation

Not I2C then, so its just a question of the naming used in this thread.

As you have pointed out this is an AVR TWI which raises sets (0)interrupt flag, which has to be cleared(1) after receiving data to allow the TWI to operate again.

• Bit 7 – TWINT: TWI Interrupt Flag
This bit is set by hardware when the TWI has finished its current job and expects application software response. It is not automatically cleared by hardware when executing the interrupt routine. So, you must clear TWINT Flag by writing a logic one to it.

Microchip states the following:

The Two-Wire Serial Interface (TWI) is compatible with Philips I2C protocol.

Loosely speaking, except I2C does not have interrupt flags.

There speaks ignorance. Phillips invented the I2C specification in agreement with other companies which are hardware based (Texas), and as far as I am aware many hardware devices did not use interrupts either for I2C.

Of course it is. As @RayLivingston pointed out, the Phillips spec has absolutely nothing to do with implementation details. I stand by my original statement. You are simply wrong.

1 Like

I am presuming that was meant for someone else.

Childish comment, given you have clearly not read the spec for I2C and have decided for yourself to call TWI, I2C.

I've been working with I2C since the 1970's when it was first introduced by Phillips. In fact, I even worked FOR Phillips in the '80s. I have designed dozens of products using I2C, and I have personally designed about a dozen chips with built-in I2C interfaces. What, exactly, is your experience that makes you so confident of your expertise on this topic?

Yes @anbz0903
But it looks like Elvis has left the building.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.