I2C Master Receiver hangs on read operation

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

I have been attempting to utilize Peter Fleury's I2C Library to have an ATmega328P communicate with a few I2C peripherals. I have already gotten the Master Transmitter working with a 4x20 LCD (NHD-0420D3Z-FL-GBW) and written a few wrapper functions to control it without a hitch. I know that the attached files are a mess, this project has become a bit of a dumping ground for me to play with new peripherals. 

 

I am having trouble with reading data from a secondary device (specifically the MCP9808); my code hangs whenever a read operation is attempted. I have found that the TWINT check in i2c_readAck() is the problem, but I am not sure why.  I checked TWSR and TWCR at various points through read16() and the results are in the comments below. The while loop in i2c_readAck() is where things get stuck. (TWCR & (1<<TWINT)) seems to be the culprit but this check is directly from the library and mentioned in the ATmega328p datasheet to wait for TWINT to be set (which it is right above this check).

 

Can someone point me in the right direction or provide some insight as to what I am doing wrong or misconstruing? I have been attacking this for a few days and can't seem to get over this hurdle.

 

Note that I commented out the i2c_read() calls in readTempC() so I could attempt to debug the issue.

 

unsigned char i2c_readAck(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);

	// Wait for TWINT Flag set. This indicates that the DATA has been
	// transmitted, and ACK/NACK has been received.
	while (!(TWCR & (1<<TWINT)))    ;	/*software hangs here*/

	return TWDR;
}/* i2c_readAck */

 

static inline void init()
{
    TWSR = (1<<TWPS1) | (1<<TWPS0);    // PRESCALER = 64
    TWBR = ((F_CPU/SCL_CLOCK) - 16) / 128;
}

static inline uint16_t read16(uint16_t reg)
{
	uint16_t val = 0;

	//TWSR = 0x20 -> SLA+W transmitted, NACK received
	//TWCR = 0x84 -> TWINT, TWEN
	i2c_start(AMBIENT_TEMP_ADDR + I2C_WRITE);

	//TWSR = 0x30 -> data byte transmitted, NACK received
	//TWCR = 0x84 -> TWINT, TWEN
	i2c_write(reg);

	//TWSR = 0x48 -> SLA+R transmitted, NACK received
	//TWCR = 0x84 -> TWINT, TWEN
	i2c_rep_start(AMBIENT_TEMP_ADDR + I2C_READ);

	val = readAck();

/*
	val = i2c_read(1);
	val <<= 8;
	val |= i2c_read(0);
*/
	i2c_stop();

	return val;
}


static double readTempC(void)
{
	uint16_t t = read16(0x05);

	double temp = t & 0x0FFF;

	temp /= 16.0;

	if (t & 0x1000) temp -= 256;

	return temp;
}

 

Thanks.

Attachment(s): 

This topic has a solution.
Last Edited: Thu. Sep 20, 2018 - 01:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You will notice in the Fleury code there is a read_ack() and a read_nak(), when the master reads data you use read_ack() to tell the slave thank you can I have some more data, or you use read_nak() to say thank you I'm done for now!

 

In your code above, you use the former when you should have used the later, because the slave is still waiting for the master to request more data and has not released the bus so the master can issue i2c_stop().

 

Always check the return code provided by the functions and act accordingly on their value.

 

 

Jim

 

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

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

ki0bk,

 

Thank you for your quick response. I just saw that readAck() has 2 return statements (even though it's not being called, I uncommented the end of the function to post and didn't remove to first statement).

 

If I understand what you are saying, then I should be using i2c_readNak() or i2c_read(0) for individual read calls. Even when I call i2c_read(0) the software hangs in the same loop. I have included the updated main.c file in this response.

 

unsigned char readNak(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN);

	// Wait for TWINT Flag set. This indicates that the DATA has been
	// transmitted, and ACK/NACK has been received.
	while (!(TWCR & (1<<TWINT)))	;

	return TWDR;
}/* readNak */

static inline uint16_t read16(uint16_t reg)
{
	uint16_t val = 0;

	//TWSR = 0x20 -> SLA+W transmitted, NACK received
	//TWCR = 0x84 -> TWINT, TWEN
	i2c_start(AMBIENT_TEMP_ADDR + I2C_WRITE);

	//TWSR = 0x30 -> data byte transmitted, NACK received
	//TWCR = 0x84 -> TWINT, TWEN
	i2c_write(reg);

	//TWSR = 0x48 -> SLA+R transmitted, NACK received
	//TWCR = 0x84 -> TWINT, TWEN
	i2c_rep_start(AMBIENT_TEMP_ADDR + I2C_READ);

	val = readNak();
//	val = TWCR;
/*
	val = i2c_read(1);
	val <<= 8;
	val |= i2c_read(0);
*/
	i2c_stop();

	return val;
}

 

Any other thoughts?

Attachment(s): 

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

My issue is definitely in the while loop of readNak() and readAck(). It seems as if the TWINT bit/flag is being reset to 0 instantaneously after being set to 1 at the start of the function. This was confirmed by returning the contents of TWCR after a number of cycles inside this loop. The returned binary value was 00000100. 

unsigned char readNak(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN);
	
	// Wait for TWINT Flag set. This indicates that the DATA has been
	// transmitted, and ACK/NACK has been received.
	while (!(TWCR & (1<<TWINT)))
	{
		vt++;

		if (vt > 100)
		{
			vt = 0;
			return TWCR;
		}
	}

	return TWDR;
}/* readNak */

I guess this leads back to my very very very basic knowledge of how I2C/TWI communication works. My understanding of is that the TWINT bit gets pulled to 0 by hardware when a data transfer is complete (among other events). Why would it be clearing to 0 immediately after being set to 1 by the software and still not update the TWDR?

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

As with most control registers in the AVR you CLEAR a flag bit by writing a 1 to it, I know that does not make sense, but that is how it works, see below:

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

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

Is that not what the first line of readNak() does?

TWCR = (1<<TWINT) | (1<<TWEN);

My understanding is that this should clear the bit and the microcontroller should wait until the data transfer from the register on the MCP9808 to the TWDR register on the ATmega328p, at which point I should be able to pull the data from the TWDR register. Is this wrong?

 

I guess I'm still stuck at the fact that no matter what I change in the polling, the TWDR register is not updating with the data from the MCP9808.

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

mmedica wrote:
I guess I'm still stuck at the fact that no matter what I change in the polling, the TWDR register is not updating with the data from the MCP9808.

Is there data coming in?  'Scope trace or logic analyzer?  Proper pullups on clock and data?  Master configured correctly?  Address set/sent correctly?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
unsigned char i2c_readAck(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);

	// Wait for TWINT Flag set. This indicates that the DATA has been
	// transmitted, and ACK/NACK has been received.
	while (!(TWCR & (1<<TWINT)))    ;	/*software hangs here*/

	return TWDR;
}/* i2c_readAck */

Any TWI command starts by clearing any TWINT bit (whether it was set or not).  

The TWINT bit will set when the command completes.

So you either poll in software for TWINT bit to set.  e.g. your while() statement

Or you let interrupts fire when TWINT sets i.e. command has completed

 

The only "un-intuitive" part is that you manually clear the TWINT bit by writing 1 to it.

 

Seriously.   TWI is easy to use.   Install a proven library e.g. Fleury.   And ALWAYS use the function return values.

 

David.

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

Thanks all. I am going to follow up with a few of the mentioned tips above and see if any help. 

 

theusch,

 

I have been keeping an eye on the clock and data lines, but I have 2 devices on the bus which makes following the trail of commands a bit convoluted. I will remove the screen and work with just the MCP9808 and see. The rest of my setup should be fine, I have an I2C controlled LCD properly showing data.

 

david.prentice,

 

david.prentice wrote:

 

Seriously.   TWI is easy to use.   Install a proven library e.g. Fleury.   And ALWAYS use the function return values.

 

David.

 

I am using the Fleury library, that's why I'm so baffled as to why this isn't working and came to this forum for advice. The readAck() and readNak() functions are straight from Fleury's code, I just made my own copies to allow me to play with them and see what outputs I am getting.

I'll strip things down to just the MCP9808 and a USART monitor so I can see what data is being transmitted (if any).

Last Edited: Thu. Sep 20, 2018 - 12:07 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Device addressing - 7bit or 8 bit addresses perhaps? I see

AMBIENT_TEMP_ADDR

in the code but no sign of what it may be defined as.

 

EDIT: on this page:

 

https://learn.adafruit.com/adafr...

 

it says:

 

The MCP9808 has a default I2C address of 0x18 but you can set the address to any of 8 values between 0x18 and 0x1F so you can have up to 8 of these sensors all sharing the same SCL/SDA pins.

The datasheet seems to confirm:

 

So assuming A2..A0 = 0 then that would be 0b0011000 which is 0x18

 

I seem to remember that Fleury counts the R/W bit as another bit so I guess that makes this 0x36 in Fleury speak?

 

Last Edited: Thu. Sep 20, 2018 - 12:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Yes.   And if you use the return values from the i2c_start() function you would see whether you have the correct address.

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

@clawson,

 

We meet again. I have it configured as 0x18 (using the lower 3 bits pulled to ground via A0-2. I think because the Adafruit example sketch works with 0x18 as the address I was dead set on using that address value.

 

Also, wouldn't shifting everthing left one bit make it 0x30 :P I blindly accepted 0x36 and still got 1, but after realizing it should be 0x30 I got i2c_Start to output 0. I feel silly.

 

@david,

 

I returned i2c_start() to the USART and found that, as clawson suggested, my address was wrong. I am now* getting values to change.

 

 

Thank you everyone for all of your help!

 

edit: changed not to now

Last Edited: Thu. Sep 20, 2018 - 03:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yup, I was typing hex but thinking decimal!!