AnsweredAssumed Answered

STM8L151G6' I2C: simple and robust communication needed

Question asked by Alex T on Jun 20, 2017

Hello,

My apologize for poor English

I'm working on the "simple" (as I thought) task: reading thermo sensor's indication over I2C interface. The procedure is:

  • send command, 1 byte
  • read sensor's answer, 3 bytes

This first part of task works fine, until I try to manage its second part: make this communication 110% robust (make it work at ALL bus' conditions). I mean that 100% recovery from ANY state is needed.

The device is designed to work in very bad environment, frequent electrical noise, so the stress-test is to, for example, pull SDA line to VDD many times. The recovery procedure, in almost all cases, is to reset STM8's I2C peripheral, as I thought it should give me guaranteed switch to the known state. Later, it was expanded to do status registers reset sequence, data register readout and STOP condition generation first. But nope. It works, say, nine times of ten, but then stucks in some erroneous condition which I can't cure no matter what do I try. In that condition device's status registers (SR1,2,3) remain all zeroes forever. SDA and SCL lines are both at HI level, so I assume the master and the slave devices are idle. But there is no any change after the master generates START condition.

I've searched I2C related topics here, and found one probably useful hint in some of answers (for example handle I2C bus error ): generate 9 STOP bits to flush slave's receiving sequence. But, even IF it is the case, it should mean only that the slave doesn't acks master's request, NOT that the master itself can't generate START condition and set therefore SB bit in status. Am I right? But of course I will try this hint once get to devices tomorrow.

 

So the question remains: how to switch (100% guaranteed) I2C peripheral to the known state from any other?

 

Here's what I try to do in code (last revision):

void reset_peripheral()
{
     I2C1->SR2 = 0x00;
     (void)I2C1->SR1;
     I2C1->CR2 |= I2C_CR2_STOP; // STOP: stop generation
     /*10 nops delay here */
     (void)I2C1->DR; (void)I2C1->DR; // data register readout
     I2C1->CR2 = 0x00;
     I2C1->CR1 = 0x00; // clear PE and the entire control reg
     DISABLE_CLOCK();
     /* 100 nops delay here */
     init_i2c();
     ENABLE_CLOCK();
     I2C1->CR1 |= (1<<0); // PE (periph enable)
}

uint16_t _v2_request_temp()
{
     uint16_t S = 0xFFFF;
     uint8_t s2;
     uint16_t rval = 0;
     uint8_t cmdSent = 0;
     uint8_t buf[3], bufCtr = 0;

     ENABLE_CLOCK();
     I2C1->CR1 |= I2C_CR1_PE; // PE: peripheral enable
     I2C1->CR2 |= I2C_CR2_ACK; // ACK: acknowledge enable

     do
     {
          switch ( S )
          {
          case 0xFFFF: // just started
               I2C1->CR2 |= I2C_CR2_START; // START
          break;
          case I2C_EVENT_MASTER_MODE_SELECT:
               if ( cmdSent ) {
                    I2C1->DR = Si7050_addr | 0x01; // master-receiver
               } else {
                    I2C1->DR = Si7050_addr | 0x00; // master-transmitter
                    cmdSent = 1;
               }
          break;

          case I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED:
               I2C1->DR = 0xE3; // t-sensor command: start measure
          break;
          case I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED:
               bufCtr = 0;
          break;

          case I2C_EVENT_MASTER_BYTE_TRANSMITTING:
               /* ... */
          break;
          case I2C_EVENT_MASTER_BYTE_TRANSMITTED:
               I2C1->CR2 |= I2C_CR2_STOP;
               while( I2C1->CR2 & I2C_CR2_STOP ) {}
               I2C1->CR2 |= I2C_CR2_START; // START -> proceed to master-receiver
          break;

          case I2C_EVENT_MASTER_BYTE_RECEIVED | I2C_SR1_BTF: // both data and shift registers are full
               if ( bufCtr == 0 ) {
                    I2C1->CR2 &= ~I2C_CR2_ACK; // ACK: acknowledge disable
                    buf[ bufCtr++ ] = I2C1->DR;
               } else {
                    I2C1->CR2 |= I2C_CR2_STOP; // STOP: stop generation
                    buf[ bufCtr++ ] = I2C1->DR;
                    buf[ bufCtr++ ] = I2C1->DR;
                    uint8_t rcrc = buf[2]; // remote crc
                    uint8_t lcrc = calc_crc( buf, 2 ); // local crc
                    //!!!if ( rcrc == lcrc ) {
                    rval = ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]);
                    //}
               }
          break;

          // ERRORS
          case 0x0000: // this is the branch, which had to cure topic's problem, but doesn't...
               reset_peripheral();
          break;
          case I2C_SR3_BUSY << 8:
          case (I2C_SR3_BUSY << 8) | (I2C_SR3_TRA << 8) | (I2C_SR3_MSL << 8):
               I2C1->CR2 |= I2C_CR2_SWRST;
               asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
               I2C1->CR2 &= ~I2C_CR2_SWRST;
               rval = 0x0003;
          break;
          }

          asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
          s2 = I2C1->SR2; S = I2C1->SR1; S |= (uint16_t)I2C1->SR3 << 8;

     } while ( !s2 && rval == 0x0000 );

     if ( s2 ) {
          reset_peripheral();
     }

     return rval;
}

 

i2c stm8 i2c stuck

Outcomes