I was studying the ISR for twi and I want to know how is it working.

I have two questions about the code handling for ISR and the other functions and what they do exactly:

  1. My first question is about how ISR for twi is working? The following example shows a piece of the ISR for twi library. My concern here is how switch statement is working?

And probably I have to understand how TWSR works; for example, I think when twi module sends or receive an address, the TWSR is updated according to TWSR Status codes. But I really don't understand exactly what is happening?! Which happens first?

The programmer issue the twi operation; like, sending a start address. Then, the TWSR is updated with the code 0x08 which means according to the datasheet "A START condition has been transmitted". If that so, then why in the following ISR code. The status code is called as a switch result for twi operation, then it issue an action based on that??!! that what is confusing to me! So, is the procedure of twi: 1. Send start address. 2. TWSR is updated with 0x08 as a confirmation for the start action. 3. Here the ISR issues another action based on the TWSR again? This is what I didn't understand.

    // 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;
  1. My second question is about the relationship between ISR and other functions. For example,

      if (true == twi_inRepStart) {
    twi_inRepStart = false;
    do {
      TWDR = twi_slarw;
    } while(TWCR & _BV(TWWC));
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE);
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA); 
    while(wait && (TWI_MTX == twi_state)){

Here is another start send address operation. So which one is the real one? The start send in the ISR or this one?

2 Answers 2


The confusing thing is that there is only one interrupt, and it is called for a number of different reasons.

To make sense of why it's being called you also have to know what you have just done in order to know what to expect. In this case we have just sent a START bit, so we set the current status to "start sent". When the interrupt fires we know that it must be in response to the start bit we sent earlier having been completed, so it's time to then send the address. It's just implementing a simple state machine.

To answer your second question: both are "real" and used.

When sending data to a slave it can be easily implemented with a non-blocking interrupt arrangement. This makes it fast and efficient, and you can get on with doing other things while the data is being sent. That uses the ISR.

When reading from a slave it's harder to implement it with an ISR and still fit into the Arduino ethos of "data is available immediately", so when using requestFrom a simpler blocking routine is called that doesn't use the ISR. This means that when the function is completed all the data has been received and you can get on with reading it from the buffer. If you used the ISR instead then data wouldn't have arrived until some unknown time after you have made the request - so you would have to implement either some kind of callback system to execute a user function when the data has arrived, or manually block waiting for the data to arrive polling a status set by the ISR, which is pretty pointless anyway. So it's just been implemented as a simple blocking routine.

It's crude, but like much of the Arduino API it "gets the job done with the minimum of fuss" - sacrificing efficiency for simplicity and ease of use.

To break it down for you further:

  1. When using Wire.beginTransmission(...) the ISR is used to empty the buffer onto the physical wires. Your sketch can continue while that is happening.
  2. When using Wire.requestFrom(...) the ISR is not used. Your sketch cannot continue until the whole buffer has been received (or it times out).

When using Wire.beginTransmission(...) you first fill the buffer (32 bytes maximum) with your data to send. Calling Wire.endTransmission(...) then starts the sending of the data from the buffer using the ISR.

When you use Wire.requestFrom(...) a call to the Wire.beginTransmission(...) and Wire.endTransmission(...) functions are called to send the address. This uses the ISR. Following that the twi_readFrom(...) function is called to receive the data in a blocking fashion. That first blocks itself waiting for the ISR to signal that the I2C bus is available and it has finished sending the address. The twi_readFrom(...) function then, if it's using repeated start, sends the start bit again. If it's not using repeated start then it just gets on with the task of reading the data into the buffer. The actual reception of the data triggers an interrupt which copies the data into the incoming RX buffer from the FIFO, but that's pretty much all the interrupt does when receiving.

Much of the confusion (and it's confusing even to me) comes because the Wire library is written in two layers - an Arduino API wrapper class which implements the Wire object that you are familiar with, and a pure C set of helper functions which most likely were ported over from some other Atmel application library originally. This makes it quite hard to follow what is going on, since the two programming styles and ways of working are very different. Following the thought processes of two different parties working in different ways with different goals in mind is tricky at best.

To better understand how it all fits together you want to sit down with a couple of example transactions (both read and write) and a copy of the datasheet and trace exactly how the program would flow and respond to the different events and sequences within those transactions. Have the transaction waveforms on paper in front of you and mark on them where the different interrupts trigger. Follow that through in the code and work out what exactly gets called when and why it gets called. Quite an arduous task, but essential if you want to fully understand exactly what is going on in the code.

  • Call back functions which is something I didn't understand until this moment. First thing you said it's one interrupt and called for different reason, that I understand because of different TWSR codes. Second, you say we sent a start bit? Where did we do that? There is no start bit in both cases the start is sent with the address. But, in the readFrom function, there are two start conditions which is connected to the ISR start case. So, if repeated start is true then it should send address without start, else is should initiate only start without address and that triggers the ISR.
    – R1S8K
    Dec 9, 2017 at 8:53
  • Setting the bit _BV(TWSTA) sends the start bit. The ISR has no relation to the receiving data function. It is not used. It is only used for pure transmission.
    – Majenko
    Dec 9, 2017 at 10:58
  • Yes, what is interesting is that the author is wrapping the wire library from twi library, but I stopped looking into the wire library and focus on the twi library, I think it's all I need to understand how to fix my I2C code. First, I had a bug in my I2C code and the reason is because I don't send a NACK after reading the last byte, the code performed much better. Now, I have another problem with the numbers I read.
    – R1S8K
    Dec 9, 2017 at 14:33
  • All the variables in the twi library are declared in uint8_t and in the Arduino application code which uses wire library the final variables to hold the 16-bit of x,z,y axis are declared in int and the results are OK. I don't know how the values are converted when they are received in uint8_t buffers and then transferred to int variables works well, I did the same but I have very different results.
    – R1S8K
    Dec 9, 2017 at 14:33
  • OK, in regard to your further explanation, I think I understand now, that everything is done in the ISR mode, and the functions like readFrom are the arrangement of handling the ISR, and the buffers are already filled in the ISR because it's passing the array and copying the received data into the passed parameter. I just left little things to understand. Like, while(TWI_READY != twi_state){ continue; } When this while loop should breaks? I mean when the code is locked by this while loop, what is the interrupt which would break it and how?
    – R1S8K
    Dec 9, 2017 at 14:43

ISRs are triggered by status (more precisely status changes). For example, once the device address is sent, the hardware will set a flag, which will be serviced by the isr; if the data is sent, the hardware will set another flag, triggering the isr....

It is through this process that the transmission is carried out in an orderly fashion, without tying up the mcu.

For a more detailed description of how avr i2c modules work, check out datasheet for older lpc/nxp mcus.

The other approach is for the mcu to poll the hardware and process the flags accordingly.

Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.