2014-02-09 06:09 AM
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 #stm32f42014-02-10 01:39 AM
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.2014-02-10 06:58 AM
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.2014-02-10 09:20 AM
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!) .2014-02-10 12:36 PM
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?2014-02-10 04:01 PM
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 Peacock2014-02-11 01:16 AM
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.2014-02-12 04:59 AM
@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.@SungCan you post a link to the I2C reference you find most helpful?2014-02-12 06:06 AM
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.pdf2016-08-14 07:01 AM
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.