I2C: 7 or 8 Bit Addresses? [Fixed! Pull Request Submitted.]

So here's my fix for the I2C address issue!

As it turns out, all the magic happens within the platform specific I2C libraries on Arduino. There's no pre-processing or anything, they simply expect you to be providing an 7-Bit address. The fix was actually quite simple, really!

../core-common-lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_i2c.c:

void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
{
  /* Check the parameters */
  assert_param(IS_I2C_ALL_PERIPH(I2Cx));
  assert_param(IS_I2C_DIRECTION(I2C_Direction));
  /* Test on the direction to set/reset the read/write bit */
  if (I2C_Direction != I2C_Direction_Transmitter)
  {
	/* Shift the address one bit to the left */
	Address = Address << 1;
	/* Set the address bit0 for read */
	Address |= OAR1_ADD0_Set;
  }
  else
  {
	/* Shift the address one bit to the left */
	Address = Address << 1;
	/* Reset the address bit0 for write */
	Address &= OAR1_ADD0_Reset;
  }
  /* Send the address */
  I2Cx->DR = Address;
}

So, basically what's happening here is this:

  1. I2C_Send7bitAddress is called anytime you're setting up to read or write over the I2C interface. It's called surprisingly late in the code, coming after the START bit has already been transmitted. (I say this is surprising, because a lot of microcontrollers would have already put the slave address into read and write registers during beginTransmission, whereas the STM32 has a single address register that's only being set once endTransmission is called and communication has started.)
  2. if (I2C_Direction != I2C_Direction_Transmitter) is checking to see if we're supposed to be reading from this address. If so, we'll use Address |= OAR1_ADD0_Set; to perform a bitwise OR operation between it and 0x0001.
  3. If we're supposed to be writing to the address, we'll perform a bitwise AND between it and 0xFFFE like this: Address &= OAR1_ADD0_Reset;
  4. However, before we can do either #2 or #3, we need to shift the address one bit to the left. That's what the problem was! I'm not sure if this was previously being done somewhere else or what? Anyway, I've added Address = Address << 1; to the top of the if and else statements.
  5. Finally, we call I2Cx->DR = Address; to transfer the full 8-Bit address into the data register, which is then transmitted across the I2C bus!

I've tested this with ten different I2C devices and everything seems to working fine with my changes. (So long as you enter a valid 7-Bit address between 0 and 127.)

I double checked with my Logic 16 and Tektronix MSO2024B and there were no changes to the signal integrity. (As expected, I just like to be thorough!)

@zach I'll be making a pull request with my fix shortly. :smiley:

As an aside, this is a really neat little article/guide on the inner-workings of the ATmega328P TwoWire library. If you want to start understanding just how different Arduino is from actual "low level" C programming on a microcontroller, give it a read.

4 Likes