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
Danish1
Lead II

There are lots of things that could be the problem.

Help us to help you by telling us what it does do.

Does the code get stuck somewhere? If so where.

Or does the code execute merrily but the I2C devices never do what they're asked / reply with sensible values?

Do you have an oscilloscope you could use to view the I2C waveforms? If so, what does it show?

You know it doesn't work on your L475 hardware but does work on other hardware. How strongly do you know it isn't a fault on your L475 board? What tests have you done and what results do you get?

And also tell us what other chips your code works on.

A specific comment is your use of delay loops. e.g. "for (delay = 0; delay < 1000; delay++) ;". They are bad. You know you need to put a delay in there because your code won't work without it, but who knows if the delay you've put there is long enough? Ok so 1000 works when you're debugging one chip, but as soon as you turn on optimisation or switch to a faster chip, that delay loop will be much faster or even optimized away altogether. And things stop working without explanation.

Scope shows some signals on the lines. But without a logic analyzer it's hard to tell what exactly the problem. Just wanna be sure the read/write procedures have no pitfalls.

Delay is just for debug. It's a single read/write right now.

Danish1
Lead II

Ok so you say there is a problem.

Are the I2C frequencies sensible? (Bit times should be at least 10 us for 100 kHz or 2.5 us for 400 kHz). I imagine 'L475 has options that clock I2C other than via APBClocks to allow you to change APB1/APB2 on the fly without having to redo all the peripherals. So you might have your frequencies all wrong.

Are the waveforms sufficiently square - are the pull-up resistor values good?

I seem to remember the L4xx part having quite complicated filtering options on its I2C inputs - are you sure the values in there are sensible?

And as you have a 'scope, a key diagnosis: Do you see the "ACK" pulse on SDA generated by the peripheral?

I'm ready to give up and use some library functions. But what's the options - HAL? - all HAL functions have  tickstart = HAL_GetTick(); inside - why should I set systick for I2C for Christ's sake! Low level? - gladly if I'd find some clear, non incomprehensible example.

Danish1
Lead II

My crystal ball is currently allocated to another project and some in this forum don't have access to one at all. So the only way we can help is by you answering questions that we ask.

This isn't an ego trip on my part. I choose to help because I want to learn too. I've had success writing my own code for I2C on L4xx, F4xx and F7xx (I don't like even calling the LL functions - although it does mean I have to understand the differences between peripherals better than if they were hidden by the LL wrappers). But you might have hit a problem that I haven't seen before, in which case I want to learn too.

Please specify your observed bit timings and say if you see the ACK pulse or not.

And where in your code things get stuck (or does the value you read back not match what you're expecting)?

As to examples, imho the only definitive source is the Reference Manual for your part. And you have to build the example from there.

Help us to help you,

Danish

I set a break point and go step by step

//send slave

LL_I2C_TransmitData8(I2Cx, address); //address = 0xBE

I2C_Wait(I2Cx);

at this point I see I2C2->TXDR = 0xBE

LL_I2C_TransmitData8(I2Cx, reg_addr); //reg_addr = 0x0F - HTS221_WHO_AM_I_REG

I2C_Wait(I2Cx);

I see I2C2->TXDR = 0xBE - it doesn't changed.

at this point I tend to ask the gurus what's going on.

I'm not sure but it seems like ACK bit is present - 9-th bit after 8 clock bits.

Danish1
Lead II

Ok we're getting somewhere.

So you're unable to write to I2C2->TXDR.

According to the Reference Manual, you can only write it while TXE in I2C2->ISR is 1.

This suggests to me an error in your I2C_Wait(). That should (presumably) wait until the previous action has completed, be it a read as far as RXNE or ACK received, or a write as far as TXE is set. I guess you need to test for both to be set for a general-purpose wait routine.

Oh..I see. TXE goes low and never returns to 1.

if I do - while ((I2Cx->ISR&I2C_ISR_TXE) == 0) - I stay here forever. Also flag BUSY is low, no reason not return to 1.

if I see clocks on SCL - the data should be clocked out from a shift register? isn't it?

What is the state of other flags?

JW