I2C read: Master stops sending clock

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

I'm trying to access the data from a temperature/humidity sensor (HDC1080) through I2C. The sequence goes like this: Write a few bytes, wait, then read 4 bytes. The basics seem to work, I get results from the sensor. But only a single byte where 4 should be received. That first byte is correct so my only issue should be that the receive just stops halfway.

 

Inspecting the signals, I see that when the ATtiny I2C master should send an ACK back to the slave, nothing happens. The clock (SCL) line is held low and no clock pulse is sent again. The next byte is awaited and the code runs into a timeout and stops I2C communication.

 

I have no idea why the master refuses to send the ACK. I've tried many different methods with and without smart mode (SMEN), with and without explicitly sending the ACK/NACK command, nothing happens. The clock just stops after receiving the first data byte. What's wrong here?

 

This is my code, parts of it, simplified (may not compile as-is). I'm running it on an ATtiny1614.

 

// Init: (400 kHz)
TWI0.MBAUD = (F_CPU - 400000 * (10 + F_CPU * 3 / 10000000)) / (2 * 400000);
TWI0.MCTRLA = TWI_SMEN_bm | TWI_ENABLE_bm;
TWI0.MSTATUS |= TWI_RIF_bm | TWI_WIF_bm | TWI_BUSERR_bm;   // Clear interrupt flags
TWI0.MSTATUS |= TWI_BUSSTATE_IDLE_gc;

// Start:
TWI0.MSTATUS |= TWI_RIF_bm | TWI_WIF_bm;   // Clear Read and Write interrupt flags
if (TWI0.MSTATUS & TWI_BUSERR_bm) return;   // Bus error
TWI0.MADDR = address;   // Send slave address, including Direction bit

while (!(TWI0.MSTATUS & TWI_RIF_bm) && !(TWI0.MSTATUS & TWI_WIF_bm))	// Wait for RIF or WIF set
{
	if (timeout) return;   // Timeout
}
TWI0.MSTATUS |= TWI_RIF_bm | TWI_WIF_bm;   // Clear Read and Write interrupt flags
if (TWI0.MSTATUS & TWI_BUSERR_bm) return;   // Bus error
if (TWI0.MSTATUS & TWI_ARBLOST_bm) return;   // Arbitration lost
if (TWI0.MSTATUS & TWI_RXACK_bm) return;   // Slave replied with NACK

// Receive:
uint8_t length = 4;
uint8_t* bytes; // assign uint8_t[4]
while (length > 0)
{
	if ((TWI0.MSTATUS & TWI_BUSSTATE_gm) != TWI_BUSSTATE_OWNER_gc) return;   // Master doesn't control bus

	while (!(TWI0.MSTATUS & TWI_RIF_bm))   // Wait for RIF set (data byte received)
	{
		if (timeout) return;   // Timeout   <-- it ends here after the first received byte
	}
	//TWI0.MSTATUS |= TWI_RIF_bm | TWI_WIF_bm;   // Clear Read and Write interrupt flags (should not be necessary at all)
	if (TWI0.MSTATUS & TWI_BUSERR_bm) return;   // Bus error
	if (TWI0.MSTATUS & TWI_RXACK_bm) return;   // Slave replied with NACK
	if (length > 1)
		TWI0.MCTRLB = TWI_ACKACT_ACK_gc;   // Setup ACK (more bytes to follow)
	else
		TWI0.MCTRLB = TWI_ACKACT_NACK_gc;   // Setup NACK (last byte read)
	*bytes++ = TWI0.MDATA;
	//if (length > 1)
	//	TWI0.MCTRLB |= TWI_MCMD_RECVTRANS_gc;   // Send ACK/NACK (should not be necessary with smart mode SMEN)
	length--;
}

// Stop:
TWI0.MCTRLB |= TWI_MCMD_STOP_gc;

 

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

Welcome to AVRFreaks!

 

We would recommend you use a known good TWI lib rather then winging your own.

Here is a link to some app notes and TWI code for the AVR 0/1 series.

https://www.avrfreaks.net/commen...

Jim

 

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

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

I have a version of the Arduino TWI for AVR 0/1 written by Microchip but ported out of the Arduino environment. It is working, right now, on an Xplained Pro 4809 board in code built from standard C. I tell it how many bytes to read after the register addressing, and it just does it.  I'd be happy to provide a copy with an example. Send me a PM (Freaks Personal Message) and I'll send it to you.

 

Jim

 

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

Last Edited: Sun. Apr 19, 2020 - 10:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:

We would recommend you use a known good TWI lib rather then winging your own.

Here is a link to some app notes and TWI code for the AVR 0/1 series.

https://www.avrfreaks.net/commen...

 

What "known good TWI lib" could you recommend?

 

I've already seen the post you linked to and inspected the code. I believe that I have the same bits in mine. At least I can't find any relevant difference (after I've written mine based on that one). I'd really like to understand what I'm doing there before blindly copying unknown code into my project. I had also seen another code snippet somewhere (can't remember where) in the form of a forum post but there was no feedback. So I'm not sure how much effort I should invest to analyse a solution that nobody confirmed working.

 

And according to the datasheet and the code I've seen so far, it's really not that complicated. You just need to set the few right bits in the right order. Don't get me wrong, it's not exactly clear what bits and what order. It's really by far not as straightforward as using a NuGet package in C#, but still not rocket science. Is it?

 

When I see C++ classes with a high number of small methods I'm worrying about the total code size. (That's good practice on a PC but not so much on a tiny microcontroller.) I've reverted my C++ classes to regular C modules and functions some time ago and it saved a significant amount of space. I'm already around 7–8 kB of the available 16 kB and there's still a lot to do.

 

I also tried to look at the Wire library for Arduino but couldn't find the relevant code for AVR 1-series.

Last Edited: Tue. Apr 21, 2020 - 07:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ygoe wrote:
I also tried to look at the Wire library for Arduino but couldn't find the relevant code for AVR 1-series.

 

https://github.com/arduino/ArduinoCore-megaavr/tree/master/libraries/Wire/src/utility

 

This is my untested stuff (twi0_mc.*), all I did was change the pin functions to a GPIO lib, you can change them to direct port manipulation if you don't want to do some sort of GPIO lib.

 

https://github.com/epccs/PiUpdi/tree/master/lib

 

I am going to switch gears on that project, the m4809 is is going to be replaced with a AVR128DA28.

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

Sorry I missed your were using AVR0/1 series, a fellow Freak donated this code for these.

Compare his to yours.

 

Attachment(s): 

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

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

That's exactly what I started with. IIRC it was published somewhere in this forum with no positive or negative feedback. I can't see any difference between the operations and their order in mine and his code. Without trying out exactly the provided code (it needs to be put in some application to actually see anything) I can only assume it doesn't work. At least I don't know why it should.

 

I've changed my code to not use Smart Mode and always explicitly send the (N)ACK after accessing the data. No change at all. SCL stops before the ACK could be sent.

 

The Arduino code and what Jim (ka7ehk) has provided me (they're at least very similar) uses interrupts instead of polling. If that improves things I'll have to do that. But I first need to adapt my project to use that. I'll report back what happens with interrupts.

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


I guess I'm not following your problem description, so your reading data from the device and the master is not sending the 9th clock bit?  Where in this diagram is it failing?

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

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

Here's the edited part of the graphic: The part marked in red is not happening.

 

 

Here's a screenshot of the signals:

 

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

Here's a small update: I've replaced my I2C functions with the code from the Arduino Wire library (a C++ class). A few minor tweaks were necessary to make it compile. With this code, the data can now be fully received and decoded. So I can successfully read both temperature and humidity and the measured values are plausible.

 

The major difference between Wire and the code I posted initially is that Wire uses interrupts instead of polling. The Wire API is still blocking though. That's fine for my application because it couldn't do much else in the main loop anyway.

 

The main drawback is that the code size has increased by about 1.3 kB. That's pretty much for an interface that's handled in hardware. I'll see if I can strip it down to what's needed and get back near the old code size.

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

What happens if you do a read/NAK there instead of a read/ACK, does it send a stop then?

 

Jim

 

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

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

To get data from the HDC1080 you write the Slave Address and pointer register.  Then do a re-start and read two data bytes (MSB and LSB).   Plus you have to wait 15 milliseconds after powering on the HDC1080 before attempting any I2C transaction.

 

 The screen shot in the last message has two colors superimposed on each other.  It might have something to do with I2C; I don't know.  The blue could be I2C clock, maybe, again I don't know (because nothing is **** documented!).

 

Where did you get that unusual formula for MBAUD?   The library linked in the ki0bk message has TWI0.MBAUD = 100 for 100K bits_per_second and 25 for 400Kbps.

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

Simonetta wrote:

The screen shot in the last message has two colors superimposed on each other.  It might have something to do with I2C; I don't know.  The blue could be I2C clock, maybe, again I don't know (because nothing is **** documented!).

 

The screenshot includes the configuration of the channels. I thought it would be enough explanation. As a description of the image and what it shows:

 

Yellow is channel 1. This is configured in the I2C decoder as data (SDA).

Cyan is channel 2. This is configured as clock (SCL).

The green line below shows the decoded I2C data in hex format. The first segment is the read request from address 40h, followed by an ACK of the slave. The second segment is the data 60h that was sent by the slave. There is no more clock after the 8 bits from the slave were received. Unfortunately the scope decoder doesn't cover the ACK bits.

 

I could have improved readability by slightly offsetting both channels vertically. I'll try to remember next time.

 

Quote:

Where did you get that unusual formula for MBAUD?   The library linked in the ki0bk message has TWI0.MBAUD = 100 for 100K bits_per_second and 25 for 400Kbps.

 

I'm not completely sure where I got that formula. Maybe from the same place where the code comes from. There's no complete documentation about it. The formula from the Atmel datasheet has two parameters: frequency and t_rise. t_rise is not defined, so I had to guess-copy some value from the source code. I took the value 300 and never changed it.

 

If the MBAUD value is critical, I could play a little with that to see if other values work better. Meanwhile I tried 400 kHz and 100 kHz with exactly the same outcome (of course with different timing).

 

The Wire library uses t_rise = 300 for 400 kHz and a different value of t_rise = 1000 for 100 kHz.

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

Reduce the gain on the SDA, SCL scope signals.   Separate the traces.

 

If you want help with the code,   ZIP up your AS7.0 project and attach the ZIP.

 

David.

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

ki0bk wrote:

What happens if you do a read/NAK there instead of a read/ACK, does it send a stop then?

 

Exactly the same. No change.

 

I've redone the screenshot. The first one shows the same situation as my previous screenshot but with hopefully improved readability.

 

 

The next one shows what happens with the Wire library. I could reduce the overhead code size to about 500 bytes by converting the C++ class (which isn't really used anyway) to C functions and more significantly by disabling the parts that are related to the I2C slave mode. The code always includes both and the ISRs keep large parts of it linked. By disabling the slave parts with the C preprocessor it all disappeared from the final executable.

 

Since there are no obvious solutions in this thread and the issue looks way more complicated than I'd expected (nobody does I2C on their own or understands the common libraries? sounds a bit dangerous) I guess I'll have to stay with the Wire library. At least I won't have to implement the other I2C features I'll need anymore.

 

This is the successful communication:

 

 

I don't know where the last clock comes from but I don't really care either as long as it works.

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

Thanks.   The scope trace is much easier to read.

I do not like the "Rigol decoding".   Logic Analyser Software e.g. Saleae is much clearer.

If you want help with the code,   ZIP up your AS7.0 project and attach the ZIP.

Many readers have I2C experience.   But I am not going to "guess" your project.   Especially when it takes you about 1 minute of your life to attach a ZIP.

 

David.

Last Edited: Sun. Apr 26, 2020 - 01:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

Especially when it takes you about 1 minute of your life to attach a ZIP.

 

That won't work. The entire project is much bigger than this. It would take you way longer to understand and use it than the code snippet I already provided in the first post. And it would take me much longer to create a demo project that you'd expect.

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

Modern PCs are pretty swift.    They can ZIP a large project directory in a few seconds.

 

Of course this is not suitable for a commercially sensitive project.    In which case you would need to create a minimal non-sensitive example that exhibits your problem.   And that would be a lot of effort.

 

There was a similar AS7.0 question today.    A project containing about 20 source files.   1 minute to ZIP.   1 minute to unzip.   A few minutes to resolve the problem and post the resultant ZIP back to the forum.

 

So far this thread has taken 7 days !!

 

David.

Last Edited: Sun. Apr 26, 2020 - 03:47 PM