zephyr: Unable to leave off stop bit for I2C transactions on STM32
Raised in Discord https://discord.com/channels/720317445772017664/883445358821249084/1209677653259329568 but no response and this seems to pretty clearly be a problem in the STM32 driver inside the Zephyr code base, hence raising it here.
We talk to I2C HW, a u-blox GNSS device (any one, they all work the same way), which behaves like some I2C flash devices do, in that, to set the register that you are addressing inside the device you send the sequence:
Start bit I2C address I2C write register address X Start bit I2C read (and the device will return the value at register address X)
This is sometimes called a “repeated start”.
The i2c_transfer() API allows this: we simply do not set the I2C_MSG_STOP flag set in the call to i2c_transfer() for the write operation.
However, as far as we can tell, the STM32 I2C driver ALWAYS sets I2C_MSG_STOP at the end of a transaction (see here), irrespective of whether the application intended it or not
This means we are unable to use STM32 MCUs with such I2C HW. nRF52/nRF53 works fine with this pattern, and in fact we can use the STM32 LL API outside Zephyr and that works fine also, it is only the STM32 I2C driver within Zephyr that has this problem.
Correct behaviour:
Incorrect behaviour:
Our GNSS code can be found here, where you can see the last parameter to uPortI2cControllerSend() (which calls i2c_transfer()) when performing this “set register address” operation is set true, meaning noStop. In case it is of interest, our “STM32 native” equivalent code, which performs the operation correctly, can be found here.
About this issue
- Original URL
- State: closed
- Created 4 months ago
- Comments: 20 (6 by maintainers)
Are you attemping to not stop in one i2c_transfer() call and then do a start in a seperate i2c_transfer() call? That’s not portable, and in the worst case could cause other issues if multiple devices are on the bus and multiple call contexts want to use that bus.
Otherwise i2c_transfer lets you do full start->write->start->read->stop style transactions already and should be the way to do it. See the i2c_write_read helper https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/i2c.h#L880
That does exactly what you are asking for in a way that’s guaranteed to work on all drivers.
For some additional reasoning… most/all drivers now have a semaphore or mutex that is taken in i2c_transfer and released just before return. Once the call returns, its perfectly possible for any other thread to come in and start a new transfer. An i2c_transfer() that leaves the bus in a state that might break the next caller is arguably a bug because of this. I actually think the st driver might be more correct in this case then if others are not doing this.
@RobMeades you mentioned this is not how the nordic driver works, please file a bug and note #69484
I’d say the final message yes, should probably imply a stop. That doesn’t mean a transfer with multiple starts and stops isn’t supported so the bit flags still do make sense.
I do think we can agree this isn’t specified in the docs though and should be. I will fix that tomorrow.
We happen to be testing with a Nucleo F767zi board but the two customers that have raised this issue with is so far have been using F466RE and WL.
Adding @teburd as the I2C maintainer, as I think that if there’s an issue with the API leaving too much place to driver specific behavior, he should be involved. @teburd What’s your take on this ?
nRF52/nRF53 work fine, we already have customers using that.
EDIT: oh, I see you, mean MCUs other than Nordic and ST - I hope not! If so, the Zephyr I2C API isn’t much of an API: having the driver override flags that the application is meant to be in control of is not something I would normally expect.