Author Topic: Problems with STM32F103 and I2C in polling mode  (Read 8120 times)

0 Members and 1 Guest are viewing this topic.

Offline tatus1969Topic starter

  • Super Contributor
  • ***
  • Posts: 1273
  • Country: de
  • Resistance is futile - We Are The Watt.
    • keenlab
Problems with STM32F103 and I2C in polling mode
« on: May 26, 2017, 10:40:19 am »
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: [Select]
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[50];

  RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, (FunctionalState)ENABLE );
 
  // Toggle clock line to release potentially locked slave
  GPIO_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 operation
  GPIO_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 lockup
  I2C_Cmd(I2C1, (FunctionalState)DISABLE);
  I2C_SoftwareResetCmd( I2C1, (FunctionalState)ENABLE );
  I2C_SoftwareResetCmd( I2C1, (FunctionalState)DISABLE );
 
  // Init I2C peripheral
  I2C_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?
« Last Edit: May 26, 2017, 10:46:09 am by tatus1969 »
We Are The Watt - Resistance Is Futile!
 

Offline mikerj

  • Super Contributor
  • ***
  • Posts: 3426
  • Country: gb
Re: Problems with STM32F103 and I2C in polling mode
« Reply #1 on: May 26, 2017, 02:02:37 pm »
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.
 

Offline tatus1969Topic starter

  • Super Contributor
  • ***
  • Posts: 1273
  • Country: de
  • Resistance is futile - We Are The Watt.
    • keenlab
Re: Problems with STM32F103 and I2C in polling mode
« Reply #2 on: May 26, 2017, 08:35:25 pm »
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.
Thanks for the confirmation, that means that I will go the DMA way.
We Are The Watt - Resistance Is Futile!
 

Offline tatus1969Topic starter

  • Super Contributor
  • ***
  • Posts: 1273
  • Country: de
  • Resistance is futile - We Are The Watt.
    • keenlab
Re: Problems with STM32F103 and I2C in polling mode
« Reply #3 on: June 30, 2017, 07:22:30 pm »
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 is pretty crappy. And glad to see that it was not me just not being able to read the datasheet correctly.
We Are The Watt - Resistance Is Futile!
 

Offline colorado.rob

  • Frequent Contributor
  • **
  • Posts: 426
  • Country: us
Re: Problems with STM32F103 and I2C in polling mode
« Reply #4 on: June 30, 2017, 07:47:49 pm »
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.
 

Offline tatus1969Topic starter

  • Super Contributor
  • ***
  • Posts: 1273
  • Country: de
  • Resistance is futile - We Are The Watt.
    • keenlab
Re: Problems with STM32F103 and I2C in polling mode
« Reply #5 on: July 01, 2017, 05:45:52 pm »
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.
that's exactly what I am doing now  :)
We Are The Watt - Resistance Is Futile!
 

Offline grantb5

  • Regular Contributor
  • *
  • Posts: 143
  • Country: ca
Re: Problems with STM32F103 and I2C in polling mode
« Reply #6 on: December 13, 2020, 05:17:10 pm »
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.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf