Skip to main content
Marco Hess
Associate II
January 12, 2017
Question

Can't reset I2C in STM32F407 to release I2C lines

  • January 12, 2017
  • 5 replies
  • 11011 views
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
This topic has been closed for replies.

5 replies

AvaTar
Senior III
January 12, 2017
Posted on January 12, 2017 at 09:33

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

A lock-up of the slave device. I would check that the communication sequences match that of the slave's datasheet. That might require a scope or logic analyzer to pin down the critical sequence and/or transition. Perhaps even a timing issue or an ignored clock stretch.

Is there a other way to properly reset the I2C?

If you can't find an issue with your software (I2C master), you might have a problem. I guess your (undisclosed) I2C slave does not have a reset pin - they usually have not. A stopgap solution would be to power-cycle the I2C slave via a GPIO pin of the master.

'Older' I2C devices had been more or less combinatorial devices, but more recent ones contain quite state machines. When you abort a communication sequence, you are lost in the woods.

waclawek.jan
Super User
January 12, 2017
Posted on January 12, 2017 at 10:13

You can reset the STM32's I2C module by setting and then clearing the respective I2CxRST bit in RCC_APByRSTR.

Maybe even easier, you can use the SWRST bit in I2C_CR1.

JW

Marco Hess
Associate II
January 13, 2017
Posted on January 13, 2017 at 01:01

Hi

Waclawek.Jan

,

That was the type of answer I was hoping for. Thanks!

Found the SWRST bit in the I2C_CR1 register and when I assert that bit just before I deinit the I2C, I can see it clears the bus lines so it seems to reset the I2C.

So my reset functions now clears the I2C peripheral, bus lines are back up. HAL driver reports no errors and status ready.

However, the next time it does a write operation, it locks up completely.HAL_I2C_MasterTransmitIT reports OK and thenthe SDA goes low for the start followed by the CLK also going low. However,both SDA and CLK now stay low until the timeout hits (20ms).

Again on the scope I can see from the low level on the SDA/SCL lines that is is the master holding them low. If it is the slave the level would be lower.

When I detect the timeout, I do my reset function again and that clears the SDA/CLK lines and then at the next write attempt the lines go low and stay low again.

So on my scope, I have this nice 1ms positive pulse repeating every 20ms :( and communications does not recover.

I as able to trace on Saleae Logic the transactions leading up to the point of failure.It seams that the master just stops clocking and locks up in the middle of the operation.

0690X000006061FQAQ.png

After this, the bus stays high for 30ms or so and then both SCK and SDA go low. Then I hit a timeout and I start doing the resets resulting in the 20ms pulse train.

0690X000006060WQAQ.png

So I am still stuck :(

Bit banging looks awfully good now :(

Any further suggestions?

S.Ma
Principal
January 13, 2017
Posted on January 13, 2017 at 03:35

I2C bus error recovery usually can happen when slave sends 0x00 and master resets during transmission, or in bus glitches. Bitbang 9 stop bits or more to get both lined raised in order to free the bus and be able to do a start bit again. Unless of a permanent clock stretching by slave, you can be sure of a turnaround bit within 9 clock periods. Smbus has clock stretch timeout as improvement feature. All the best!

piotreklotrek7
Visitor II
April 30, 2017
Posted on May 01, 2017 at 00:57

I would love to know if anybody managed to workaround this issue. I have exactly the same problem with i2c getting stuck after trying to reset it. (stm is pulling both lines down)

S.Ma
Principal
May 1, 2017
Posted on May 01, 2017 at 18:01

Bitbang 9 stop bits work for me even if the i2c lines are on a video cable with hot plug unplug. Without it, the bus hangs is few seconds of manipulation. Check bus idle before attempting to generate a start bit and then, when needed, run 9 stop bits prior to a start.

piotreklotrek7
Visitor II
May 1, 2017
Posted on May 01, 2017 at 19:28

Hi, thanks for response! I spent all day trying different solutions and it looks like those are the steps to reliably restart stuck i2c (at least on my board

STM32F446)

:

> disable I2C

> change sda & scl pin mode to output

> generate 9 stop bits

> change sda & scl pin mode back to alternate function

> set SWRST flag in i2c control register

> then enable i2c and initialize it

There's a helpful snippet of code in an answer to a very similar problem on stackexchange, which can be easily modified to generate more that one stop bit: 

https://electronics.stackexchange.com/a/281046

Martin Round
Visitor II
March 16, 2018
Posted on March 16, 2018 at 21:14

After a few days of frustration, I've pretty much proved to myself there are bugs in the HAL I2C (using interrupt) routines.  I've an application that reads a MPU-6050 gyro via I2C.  There are also other simple edge-detecting GPIO interrupts - nothing to do with the I2C.  I find that the I2C works flawlessly for hours on end providing the other interrupts never occur.  When activity begins on the GPIO lines causing other interrupts then the I2C comms stops at random - sometimes after a few minutes or sometimes within seconds.  I've also found that using UART transmits with interrupt callback (to send log messages to a debug monitor) will also cause the I2C to stall.

If anyone has found a fix to this problem I'd be happy to know - I've spent hours searching but found nothing really helpful. I think it's time to ditch the HAL routines and research how to hit the I2C hardware more directly.

waclawek.jan
Super User
March 17, 2018
Posted on March 17, 2018 at 15:20

When activity begins on the GPIO lines causing other interrupts then the I2C comms stops at random - sometimes after a few minutes or sometimes within seconds. 

I'm not going to defend Cube, but this may be not entirely software-related. Depending on particular setup, GPIO lines may introduce unexpected noise into the I2C lines (especially SDA), and the I2C machine may go in an unexpected way then. Check with exercising the GPIO and UART without having the interrupts enabled.

If this proves to be the problem, check and recheck the I2C timing settings, decrease pullup values, improve layout (grounds are often a neglected issue) or introduce timeouts/recovery mechanisms if these methods won't help.

If hardware proves to be innocent, it's time to ditch Cube (frankly, it's always a good time to ditch Cube, except perhaps when beginners try to make applications straighforwardly fitting the provided examples; but it's also a bad idea to take substantial measures without being sure of the primary cause of problem).

Also, note, that there are several rather different incarnations of the I2C in various STM32 subfamilies, so it always helps to state which model are you using.

JW

Martin Round
Visitor II
March 18, 2018
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
Visitor II
July 27, 2018

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.