cancel
Showing results for 
Search instead for 
Did you mean: 

I2C Low level.

I want to implement a I2C low level driver on STM32L475 as I work with others chips.

int I2C_Wait(I2C_TypeDef * I2Cx)
{
      unsigned int timeout = 0;
      
      while(LL_I2C_IsActiveFlag_NACK(I2Cx) == 0)
      {
          if(++timeout > I2C_TIMEOUT)
          {
              LL_I2C_ClearFlag_NACK(I2Cx);
              return -1;
          }
      } 
      
      LL_I2C_ClearFlag_NACK(I2Cx);
      
      return 0;
}
 
void I2C_Read(I2C_TypeDef * I2Cx, uint8_t slave_addr, uint8_t reg_addr, uint8_t *data)
{
  
    /*----------first step - set register address to read from-----------*/
  
    uint8_t address = slave_addr & 0xFE; 
    
    LL_I2C_SetTransferRequest(I2Cx, LL_I2C_REQUEST_WRITE);
    
    // send start signal 
    LL_I2C_GenerateStartCondition(I2Cx);
     
    //send slave
    LL_I2C_TransmitData8(I2Cx, address);
    I2C_Wait(I2Cx);
    
    //send register address
    LL_I2C_TransmitData8(I2Cx, reg_addr);
    I2C_Wait(I2Cx);
    
    /*-----------second step - read from the written register address-----*/
    
     //slave address for read
    address = slave_addr | 0x01;
   
    //Repeated Start 
    LL_I2C_GenerateStartCondition(I2Cx);
    
    LL_I2C_TransmitData8(I2Cx, address);
    I2C_Wait(I2Cx);
    
    LL_I2C_SetTransferRequest(I2Cx, LL_I2C_REQUEST_READ);
    
    LL_I2C_AcknowledgeNextData(I2Cx, LL_I2C_ACK);
    
    *data = LL_I2C_ReceiveData8(I2Cx);
    
     for (delay = 0; delay < 1000; delay++)  ;  
}
 
void I2C_Write(I2C_TypeDef * I2Cx, uint8_t slave_addr, uint8_t reg_addr, uint8_t data)
{
    //for write operation R/Wn bit should be low
     uint8_t address = slave_addr & 0xFE;  
     
     LL_I2C_SetTransferRequest(I2Cx, LL_I2C_REQUEST_WRITE);
     
     // send start signal 
     LL_I2C_GenerateStartCondition(I2Cx);
     
    // send ID with W/R bit 
    LL_I2C_TransmitData8(I2Cx, address);
    I2C_Wait(I2Cx);
    
    // Write Register Address 
    LL_I2C_TransmitData8(I2Cx, reg_addr);
    I2C_Wait(I2Cx);
    
    LL_I2C_TransmitData8(I2Cx, data);
    I2C_Wait(I2Cx);
    
    LL_I2C_GenerateStopCondition(I2Cx);
    
    for (delay = 0; delay < 1000; delay++)  ;  
}
 

But it doesn't work. What could be a problem?

15 REPLIES 15

I did two snapshots - before LL_I2C_TransmitData8(I2Cx, address); and after. The only difference TXE bit.

0690X00000D9NIrQAN.png0690X00000D9NJVQA3.png

STOPF=1 and NACKF=1, what do you think is it OK?

> LL_I2C_TransmitData8(I2Cx, address);

I don't and won't use Cube, but I am pretty sure you are not supposed to send address in the same way as other data - it even goes into a different register. Read the I2C chapter in RM.

The I2C modules in STM32 are pretty complex and relatively fragile, you MUST follow the description, it's not upon you to invent how to use it.

JW

Piranha
Chief II

Waiting on NACKF is fundamentally wrong, as it happens only at the end of read data and on reading wrong device. Unfortunately I2C is not so simple and you can't implement it with a single flag to wait for everything. Read the RM0351 section 39.4.8 and look at figures 407 and 410.

Additionally for a final implementation I suggest moving to interrupt mode and processing not only NACKF, but also ARLO and BERR errors.

OK.

I put together the weird examples pieces, provided by ST

void Handle_I2C_Master(I2C_TypeDef * I2Cx, uint32_t slave_addr, uint8_t *tx_data, uint8_t *rx_data,  uint8_t size)
{
    uint32_t Timeout;
    i2c_idx = 0;
    
  LL_I2C_HandleTransfer(I2Cx, slave_addr, LL_I2C_ADDRSLAVE_7BIT, size,     LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);
 
  Timeout = 0; 
 
  while(!LL_I2C_IsActiveFlag_STOP(I2Cx))
  {
    if(LL_I2C_IsActiveFlag_TXIS(I2Cx))
    {
      LL_I2C_TransmitData8(I2Cx, (*tx_data++));
      Timeout = 0;
    }
 
    Timeout++;
    Delay_ms(1);
    
      if(Timeout >= I2C_SEND_TIMEOUT_TXIS_MS)
      {
      }
  }
 
    LL_I2C_ClearFlag_STOP(I2Cx);
  
    while (!i2c_data_ready)  ;
    
    memcpy (rx_data, i2c_data_buf, i2c_idx);
    i2c_data_ready = 0; 
    i2c_idx = 0;   
}
 
void I2C2_EV_IRQHandler(void)
{
  /* Check RXNE flag value in ISR register */
  if(LL_I2C_IsActiveFlag_RXNE(I2C2))
  {
       /* Call function Master Reception Callback */
      // Master_Reception_Callback();
      if (i2c_idx < 8)
      {
          i2c_data_buf[i2c_idx] =  LL_I2C_ReceiveData8(I2C2);
          i2c_idx++;
      }
  }
  /* Check STOP flag value in ISR register */
  else if(LL_I2C_IsActiveFlag_STOP(I2C2))
  {
    /* End of Transfer */
    LL_I2C_ClearFlag_STOP(I2C2);
 
    /* Call function Master Complete Callback */
    //Master_Complete_Callback();
    i2c_data_ready = 1;
    
  }
  else
  {
    /* Call Error function */
    //Error_Callback();
  }
}

And then

i2c_tx_data[0] = HTS221_WHO_AM_I_REG;
 Handle_I2C_Master(I2C2, 0xBE, i2c_tx_data, i2c_rx_data, 2  /*1*/ );

I stay in while(!LL_I2C_IsActiveFlag_STOP(I2Cx))

You clear a flag in interrupt and loop waiting for it in main?

JW

Danish1
Lead II

There's a complication with taking snapshots using the debugger, in that reading certain registers will change the state of the I2C peripheral.

I found the flowcharts and transfer bus diagrams in the reference manual to be the right thing to follow. So that's Section 39.4.8 I2C Master Mode with figures 407, 409, 411, 412 of RM0351 Rev 6.