TWI Timing problem in ISR

Go To Last Post
30 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm having a hell of a time working figuring out what I'm doing wrong. I have a Mega128 as the master and some Mega16's that are running as I2C slaves. The Mega16 slaves is where I'm having the problem. The datasheet says that SCL is held low until the TWINT is set, but that is not happening like it should.

Let me give an example. If I use this as my ISR (codevision):
interrupt [TWI] void twi_isr(void) {
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA)|(1<<TWIE);
}
About .5% of the time, the I2C master receives a NAK when it puts the address of the Mega16 slave on the bus.

Now, if I add a bunch of NOP's:
interrupt [TWI] void twi_isr(void) {
#asm("nop\nop\nop\nop\nop\nop\nop\nop\nop\nop\nop\nop\nop\nop\nop\nop");
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA)|(1<<TWIE);
}
The failure suddenly jumps to about 3%. Increasing the NOP's increases the failures until it pretty much fails constantly.

I just can't understand why this is occuring. There are not timing errors on the master as the M128 master is running in polled mode and is returning error code of $20 when the NAK's occur (error generated by hardware). This error code indicates that the master successfully put out the address and that the slave NAKed in response. The bus is set at 100Khz on both cpu's, with standard 4.7K pullups. The M128 has no problems talking to about a dozen other non-AVR devices on the same bus (removed for debugging).

If the datasheet is correct about SCL being held down until TWINT is set, shouldn't the number of NOP's be irrelevent?

BTW: The NOP's are what I used during debugging this problem to eliminate other issues. Of course you wouldn't use NOP's in a real program. :-)

Can anybody provide any suggestions or guidance?

admin's test signature
 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've been working on the same problem a bit.

It occurs when a slave receives a "stop" interrupt (status code 0xA0). This is the only case when an interrupt occurs while SCL is high, and the slave therefore has no means of extending the idle time of SCL (without cuasing a bus error). If TWINT is still set when a master sends a start condition, the slave will miss that start condition, and the following slave address.

As far as I've been able to see, this will only cause a problem if the same slave (that was addressed prior to the stop condition) is addressed again. My workaround to that problem is to let the master send a repeated start and the slave address again if it receives NACK on the first slave address. If I inserted a very long delay in the slave interrupt routine, the slave held SCL low after the start condition until TWINT was cleared, thus no more than one slave address is missed.

Hope this helps.

Speedy

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Speedy! This is exactly what I've been seeing this morning, but I did not understand what was occurring. Thank you! It all makes sense now. :-)

I've been able to reduce the problem considerably by moving the check for the 0xA0 value to the very start of the ISR. It also took converting this beginning part to hand crafted assembler. But with that done the latency seems to be reduced to a point where these errors rarely occur (at 100Khz at least).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HI,

Speedy & Ravton: Any updates on this?
Did you find any other work around or way to fix that problem?
I read your workaround from May 2003 but I'm afraid I don't quite get it yet :oops:.
I'm running into the same problem with a mega128 master talking to a mega8 slave (a smart motor controller I'm designing for a robot).
At first I thought it was because the poor mega8 was running out of real-time because It's polling 2 512-count quadrature encoders in X2 mode but even after disabling all that code it still occurred which is totally unnacceptable.

I'll keep working at it but if you get a chance email me please.

Thanks in advance,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know about this problem but why ATMEL does not?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good point!!

I ended up doing SPeedy's workaround and it works very well but I'd be happier knowing I could avoid the problem altogether and eliminate unecessary traffic on my busy bus.

Is it really a bug in the silicon or simply something we have to fix in software???

I didn't quite get his explanation of the STOP message interrupting. Is sounds as if he has his logic inverted when he's talking about the SCL being high. Or is it me?

Later,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My understanding:

A STOP condition is a low->high transition on SDA while SCL is high, so
by definition it can't happen while a slave is holding the bus, ergo the
slave can't extend holding the bus.

The problem is that TWINT pretty much freezes the TWI state machine, so
the slave can't recognize a new START. However (in all cases but STOP)
the slave is also holding the bus, so (in those cases) the master can't
send a new START, so it works out.

But in the STOP case the slave can't hold the bus and it also can't
recognize a new START until it makes it to the ISR and clears TWINT,
thus the race: if the slave doesn't clear TWINT in time, and the master
sends another message to that slave, the slave will miss it. The suggested
slave-side workaround just moves the TWINT-clear (for this case) to the
top of the ISR, so the slave typically wins the race.

The I2C specification does recognize this problem, and requires the
master to delay between sending a STOP and sending the next START;
this delay, though, is <5us for "standard mode" (0.5x bit time at 100kHz),
which isn't very long.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Cool.
Thanks for the great explanation.
I'll try stuff.

Take care,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

We've got a similar problems in using a mega16L as TWI slave.

The "solution" to handle the STOP/RS in the top of the ISR doesn't
work for us. Fcpu = 7.3728MHz.

Hope someone comes up with a better solution.

Bye,
Christiaan.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think glitch did come up with a possible explanation for this problem.

Cant remember the thread number , but it was certainly worth reading.

I seem to remember it was the way one of the TWI registers was accessed maybe the Interrupt reg , as it had something to do with clearing the int bit by accident (well by a rmw sequence).

/Bingo

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ahh

Here it is , especially page 2 & 3

http://www.avrfreaks.net/phpBB2/...

/Bingo

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, Speedy does mention a master-side workaround -- simply retry
(once?) after a NAK. This sounds klugey, but one could argue it should be
there in any case to make the communication (generally) more robust.
It does assume access to the master.

One thing I still don't see (failure of imagination) is how Atmel would
go about "fix"ing this in the AVR, since it seems an intrinsic part (flaw)
of the protocol.

Disclaimer: I'm not a TWI/I2C wizard, but I do read a lot of books.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There is a minimum idle time between a stop condition and the next start condition on the bus. The TWI interface should obey this timing (it does AFAIK).

You need to make sure that your slave is ready for a start before this minimum time expires. The worst thing that should happen, is that the slave will not see it's own address, and therefore not ACK it, causing the master to retry after a period of time. It should not lock the bus. If switching from write to read on the same slave, then you should always use a restart instead of a stop/start combination, especially in multi-master environments.

If you keep your ISR lean, you should have no problems. Many people tend to try and do too much in their ISR, thus creating problems. If you MUST do extended processing after the STOP, then you need to acknowledge the stop (clear TWINT) as early as possible. This is contrary to what you would do during the data phase, where you want to hold off on clearing TWINT flag until the very last stage.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've already tried to make my ISR as lean as possible
by settings IARs optimize for speed to '9' maximum
and moved the STOP / RS condition above the switch(){}
(usually slow code) in a separate if(){}.

I've peeked in the compiler's output, and counted the
CPU cycles. The if(){} is reached after about 23 cycles
or 3.1 usec on our clock. This is within the 5 usec for a repeated start.

Our master does a fast RS just within the I2C timing (5 usec)
and I can't change the master software / hardware.

I would like to know from Atmel how fast the statusses should
be handled from ISR entry and if a RS in slave mode works or not.
I think they didn't test this since their app notes do not use this
sequence in slave mode. Their support has become a little silent as well.

(I can tell from previous experiences with Atmel that this is a bad sign)

Software I2C slave isn't a real option, since it can barely keep up
with the 100 kbit clock, even from a INT0 ISR. This ISR would need
to be even fatter than the TWI one since you have to deserialize and
react on address matches etc.

Thanks for your input, Christiaan.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am a little bit confused over all your experiences together. It's hard for me to see who has exactly which problem. Anyway, as I have been playing around with it I might as well add my experience as well.

I noticed that when the user does not manage to make it work within timing limits the bus can hangup and it will be hard to recover from it. To issue a "double stop" fix the hangup if you do it at all stations. But you need to know when to do it. If the master initiated a transmission and failed to end it. all active slaves and other masters wil have to reset. You can only fix this by keeping track of how low a transmission is in progress and reset on time-out. This is quite a job to implement it.

Some basic rules.

A slave is only allowed to pull SCL (delay) or SDA (ack) when it is adressed already. This means that if the slave is busy during addressing it will miss the addressing and ignore it. In this case don't count on the SCL trick. You just have to end the transmission by issuing "stop" and then try again until the slave responses. You can do this infinitely or a couple of times and then give up and issue an error.

Actually this goes for any error. Also if you don't get ack. try again. If you give up, end the transmission by "stop". This is always necessary.

If you reset TWINT too early your data is lost. This might result in bus hangup.

Hope it helps a bit.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't have a bus "hangup".
The atmel as TWI slave seemed to miss repeated starts.

After rewriting my code, the TWI seems to work at last :D Hurray!

My question remains:
Are there (less obvious) time constraints on handling the TWI registers from an ISR?

It is still "black magic"; I don't know in how many
cpu cylce counts I should keep my code.

You really need to know this before selecting your crystal.
Or choose if it makes sense to code some lines in assembly
to ensure you can comply with a certain timing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
My question remains:
Are there (less obvious) time constraints on handling the TWI registers from an ISR?

Yes and no.

Starting a transmission:

The slave will get an interrupt call when a "stop" is issued. If the master starts a new communication directly after this the slave might still be busy handling the "stop". this makes sense as you have to do something after "stop". This can either be:
- copy the message to a safe place, or
- handle it.

The slave can't receive a new message before this is done. If the master issues an addressing at this moment it will be ignored.
You will either have to wait until the slave is done being busy. You will have to know yourself how long this takes. Or you just try addressing it. If it does not work you end the transmission by "stop" and try again.

During a transmission:

TWI automatically blocks SCL until the data has been saved. The user tells the TWI interface that it is saved by resetting TWINT. If you do this right the system automatically waits until it's ready.

Only thing you will have to watch is the clock rate. Make sure you don't go over 100000 bps. TWI is specified by Philips (creator) upto 400000, but the AVR TWI doesn't make it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is all sensible talk. But flow control is not my problem.
I2C leaves this problem to be solved by the application.

What i've found is that the ISR you create
_must_ meet certain timing criteria.
It is obvious that it may not exceed 5usec.
My question is: how fast must it be?
How fast must the interrupt clear flag be written
after a repeated start?
This is nowhere to be found in the datasheet / app notes.

So a simple C ISR routine may blindly fail
if you don't look at your compiler output.
E.g. if there are too many ST instructions
needed (they take up 2 cycles).

This all depends on compiler flags and your code,
and your cpu clock.

If the standard says that a RS may be as small as
5usec, than Atmel could include some guidelines
on how to meet this constraint by your ISR.

In my opinion the TWI is not documented in a way
so that you can use it reliably.
It works, but merely by experimentation than wisdom.

Thanks for your input.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cc_simons wrote:
I've already tried to make my ISR as lean as possible
by settings IARs optimize for speed to '9' maximum
and moved the STOP / RS condition above the switch(){}
(usually slow code) in a separate if(){}.

Don't count on the optimizer to make it lean for you, you need to write efficiently.

Please post your code, I will look at it to see what I can find.

cc_simons wrote:

I've peeked in the compiler's output, and counted the
CPU cycles. The if(){} is reached after about 23 cycles
or 3.1 usec on our clock. This is within the 5 usec for a repeated start.

Our master does a fast RS just within the I2C timing (5 usec)
and I can't change the master software / hardware.


If the timing is within the i2c spec the twi hardware will work. If you're having problems it is likely due to poor code construction in your slave.

cc_simons wrote:

I would like to know from Atmel how fast the statusses should
be handled from ISR entry and if a RS in slave mode works or not.
I think they didn't test this since their app notes do not use this
sequence in slave mode. Their support has become a little silent as well.

The status should be read and handled as quickly as possible in the ISR, but you should not clear the status until late in the ISR. (except in the case of a stop) Restarts work fine in slave mode (that is the slave will recognize them, it cannot issue them).

cc_simons wrote:

(I can tell from previous experiences with Atmel that this is a bad sign)

Software I2C slave isn't a real option, since it can barely keep up
with the 100 kbit clock, even from a INT0 ISR. This ISR would need
to be even fatter than the TWI one since you have to deserialize and
react on address matches etc.

Thanks for your input, Christiaan.


Depending on your AVR's clock rate 100KHz should be no problem. I'm running a multi master/slave i2c implementation at 400KHz with no troubles, on several AVRs running at 16MHz.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I try to code efficiently. Please do not doubt this. I admit it is scary :shock: if you need to depend on compiler flags, maybe in such a case its better to hand-code these lines.
But when hand coding assembly you also need to know how many instructions / cycles
you can use before getting into trouble. I prefer to keep it all in C, and I just want some timing parameters to verify if my code is fine.

I've worked with Atmel parts for over 5 years now (8515, mega163, mega16) and done douzens of projects with this and other compilers and devices. I'm doing commercial product development, so I'm not going to post our code. (Thanks for the offer anyway.)

I've found that my initial "poor code" (which allready was within the i2c timing) didn't function. After more carefull code reviewing and some optimalisations our "good" code works (also within i2c timing, but with a greater margin).

I need to know what the worst case margin must be. This will define if our or any other TWI source is poor or not.

If someone can explain this, I'll be very happy to stick to these timing limits. As I said before; the code must be within the I2C timing. This is obvious,so this is no discussion topic at all.

Glitch, it is good to hear your TWI code works fine. This gives me a little more confidence in using the TWI hardware. I'm still waiting on some answers from Atmel I've issued via the usual support channels. As soon as I know the ansers, I'll post them.

Bye, Christiaan.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK I know it's VERY LONG TIME ago but I face the same trouble as mentioned in the original thread.

What I do:
For testing my I2C library I have a ATmega8 @ 1MHz running as I2C Master and a ATMega32 as I2C Slave.

Without going into deep the Protocol is:

    1. Transmitting some Bytes from Master->Slave (last byte 0xE2) 2. Stop condition
    3. Start condition (Address 0x21)
    4. Reading some bytes
    5. Stop condition
This works fine so far (see 'STOP OK No Stretching.JPG').

Now the problem:

To test my I2C Master for stretching I did (only code extract because of companies property) add in the I2C state 0x80 (data received; ACK has been returned):

		// Previously own SLA+W received; data received; ACK has been returned
	case 0x80:
	// stretching test 
	// (delay allow other interrupts 
	// + prevent re entrance)
	TWCR = 	
		(0<<TWINT)|	// TWINT remains
		(1<<TWEA) |	// enable Acknowledge
		(0<<TWSTA)|	// slave: no START
		(0<<TWSTO)|	// slave: no STOP
		(1<<TWEN) |	// enable TWI 
		(0<<TWIE);	// disable interrupts
	sei();
	_delay_loop_2(0x1000);		
	cli();

	// .. (short code for I/O)

	TWCR =
		(1<<TWINT)|// Clear SCL blocking	
		(1<<TWEA)| // Enable ACK
		(1<<TWEN)| // TWI Interface enable	
		(1<<TWIE); // enable TWI interrupt
	break;
	}
} // End of ISR

Slave is stretching after each byte (see last byte in 'STOP NOT OK Stretching.JPG') as expected but the next address is not acknowledged properly (see 'STOP NOT OK Stretching detailed.JPG'). To me it seems that the modification does NOT affect the STOP handling. The STOP condition is delayed by the stretching of the last byte. The signals seem as if the ACK comes too late!

What completely confuses me is that it works properly when I replace the STOP condition by a REPEATED START (see 'REP START OK Stretching.JPG' and 'REP START OK Stretching detailed.JPG'.

I have the feeling that I might have trouble when working with many Interrupts (by the way: TWI interrupt has a very low priority!!)
Any ideas to fix?

Attachment(s): 

Last Edited: Fri. Apr 24, 2009 - 02:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Further screenshots

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I have the feeling that I might have trouble when working with many Interrupts (by the way: TWI interrupt has a very low priority!!)
Any ideas to fix?

I think you may be right about the multiple interrupts.
You should make a test program with all ISR's removed except TWI to see if the problem continues.

Are you meeting the I2C timing specs like Bus free time between a STOP and START condition?

What are you doing in case 0xA0?

You should step through the code with a simulator to see if TWEA stays set.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I checked:

    1. The bus free time (time between stop and start) is ~10µS. In NXP doc UM10204 worst case for standard I2C is 4,7µS -> OK 2. In 0xA0 after two 8Bit comparisons I set

    TWCR =	(1<<TWINT)|// Finish immediately
    			(1<<TWEA)| // enable Acknowledge
    			(0<<TWSTA)|// slave
    			(0<<TWSTO)|// slave
    			(1<<TWEN)| // enable TWI
    			(0<<TWIE); // disable interrupts
    

After some calculations the main loop sets

TWCR =	(0<<TWINT)|// Handle waiting state
			(1<<TWEA)| // enable Acknowledge
			(0<<TWSTA)|// slave
			(0<<TWSTO)|// slave
			(1<<TWEN)| // enable TWI
			(1<<TWIE); // enable interrupts
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I had further investigations: I tested the delayed slave with my software implemented master and everything worked fine :roll: .
So I checked the hardware implemented master again. The following modification lead to success:
Before sending a start condition it must be ensured that the stop condition is finished. Unfortunately this check is only possible by polling TWSTO because there is no interrupt thrown after sending STOP.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Even if that was some 8 years ago you guys just made my day after 4 days of desperately studying ATMEL's datasheets, reading application notes, example code and so on ... of course I had that great debugging code, that translated status values into respective string representations generously using sprintf and sending them over the USART and furthermore handling everything in a veritably switched case desert as last but one item :-)

 

guess what happened after disabling the debug code and modifying the ISR to:

 

ISR (I2C_TWINT_VECT)
{
	uint8_t		byteTWSR = I2C_STATUS,
				byteTWDR;
	bool		ack;

	cli();

//	sprintf (buf, "TWSR = %s\013\015", i2csStatusToString(byteTWSR));
//	usartPrintString (buf);
//	sprintf (buf, "TWDR = 0x%02X\013\015", I2C_REG_TWDR);
//	usartPrintString (buf);

	switch (byteTWSR)
	{
		// handle repeated start condition immediately!

		case I2C_SRM_STOP_REP_START_RECVD :
		{
			_Start(true);;
			break;
		}

		// transmitter mode:

		case I2C_STM_SLA_R_REVCD_ACK :
		case I2C_STM_BYTE_TRANS_ACK_RVCVD :
		case I2C_STM_ARB_LOST_SLA_R_REVCD_ACK:
		{
			ack = _handleTransmitRequest (byteTWSR, &byteTWDR);
			I2C_REG_TWDR = byteTWDR;
			_Start(ack);
			break;
		}
		case I2C_STM_LAST_BYTE_TRANS_ACK_RVCVD :
		case I2C_STM_BYTE_TRANS_NACK_RVCVD	:
		{
			_Start(true);
			break;
		}

Dude, I started serious coding in 1991 under OS/2 1.3, when stack was small, memory expensive, hi-end CPUs were named 80386 and we had to handle each windows (be it OS/2 or Windows 3.1) event personally from within endless case orgies :-) I completely forgot about time and resource critical coding since moving to C++, Java and other bulky stuff... :-)

 

Anyways - you kept me from social suicide! :-) Thanks! :-)

 

Axel

Last Edited: Sun. Sep 10, 2017 - 03:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have deleted you signature file, please keep it clean! Also the link to your website doesn't work.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

my web site works perfectly fine but slow - you might know this from Telstra & Co... just be patient...

Last Edited: Tue. Sep 12, 2017 - 05:03 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HerrRausB wrote:
debugging code, that translated status values into respective string representations generously using sprintf and sending them over the USART 

To reduce the impact, make the UART ouput as terse as possible - and prettify at the receiving end, if required...

 

Another option is to record events in a (circular) RAM buffer - and only extract at a "convenient" time...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

i simply switched the USART off because it was just in place to show me what's generally going on - works fine now :-) i just wanted to visually understand, whats going on.

 

anyways - i am happy now and on to the the next step...