HTS221 Humidity and Temperature Sensor
Environmental Sensing in Rust
HTS221
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).
API
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)
.with_avg_t(hts221::AvgT::Avg256)
.with_avg_h(hts221::AvgH::Avg512)
.powered_up() // default
.with_update_mode(hts221::UpdateMode::Block) // default
.with_data_rate(hts221::DataRate::Continuous1Hz)
.with_data_ready_disabled() // default
.build()?;
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. Ifwith_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
orpowered_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 eitherUpdateMode::Block
orUpdateMode::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
orwithout_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:
hts221.cr2()?.set_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.