HTS221 Humidity and Temperature Sensor

Environmental Sensing in Rust

Daniel Gallagher
Mar 20, 2018 · 5 min read
Image for post
Image for post
The HTS221 is a tiny (2x2x0.9mm) MEMS environmental sensor


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

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).


Sensor-Level API

// 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

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

  • 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.
Image for post
Image for post
  • 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.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store