HTS221 Humidity and Temperature Sensor

Environmental Sensing in Rust

Daniel Gallagher
5 min readMar 20, 2018
The HTS221 is a tiny (2x2x0.9mm) MEMS environmental sensor


ST Micro’s HTS221 is a “capacitive digital sensor for relative humidity and temperature” that can be used over either I²C or SPI. It is factory-calibrated, so there is no end-user calibration to incorporate into your application.

The driver provides a simple, high-level API to configure the chip once on startup, then read the temperature and humidity. It also provides type-safe access to configuration registers to support more complex applications (such as turning the chip off to save power, or running the integrated heating element to eliminate condensation).

Basic Usage

To use hts221, you need a implementation of embedded-hal; I’ve been using stm32f30x-hal, though you could use any concrete HAL that provides I²C support (specifically just Write and WriteRead support). There is a Builder that provides an easy way to configure the chip on creation, after which you can read simply the humidity and temperature from the sensor.

The relative humidity is reported from the chip at a resolution of 0.5%, and temperature is reported at a resolution of 0.125 °C, which is reflected in the x2 and x8 suffixes, respectively. That is, the results are fixed precision, where the exponent for humidity is -1, and the exponent for temperature is -3. For example, if humidity_x2 returns 97, then the relative humidity is 46.5%. And if temperature_x8 returns 171, then the temperature is 21.375 °C. These resolutions were chosen because they are the resolution of the calibration values that are stored on the chip. While these resolutions are significantly higher than the stated sensitivity of the chip (0.004 rH% and 0.016 °C, respectively), they are well within the stated accuracy (3.5 rH% and 0.5 °C).


The API is split into two modules: the primary module provides a high-level access to the sensor, and the device module provides low-level access to the device registers.

Sensor-Level API

Given an HTS221 value, you can read the relative humidity and temperature using humidity_x2 and temperature_x8. These calls are blocking:

// given hts221
let rel_humidity = hts221.humidity_x2() / 2;
let temperature = hts221.temperature_x8() / 8;

You can also access most registers from the HTS221 value.

To create an HTS221 value, you need to use the Builder to set up the chip. The builder handles the low-level details you will need to initialize the chip for simple applications. Once you have an HTS221, you can read the humidity and temperature directly. The builder reads and stores all of the calibration registers, so there is no need to read them after initialization.

let hts221 = hts221::Builder::new(i2c)
.powered_up() // default
.with_update_mode(hts221::UpdateMode::Block) // default
.with_data_ready_disabled() // default

The Builder provides an option for every field of every configuration register on the chip.

  • Number of temperature samples averaged. The chip will average a number of internal samples to produce one output sample. The more samples averaged, the more power the chip draws. with_avg_t sets this value. If with_avg_t is not called on the builder, this value is unchanged. This register is persistent across power loss. I would recommend setting this explicitly anyway.
  • Number of humidity samples averaged. with_avg_h sets this value. Similarly, if this is not called on the Builder, the value is unchanged.
  • Power-down. This bit is set by calling powered_up or powered_down on the Builder. By default, the chip is powered on.
  • Block data update mode. This bit is set by calling with_update_mode using either UpdateMode::Block or UpdateMode::Continuous. Block mode is the default. The humidity and temperature ADC values are 16-bits, which are stored in 2 separate 8-bit registers each. Block update mode ensures that the chip does not update the high byte after the low byte is read until the high byte is read, so the two bytes are always from the same sample.
  • Output data rate. This is set by calling with_data_rate on the builder. It defaults to one-shot mode.
  • Boot mode. This is set by calling with_boot or without_boot. Without is the default. Setting this bit resets the internal registers to the factory defaults.
  • Heating element. This is not exposed by the Builder.
  • One-shot enable. This is not exposed by the Builder.
  • Data-ready polarity. This is set by calling with_data_ready_polarity. By default, this value is not changed.
  • Data-ready output mode. with_data_ready_mode selects either Open-drain or push-pull mode on the output pin. By default, the value is not changed.
  • Data-ready interrupt enable. with_data_ready_enabled enables the external interrupt line (pin 3). with_data_ready_disabled disables it. By default, it is disabled.

Register-Level API

From an HTS221 object, you can access all of the read-write registers as well as some important read-only registers. Accessing any of the registers performs a blocking read to get the current register value. All of the read-write registers also provide a modify method (similar to a RegisterBlock created by svd2rust) that runs a function (that should modify the register value) then writes the value back to the chip for that register.

let cr2 = hts221.cr2()?;
cr2.modify(|w| w.set_heater_on())?;

Note that the register types are not as type-safe as those created by svd2rust, and that w above is the same type (even the same object) as cr2. So the following will compile, but will not turn the heater on:


You can access registers that are not available from the HTS221 struct in the same way that hts221 does internally, through the structs in the device module:

let h_out = hts221::device::HumidityOut::new(i2c)?;
let adc_value: i16 = h_out.value();
let calibration = hts221::device::Calibration::new(i2c)?;

The crate does not provide separate access to the high or low bytes of the humidity or temperature reading, or to any of the calibration registers, all of which are returned as part of the Calibration struct.

Future Directions

  • Increase the precision of the returned humidity. Within the operating range, we only have 8 bits of sensitivity on the humidity, though the chip provides up to 16 bits, depending on calibration values. The two examples I have on hand both support about 14 bits of precision. For temperature, the API currently supports 11 bits of precision within the operating range, which is sufficient for the two chips I have on hand, which only actually support about 10 bits of precision, based on their calibration values.
  • Add errors for saturation conditions. The driver currently silently clamps readings outside the operating range for both temperature and humidity.
  • Add SPI support. embedded-hal has traits that should support both blocking and non-blocking SPI communication. The board that my HTS221 is mounted on has VDD tied to CS, which puts the chip into I²C mode, so I am currently unable to test SPI.
  • Add non-blocking I²C support. This depends on support from embedded-hal.
  • Resolve the I²C bus ownership problem in a way that is compatible with other drivers.