Strange I2C abberation / freezes halfly repairable (Logic Analyzer imgs)

Hey guys,

I have really serious problem on which I work for a couple of days now.
I get some strange behaviour on the I2C bus of my Arduino, the components I use are the following:

  • Arduino Leonardo (ATMega 32u4)
  • NFC Module V1.1 from elecfreaks (PN532 Chip)

Here are the Links to the datasheets of these 2 components

Arduino Leonardo Site
PN532 Chip Datasheet

I use the I2C Library from dsscuircits.com because I need to send / read more than 32 bytes over the I2C bus.
I made some slight modifications, which do not really effect the core functions, for example I splitted the send and read methods into 3 methods to have a seperate start, actual read/write and stop.
But I tested everything with the official read and write methods from this library and had the same problems, so that can't be the source of it.

At start everything works properly, I can read NFC Tags and send Peer Data with no problems.
The LLCP, SNEP and NDEF Protocols work properly.
Reading and Sending on the I2C Bus too.
Everything works well, repeatedly.

BUT

There is this one thing that happens, which I can reproduce repeatedly with a specific smartphone and it sometimes happens just out of nothing.

The following logic analyzer images (with a DigiView DV3400) shows you the different situations:

This is a close up of some I2C Bus activity, as you can see everything is fine

Here is a closer close up to see the end of a frame

And another close up of the start of a "healthy" frame

Also in a bigger overview, you can see that everything is working properly

For "debugging" purposes, I printed some TWI registers before the read/write processes, here is the typical output while reading:
(The big gaps between the clock (SCL) cycles are because of the print output while reading. It does not effect the error, it still happens when I remove them.)

.
...
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:84 TWSR:40 TWINT:7 TWDR:49
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:84 TWSR:40 TWINT:7 TWDR:49
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:84 TWSR:40 TWINT:7 TWDR:49
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:84 TWSR:40 TWINT:7 TWDR:49
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
...
.

Now when the error happens (just after some time or putting the specific smartphone to the NFC Module) it looks like this:

This is the moment when it breaks. This always happens in a read frame.

And another time

And another time...

Additional Info:
Some time ago, after that happened, the I2C bus was freezing at all and I needed a hard reset on the Arduino. After using the timeOut method from the I2C library, the I2C bus did not keep frozen anymore, but it also did not get back into the normal workflow, as seen in the following picture:

This is how the workflow looks like after the problem occured. The lower image shows one of the impulses of the upper image. It looks like this for read and write.

Here in a bigger timeframe:

The output of some TWI registers change like this:

.
..
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:84 TWSR:40 TWINT:7 TWDR:49
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:84 TWSR:40 TWINT:7 TWDR:49
READ | TWCR:C4 TWSR:50 TWINT:7 TWDR:0
READ | TWCR:4 TWSR:F8 TWINT:7 TWDR:49
READ | TWCR:44 TWSR:F8 TWINT:7 TWDR:93
READ | TWCR:44 TWSR:F8 TWINT:7 TWDR:27
READ | TWCR:44 TWSR:F8 TWINT:7 TWDR:4F
READ | TWCR:44 TWSR:F8 TWINT:7 TWDR:9F
..
.

I can reset everything and make it work again, by just removing the VCC from the NFC module and reattaching it. Sometimes I have to do this multiple times, but in the most cases it works after the first time. I can see that it works again by the serial output that prints the TWI Registers.
All the jobs are also done well again after resetting it like this.

The usual routine in the error cycle is that
TWSR remains F8
TWDR changes but not with the correct values
TWCR switches between 4, 44 and 4C

The most common TWCR values in binary here:

the bad ones / the good ones

0x4c - 0 1 0 0 1 1 0 0
0x44 - 0 1 0 0 0 1 0 0
0x04 - 0 0 0 0 0 1 0 0
0xC4 - 1 1 0 0 0 1 0 0
0x84 - 1 0 0 0 0 1 0 0
Bit# 7 6 5 4 3 2 1 0

according to the ATMega Datasheet which I linked here, this is the short form of the different bits:

Bit 7 - TWINT - TWI Interrupt Flag:
is set by hardware when TWI finished the current job

Bit 6 - TWEA - TWI Enable Ackowledge Bit:
if set to one, ACK pulse is generated

Bit 5 - TWSTA - TWI START Condition Bit:
if one, checks if bus is enabled and generates start condition

Bit 4 - TWSTO - TWI STOP Condition Bit:
same as 5 but for stop

(I think, at least for now, that this is the problem)
Bit 3 - TWWC - TWI Write Collision Flag:
".. is set when attempging to write to the TWI Data Register.."

Bit 2 - TWEN - TWI Enable Bit:
somehow the on/off switch for the I2C

Bit 1 - Res - Reserved Bit
always zero

Bit 0 - TWIE - TWI Interrupt Enable
when this is 1, the TWI interrupt request will be activated as long as TWINT is high

The I2C library I use has a lockUp() method which is called when something wents wrong.
It is also called when my problem happens, so I tried to do some things inside this method, like writing TWDR when Bit 3 is set, because it says in the datasheet that then it is cleared.
Tried to set the TWI Enable Bit to 0 and back 1 in the hope that it resets somehow, but nothing worked.
I have to admit, that I didn't keep protocoling all my tries scientifically. I tested around with a lot of settings but nothing worked, so if you have any suggestions that you think I might already tested them, just let me try them again.

For now I somehow think I can just reset the I2C bus, clear it or unfreeze it somehow, but I did not manage to do so yet.

The Sourcecode is following in the next post because else this one becomes to long.

CONTINUING NEXT POST...

...CONTINUED

I removed the unnecessary parts, if you want anything else, just tell me, but as I said: everything works fine but this one thing, and I bet the solution is somwhere here in this lines.

Another Class

// WRITE COMMAND
int8_t PN532::writeCommand(uint8_t* data, uint8_t dataLen)
{
	D("    PN532 writeCommand()");
	writePreStuff(dataLen);

	uint8_t dataSum = 0;
	int i = 0;
	for (i = 0; i < dataLen; i++) {
		dataSum += data[i];
		I2c.writeByte(data[i]);
	}

	writePostStuff(dataSum);
	
	return 1;
}


// WRITE PREAMBLE AND CHEKS
int8_t PN532::writePreStuff(uint8_t len)
{
	D("    PN532 writePreStuff()");

	I2c.prepareWrite(PN532_I2C_ADDRESS);

	I2c.writeByte(PN532_PREAMBLE);
	I2c.writeByte(PN532_STARTCODE1);
	I2c.writeByte(PN532_STARTCODE2);
	I2c.writeByte(len + 1);
	I2c.writeByte(~(len));

	I2c.writeByte(PN532_HOSTTOPN532);
}


// WRITE POSTAMBLE STUFF AND CHECK
int8_t PN532::writePostStuff(uint8_t dataSum)
{
	//Serial.println("    PN532 writePostStuff()");

	I2c.writeByte((uint8_t)0xFF - (PN532_HOSTTOPN532 + dataSum) + 1);
	I2c.writeByte(PN532_POSTAMBLE);
	I2c.stopWrite();
	delay(1);
}


// READ COMMAND WITH AUTO CHECKSUM
int8_t PN532::readResponseAuto(uint8_t* data, uint8_t len, uint32_t triesMax)
{
	uint8_t status = 0;
	uint32_t tries = 0;

	while (tries < triesMax && status == 0)
	{
		I2c.prepareRead();
		I2c.readByte(data);

		if (data[0] & 0x01 && data[0] != 0xFF)
		{
			for (int i = 1; i < len - 1; i++)
			{
				I2c.readByte(data + i);
			}
		
			status = 1;
		}

		I2c.stopRead(data + len - 1);
	
		tries++;
		delay(1);
	}

	return status;
}


// READ ACK
int8_t PN532::readAckFrame()
{
	uint8_t res[6];

	readResponseAuto(&res[0], sizeof(res));

	if (res[1] == 0x00 && res[2] == 0x00 && res[3] == 0xFF && res[4] == 0x00 && res[5] == 0xFF) {
		return 1;
	}

	return 0;	
}

Most of the time, the method readResponseAuto() is called. This is also where the problem happens

The rest of the sourcecode is following in the next post because else this one becomes to long.

CONTINUING NEXT POST...

...CONTINUED

I2C.cpp

void I2C_NFC::begin()
{
	D("        I2C begin()");

	timeOut(5);

#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
	// activate internal pull-ups for twi
	// as per note from atmega8 manual pg167
	sbi(PORTC, 4);
	sbi(PORTC, 5);
#else
	// activate internal pull-ups for twi
	// as per note from atmega128 manual pg204
	sbi(PORTD, 0);
	sbi(PORTD, 1);
#endif
	// initialize twi prescaler and bit rate
	cbi(TWSR, TWPS0);
	cbi(TWSR, TWPS1);
	TWBR = ((F_CPU / 100000) - 16) / 2;
	// enable twi module and acks
	TWCR = _BV(TWEN) | _BV(TWEA);
}

void I2C_NFC::end()
{
	TWCR = 0;
}

void I2C_NFC::timeOut(uint16_t _timeOut)
{
	timeOutDelay = _timeOut;
}

void I2C_NFC::setSpeed(uint8_t _fast)
{
	if (!_fast)
	{
		TWBR = ((F_CPU / 100000) - 16) / 2;
	}
	else
	{
		TWBR = ((F_CPU / 400000) - 16) / 2;
	}
}

void I2C_NFC::pullup(uint8_t activate)
{
	if (activate)
	{
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
		// activate internal pull-ups for twi
		// as per note from atmega8 manual pg167
		sbi(PORTC, 4);
		sbi(PORTC, 5);
#else
		// activate internal pull-ups for twi
		// as per note from atmega128 manual pg204
		sbi(PORTD, 0);
		sbi(PORTD, 1);
#endif
	}
	else
	{
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
		// deactivate internal pull-ups for twi
		// as per note from atmega8 manual pg167
		cbi(PORTC, 4);
		cbi(PORTC, 5);
#else
		// deactivate internal pull-ups for twi
		// as per note from atmega128 manual pg204
		cbi(PORTD, 0);
		cbi(PORTD, 1);
#endif
	}
}


uint8_t I2C_NFC::receive()
{
	bufferIndex = totalBytes - bytesAvailable;
	if (!bytesAvailable)
	{
		bufferIndex = 0;
		return(0);
	}
	bytesAvailable--;
	return(data[bufferIndex]);
}



/*################################################ SELFMADE / CHANGED METHODS ################################################*/

// WRITE

// prepare the write process
uint8_t I2C_NFC::prepareWrite(uint8_t address)
{
	D("        I2C prepareWrite()");

	returnStatus = 0;

	returnStatus = start();

	//if (returnStatus){ return(returnStatus); }

	returnStatus = sendAddress(SLA_W(address));

	/*
	if (returnStatus)

	{
		if (returnStatus == 1){ return(2); }
		return(returnStatus);
	}
	*/

	return(returnStatus);
}

// write a single byte with no start and stop
uint8_t I2C_NFC::writeByte(uint8_t data)
{
	D("        I2C writeByte()");

	returnStatus = 0;

	returnStatus = sendByte(data);

	if (returnStatus)
	{
		if (returnStatus == 1){ return(3); }
		return(returnStatus);
	}

	return(returnStatus);
}

// stop the write process
uint8_t I2C_NFC::stopWrite()
{
	D("        I2C stopWrite()");

	returnStatus = 0;

	returnStatus = stop();

	return(returnStatus);
}


// READ

// prepare the read process
uint8_t I2C_NFC::prepareRead(uint8_t address)
{
	start();
	sendAddress(SLA_R(address));
}

// read a single byte with no start and stop
int8_t I2C_NFC::readByte(uint8_t* byte)
{
	bytesAvailable = 0;
	bufferIndex = 0;
	uint8_t numberBytes = 0;

	uint8_t returnStatus = receiveByte(1);

	*byte = TWDR;

	return returnStatus;
}

// stop the read process
uint8_t I2C_NFC::stopRead(uint8_t* lastByte)
{
	returnStatus = receiveByte(0);

	*lastByte = TWDR;

	returnStatus = stop();

	return returnStatus;
}


/*################################################ WRITE & READ METHODS ################################################*/

// CURRENTLY NOT USED
// best read method
uint8_t I2C_NFC::read(uint8_t address, uint8_t numberBytes, uint8_t *dataBuffer)
{
	D("        I2C read()");

	bytesAvailable = 0;
	bufferIndex = 0;

	if (numberBytes == 0){ numberBytes++; }

	nack = numberBytes - 1;

	returnStatus = 0;

	returnStatus = start();

	if (returnStatus){ return(returnStatus); }

	returnStatus = sendAddress(SLA_R(address));

	if (returnStatus)
	{
		if (returnStatus == 1){ return(5); }
		return(returnStatus);
	}

	for (uint8_t i = 0; i < numberBytes; i++)
	{
		if (i == nack) 
		{
			returnStatus = receiveByte(0);
			if (returnStatus == 1){ return(6); }
			if (returnStatus != MR_DATA_NACK){ return(returnStatus); }
		} else {
			returnStatus = receiveByte(1);
			if (returnStatus == 1){ return(6); }
			if (returnStatus != MR_DATA_ACK){ return(returnStatus); }
		}

		dataBuffer[i] = TWDR;
		bytesAvailable = i + 1;
		totalBytes = i + 1;
	}

	returnStatus = stop();

	if (returnStatus)
	{
		if (returnStatus == 1){ return(7); }
		return(returnStatus);
	}

	return(returnStatus);
}


/*################################################ PRIVATE METHODS ################################################*/


uint8_t I2C_NFC::start()
{
	/*
	if ((TWCR & 0x80) == 0x80)
	{
		TWCR &= 0x7F;
	}

	if (TWSR == 0x00 || TWCR == 0x44 ||TWCR == 0x4C)
	{
		TWCR += 0x80;
	}
	*/

	unsigned long startingTime = millis();
	TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
	while (!(TWCR & (1 << TWINT)))
	{
		if (!timeOutDelay){ continue; }
		if ((millis() - startingTime) >= timeOutDelay)
		{
			lockUp();
			return(1);
		}

	}
	if ((TWI_STATUS == START) || (TWI_STATUS == REPEATED_START))
	{
		return(0);
	}
	if (TWI_STATUS == LOST_ARBTRTN)
	{
		uint8_t bufferedStatus = TWI_STATUS;
		lockUp();
		return(bufferedStatus);
	}
	return(TWI_STATUS);
}

uint8_t I2C_NFC::sendAddress(uint8_t i2cAddress)
{
	TWDR = i2cAddress;
	unsigned long startingTime = millis();
	TWCR = (1 << TWINT) | (1 << TWEN);
	while (!(TWCR & (1 << TWINT)))
	{
		if (!timeOutDelay){ continue; }
		if ((millis() - startingTime) >= timeOutDelay)
		{
			lockUp();
			return(1);
		}

	}
	if ((TWI_STATUS == MT_SLA_ACK) || (TWI_STATUS == MR_SLA_ACK))
	{
		return(0);
	}
	uint8_t bufferedStatus = TWI_STATUS;
	if ((TWI_STATUS == MT_SLA_NACK) || (TWI_STATUS == MR_SLA_NACK))
	{
		stop();
		return(bufferedStatus);
	}
	else
	{
		lockUp();
		return(bufferedStatus);
	}
}

uint8_t I2C_NFC::sendByte(uint8_t i2cData)
{
	Serial.print("SEND | ");
	printStatus();

	if ((TWCR & 0x80) == 0x80)
	{
		TWCR &= 0x7F;
	}

	TWDR = i2cData;
	unsigned long startingTime = millis();
	TWCR = (1 << TWINT) | (1 << TWEN);
	while (!(TWCR & (1 << TWINT)))
	{
		if (!timeOutDelay){ continue; }
		if ((millis() - startingTime) >= timeOutDelay)
		{
			lockUp();
			return(1);
		}

	}
	if (TWI_STATUS == MT_DATA_ACK)
	{
		return(0);
	}
	uint8_t bufferedStatus = TWI_STATUS;
	if (TWI_STATUS == MT_DATA_NACK)
	{
		stop();
		return(bufferedStatus);
	}
	else
	{
		lockUp();
		return(bufferedStatus);
	}
}

uint8_t I2C_NFC::receiveByte(uint8_t ack)
{
	Serial.print("READ | ");
	printStatus();

	if ((TWCR & 0x80) == 0x80)
	{
		TWCR &= 0x7F;
	}

	unsigned long startingTime = millis();
	if (ack)
	{
		TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);

	}
	else
	{
		TWCR = (1 << TWINT) | (1 << TWEN);
	}
	while (!(TWCR & (1 << TWINT)))
	{
		if (!timeOutDelay){ continue; }
		if ((millis() - startingTime) >= timeOutDelay)
		{
			lockUp();
			return(1);
		}
	}
	if (TWI_STATUS == LOST_ARBTRTN)
	{
		uint8_t bufferedStatus = TWI_STATUS;
		lockUp();
		return(bufferedStatus);
	}
	return(TWI_STATUS);
}

uint8_t I2C_NFC::stop()
{
	unsigned long startingTime = millis();
	TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
	while ((TWCR & (1 << TWSTO)))
	{
		if (!timeOutDelay){ continue; }
		if ((millis() - startingTime) >= timeOutDelay)
		{
			lockUp();
			return(1);
		}

	}
	return(0);
}

void I2C_NFC::lockUp()
{
	TWCR = 0; //releases SDA and SCL lines to high impedance
	TWCR = _BV(TWEN) | _BV(TWEA); //reinitialize TWI 
}

I2C_NFC I2c = I2C_NFC();

void I2C_NFC::printStatus()
{
	Serial.print("TWCR:"); Serial.print(TWCR, HEX);
	Serial.print(" TWSR:"); Serial.print(TWSR, HEX);
	Serial.print(" TWINT:"); Serial.print(TWINT, HEX);
	Serial.print(" TWDR:"); Serial.println(TWDR, HEX);
}

I know that the code could be more beautyfull, but I usually do the fine work at the end.

Allright...
Know everything is said that I think is important. I REALLY hope someone has any ideas of what is going on here and how to fix this.

Thx for reading and have a nice day!
Skibby