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:
- When using
Wire.beginTransmission(...) the ISR is used to empty the buffer onto the physical wires. Your sketch can continue while that is happening.
- When using
Wire.requestFrom(...) the ISR is not used. Your sketch cannot continue until the whole buffer has been received (or it times out).
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.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.