1
\$\begingroup\$

I2C frames each group of message bytes in START and STOP conditions, defined as SDA changing state 1>0 or 0>1 (respectively) while SCL is high, as described here.

I am writing interrupt driven handlers for PIC32MX170, and I got quite far using the STOP bit as the signal to the software that the message is done. This then allows for things like checking the rx/tx byte count and so on. I found testing the STOP flag in software to be quite reliable, with the combination of hardware and clock that I used.

However, I now discover that it is not reliable at all : either using a faster clock or slower driver means that the ISR can exit and miss the STOP bit completely. Worse, as the next byte will be START of a new message, there is no way to know if it ever arrived (unless you sit there polling the bus, which is not really the kind of thing I want in an ISR, even with timeout).

However, code that depends on flaky combinations of hardware and clocks is also pretty bad, so I am facing a redesign that assumes STOP bits to be unreliable (luckily I don't try to do variable byte counts).

(Some uC's always raise an interrupt on STOP, but unfortunately not this one, as far as I can tell.)

But this raises the question : should an ISR wait on the STOP bit? If so, for how long? Is there anything in the spec about this?

EDIT: I am adding some information, as perhaps I did not express myself completely clearly in my original. My question was really about the means of detecting the message start and stop, which of course is essential. (We have some discussion below about related matters such as where in the code the decoding is done - also very valuable but not what I was asking.)

The issue is basically that, although START and STOP (S and P) conditions (actually the status bits that signal them on the device) are not always set when the ISR runs, even though this might be the last time for the message. (There is also the question of whether the ISR needs to look at those bits, which I think is more about system design, but also interesting and relevant.)

As well as S/P flags, there are also flags which tell you what kind of byte you just received: Address R/W and Data R/W. Address Write always signals the start of a message. After this point a certain structure must be observed, which may involve repeated S conditions and so on. Depending on how you design your message protocol (especially whether you support variable length or not) these can also be used to understand the structure of the message. This is what the question is about.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I am not sure that I understand you well. What condition triggers the ISR? What happens inside it? I have seen slaves that can work down to 1 Hz, so yo might need to wait for the stop for 100s of ms... Why do you need to wait for the stop anyway? \$\endgroup\$ Commented Jun 18, 2018 at 21:26
  • \$\begingroup\$ the ISR happens whenever a byte is received on the bus. The stop condition has nothing to do with slave speed - only the master can produce it. \$\endgroup\$
    – danmcb
    Commented Jun 18, 2018 at 22:40
  • \$\begingroup\$ The stop condition has a lot to do with slave speed because slaves time out, which appears to be precisely the issue you are discussing here, at least to me... Why do you need the stop bit in the ISR? Why are you not using the hardware I2C? \$\endgroup\$ Commented Jun 19, 2018 at 8:10

4 Answers 4

2
\$\begingroup\$

You really need proper firmware architecture for something like this where you react to external asynchronous events not under your control.

Interrupt routines should service the immediate hardware event, then get out of the way. This is NOT where dealing with arbitrary timing between events should take place. I is also NOT where you should be trying to understand the individual events at a higher level, like a whole IIC message.

Last time I had to implement a IIC slave on a dsPIC, I used the hardware to receive events in a interrupt routine. However, that interrupt routine mostly pushed events onto a FIFO. That FIFO was then drained in a separate dedicated tasks to interpret the events as IIC sequences and act upon them. This worked quite well.

REsponse to comments

"Foreground" means running a task from your main loop, right?

It means running from not-interrupt code. Whether that is from the main event loop or a different task is up to your firmware design.

the I2c ISR is at a higher or lower priority?

Higher, obviously. That's part of the point of interrupts. If they weren't at a higher priority, they wouldn't be able to interrupt anything.

if it is clock stretching while it waits for the message - doesn't that actually make it longer running, not shorter?

No. The interrupt routine isn't running at all during the clock stretch time. The interrupt routine gets the address byte. It pushes that on a FIFO and exits. The foreground code interprets the start of the IIC message, realizes that it must respond, fills in a buffer of response bytes, and enables the IIC byte-sending interrupt. That interrupt happens immediately. The interrupt routine fetches the first byte from the buffer and writes it to the IIC hardware. That ends the clock stretch and starts the first data byte getting sent. The interrupt routine exits and is run again when the IIC hardware is ready to accept the next data byte.

\$\endgroup\$
5
  • \$\begingroup\$ I take your point, but in practice you are not only consuming bytes, you are producing them. You can have this sequence: <S><ADD WR><DATA WR (cmd)><S><ADD RD><DATA RD> .... <DATA RD><P> and now you need to supply a byte back to the master as soon as you have see the <ADD RD>. So the ISR has to know when to produce and when to consume bytes. And the bytes are back to back in time. \$\endgroup\$
    – danmcb
    Commented Jun 18, 2018 at 22:21
  • \$\begingroup\$ @dmb: The system I mentioned had to do exactly that too. The foreground code interpreted the IIC message. If it was a read, then the foreground code built a buffer of bytes to send. The interrupt code was notified when the buffer was ready, and the bytes were sent from the interrupt routine when appropriate. The foreground code meanwhile went back to processing the next message as it comes in. If set up properly, the IIC slave hardware will hold SCL low on a read after the address byte until it sends the first data byte. Again, this all worked very nicely. \$\endgroup\$ Commented Jun 19, 2018 at 10:50
  • \$\begingroup\$ thanl you, olin, for the explanation. If I can ask for clarification - I don't get this. "Foreground" means running a task from your main loop, right? so the I2c ISR is at a higher or lower priority? and if it is clock stretching while it waits for the message - doesn't that actually make it longer running, not shorter? \$\endgroup\$
    – danmcb
    Commented Jun 19, 2018 at 19:30
  • \$\begingroup\$ OK, thank you again - just clarifying. So basically - every time you receive a byte, your ISR feeds the FIFO, starts clock stretching and exits. Then your foreground code handles the decoding and supplies bytes in a separate buffer if necessary, also signalling back to the ISR what needs to be done. But an I2C byte is not just the byte itself - but also the associated flags, so I guess the ISR attaches these to the received byte somehow? Without them, the foreground process can't properly decode the message. thanks again. \$\endgroup\$
    – danmcb
    Commented Jun 20, 2018 at 3:19
  • 1
    \$\begingroup\$ @dmb: Right. As I said, the IIC receive interrupt pushes events onto the FIFO. Events are more than just data bytes. \$\endgroup\$ Commented Jun 20, 2018 at 10:34
2
\$\begingroup\$

I2C has got an acknowledge bit and a stop condition. I'm making an operational assumption that you are asking about those. Please correct me if I'm wrong.
(I2C doesn't have a stop bit.)

But this raises the question: should an ISR wait on the STOP bit? If so, for how long? Is there anything in the spec about this?

The I2C specification doesn't cover any timeouts. In theory, the slave shall wait indefinitely. Likewise, the master shall wait indefinitely when the slave does clock stretching.

You could introduce a timeout in the slave device. However, you'd have to address the next layer of questions about the overall handling of the timeout. If the slave had timed out, that means that communication got corrupt. How will you signal that kind of exception back to the master controller? Do you try to recover? Do you try to shut down the entire system gracefully?

... flaky combinations of hardware and clocks... , so I am facing a redesign that assumes STOP bits to be unreliable (luckily I don't try to do variable byte counts).

I've been in a similar situation. Get the hardware fixed. You have to have stop condition in the I2C.

\$\endgroup\$
1
  • \$\begingroup\$ There is no hardware fault - at least, unless you consider the fact that PIC32MX170 doesn't produce an explicit int on STOP condition a fault (arguably it is, but the only fix is to use a different uc). (Note others in the family do do this.) \$\endgroup\$
    – danmcb
    Commented Jun 18, 2018 at 22:44
1
\$\begingroup\$

ISR should not wait. Ever.

In fact, it should not do any message processing at all. It should stuff received data into buffer, ACKing when necessary, triggering "ready" flag when encountering stop condition and resetting buffer counter to the beginning on start condition.

The main program then can start processing message in parallel. It can control ISR by flags like "stretch the clock, I am not ready to respond yet" etc.

Or, if you want to be fancy, make several buffers so that one can be filling up while the other(s) are processed in main (assuming that incoming messages do not require immediate response).

\$\endgroup\$
2
  • \$\begingroup\$ I agree, but see my response to Olin above. You have to send as well as receive, and you don't know which you will be doing until the middle of the overall packet of bytes. \$\endgroup\$
    – danmcb
    Commented Jun 18, 2018 at 22:45
  • \$\begingroup\$ I think you've missed my point. If ISR only does what it is supposed to do (i.e. almost nothing) then most time is spent in main code, which analyses the packet and ultimately makes a decision. It might have enough time to make it in time for sending something back, in which case it puts data into transmit buffer and raises flag "ready to transmit". Otherwise it activates flag "delay response" and ISR begins clock stretching. \$\endgroup\$
    – Maple
    Commented Jun 18, 2018 at 23:21
0
\$\begingroup\$

Don't rely on the stop bit being present at all. It is not necessary for a transfer to end with a stop bit. In many cases, transfers will end with a repeated start at the beginning of the next transfer.

\$\endgroup\$
1
  • \$\begingroup\$ On my system, absolutely every packet does end with a stop condition - the issue is that they haven't always happened before the ISR exits. But I agree - don't rely on it - but for this reason - it is too timing dependent. \$\endgroup\$
    – danmcb
    Commented Jun 18, 2018 at 22:47

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.