Точный датчик температуры и влажности на шине I2C. Модуль GY-21 и его аналоги (Si7021, SHT21) понимают те же команды, поэтому один скетч подходит им всем.
HTU21D отвечает по I2C на адресе 0x40, и поменять его нельзя. Регистров, как у BME280, здесь нет: датчик понимает несколько однобайтовых команд. После команды измерения (0xF3 — температура, 0xF5 — влажность) нужно подождать до 50 мс и прочитать три байта: старший и младший байт результата и контрольную сумму CRC-8.
Пересчёт из даташита: T = −46,85 + 175,72 · S / 65536 и RH = −6 + 125 · S / 65536, где два младших бита результата обнуляются. Есть и готовые библиотеки, но по такому скетчу видно, что происходит на шине.
#include <Wire.h>
// HTU21D (модуль GY-21) на I2C. Библиотека не нужна: у датчика всего
// несколько команд, и вся работа видна в скетче.
//
// Алгоритм один на оба измерения:
// 1. послать команду «измерь» (0xF3 — температура, 0xF5 — влажность);
// 2. подождать: измерение занимает до 50 мс, и раньше времени датчик
// не отвечает;
// 3. прочитать три байта: старший, младший и контрольную сумму CRC-8;
// 4. сверить сумму и пересчитать число по формуле из даташита.
const uint8_t HTU21D = 0x40;
const uint8_t CMD_TEMPERATURE = 0xF3;
const uint8_t CMD_HUMIDITY = 0xF5;
uint8_t crc8(uint8_t msb, uint8_t lsb) {
// Многочлен датчика x^8 + x^5 + x^4 + 1 (0x131), начальное значение 0.
uint16_t crc = 0;
uint8_t data[2] = {msb, lsb};
for (int i = 0; i < 2; i++) {
crc ^= data[i];
for (int bit = 0; bit < 8; bit++) {
crc = (crc & 0x80) ? ((crc << 1) ^ 0x131) : (crc << 1);
}
}
return crc & 0xFF;
}
// Возвращает false, если датчик не ответил или сумма не сошлась.
bool measure(uint8_t command, uint16_t &raw) {
Wire.beginTransmission(HTU21D);
Wire.write(command);
if (Wire.endTransmission() != 0) return false;
delay(60);
if (Wire.requestFrom((int)HTU21D, 3) != 3) return false;
uint8_t msb = Wire.read();
uint8_t lsb = Wire.read();
uint8_t crc = Wire.read();
if (crc8(msb, lsb) != crc) return false;
// Два младших бита — служебные, перед пересчётом их обнуляют.
raw = ((uint16_t)msb << 8) | (lsb & 0xFC);
return true;
}
void setup() {
Serial.begin(9600);
Wire.begin();
}
void loop() {
uint16_t rawT, rawH;
if (!measure(CMD_TEMPERATURE, rawT) || !measure(CMD_HUMIDITY, rawH)) {
Serial.println(F("датчик не ответил: проверьте A4, A5 и питание"));
delay(2000);
return;
}
float t = -46.85 + 175.72 * rawT / 65536.0;
float h = -6.0 + 125.0 * rawH / 65536.0;
h = constrain(h, 0.0, 100.0); // у границ формула может чуть выйти за 0..100
Serial.print("t:");
Serial.print(t, 2);
Serial.print(" h:");
Serial.println(h, 1);
delay(2000);
}
В железе
Сама микросхема HTU21D рассчитана на питание до 3,6 В. На готовых модулях типа GY-21 стоит стабилизатор и подтяжки шины, и их можно питать от 5 В, а на «голой» микросхеме — нельзя. Адрес один и менять его нельзя, поэтому два таких датчика на одной шине не работают без мультиплексора.
Частые ошибки
Чтение сразу после команды измерения: датчик занят и не отвечает (NACK). Подождите хотя бы 50 мс.
Контрольная сумма не проверяется: помеха на шине превращается в «температуру» в сотни градусов.
Два служебных бита результата не обнулены: ошибка на сотые доли, но в формулу они не входят.
Два датчика с адресом 0x40 на одной шине: ответ получится смешанным. Нужен второй канал I2C или мультиплексор.