ESP32 (24) – I2C a pratical example with HTU21D sensor

by luca

In my previous tutorial I explained how to use the I2C driver included in the esp-idf framework to communicate with I2C devices. Today I’ll show you a pratical example: the use of a temperature/humidity sensor.

The sensor

For this tutorial I chose the HTU21D sensor by Te Connectivity. This sensor offers a good level of accuracy and it’s available, already soldered on a breakboard, for few euros:

htu21d-00a htu21d-00b


Sparkfun was the first manufacturer to sell a board based on this sensor… even if now this board is retired, other websites (for example Banggood) sell similar products.

To connect the sensor to your esp32 development board, first you have to choose which pins will be used for SDA and SCL signals. Besides these, you have also to connect VDD (3.3V) and GND:

htu21d-00d htu21d-00e

htu21d-00f htu21d-00g

Datasheet and commands

After having completed the physical connection between the devboard and the sensor, you have to understand how to write a program that communicates with it. The first thing to do is certainly to read the datasheet of the sensor (here in PDF format). Do not be scared if you realize that it contains 21 pages of technical details, in the following paragraphs I’ll explain what are the information you absolutely have to know!

On page 10, it starts the chapter about the communication protocol (COMMUNICATION PROTOCOL WITH HTU21D(F) SENSOR). Immediately you read that the sensor offers an I2C slave interface with address 0x40; just below you can also find the table with the supported commands:


The sensor offers two different modes when measuring temperature and humidity:


  • hold master
  • no hold master


In the first mode, the sensor blocks the clock signal (SCK) during measurement process: this means that the master can send the read command only when the measure is completed. In the second mode instead the master can operate on the bus (for example send commands to other devices) during the measurement process.

In no hold mode the master, after having sent the “trigger maesurement” command, must wait for the sensor to complete the measurement before reading the value. You can verify if the measuring process is complete sending to the sensor a read command and wait for the ACK: if you receive it, it means that the value is ready.

The sensor returns a raw value of 16bits (= 2 bytes). In addition to the value, it also returns a checksum byte; this byte allows the master to verify that no errors occurred when data was transmitted on the bus.

In the previous post you’ve already learned how to query a slave device; therefore the instructions to read the temperature value from a HTU21D sensor in no hold mode are:

// constants
#define HTU21D_ADDR			0x40
// send the command
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_write_byte(cmd, (HTU21D_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, TRIGGER_TEMP_MEASURE_NOHOLD, true);
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_RATE_MS);
// wait for the sensor (50ms)
vTaskDelay(50 / portTICK_RATE_MS);
// receive the answer
uint8_t msb, lsb, crc;
cmd = i2c_cmd_link_create();
i2c_master_write_byte(cmd, (HTU21D_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, &msb, 0x00);
i2c_master_read_byte(cmd, &lsb, 0x00);
i2c_master_read_byte(cmd, &crc, 0x01);
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_RATE_MS);

The code above waits 50ms before fetching the data; from the datasheet you can indeed read that the maximum measuring time is exactly 50ms for the temperature value at the maximum resolution:


To convert the raw value into the “real” temperature (in °C) or humidity (in %) you can use the formulas in the datasheet:

uint16_t raw_value = ((uint16_t) msb << 8) | (uint16_t) lsb;
float temperature = (raw_value * 175.72 / 65536.0) - 46.85;
float humidity = (raw_value * 125.0 / 65536.0) - 6.0;


The sensor offers 4 different resolution combinations for temperature and humidity:

  • humidity 12bit, temperature 14bit
  • humidity 8bit, temperature 12bit
  • humidity 10bit, temperature 13bit
  • humidity 11bit, temperature 11bit

You can select the resolution changing the value of a configuration register with the command write user register. Specifically, the registry bits to be modified are bit 0 and bit 7:



The algorithm for verifying the CRC value is explained in the datasheet.

The polynomial used is x^8 + x^5 + x^4 + 1, in binary:


First add to the polynomial as many zeroes as the bit length of the CRC:


The resulting value, in hexadecimal, is 0x98800.

Add, at the right of the value you want to verify, the CRC value received from the sensor as third byte:

uint32_t row = (uint32_t)value << 8;
row |= crc;

Then check the input bit above the leftmost divisor bit: if the bit is 1, the input value is XORed to the divisor. Finally, the divisor is shifted one bit right:

for (int i = 0 ; i < 16 ; i++) {
 if (row & (uint32_t)1 << (23 - i)) row ^= divisor;  divisor >>= 1;

If, at the end of the process, the row value equals zero, the CRC is verified.


I developed a component for the esp-idf framework that implements what explained in this post. The component is available on Github and the documentation about its use is published in a dedicated page.

Here’s a video about it:

[youtube id=”rroEG1nlxzI” width=”600″ height=”350″]

Related Posts


Alan Duncan Friday April 27th, 2018 - 10:46 AM

In the function htu21d_init(), you create an i2c_cmd_handle_t, but once you’ve verified a slave device on the bus, you do not call i2c_cmd_link_delete(cmd) to remove the link. Is this omission intentional?

luca Thursday May 3rd, 2018 - 01:32 PM

Hi Alan, that’s because the handler i2c_cmd_handle_t cmd is in scope of that function and therefore it’s automatically removed by the GC when the function ends. It’s anyway a good practice to delete the unused objects/pointers, thanks for the comment!

Pedro Lopes Thursday May 10th, 2018 - 07:38 PM

Hello Luca.

Both this tutorial and the previous one were incredibly useful for me but I was wondering if you could explain or give an example of how to configure the ESP32 as a slave device, rather than the master.

I’m trying to connect the ESP32 to a PSOC5 and i’m having a hard time understanding what I have to do in the loop function of the esp32 configuration in order to be able to receive the data being written by the master (the psoc).

Thank you


Leave a Reply to Pedro Lopes Cancel Reply

thirteen − 1 =