Page 1 of 2

(solved) I2C device poses a challenge

Posted: Fri Jan 04, 2019 11:58 pm
by mzimmers
Hi all -

I'm trying to integrate a Sensirion SHT20 thermometer/hygrometer to the STM32. It uses a standard I2C interface, but with a wrinkle: after I send it a command to give me the temp (or humidity), I have to insert a delay before I attempt to read the reply. From the SHT20 datasheet (sorry this is kind of long):
There are two different operation modes to communicate with the sensor: Hold Master mode or No Hold Master mode. In the first case the SCL line is blocked (controlled by sensor) during measurement process while in the latter case the SCL line remains open for other communication while the sensor is processing the measurement. No hold master mode allows for processing other I2C communication tasks on a bus while the sensor is measuring. A communication sequence of the two modes is displayed in Figure 15 and Figure 16, respectively.
In the hold master mode, the SHT2x pulls down the SCL line while measuring to force the master into a wait state. By releasing the SCL line the sensor indicates that internal processing is terminated and that transmission may be continued.
In no hold master mode, the MCU has to poll for the termination of the internal processing of the sensor. This is done by sending a Start condition followed by the I2C header (1000’0001) as shown in Figure 16. If the internal processing is finished, the sensor acknowledges the poll of the MCU and data can be read by the MCU. If the measurement processing is not finished the sensor answers no ACK bit and the Start condition must be issued once more.
When using the no hold master mode it is recommended to include a wait period of 20 μs after the reception of the sensor’s ACK bit (bit 18 in Figure 16) and before the Stop condition.
I've verified that I'm talking with the device, by reading its user register (this doesn't require a delay). Regarding a temp read, here's what I've tried with no success:

Code: Select all

int Thermometer::thermRead(uint8_t cmd, uint8_t *data, uint16_t len)
{
    int rc;

    m_i2c_cmd = i2c_cmd_link_create();
    //ESP_LOGI(TAG, "i2cReadReg(): m_i2c_cmd is %x.", (unsigned) m_i2c_cmd);
    ESP_ERROR_CHECK(i2c_master_start(m_i2c_cmd));
    ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, SHT20_ADDR_WRITE, true));
    ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, cmd, true));
    ESP_ERROR_CHECK(i2c_master_start(m_i2c_cmd));
    ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, SHT20_ADDR_READ, true));
//    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, len, I2C_MASTER_LAST_NACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_NACK));
    ESP_ERROR_CHECK(i2c_master_stop(m_i2c_cmd));
    rc = (i2c_master_cmd_begin(I2C_PORT_NBR, m_i2c_cmd, I2C_WAITTIME_TICKS));
I get a TIMEOUT error on this.

Can anyone see a way to do this using the I2C library, or does it look like I'm going to have to bit-bang this one?

Thanks...

Re: I2C device poses a challenge

Posted: Sat Jan 05, 2019 1:43 am
by vonnieda
Just perform the write in it's entirety, delay, then perform the read. Two separate transactions.

Like this:

Code: Select all

int rc;

m_i2c_cmd = i2c_cmd_link_create();
ESP_ERROR_CHECK(i2c_master_start(m_i2c_cmd));
ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, SHT20_ADDR_WRITE, true));
ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, cmd, true));
ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_PORT_NBR, m_i2c_cmd, I2C_WAITTIME_TICKS));
ESP_ERROR_CHECK(i2c_cmd_link_delete(m_i2c_cmd));

vTaskDelay(pdMS_TO_TICKS(10));

m_i2c_cmd = i2c_cmd_link_create();
ESP_ERROR_CHECK(i2c_master_start(m_i2c_cmd));
ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, SHT20_ADDR_READ, true));
ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_ACK));
ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_ACK));
ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_NACK));
ESP_ERROR_CHECK(i2c_master_stop(m_i2c_cmd));
rc = (i2c_master_cmd_begin(I2C_PORT_NBR, m_i2c_cmd, I2C_WAITTIME_TICKS));
ESP_ERROR_CHECK(i2c_cmd_link_delete(m_i2c_cmd));
Jason

Re: I2C device poses a challenge

Posted: Sat Jan 05, 2019 3:38 pm
by mzimmers
Hi Jason -

I'm not sure that will work. I tried it (using a couple different delay values) and the second part still fails.

If I'm reading the datasheet correctly, I think the ESP32 has to be prepared to begin accepting data from the SHT20 as soon as the SCL line is released...agree? If this is true, we can't impose an arbitrary delay or we risk missing incoming data. (At least that's how I see it, but I'm hardly an I2C expert.)

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 1:39 am
by WiFive
mzimmers wrote:
Sat Jan 05, 2019 3:38 pm

If I'm reading the datasheet correctly, I think the ESP32 has to be prepared to begin accepting data from the SHT20 as soon as the SCL line is released...agree?
In the Hold Master mode, so don't use that mode. Otherwise you will have to use some of the patches floating around GitHub to support long clock stretching

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 2:21 am
by mzimmers
I don't understand what the ESP32 is supposed to do after the first stop condition. The SHT20 needs time to complete its measurement. Do I need to poll the GPIO that the SCL is on?

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 8:09 am
by WiFive
There is a maximum measurement time in the datasheet, so just vtaskdelay for at least that long. If you need to optimize you can consider other options.

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 11:09 am
by fivdiAtESP32
What I don't understand is how to wait 20 microseconds directly before the first stop condition.

You don't need to poll the GPIO that the SCL is on.

If I understand things correctly, after the first stop condition (which is the P after the 20 microsecond delay in the above diagram,) the idea is to attempt to read three bytes from the device in a loop until the three bytes can be read successfully. I say attempt here because it will keep failing while the device is still performing the measurement.

Another point is that it looks like there is a bug in the following code as each read will store the data read in the same byte:

Code: Select all

    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, (uint8_t *) data, 1, I2C_MASTER_NACK));
Something like this is needed:

Code: Select all

    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, &data[0], 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, &data[1], 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, &data[2], 1, I2C_MASTER_NACK));

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 3:37 pm
by mzimmers
When I broke the sequence into two parts (per Jason's suggestion), the first one succeeds. So, that 20us delay doesn't seem absolutely necessary.

I agree that a loop would be in order - keep sending the read byte until you get an ACK - but I don't know how to do that from the library.

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 4:41 pm
by fivdiAtESP32
Would something like the following not work?

Code: Select all

  m_i2c_cmd = i2c_cmd_link_create();
  ESP_ERROR_CHECK(i2c_master_start(m_i2c_cmd));
  ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, SHT20_ADDR_WRITE, true));
  ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, cmd, true));
  ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_PORT_NBR, m_i2c_cmd, I2C_WAITTIME_TICKS));
  ESP_ERROR_CHECK(i2c_cmd_link_delete(m_i2c_cmd));

  for (int i = 0; i <= 100; ++i) {
    vTaskDelay(pdMS_TO_TICKS(10));

    m_i2c_cmd = i2c_cmd_link_create();
    ESP_ERROR_CHECK(i2c_master_start(m_i2c_cmd));
    ESP_ERROR_CHECK(i2c_master_write_byte(m_i2c_cmd, SHT20_ADDR_READ, true));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, &data[0], 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, &data[1], 1, I2C_MASTER_ACK));
    ESP_ERROR_CHECK(i2c_master_read(m_i2c_cmd, &data[2], 1, I2C_MASTER_NACK));
    ESP_ERROR_CHECK(i2c_master_stop(m_i2c_cmd));
    rc = i2c_master_cmd_begin(I2C_PORT_NBR, m_i2c_cmd, I2C_WAITTIME_TICKS);
    i2c_cmd_link_delete(m_i2c_cmd);

    if (rc == ESP_OK) {
      printf("Excellent, it worked\n");
      break;
    } else if (rc == ESP_FAIL) {
      printf("Error sending command, device didn't ACK, lets try again\n");
    } else if (rc == ESP_ERR_TIMEOUT) {
      printf("Operation timeout because the bus is busy, lets try again\n");
      printf("It may be a better idea to bail out here\n");
    } else {
      printf("Some other error, bail out\n");
      ESP_ERROR_CHECK(rc);
    }
  }

  if (rc == ESP_OK) {
    printf("Excellent, it worked\n");
  } else {
    printf("It didn't work, bail out\n");
    ESP_ERROR_CHECK(rc);
  }
 

Re: I2C device poses a challenge

Posted: Sun Jan 06, 2019 5:07 pm
by mzimmers
I tried it; it just timed out each iteration.

I also tried separating the write of the read address from the reads; basically the same thing happened.