Problems with STM32F103 and I2C in polling mode - Page 1
Forum section: electronics / general

author: tatus1969
author_rank: Super Contributor
timestamp: May 26, 2017, 10:40:19 am
content: The microcontroller has to read sequential bytes from a slave, and it needs to send a NAK to the slave after the last read byte, in order to inform it that the sequence is complete. This picture shows the expected sequence:This code does the job when left alone (meaning no interrupt activity in the background).Code:bool I2C_WaitEvent( uint32_t flag ){uint32_t timeout = 1000;while( I2C_CheckEvent( I2C1, flag ) != SUCCESS ){if( !--timeout ){return FALSE;}}return TRUE;}bool I2C_BattRead( uint8_t address, uint8_t * data, uint32_t length ){I2C_GenerateSTART(I2C1, (FunctionalState)ENABLE);if( !I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT) ){I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);return FALSE;}I2C_Send7bitAddress(I2C1, 0x28 << 1, I2C_Direction_Transmitter);if( !I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) ){I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);return FALSE;}I2C_SendData(I2C1, address);I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTART(I2C1, (FunctionalState)ENABLE);I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT);length--;if( length == 0 ){I2C_AcknowledgeConfig(I2C1, (FunctionalState)DISABLE);}else{I2C_AcknowledgeConfig(I2C1, (FunctionalState)ENABLE);}I2C_Send7bitAddress(I2C1, 0x28 << 1, I2C_Direction_Receiver);I2C_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);while( length ){I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_RECEIVED);// for( volatile int i=1; i<1000;i++); // artificial delay!length--;if( length == 0 ){I2C_AcknowledgeConfig(I2C1, (FunctionalState)DISABLE);}else{I2C_AcknowledgeConfig(I2C1, (FunctionalState)ENABLE);}*data++ = I2C_ReceiveData(I2C1);}I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);*data++ = I2C_ReceiveData(I2C1);I2C_WaitEvent(I2C_FLAG_BUSY);return TRUE;}void updatePackState( void ){GPIO_InitTypeDef GPIO_InitStructure;I2C_InitTypeDef I2C_InitStruct;uint8_t data;RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, (FunctionalState)ENABLE );// Toggle clock line to release potentially locked slaveGPIO_InitStructure.GPIO_Pin = GPIO_SCL | GPIO_SDA;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init( GPIOB, &GPIO_InitStructure );GPIO_WriteBit( GPIOB, GPIO_SDA, Bit_SET );for( int j=0; j<8; j++ ){GPIO_WriteBit( GPIOB, GPIO_SCL, Bit_RESET );for( volatile int i=1; i<50;i++);GPIO_WriteBit( GPIOB, GPIO_SCL, Bit_SET );for( volatile int i=1; i<50;i++);}// Init IOs to battery pack for I2C operationGPIO_InitStructure.GPIO_Pin = GPIO_SCL | GPIO_SDA;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init( GPIOB, &GPIO_InitStructure );GPIO_PinRemapConfig( GPIO_Remap_I2C1, (FunctionalState)ENABLE );// Deinit and reset I2C peripheral in case of internal lockupI2C_Cmd(I2C1, (FunctionalState)DISABLE);I2C_SoftwareResetCmd( I2C1, (FunctionalState)ENABLE );I2C_SoftwareResetCmd( I2C1, (FunctionalState)DISABLE );// Init I2C peripheralI2C_InitStruct.I2C_ClockSpeed = 50000;I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;I2C_InitStruct.I2C_OwnAddress1 = 0;I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_Init(I2C1, &I2C_InitStruct);I2C_Cmd(I2C1, (FunctionalState)ENABLE);I2C_BattRead( 0x80, data, 2 );}Screenshot of transmitted sequence:The problem starts when this code has to live in an environment with interrupts running in the background. As this is a realtime motor control application, the interrupts tend to have substantial runtime, I cannot change this. The latency that is randomly created by the interrupts break the behavior of my I2C code. To test this, I uncommented the line "// for( volatile int i=1; i<1000;i++); // artificial delay!" above. The result is as shown here:The microcontroller apparently acknowledges the last received byte, and the slave thinks that it should continue transmitting.So I checked the STM32 datasheet again, and realized that, when in master receive mode, it seems to clock in 8 bits, store it in the data register, and immediately clock in the next 8 bits into the shift register. I need to tell the peripheral that the second transfer should not be acknowledged, but I miss that opportunity when interrupted.Can someone confirm this observation (meaning the STM32 I2C peripheral is not really suited for polling mode), or did I miss something?

author: mikerj
author_rank: Super Contributor
timestamp: May 26, 2017, 02:02:37 pm
content: As you have discovered the STM32 I2C module has some very specific timing requirements that are rather easy to break, so polling is not likely to be reliable if you have interrupts enabled.I would strongly recommend either using interrupt driven code, or better still using a DMA channel to read back the data. It feels like the I2C module was always designed for use via DMA, and it works very reliably in this case.

author: tatus1969
author_rank: Super Contributor
timestamp: May 26, 2017, 08:35:25 pm
content: Quote from: mikerj on May 26, 2017, 02:02:37 pmThanks for the confirmation, that means that I will go the DMA way.

author: tatus1969
author_rank: Super Contributor
timestamp: June 30, 2017, 07:22:30 pm
content: An update for others struggling with the same problems: I have switched to using ST's CPAL library,http://www.st.com/web/catalog/tools/FM147/CL1794/SC961/SS1743/PF258336. It worked right from the start in DMA mode, but it appears that even DMA does not work with one or two bytes to receive. The CPAL library switches to interrupt mode in this special case, and this again only works when the I2C interrupt has highest priority. Turns out that the STM32's I2C isprettycrappy. And glad to see that it was not me just not being able to read the datasheet correctly.

author: colorado.rob
author_rank: Frequent Contributor
timestamp: June 30, 2017, 07:47:49 pm
content: Why don't you just give I2C interrupt a higher priority so i can send the NAK when required? Anything that consumes a lot of time in an interrupt should be lower priority anyway.

author: tatus1969
author_rank: Super Contributor
timestamp: July 01, 2017, 05:45:52 pm
content: Quote from: colorado.rob on June 30, 2017, 07:47:49 pmthat's exactly what I am doing now

author: grantb5
author_rank: Regular Contributor
timestamp: December 13, 2020, 05:17:10 pm
content: Since you are the I2C master, you can bit-bang I2C in standard or fast speed. This will be completely interruptable since the state machine in the attached I2C peripheral will likely wait forever for transactions to complete. It's really not more code than baby-sitting the hardware internal I2C and can be placed on almost any microcontroller pins. Downside is the processing time lost, but you can make this a very low priority thing if needed.