cancel
Showing results for 
Search instead for 
Did you mean: 

Proper Initialization of the I2C Peripheral

dibs
Associate II
Posted on February 09, 2014 at 15:09

I am starting this post to hopefully determine the proper initialization procedure for the I2C peripheral. I am specifically using an STM32F407, but I am assuming that this will be helping in a broader sense.

The background is that I wrote an I2C driver that, at first past, was extremely unreliable. It had the problem that it would sometimes start up fine, sometimes start up with a bus error, and sometimes start up every or every other time with the BUSY flag never clearing.

In my case, I was testing my code with a dev kit connected to an external LCD screen which hosted the pull up resistors for the bus. I noticed that as I moved around my initialization code, the start up behavior would change. The confusing part was that scoping the SDA and SCL lines generally showed no difference, electrically, on the bus. In order to solve the problem, I disconnected the display, enabled the internal pull ups, and scoped the lines. This showed me the culprit:

    GPIO_InitStructure.GPIO_Pin   = LCD_SCL_GPIO_Pin_x | LCD_SDA_GPIO_Pin_x;

    GPIO_Init(LCD_GPIOx, &GPIO_InitStructure);

What I could then see, was SDA going hi and then SCL going hi. In my interpretation, this action was interpreted by the processor as an external I2C master trying to communicate with it. It may have been waiting for a STOP condition before it would respond to any commands.

The real problem was that I had tried issuing START and STOP commands, waiting it out, and various other things, but the peripheral would just not respond when it was stuck in BUSY mode.

I will post my init code below, but any thoughts on how to be sure that one can avoid this issue? The primary issue here is that I should not be able to lose control of the bus. If this kind of thing can happen, what would I do to reset at start up and then anytime afterwards.

http://www.best-microcontroller-projects.com/i2c-tutorial.html

void init_lcd(void) {

    GPIO_InitTypeDef GPIO_InitStructure;

    I2C_InitTypeDef  I2C_InitStructure;

    DMA_InitTypeDef  DMA_InitStructure;

    // I2C Clock Enable

    RCC_APB1PeriphClockCmd(LCD_APB1Periph_I2Cx, ENABLE);

    // GPIOx clock enable

    RCC_AHB1PeriphClockCmd(LCD_AHB1Periph_GPIOx, ENABLE);

    // Reset I2C peripheral

    RCC_APB1PeriphResetCmd(LCD_APB1Periph_I2Cx, ENABLE);

    RCC_APB1PeriphResetCmd(LCD_APB1Periph_I2Cx, DISABLE);

    // Connect I2C pins to AF

    GPIO_PinAFConfig(LCD_GPIOx, LCD_SCL_GPIO_PinSourcex, LCD_GPIO_AF_I2Cx);

    GPIO_PinAFConfig(LCD_GPIOx, LCD_SDA_GPIO_PinSourcex, LCD_GPIO_AF_I2Cx);

    // I2C GPIO pin configuration

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;

    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;

    GPIO_InitStructure.GPIO_Pin   = LCD_SCL_GPIO_Pin_x;

    GPIO_InitStructure.GPIO_Pin   = LCD_SCL_GPIO_Pin_x | LCD_SDA_GPIO_Pin_x;

    GPIO_Init(LCD_GPIOx, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = LCD_SDA_GPIO_Pin_x;

    GPIO_Init(LCD_GPIOx, &GPIO_InitStructure);

    // I2C struct init

    I2C_StructInit(&I2C_InitStructure);

    I2C_InitStructure.I2C_ClockSpeed          = I2C_SPEED;

    I2C_InitStructure.I2C_Mode                = I2C_Mode_I2C;

    I2C_InitStructure.I2C_DutyCycle           = I2C_DUTYCYCLE;

    //I2C_InitStructure.I2C_Ack                 = I2C_Ack_Disable;

    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

    I2C_Init(LCD_I2Cx,&I2C_InitStructure);

    //Enable Clock Stretching

    I2C_StretchClockCmd(LCD_I2Cx,ENABLE);

    // Clear any set flags on the DMA TX stream

    DMA_ClearFlag(LCD_DMA_STREAM_TX, LCD_DMA_FLAG_TCIFx | LCD_DMA_FLAG_FEIFx | LCD_DMA_FLAG_DMEIx |

            LCD_DMA_FLAG_TEIx | LCD_DMA_FLAG_HTIx);

    // Disable the I2C Tx DMA stream

    DMA_Cmd(LCD_DMA_STREAM_TX, DISABLE);

    //Deinit I2C TX DMA

    DMA_DeInit(LCD_DMA_STREAM_TX);

    DMA_InitStructure.DMA_Channel             = LCD_DMA_CHANNEL;

    DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)(&(LCD_I2Cx->DR));

    DMA_InitStructure.DMA_PeripheralInc       = DMA_PeripheralInc_Disable;

    DMA_InitStructure.DMA_MemoryInc           = DMA_MemoryInc_Enable;

    DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;

    DMA_InitStructure.DMA_MemoryDataSize      = DMA_MemoryDataSize_Byte;

    DMA_InitStructure.DMA_Mode                = DMA_Mode_Normal;

    DMA_InitStructure.DMA_Priority            = DMA_Priority_VeryHigh;

    DMA_InitStructure.DMA_FIFOMode            = DMA_FIFOMode_Disable;

    DMA_InitStructure.DMA_FIFOThreshold       = DMA_FIFOThreshold_Full;

    DMA_InitStructure.DMA_MemoryBurst         = DMA_MemoryBurst_Single;

    DMA_InitStructure.DMA_PeripheralBurst     = DMA_PeripheralBurst_Single;

    DMA_InitStructure.DMA_DIR                 = DMA_DIR_MemoryToPeripheral;

    DMA_InitStructure.DMA_Memory0BaseAddr     = (uint32_t)txBuff;

    DMA_InitStructure.DMA_BufferSize          = sizeof(txBuff);

    DMA_DeInit(LCD_DMA_STREAM_TX);

    DMA_Init(LCD_DMA_STREAM_TX, &DMA_InitStructure);

} // end - void init_lcd(void) {

#i2c #ii2c #stm32f4
11 REPLIES 11
chen
Associate II
Posted on February 10, 2014 at 10:39

Hi

Thanks for your efforts.

I have found that most people's problems with I2C at start up is trying to initialise the I2C bus too early. Usually, the peripherals attached on the I2C line take longer than the STM32 to initialise and come out of reset.

The I2C initialise should be delayed (sometimes for many ms). It should also check the bus state. All comms functions should also check the bus state before attempting to access the bus.

dibs
Associate II
Posted on February 10, 2014 at 15:58

In terms of initializing the I2C bus too early; do you mean that we should wait several ms before initializing because of externally connected devices or b/c of something inside of the processor. Also, I am assuming that you mean to say that we should wait a few ms after power up.

In terms of checking the bus state, I am used to checking for BUSY and BERR. Are there other things that you would suggest checking?

Also, assuming that I want my processor to have the higher priority, I would need a routine that grabs the bus from any device that is trying to talk.

chen
Associate II
Posted on February 10, 2014 at 18:20

Hi

''do you mean that we should wait several ms before initializing because of externally connected devices ''

Yes.

''Also, I am assuming that you mean to say that we should wait a few ms after power up.''

Yes

'' I am used to checking for BUSY and BERR. Are there other things that you would suggest checking?''

Yes, that is what I mean. Not sure you can do any more.

''Also, assuming that I want my processor to have the higher priority, I would need a routine that grabs the bus from any device that is trying to talk.''

The nature of I2C means that it is not possible to 'grab prioirty' over other devices (bus masters). I2C uses open collector/drain to pull the data/clock lines low - there is nothing the bus master can do to stop that!

Any device can pull either line low and disrupt communications - that would be very 'rude'.

All any I2C master/slave can do is to abide by the specifications (and not screw up the bus for other devices!) .

dibs
Associate II
Posted on February 10, 2014 at 21:36

Instead of grabbing priority, perhaps I should say it differently;

Assume that our MCU reset when it was communicating with a slave device, do we only need to send a stop condition?

Assume that a master was communicating with a slave device, how do we tell that we don't have control over the bus?

jpeacock2399
Associate II
Posted on February 11, 2014 at 01:01

Over the years I have encountered several I2C peripherals that don't always reset correctly on power up.  When I initialize the I2C controller, or after a timeout error, I disable the I2C on the STM32, put the I2C pins in GPIO open drain mode, and then toggle the SCK pin (at < 100KHz) until the SDA pin shows the bus is free.  Then I enable the I2C on the STM32 and start a new transaction.

  Jack Peacock
chen
Associate II
Posted on February 11, 2014 at 10:16

Hi

''Assume that our MCU reset when it was communicating with a slave device, do we only need to send a stop condition?''

Good question. I had a look through the I2C specification and it does not specify what happens if a communication is NOT terminated by a stop condition.

It DOES specify that on error conditions, the preference is for the host to reset the device.

''Assume that a master was communicating with a slave device, how do we tell that we don't have control over the bus?''

If there is other bus activity - then this is a simple case of bus arbitration.

If you are asking - how do we know that the slave is still waiting for communications to continue or terminate - you cannot.

You could take the pragmatic approach that Jack suggested.

dibs
Associate II
Posted on February 12, 2014 at 13:59

@Jack

Good idea, even if there currently is another I2C device which is trying to communicate on the bus, doing that should get it to at least complete its communication. If the other device is functioning normally, and trying to talk to my MCU, it should end after receiving a NACK on the next byte sent.

@Sung

Can you post a link to the I2C reference you find most helpful?

chen
Associate II
Posted on February 12, 2014 at 15:06

Hi

''Can you post a link to the I2C reference you find most helpful?''

I get it from here :

http://en.wikipedia.org/wiki/I%C2%B2C

Direct link

http://www.nxp.com/documents/user_manual/UM10204.pdf

kwikius
Associate II
Posted on August 14, 2016 at 16:01

It is a little late to reply but hope it helps somebody. I solved this issue by changing the initialization order from that shown in your code. First initialize the I2C port pins as I2C function pins( open collector) and enable their RCC module, then start the I2C RCC module running.