Morphology
Posts: 36
Joined: Tue Jan 10, 2012 11:16 am
Contact: Website

I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 9:55 am

Hi. I have run into the dreaded inability of the BCM2807 / Rapberry Pi to handle repeated starts on the I2C bus.

Bit of history: I have designed and build a series of add-on boards based on the DAC8574 16-Bit I2C DAC. They work very well, and I can drive them no problem from a Pi using C/PIGPIO I2C commands at 400KHz. So far so good.

The DAC8574 has a 'base' I2C address of 0x4C and a user-selectable 4-Bit address (A0-A3). A0,A1 are 'Physical' address selection bits, and A2,A3 are so-called 'Extended' address selection bits.

By setting A0,A1 the device appears on Physical I2C addresses 0x4C - 0x4F to talk to 16 devices you then set A2,A3 via a control register. Obviously there is duplication of the 'Physical' addresses - A device set to 0x0000 will be on the same Physical address as those set 0x0100, 0x1000 and 0x1100.

I can write to these overlapping devices separately with no problems. The write sequence being:

[P][Addr][w][CTRL][MSB][LSB][P]

Where MSB & LSB are the output level, and the [CTRL] byte contains A2,A3 and bits to select which of the 4 output channels you are writing to. Etc., standard stuff. IOt all works very well, and my add-on boards have DIP switches to select the address and can be daisy-chained together to provide up to 16x4 = 48 Analogue outputs.

So, if my software knows what boards to expect, I can write to them without a problem. Then, I thought: what happens if a user just has, say, 2 boards, and sets them to Eg. 0x0100 and 0x0000 - is there a way I can tell what hardware is present.

From the DAC8574's perspective, yes you can. You can write a value to an output channel, then read it back. If you get back what you wrote, the device is there. Great, I thought, I'll write a simple start-up routine that scans the 16 possible addresses and notes which address(es) have devices sitting on them.

However, the read sequence requires the dreaded Repeated Start bit. The sequence being as follows:

[Addr][w][CTRL][Sr][Addr][r][MSB][LSB][P]

where Sr is a repeated start bit.

PIGPIO cannot send the Sr bit as it's not supported by the underlying SMB driver so I thought I'd try bit-banging the I2C connection instead.

First question: My hardware has already been built to use the normal Pi I2C bus connection on pins 3 & 5 (GPIO2 & 3), and I'd rather not change this. So, can I bit-bang the DACs over GPIO2 & 3 to find out what's physically there, and then switch to 'normal' I2C operation. ie:

bbI2COpen
bbI2CZip -- muiltiple commands to query the devices etc
bbI2CClose
then carry on as before:
i2cOpen
i2cWriteWordData -- normal operation of the software
i2cClose

My issue is that I am getting -82 errors (i2c write failed) when I call bbI2CZip, so I just wanted to check that bit banging over the normal I2C pins is OK, and/or do I need to take any particular precautions?

Also, I'd like to check the syntax of the of the Byte String I am passing to bbI2CZip. Specifically:

1) Do I need to include a Start bit? Joan's example on the PIGPIO library doesn't include one, so is it prepended automatically?

2) when I re-issue the Address, does it also need to be pre-pended by the Address identifier 0x04?

My byte sequence is currently:

0x02 0x04 0x4C 0x07 0x01 0x90 0x02 0x04, 0x4C 0x06 0x06 0x03

which reads as:

Start, Set Addr, 0x4C, Write, 1 Byte, 0x90, ReStart, Set Addr, 0x4C, Read, 2 Bytes, Stop

Assume my Address and Control register bytes are correct.

Do I need the first 0x02 (start) and the second 0x04 (Set Address)?

Sorry for the long post, but I needed to include sufficient detail.

Morph

User avatar
joan
Posts: 14394
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 10:06 am

Set the address before the I2C operation. You can't reset the address in the middle of an I2C transaction.

0x04 0x4C 0x02 0x07 0x01 0x90 0x02 0x06 0x06 0x03 0x00

Try the above.

6 6 means read 6 bytes, change to 6 2 to read two bytes,

TheodoreFletcher
Posts: 1
Joined: Fri Oct 28, 2016 10:02 am
Location: Branchland, WV
Contact: Website

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 10:08 am

How he device appears on Physical I2C addresses 0x4C - 0x4F to talk to 16 devices you then set A2,A3 via a control register.

User avatar
joan
Posts: 14394
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 10:17 am

One point I missed.

When you bit bang GPIO 2/3 are taken out of I2C mode (ALT0). After you bit bang make sure your software sets the mode for GPIO 2/3 back to ALT0 otherwise the Linux I2C driver will fail.

Morphology
Posts: 36
Joined: Tue Jan 10, 2012 11:16 am
Contact: Website

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 2:22 pm

TheodoreFletcher wrote:How he device appears on Physical I2C addresses 0x4C - 0x4F to talk to 16 devices you then set A2,A3 via a control register.
yes, I know. I can write to all 16 devices no problem. It's the need for a repeated start that is the problem.

Morphology
Posts: 36
Joined: Tue Jan 10, 2012 11:16 am
Contact: Website

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 2:45 pm

joan wrote:Set the address before the I2C operation. You can't reset the address in the middle of an I2C transaction.

0x04 0x4C 0x02 0x07 0x01 0x90 0x02 0x06 0x06 0x03 0x00

Try the above.

6 6 means read 6 bytes, change to 6 2 to read two bytes,
Thanks Joan. Yes, sorry, the 0x06 0x06 was my mistake when typing the question (should have been 0x06 0x02), and I know I should have had the 0x00 on the end too. I didn't make either of these mistakes when testing!

So, I see - you set the Slave Address BEFORE issuing the first Start. That I definitely didn't try and, re-reading the example in the bbI2CZip documentation, I see that's exactly what you're doing there too.

Though, in the above, how do you tell it to re-send the address after the repeated start, which is what the DAC is expecting? From the datasheet:
Capture.JPG
Capture.JPG (31.3 KiB) Viewed 2004 times
It is expecting to receive the Address a second time after the Repeat Start bit. It's not changing or resetting the address, just re-issuing it. Does the bbI2CZip function ALWAYS re-send the same slave address after a repeated start? If that's what it does, then the string above makes perfect sense.

Thanks for the pointer to changing back to ALT0, and confirmation I ought to be able to do this using GPIO2/3. The bit banging is just something I want to do once during the initialisation routines, in order to scan the I2C bus for available hardware so changing it back when I'm done querying the hardware is no problem,

Many thanks for your help (again!) - I will try this over the weekend, and report back.

Morph.

User avatar
joan
Posts: 14394
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 3:10 pm

A read or write always sends an initial address + R/W byte on the bus.

Remember that whether the command is a read or write is encoded in the bottom bit of the address byte. As per the I2CPutByte() calls in the following snippet.

Code: Select all

case PI_I2C_READ:

   bytes = myI2CGetPar(inBuf, &inPos, inLen, &esc);

   if (bytes >= 0) ack = I2CPutByte(w, (addr<<1)|1);

   ...

   break;

case PI_I2C_WRITE:

   bytes = myI2CGetPar(inBuf, &inPos, inLen, &esc);

   if (bytes >= 0) ack = I2CPutByte(w, addr<<1);

   ...

   break;

Morphology
Posts: 36
Joined: Tue Jan 10, 2012 11:16 am
Contact: Website

Re: I2C and the dreaded Repeated Start bit

Fri Oct 28, 2016 4:17 pm

Oh, I see - it's the read or write that sends it, rather than that it follows the Start bit.

That makes a lot of sense.

I know not to shift the address up one bit, as that's done by the sending routing when it tacks the r/w bit on the end.

This all makes sense when I see it in the context of a known situation, but I was finding it very difficult to find a worked example that I could relate to. Hopefully this thread will help others in a similar situation.

Morph

Morphology
Posts: 36
Joined: Tue Jan 10, 2012 11:16 am
Contact: Website

Re: I2C and the dreaded Repeated Start bit

Sun Oct 30, 2016 10:53 am

Well, sad to say I am giving up on getting this to work.

Bit banging over the standard I2C GPIOs 2 & 3 now works absolutely fine (thanks to Joan pointing me in the right direction), and I can write to all 16 devices individually even though they share the same 4 'physical' I2C addresses - the individual DACs only respond if the Extended address bits in the control register match the state of the A2, A3 pins on the device itself. Exactly as I would expect.

However, when reading, any DACs sharing an I2C physical address completely refuse to yield sensible data - it's as if all devices on the same Physical address are responding, and the extended addressing is being ignored.

Unfortunately I don't have a decent enough scope to be able to capture what is actually happening at a bus level.

If I remove all the duplicate devices, so that I just have one device on each of the I2C 'Physical' addresses, it works absolutely fine - when reading from the DAC I get back what I wrote. This is the same behaviour I was seeing using the normal i2cReadWordData function and which I had put down to the fact that that function issues a Start rather than a Repeated Start bit - hence why i thought I'd try bit banging the connection instead.

So, either bit banging the connection is having the same effect of issuing a Start, rather than a Repeated Start, or the DAC8574 completely ignores the extended address bits during a Read cycle (which doesn't seem very likely and certainly isn't the impression you get from reading the datasheet).

My bbI2C Zip code fwiw is as follows:

Code: Select all

	 i2cAddr = DAC_BASE_ADDR | (address & 0x3);
	 ctrl_reg = (((address & 0xC) << 4) & 0xC0) | 0x10;
	 test_val = rand()%0xFFFF;
 
	 inBuf[0] = 0x04;		// Set Address
	 inBuf[1] = i2cAddr;	// Device Address
	 inBuf[2] = 0x02;		// Start
	 inBuf[3] = 0x07;		// Write
	 inBuf[4] = 0x03;		// Write 3 Bytes
	 inBuf[5] = ctrl_reg;	// Control Register
	 inBuf[6] = (test_val >> 8) & 0x00FF;		// MSB
	 inBuf[7] = (test_val & 0x00FF);			// LSB
	 inBuf[8] = 0x03;		// Stop
	 inBuf[9] = 0x00;		// End

	 retval = bbI2CZip(I2C_SDA, inBuf, 10, outBuf, 0);
	 
	 // Did we get back what we wrote out
	 inBuf[0] = 0x04;		// Set Address
	 inBuf[1] = i2cAddr;	// Device Address
	 inBuf[2] = 0x02;		// Start
	 inBuf[3] = 0x07;		// Write
	 inBuf[4] = 0x01;		// Write 1 Byte
	 inBuf[5] = ctrl_reg;	// Control Register
	 inBuf[6] = 0x02;		// Repeated Start
	 inBuf[7] = 0x06;		// Read
	 inBuf[8] = 0x02;		// 2 Bytes
	 inBuf[9] = 0x03;		// Stop
	 inBuf[10] = 0x00;		// End
	 
	 retval = bbI2CZip(I2C_SDA, inBuf, 11, outBuf, 20);
	 readval = ((outBuf[0] << 8) & 0xFF00) | outBuf[1];
	 printf("Test: %0x, Read: %0x\n",test_val, readval);
I'll go back to what I was doing before, which is manually configuring my prog to tell it what devices are actually connected, rather than attempt to have it discover them during initialisation.

Morph

Return to “C/C++”