cancel
Showing results for 
Search instead for 
Did you mean: 

Can't reset I2C in STM32F407 to release I2C lines

Marco Hess
Associate II
Posted on January 12, 2017 at 08:18

I am using an STM32F407 on a customer board and have trouble with the I2C handling.

I am using the latest HAL HAL_I2C_MasterTransmitIT and HAL_I2C_MasterReceiveIT to poll data from a sensor.

The problem is that after a while the I2C lines lock up (both SDA and SCL low) and the 

HAL_I2C_MasterTransmitIT returns errors.

I do detect the error and try to handle the problem by deinit the I2C. I then reconfigure the I/O to software handle some clocking that release SDA if it is stuck by a slave on the bus. At that point I can see that the lines go high again so it is the I2C peripheral in the STM that is pulling it low ( I can also see that on the scope with the low level slightly above ground while when the lines are pulled by a slave device, the go lower).

I then 

reinit the I2C peripheral but as soon as the I2C is activated and connected to the I/O lines, the SDA and SCL are pulled low again.

So I can't seem to reset the I2C without restarting the whole chip.

Is there a other way to properly reset the I2C?

Also, what can be causing this lockup in the first place?

#stm32-i2c-master
21 REPLIES 21
Posted on March 18, 2018 at 21:44

Thanks for the suggestions.  The chip is an STM32F103C8T6.  All the signals (Power, I2C, and GPIO) look clean on a scope (I fitted extra decoupling capacitors to the chip's power pins a week or so back but it made no difference).  The I2C is set to run at 400kHz (and the scope verifies that it is indeed running at that speed).  I'm using 3k3 pull-ups on SDA and SCL.

I found that simply adding the following lines before entering my main while() loop - and changing nothing else - prevents the I2C from locking up even when the GPIO is in heavy use.

  HAL_NVIC_DisableIRQ(EXTI1_IRQn);

  HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);

  HAL_NVIC_DisableIRQ(EXTI2_IRQn);

  HAL_NVIC_DisableIRQ(EXTI3_IRQn);

  HAL_NVIC_DisableIRQ(EXTI4_IRQn);

  HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);

The interrupt routines (when they are enabled) are very short - they just increment or decrement some 16-bit variables - and those variables aren't used at all by the I2C routines.

I also tried using the non-interrupt (blocking) I2C HAL routines - the I2C doesn't then lock up, but my program then does miss some edges on the GPIO interrupts - so it looks like those HAL routines must disable interrupts, meaning those are no use to me either.

I was going to try the more complicated DMA I2C HAL routines, but I think I'll still need an interrupt callback when the transactions are complete, so I'm not inclined to waste any more time on those.

I shall now peruse the chip's data sheet and see what's involved in bypassing the HAL I2C code.  If I get stuck with that, I suppose I could always write some low level bit-banging code to just do I2C on any pair of GPIO pins - it will occupy a lot of the chip's time, but my time is more valuable than the chips!

Sandro G
Associate II

I had a similar problem with an F072 as master and L011 as slave, where the L011 kept locking up and pulling SCL and SDA low forever. I solved it by using only DMA functions on the L011 instead of blocking or interrupt functions. This approach also solved other problems in other projects, so I can only recommend to exclusively use DMA functions for I2C.

How To:

  1. in cubeMX in the peripheral configuration, click the DMA tab and add one channel for TX and RX each, select increment for memory, but not for peripheral (default).
  2. use _DMA instead of _IT functions
  3. callbacks and everything is identical to using _IT functions

Edit: The bug still occasionally shows up, I'm still investigating how to solve it for good :|

Edit 2: The bug is most likely caused by the master (F072) trying to read from the slave (L011), but the slave is receiving. Since the TXDR is never filled with data to send, the slave clock-stretches infinitely. It would be possible to listen to the address match flag, then check the DIR bit and thus decide whether to transmit or receive, but the HAL unfortunately doesn't offer such functionality :(

I therefore theck the BUSY flag (only seems to work in main, not in systick callback), and if it is set for more than 20 ms, I initialize the i2c bus again (only init, no de-init before, and this only works in main, not in an interrupt such as systick-callback!). This releases SDA and SCL lines, which is read as NACK by the master. Don't forget to start receiving again afterwards.