HDC1080 – это цифровой датчик температуры и влажности воздуха, разработанный компанией Texas Instruments. Это очередной климатический сенсор, но он выгодно отличается от конкурентов тем, что сочетает в себе доступность, высокую точность, простоту эксплуатации и рекордно низкое энергопотребление, что делает его идеальным вариантом для точных устройств с батарейным питанием.

Содержание
- Общее описание
- Устройство и принцип работы
- Параметры и характеристики
- Плата (модуль) CJMCU-1080
- Режимы работы и настройка (примеры кода)
- Схема подключения к ESP32
- Программный код (скетч)
- Заключение
Общее описание
HDC1080 представляет собой специализированную интегральную схему (Application-Specific Integrated Circuit – ASIC) гигрометра и термометра. Данная модель относится к первому поколению климатических преобразователей серии HDC от компании Texas Instruments.
Микросхема включает в себя два ключевых сенсора:
– полупроводниковый емкостной полимерный гигрометр, который измеряет относительную влажность воздуха в диапазоне от 0 до 100 %, при этом гарантирует точность ± (2…4) %;
– встроенный кремниевый преобразователь температуры, обеспечивающий точность ±0,2 °C в диапазоне от +10 до +50 градусов Цельсия, что критически важно для корректной температурной компенсации показаний влажности.
Второе поколение датчиков серии HDC включает в себя такие модели, как 2010, 2080, 2021 и 2022. У них примерно одинаковая точность, но каждая новая модель включала в себя небольшие доработки и дополнения в функционале.
Третье поколение – HDC3020. Это премиальный сегмент, с максимальной точностью (±0,1 °C и ±0,2 %RH), высокой энергоэффективностью, а также дополнительными параметрами в некоторых функциях.
Ближайшими аналогами и конкурентами для 1080 являются SHT30(31) и Si7021.Если выбирать сенсоры на ступень выше, то можно рассмотреть модель SHT35 или из серии SHT4x.
Устройство и принцип работы
Принцип работы преобразователей рассматриваемого чипа основан на двух физических эффектах: адсорбция водяного пара полимерной пленкой (гигрометр) и изменение электронных свойств полупроводника от степени нагрева (термометр).
В основе прибора лежит единый кремниевый чип, на котором интегрированы два независимых чувствительных элемента и специализированная микросхема, которая обеспечивает функционирование всей системы. В даташите не описывается устройство чувствительных элементов. Однако, как правило, такие устройства строятся по примерно одинаковому принципу, о котором можно подробно прочитать в статье про комбинированный модуль BMP280 + AHT20.
Особенностью HDC1080 является наличие встроенного резистивного элемента на кристалле, который выполняет функцию нагревателя. Им можно программно управлять, чтобы производить подогрев гигрометра с целью испарения конденсата. Это позволяет повысить точность измерений влажности воздуха.
Дополнительно в чип встроен детектор уровня напряжения питания. Эта функция полезна в том случае, если устройство питается от батарейки или аккумулятора. Как только напряжение падает ниже 2,8 Вольта (порог фиксированный), в регистре конфигурации (0x02) устанавливает флаг BTST=1, который нужно отдельно читать. Таким образом, ведущему устройству не нужно дополнительно производить мониторинг напряжения на источнике.
На рисунке ниже представлена упрощённая блок-схема из официальной документации:

Микросхема включает в себя следующие основные компоненты:
– чувствительные элементы (RH и TEMPERATURE);
– входные цепи в виде мультиплексора, который согласует выходы аналоговых преобразователей со входом аналого-цифрового преобразователя (ADC);
– логический блок (Registers + Logic) предназначен для обработки данных и выполнения операций, которые поступают в виде команд от внешнего ведущего устройства (например, ESP32);
– аппаратный блок I2C-интерфейса, посредством которого осуществляется передача данных между логическим блоком чипа и внешним ведущим микроконтроллером;
– блок энергонезависимой памяти (One-Time Programmable – OTP). В память на заводе-изготовителе записываются уникальные калибровочные коэффициенты (Calibration Coefficients) для конкретного экземпляра микросхемы.
Параметры и характеристики

HDC1080 в составе платы (модуля) CJMCU-1080
Для экспериментов с климатическим сенсором воспользуемся популярной платой CJMCU-1080, фотография которой представлена ниже:

На плате уже есть подтягивающие резисторы для выводов SCL и SDA, а также керамический конденсатор для фильтрации питания, поэтому можно сразу производить подключение к отладочной плате микроконтроллера.
Ниже представлена таблица распиновки модуля CJMCU-1080:

Режимы работы и настройка
Микросхема HDC1080 имеет набор регистров, с помощью которых производится настройка сенсора и чтение данных с него:

Чтобы оперировать нагревателем и производить мониторинг напряжения на батарее, а также выполнять общую настройку чипа, применяется регистр конфигурации 0x02. Это 16-битный регистр, который управляет всем:

Если бит MODE установить 0, то сенсор будет измерять только либо влажность, либо температуру. Это определяется адресом регистра при запуске измерения.
Когда на микросхему подаётся питание, допускается отдельно не производить настройку, потому что по умолчанию используются заводские настройки (в конфигурационный регистр записано 0x1000), что соответствует максимальному разрешению при измерениях всех физических величин.
Далее предлагаю ознакомиться с примерами кода, которые наглядно демонстрируют настройку и функционирование микросхемы в различных режимах.
Режим “Измеряем только что-то одно”
Ниже представлен очень подробно прокомментированный код, который объясняет как работает каждая функция. Далее другие примеры кода будут без подробных комментариев.
#include <Wire.h>
// I2C адрес датчика HDC1080
#define HDC1080_ADDR 0x40
// Функция чтения 16-битных данных с датчика
// Возвращает 16-битное значение (2 байта) или 0xFFFF в случае ошибки
uint16_t readData() {
// Запрашиваем у устройства 2 байта данных
// Функция requestFrom(address, quantity) отправляет запрос на чтение
// address - I2C адрес устройства, quantity - количество байт для чтения
Wire.requestFrom(HDC1080_ADDR, 2);
// Проверяем, что запрошенные 2 байта доступны для чтения
// Wire.available() возвращает количество байт, готовых к чтению из буфера
if (Wire.available() >= 2) {
// Читаем первый байт (старший) и сдвигаем его влево на 8 бит
// Затем читаем второй байт (младший) и объединяем с помощью OR
// В результате получаем 16-битное значение: [старший_байт][младший_байт]
return (Wire.read() << 8) | Wire.read();
}
// Если данных недостаточно, возвращаем специальное значение-индикатор ошибки
return 0xFFFF; // Ошибка
}
// Функция записи значения в регистр датчика
// reg - адрес регистра (8 бит), value - 16-битное значение для записи
void writeRegister(uint8_t reg, uint16_t value) {
// Начинаем передачу данных на устройство с указанным адресом
// beginTransmission() подготавливает шину I2C к отправке
Wire.beginTransmission(HDC1080_ADDR);
// Отправляем адрес регистра (первый байт после адреса устройства)
// Это указатель, который определяет, в какой регистр будет производиться запись
Wire.write(reg);
// Отправляем старший байт (MSB) 16-битного значения
// Сдвиг вправо на 8 бит: value >> 8 = (value / 256)
Wire.write(value >> 8);
// Отправляем младший байт (LSB) 16-битного значения
// Операция & 0xFF оставляет только младшие 8 бит
Wire.write(value & 0xFF);
// Завершаем передачу и отправляем стоп-условие на шину I2C
// Это освобождает шину для других устройств
Wire.endTransmission();
}
// Функция чтения конфигурационного регистра (адрес 0x02)
// Возвращает текущее 16-битное значение конфигурации
uint16_t readConfig() {
// Начинаем передачу для записи указателя на нужный регистр
Wire.beginTransmission(HDC1080_ADDR);
// Отправляем адрес регистра конфигурации (0x02)
// Согласно даташиту (раздел 8.6.3), конфигурационный регистр имеет адрес 0x02
Wire.write(0x02);
// Завершаем передачу, устанавливая указатель на регистр 0x02
// Теперь датчик "знает", что следующий запрос на чтение будет из этого регистра
Wire.endTransmission();
// Читаем 16-битное значение из установленного регистра и возвращаем его
return readData();
}
// Функция настройки датчика в режим отдельных измерений
// Устанавливает бит MODE (бит 12) в 0, что означает:
// - Измеряем только температуру ИЛИ только влажность (не оба параметра последовательно)
// Согласно даташиту (раздел 8.6.3, бит 12 MODE)
void setSingleMode() {
// Читаем текущую конфигурацию, чтобы не изменить другие настройки (HEAT, TRES, HRES)
uint16_t config = readConfig();
// Сбрасываем бит MODE (бит 12) в 0
// 0x1000 в двоичном виде: 0001 0000 0000 0000 (бит 12 установлен в 1)
// Операция &= ~0x1000: инвертируем маску и выполняем побитовое И
// Это обнуляет только 12-й бит, остальные биты остаются без изменений
config &= ~0x1000;
// Записываем изменённую конфигурацию обратно в регистр 0x02
writeRegister(0x02, config);
// Ждём 15 миллисекунд для применения настроек
// Согласно даташиту (раздел 7.7), время запуска (Start-up time) может быть до 15 мс
delay(15);
}
// Функция измерения только температуры
// Возвращает температуру в градусах Цельсия или -999.0 в случае ошибки
float readTempOnly() {
// Запускаем измерение температуры
Wire.beginTransmission(HDC1080_ADDR);
// Записываем указатель на регистр температуры (0x00)
// Согласно даташиту (раздел 8.5.1.3, шаг 2):
// Запись в регистр 0x00 (температура) или 0x01 (влажность) запускает измерение
Wire.write(0x00);
// Завершаем передачу, что инициирует начало измерения температуры
Wire.endTransmission();
// Ожидаем завершения измерения
// Для 14-битного измерения температура: 6.35 мс (даташит, раздел 7.5, TEMP_CT)
// Для 14-битного измерения влажность: 6.50 мс
// Запас 20 мс достаточно для обоих измерений с любой резолюцией
delay(20);
// Читаем 16-битный результат измерения
uint16_t raw = readData();
// Проверяем на ошибку чтения (0xFFFF - специальное значение ошибки)
if (raw == 0xFFFF) return -999.0;
// Преобразуем сырые данные в температуру по формуле из даташита (раздел 8.6.1):
// T = (raw / 2^16) * 165 - 40
// raw - 16-битное значение (младшие 2 бита всегда 0, т.к. датчик выдает 14 бит)
// 2^16 = 65536 - максимальное значение 16-битного числа
// Диапазон температуры: от -40°C (при raw = 0) до +125°C (при raw = 65535)
return (raw / 65536.0) * 165.0 - 40.0;
}
// Функция измерения только влажности
// Возвращает относительную влажность в процентах или -999.0 в случае ошибки
float readHumOnly() {
// Запускаем измерение влажности
Wire.beginTransmission(HDC1080_ADDR);
// Записываем указатель на регистр влажности (0x01)
// Согласно даташиту (раздел 8.5.1.3): запись в регистр 0x01 запускает измерение влажности
Wire.write(0x01);
// Завершаем передачу, что инициирует начало измерения влажности
Wire.endTransmission();
// Ожидаем завершения измерения
// Для 14-битного измерения влажность: 6.50 мс (даташит, раздел 7.5, RH_CT)
// Запас 20 мс достаточно для любой резолюции
delay(20);
// Читаем 16-битный результат измерения
uint16_t raw = readData();
// Проверяем на ошибку чтения
if (raw == 0xFFFF) return -999.0;
// Преобразуем сырые данные в относительную влажность по формуле из даташита (раздел 8.6.2):
// RH = (raw / 2^16) * 100%
// raw - 16-битное значение (младшие 2 бита всегда 0)
// 2^16 = 65536 - максимальное значение 16-битного числа
// Диапазон влажности: от 0% (при raw = 0) до 100% (при raw = 65535)
return (raw / 65536.0) * 100.0;
}
// Функция инициализации, вызывается один раз при старте микроконтроллера
void setup() {
// Инициализируем последовательный порт на скорости 115200 бод
// Используется для вывода данных на монитор порта
Serial.begin(115200);
// Инициализируем шину I2C на пинах 21 (SDA) и 22 (SCL)
// Эти пины выбраны для конкретной платы (вероятно ESP32)
// Для стандартного Arduino Uno используются пины A4 (SDA) и A5 (SCL)
Wire.begin(21, 22);
// Небольшая задержка для стабилизации питания и I2C шины после включения
delay(100);
// Настраиваем датчик в режим отдельных измерений (MODE=0)
// Теперь измерения температуры и влажности будут запускаться отдельно
setSingleMode();
}
// Основной цикл программы, выполняется бесконечно после setup()
void loop() {
// Измерение температуры (вызов функции возвращает значение в градусах Цельсия)
float temp = readTempOnly();
// Измерение влажности (вызов функции возвращает значение в процентах)
float hum = readHumOnly();
// Выводим разделительную линию для наглядности вывода
Serial.println("--------------------");
// Выводим измеренную температуру
Serial.print("Температура: ");
Serial.print(temp);
Serial.println(" °C");
// Выводим измеренную влажность
Serial.print("Влажность: ");
Serial.print(hum);
Serial.println(" %");
// Пауза 1000 миллисекунд (1 секунда) между измерениями
// Согласно рекомендациям даташита (раздел 9.2.1):
// Максимальная частота измерений для минимизации самонагрева - 1 sps (измерение в секунду)
delay(1000);
}
Режим “Измеряем всё”
#include <Wire.h>
#define HDC1080_ADDR 0x40
uint16_t readData() {
Wire.requestFrom(HDC1080_ADDR, 2);
if (Wire.available() >= 2) {
return (Wire.read() << 8) | Wire.read();
}
return 0xFFFF;
}
void writeRegister(uint8_t reg, uint16_t value) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(reg);
Wire.write(value >> 8);
Wire.write(value & 0xFF);
Wire.endTransmission();
}
uint16_t readConfig() {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x02);
Wire.endTransmission();
return readData();
}
// Настройка: режим "оба измерения" (MODE=1) - максимальная точность
void setBothMode() {
uint16_t config = readConfig();
config |= 0x1000; // Устанавливаем бит 12 (MODE=1)
writeRegister(0x02, config);
delay(15);
}
// Измеряем температуру и влажность вместе (один запуск)
void readBoth(float &temp, float &hum) {
// Запускаем измерение (достаточно указать 0x00)
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x00);
Wire.endTransmission();
delay(15); // Ждём оба измерения (темп + влажн)
Wire.requestFrom(HDC1080_ADDR, 4);
uint16_t rawTemp = (Wire.read() << 8) | Wire.read(); // Читаем температуру
uint16_t rawHum = (Wire.read() << 8) | Wire.read(); // Читаем влажность
// Преобразуем
temp = (rawTemp / 65536.0) * 165.0 - 40.0;
hum = (rawHum / 65536.0) * 100.0;
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
delay(100);
setBothMode(); // Настройка на максимальную точность + оба измерения
}
void loop() {
float temp, hum;
readBoth(temp, hum); // Один вызов — оба значения
Serial.println("--------------------");
Serial.print("Температура: ");
Serial.print(temp);
Serial.println(" °C");
Serial.print("Влажность: ");
Serial.print(hum);
Serial.println(" %");
delay(2000);
}
Чтение ID производителя и ID модели
#include <Wire.h>
#define HDC1080_ADDR 0x40
uint16_t readReg(uint8_t r) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(r);
Wire.endTransmission();
Wire.requestFrom(HDC1080_ADDR, 2);
return (Wire.read() << 8) | Wire.read();
}
void setup() {
Serial.begin(115200);
Wire.begin();
delay(100);
uint16_t manuf = readReg(0xFE);
uint16_t dev = readReg(0xFF);
Serial.print("Manufacturer ID: 0x");
Serial.println(manuf, HEX);
Serial.print("Device ID: 0x");
Serial.println(dev, HEX);
}
void loop() {}
Чтение серийного номера
#include <Wire.h>
#define HDC1080_ADDR 0x40
uint16_t readRegister(uint8_t reg) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(HDC1080_ADDR, 2);
return (Wire.read() << 8) | Wire.read();
}
void setup() {
Serial.begin(115200);
Wire.begin();
delay(100);
// Читаем три регистра серийного номера
uint16_t ser0 = readRegister(0xFB); // биты [40:25]
uint16_t ser1 = readRegister(0xFC); // биты [24:9]
uint16_t ser2 = readRegister(0xFD); // биты [8:0] (в битах 15:7)
// Собираем 40 бит в uint64_t
uint64_t serial = ((uint64_t)ser0 << 24) | ((uint64_t)ser1 << 8) | (ser2 >> 7);
// Вывод в монитор порта
Serial.print("Серийный номер (HEX): 0x");
Serial.println(serial, HEX);
Serial.print("Серийный номер (DEC): ");
Serial.println(serial);
}
void loop() {}
Проверка термометра встроенным нагревателем
#include <Wire.h>
#define HDC1080_ADDR 0x40
// Чтение данных БЕЗ записи указателя
uint16_t readData() {
Wire.requestFrom(HDC1080_ADDR, 2);
if (Wire.available() >= 2) {
return (Wire.read() << 8) | Wire.read();
}
return 0xFFFF;
}
void writeRegister(uint8_t reg, uint16_t value) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(reg);
Wire.write(value >> 8);
Wire.write(value & 0xFF);
Wire.endTransmission();
}
// Чтение конфигурации (с указателем)
uint16_t readConfig() {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x02);
Wire.endTransmission();
return readData();
}
// Функция измерения температуры
float readTemperature() {
// Запускаем измерение
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x00);
Wire.endTransmission();
delay(20);
// Читаем результат
uint16_t raw = readData();
if (raw == 0xFFFF) return -999.0;
return (raw / 65536.0) * 165.0 - 40.0;
}
void heaterOn() {
uint16_t config = readConfig();
config |= 0x2000;
writeRegister(0x02, config);
Serial.println("Нагреватель ВКЛЮЧЕН");
}
void heaterOff() {
uint16_t config = readConfig();
config &= ~0x2000;
writeRegister(0x02, config);
Serial.println("Нагреватель ОТКЛЮЧЕН");
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
delay(100);
// Устанавливаем MODE=0 (одиночные измерения)
uint16_t config = readConfig();
config &= ~0x1000; // MODE=0
writeRegister(0x02, config);
delay(15);
Serial.println("Проверка нагревателя HDC1080");
// Измеряем температуру без нагрева
heaterOff();
delay(100);
float tempNormal = readTemperature();
if (tempNormal != -999.0) {
Serial.print("Температура БЕЗ нагрева: ");
Serial.print(tempNormal);
Serial.println(" °C");
}
// ВКЛючаем нагреватель
heaterOn();
// Делаем несколько измерений для прогрева
for(int i = 0; i < 5; i++) {
readTemperature();
delay(50);
Serial.print(".");
}
Serial.println();
// Измеряем температуру с нагревом
float tempHeated = readTemperature();
if (tempHeated != -999.0) {
Serial.print("Температура С нагревателем: ");
Serial.print(tempHeated);
Serial.println(" °C");
Serial.print("Разница: ");
Serial.print(tempHeated - tempNormal);
Serial.println(" °C");
}
// ОТКЛючаем нагреватель
heaterOff();
}
void loop() {}

Автоматическая сушка гигрометра
#include <Wire.h>
#define HDC1080_ADDR 0x40 // I2C адрес датчика (7 бит)
// Чтение 2 байт данных без записи указателя (после запуска измерения)
uint16_t readData() {
Wire.requestFrom(HDC1080_ADDR, 2); // Запрашиваем 2 байта
return (Wire.available() >= 2) ? // Если данные получены
(Wire.read() << 8) | Wire.read() : 0xFFFF; // Собираем 16 бит, иначе ошибка
}
// Запись 16-битного значения в регистр
void writeReg(uint8_t reg, uint16_t val) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(reg); // Указываем регистр
Wire.write(val >> 8); // Старший байт
Wire.write(val & 0xFF); // Младший байт
Wire.endTransmission();
}
// Чтение регистра (с записью указателя)
uint16_t readReg(uint8_t reg) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(reg); // Устанавливаем указатель на регистр
Wire.endTransmission();
return readData(); // Читаем данные
}
// Измерение температуры и влажности (режим MODE=1)
void readBoth(float &t, float &h) {
// Запуск измерения (достаточно указать регистр 0x00)
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x00); // Указатель на регистр температуры
Wire.endTransmission();
delay(15); // Ждём оба измерения (6.35 + 6.5 мс + запас)
Wire.requestFrom(HDC1080_ADDR, 4);
uint16_t rt = (Wire.read() << 8) | Wire.read(); // Читаем температуру
uint16_t rh = (Wire.read() << 8) | Wire.read(); // Читаем влажность
// Преобразование по формулам из даташита
t = (rt / 65536.0) * 165.0 - 40.0; // Диапазон: -40°C ... +125°C
h = (rh / 65536.0) * 100.0; // Диапазон: 0% ... 100%
}
// Управление нагревателем (бит 13 регистра конфигурации)
void heater(bool on) {
uint16_t cfg = readReg(0x02); // Читаем текущую конфигурацию
on ? (cfg |= 0x2000) : (cfg &= ~0x2000); // Устанавливаем или сбрасываем бит 13
writeReg(0x02, cfg); // Записываем обратно
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA=21, SCL=22 для ESP32
// Настройка: MODE=1 (температура + влажность последовательно), 14 бит (макс. точность)
writeReg(0x02, readReg(0x02) | 0x1000); // Устанавливаем бит 12 (MODE=1)
delay(15); // Время применения настроек
}
void loop() {
static unsigned long lastDry = 0; // Время последней сушки (мс)
float t, h;
readBoth(t, h); // Получаем показания
// Проверка валидности (диапазоны из даташита)
if (t < -40 || t > 125 || h < 0 || h > 100) {
Serial.println("Sensor error");
delay(5000);
return;
}
// Вывод в Serial с одним знаком после запятой
Serial.print(t, 1); Serial.print("°C, ");
Serial.print(h, 1); Serial.println("%");
// Автосушка: если влажность >80% и прошёл час с последней сушки
if (h > 80 && millis() - lastDry > 3600000) {
Serial.print("Drying");
heater(true); // Включаем нагреватель
for (int i = 0; i < 10; i++) { // 10 быстрых измерений для прогрева
readBoth(t, h);
delay(30);
Serial.print(".");
}
heater(false); // Выключаем нагреватель
Serial.println(" Done");
lastDry = millis(); // Обновляем время последней сушки
}
delay(5000); // Пауза 5 секунд между измерениями
}
Проверка уровня напряжения на батареи
#include <Wire.h>
#define HDC1080_ADDR 0x40
// Чтение данных БЕЗ записи указателя
uint16_t readData() {
Wire.requestFrom(HDC1080_ADDR, 2);
if (Wire.available() >= 2) {
return (Wire.read() << 8) | Wire.read();
}
return 0xFFFF;
}
void writeRegister(uint8_t reg, uint16_t value) {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(reg);
Wire.write(value >> 8);
Wire.write(value & 0xFF);
Wire.endTransmission();
}
// Чтение конфигурации (с записью указателя)
uint16_t readConfig() {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x02);
Wire.endTransmission();
return readData();
}
// Запуск измерения и чтение температуры
float readTemperature() {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x00); // Запускаем измерение
Wire.endTransmission();
delay(15); // Ждём конверсии
uint16_t raw = readData(); // Читаем БЕЗ повторной записи!
if (raw == 0xFFFF) return -999.0;
return (raw / 65536.0) * 165.0 - 40.0;
}
// Запуск измерения и чтение влажности
float readHumidity() {
Wire.beginTransmission(HDC1080_ADDR);
Wire.write(0x01); // Запускаем измерение
Wire.endTransmission();
delay(15); // Ждём конверсии
uint16_t raw = readData(); // Читаем БЕЗ повторной записи!
if (raw == 0xFFFF) return -999.0;
return (raw / 65536.0) * 100.0;
}
// Проверка статуса батареи
bool isBatteryLow() {
uint16_t config = readConfig();
return (config & 0x0800) != 0;
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
delay(100);
// Устанавливаем MODE=0 (одиночные измерения)
uint16_t config = readConfig();
config &= ~0x1000;
writeRegister(0x02, config);
delay(15);
Serial.println("Мониторинг уровня заряда батареи HDC1080");
}
void loop() {
float temp = readTemperature();
float hum = readHumidity();
bool lowBattery = isBatteryLow();
Serial.println("--------------------");
Serial.print("Температура: ");
Serial.print(temp);
Serial.println(" °C");
Serial.print("Влажность: ");
Serial.print(hum);
Serial.println(" %");
if(lowBattery) {
Serial.println("Низкий уровень батареи (< 2,8 В)");
} else {
Serial.println("Норма (> 2,8 В)");
}
delay(5000);
}

Схема подключения HDC1080 к ESP32
Для проведения экспериментов воспользуемся отладочной платой NodeMCU-32S (38 pin), построенной на базе модуля ESP-WROOM-32.
Отображать показания будем на OLED-дисплее (контроллер SH1106) с диагональю 1,3 дюйма и разрешением 132×64 пикселей. Обратите внимание, что этот дисплей частично не совместим с библиотекой для популярного OLED-дисплея 0,96” на базе контроллера SSD1306.
В качестве дополнительного проверочного измерительного канала воспользуемся ближайшим конкурентом – SHT30, который имеет аналогичные характеристики по точности.
Ниже представлена таблица подключений преобразователей и дисплея к отладочной плате.

Также для наглядности приведена схема подключения климатических модулей и экрана к отладочной плате (для увеличения – кликнуть по картинке):

Программный код (скетч)
Проект создавался в среде программирования Arduino IDE 2. Если нужно настроить среду под ESP32, то подробная инструкция представлена в статье про диктофон на ESP32 с цифровым микрофоном INMP411.
Для взаимодействия с OLED-дисплеем на базе контроллера SH1106 воспользуемся библиотекой Adafruit_SH110x.
Чтобы обеспечить взаимодействие с комбинированным сенсором, можно применить популярную библиотеку Adafruit_HDC1000_Library или более простую ClosedCube_HDC1080. Но мы сами составим все нужные функции, используя методы из библиотеки Wire для I2C-интерфейса, чтобы лучше понять принципы настройки датчика и каким образом можно более гибко организовывать взаимодействие с ним. В разделе «Режимы работы и настройка» есть подробно описанные примеры.
Для чтения данных с чипа SHT30 можно воспользоваться удобной и надёжной библиотекой Adafruit_SHT31, однако мы также будем самостоятельно составлять функции.
Ниже представлен подробно прокомментированный полный листинг кода:
#include <Wire.h>
#include <Adafruit_GFX.h>
// Библиотека для управления OLED дисплеем на контроллере SH1106/SH1107
#include <Adafruit_SH110X.h>
// Определение адресов устройств на шине I2C
#define HDC1080_ADDR 0x40 // I2C адрес датчика температуры и влажности HDC1080 (7-битный адрес 1000000)
#define SHT30_ADDR 0x44 // I2C адрес датчика температуры и влажности SHT30
#define OLED_ADDR 0x3C // I2C адрес OLED дисплея (типичный адрес для дисплеев на SH1106)
// Определение размеров экрана и пинов I2C для ESP32
#define SCREEN_WIDTH 128 // в datasheet указано 132x64, но в библиотеке принято 128x64
#define SCREEN_HEIGHT 64
#define I2C_SDA 21
#define I2C_SCL 22
// Создание объекта для работы с OLED дисплеем
// Adafruit_SH1106G - класс для дисплеев SH1106, передаём размеры экрана и указатель на объект Wire
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
// Функция проверки наличия датчика HDC1080 на шине I2C
bool initHDC1080() {
// Начинаем передачу данных на устройство с адресом HDC1080_ADDR
Wire.beginTransmission(HDC1080_ADDR);
// Завершаем передачу и возвращаем true, если устройство ответило (код возврата 0)
return (Wire.endTransmission() == 0);
}
// Функция настройки конфигурационного регистра датчика HDC1080
void configureHDC1080() {
// Начинаем передачу данных на датчик HDC1080
Wire.beginTransmission(HDC1080_ADDR);
// Отправляем адрес регистра, который будем изменять (0x02 - Configuration Register)
Wire.write(0x02);
// Отправляем старший байт конфигурации (0x10):
// Бит 15 (RST) = 0 - без сброса
// Бит 14 = 0 - зарезервирован, должен быть 0
// Бит 13 (HEAT) = 0 - нагреватель отключён
// Бит 12 (MODE) = 1 - режим последовательного измерения температуры и влажности
// Бит 11 (BTST) = 0 - чтение статуса батареи, но здесь мы его устанавливаем, это не имеет смысла, возможно ошибка
// По даташиту: MODE=1, остальные биты в этом байте: HEAT=0, RST=0, резерв=0 -> получается 0b00010000 = 0x10
Wire.write(0x10);
// Отправляем младший байт конфигурации (0x00):
// Бит 10 (TRES) = 0 - 14-битное разрешение температуры
// Биты 9-8 (HRES) = 00 - 14-битное разрешение влажности
// Биты 7-0 = 0 - зарезервированы, должны быть 0
Wire.write(0x00);
// Завершаем передачу данных на шине I2C
Wire.endTransmission();
// Ожидаем 15 мс, чтобы датчик применил настройки (согласно даташиту время старта до 15 мс)
delay(15);
}
// Функция чтения температуры и влажности с датчика HDC1080
// Передаём по ссылкам переменные, в которые будут записаны результаты
void readHDC1080(float &temp, float &hum) {
// Начинаем передачу на HDC1080
Wire.beginTransmission(HDC1080_ADDR);
// Отправляем адрес регистра 0x00 (Temperature Register) - это триггер начала измерения
Wire.write(0x00);
// Завершаем передачу. Это действие запускает процесс измерения (согласно даташиту)
Wire.endTransmission();
// Ожидаем завершения измерения. Для 14-битного режима время конверсии ~6.5 мс,
// даём запас 15 мс для надёжности
delay(15);
// Запрашиваем 2 байта (16 бит) данных температуры от HDC1080
// а также запрашиваем следующие 2 байта (значение влажности)
Wire.requestFrom(HDC1080_ADDR, 4);
// Читаем старший байт, сдвигаем его на 8 бит влево и объединяем с младшим байтом,
// получая 16-битное значение температуры (формат: 14 значащих бит, 2 младших всегда 0)
// и 16-битное значение влажности
uint16_t rawTemp = (Wire.read() << 8) | Wire.read();
uint16_t rawHum = (Wire.read() << 8) | Wire.read();
// Преобразуем сырое значение температуры в градусы Цельсия по формуле из даташита:
// Temperature = (rawTemp / 2^16) * 165 - 40
// rawTemp - это 16-битное число (0-65535), делим на 65536.0 для получения доли от диапазона
temp = (rawTemp / 65536.0) * 165.0 - 40.0;
// Преобразуем сырое значение влажности в проценты по формуле из даташита:
// Relative Humidity = (rawHum / 2^16) * 100%
hum = (rawHum / 65536.0) * 100.0;
}
// Функция проверки наличия датчика SHT30 на шине I2C
bool initSHT30() {
// Начинаем передачу на адрес SHT30_ADDR
Wire.beginTransmission(SHT30_ADDR);
// Завершаем передачу, возвращаем true при успешном обнаружении устройства
return (Wire.endTransmission() == 0);
}
// Функция чтения температуры и влажности с датчика SHT30
void readSHT30(float &temp, float &hum) {
// Команда измерения с высокой точностью (clock stretching mode)
// По даташиту SHT30: команда 0x2C06 запускает измерение с высокой повторяемостью
Wire.beginTransmission(SHT30_ADDR);
Wire.write(0x2C); // Отправляем первый байт команды
Wire.write(0x06); // Отправляем второй байт команды
Wire.endTransmission();
// Ожидаем завершения измерения. Время конверсии для высокого разрешения ~15 мс
delay(15);
// Запрашиваем 6 байт данных: 2 байта температуры + CRC, 2 байта влажности + CRC
Wire.requestFrom(SHT30_ADDR, 6);
// Проверяем, что в буфере есть как минимум 6 байт
if (Wire.available() >= 6) {
// Читаем старший байт температуры, сдвигаем и добавляем младший
uint16_t rawTemp = (Wire.read() << 8) | Wire.read();
Wire.read(); // Пропускаем байт контрольной суммы (CRC) температуры
// Аналогично читаем значение влажности
uint16_t rawHum = (Wire.read() << 8) | Wire.read();
Wire.read(); // Пропускаем байт CRC влажности
// Преобразуем сырое значение температуры в градусы Цельсия по формуле из даташита SHT30:
// Temperature = -45 + 175 * (rawTemp / 65535)
// rawTemp - это 16-битное число, для SHT30 используется диапазон 0-65535
temp = -45.0 + (175.0 * rawTemp / 65535.0);
// Преобразуем сырое значение влажности в проценты:
// Humidity = 100 * (rawHum / 65535)
hum = 100.0 * rawHum / 65535.0;
}
}
// Функция отображения данных от двух датчиков на OLED экране
// Принимает температуру и влажность от HDC1080 (t1, h1) и от SHT30 (t2, h2)
void displayData(float t1, float h1, float t2, float h2) {
// Очищаем буфер дисплея (стираем всё предыдущее изображение)
display.clearDisplay();
// Устанавливаем размер шрифта 1 (базовый размер, 6x8 пикселей)
display.setTextSize(1);
// Устанавливаем цвет текста: белый (для монохромного дисплея это включённые пиксели)
display.setTextColor(SH110X_WHITE);
// Строка 1: заголовок
display.setCursor(0, 0); // Устанавливаем курсор в координаты X=0, Y=0 (верхний левый угол)
display.println("SPRYTRON.RU - Sensors"); // Выводим строку и переводим курсор на новую строку
display.drawLine(0, 10, 127, 10, SH110X_WHITE); // Рисуем горизонтальную линию от (0,10) до (127,10)
// Строка 2: температура HDC1080
display.setCursor(0, 12); // Курсор на X=0, Y=12 (ниже линии)
display.print("Temp_HDC1080: "); // Выводим текст без перевода строки
display.print(t1, 1); // Выводим значение температуры с 1 знаком после запятой
display.print((char)247); // Выводим символ градуса (код 247 в таблице ASCII)
display.println("C"); // Выводим букву C и переводим строку
// Строка 3: влажность HDC1080
display.setCursor(0, 22);
display.print("Humi_HDC1080: ");
display.print(h1, 1);
display.println("%");
// Строка 4: температура SHT30
display.setCursor(0, 32);
display.print("Temp_SHT30: ");
display.print(t2, 1);
display.print((char)247);
display.println("C");
// Строка 5: влажность SHT30
display.setCursor(0, 42);
display.print("Humi_SHT30: ");
display.print(h2, 1);
display.println("%");
// Отправляем содержимое буфера на физический дисплей
display.display();
}
// Функция setup() выполняется один раз при запуске или сбросе микроконтроллера
void setup() {
// Инициализируем последовательный порт для вывода отладочной информации
Serial.begin(115200); // Устанавливаем скорость обмена 115200 бод
delay(100); // Небольшая задержка для стабилизации порта
// Инициализируем шину I2C с указанными пинами SDA и SCL
Wire.begin(I2C_SDA, I2C_SCL);
Serial.println("I2C шина OK"); // Выводим сообщение об успешной инициализации I2C
// Инициализация OLED дисплея
delay(250); // Небольшая задержка перед инициализацией дисплея
// Вызываем метод begin для инициализации дисплея: передаём адрес и флаг сброса
if (!display.begin(OLED_ADDR, true)) {
Serial.println("OLED не найден!"); // Если инициализация не удалась, выводим ошибку
while (1); // Останавливаем выполнение программы в бесконечном цикле
}
Serial.println("OLED инициализирован"); // Сообщение об успешной инициализации дисплея
// Вывод приветственного сообщения на дисплей
display.clearDisplay(); // Очищаем экран
display.setTextSize(1); // Размер шрифта 1
display.setCursor(20, 20); // Курсор на X=20, Y=20
display.println("ESP32 + Sensors"); // Выводим первую строку
display.setCursor(33, 35); // Курсор на X=33, Y=35
display.println("SPRYTRON.RU"); // Выводим вторую строку
display.display(); // Обновляем экран
delay(3000); // Показываем заставку 3 секунды
// Проверка наличия датчика HDC1080
if (!initHDC1080()) { // Если датчик не отвечает
Serial.println("HDC1080 не найден!"); // Выводим сообщение об ошибке
} else {
configureHDC1080(); // Если датчик найден, настраиваем его
Serial.println("HDC1080 OK"); // Выводим сообщение об успехе
}
// Проверка наличия датчика SHT30
if (!initSHT30()) {
Serial.println("SHT30 не найден!");
} else {
Serial.println("SHT30 OK");
}
delay(1000); // Финальная задержка перед входом в основной цикл
}
// Функция loop() выполняется циклически бесконечно после завершения setup()
void loop() {
// Объявляем переменные для хранения показаний датчиков
float t1 = 0, h1 = 0, t2 = 0, h2 = 0;
// Чтение данных с HDC1080, только если датчик обнаружен на шине
if (initHDC1080()) { // Проверяем наличие датчика перед чтением
readHDC1080(t1, h1); // Читаем температуру и влажность в переменные t1, h1
}
// Чтение данных с SHT30, только если датчик обнаружен
if (initSHT30()) {
readSHT30(t2, h2);
}
// Вывод данных в монитор последовательного порта (Serial)
Serial.println("-------------------------"); // Разделитель
Serial.print("HDC1080: "); // Выводим метку для HDC1080
Serial.print(t1, 1); Serial.print("°C, "); // Температура с одним знаком после запятой
Serial.print(h1, 1); Serial.println("%"); // Влажность
Serial.print("SHT30: "); // Выводим метку для SHT30
Serial.print(t2, 1); Serial.print("°C, ");
Serial.print(h2, 1); Serial.println("%");
// Вывод данных на OLED дисплей
displayData(t1, h1, t2, h2);
// Задержка перед следующим циклом измерений (2 секунды)
delay(2000);
}
Для достоверности измерений рекомендуется выдержать всю измерительную систему включенной как минимум в течение 1 часа в нормальных условиях (в жилом отапливаемом помещении).
Велика вероятность, что при первом включении между датчиками будет очень большая разница в показаниях влажности, как это было у меня:

Показания отличаются примерно на 8 %. При быстрой проверке дыханием значение влажности ожидаемо резко подскочило у SHT30, а вот у HDC1080 – совсем немного. Возможно, за время длительного хранения чувствительный элемент у сенсора накопил влаги, потому необходима сушка, которую можно обеспечить за счёт встроенного нагревателя (подробнее смотри в разделе «Режимы работы и настройка»).
Можно провести 5-10 циклов сушки по 30 секунд с паузами по несколько минут (чтобы не повредить чип). Также макетную плату с датчиками поставить рядом (но не вплотную) с радиатором отопления и выдержать несколько часов, чтобы сухой тёплый воздух просушил полимерный чувствительный элемент гигрометра. Альтернативный вариант: можно положить модуль в коробку с пакетиками силикагеля на пару дней.
После сушки следует выдержать преобразователи на столе при нормальных условиях не менее 1 часа. По-хорошему, разница по температуре не должна быть выше ±0,8 ̊C, а по влажности – не более ±3 %:

Заключение
В рамках данной статьи мы познакомились с комбинированным климатическим сенсором HDC1080. Он сочетает в себе высокую точность, низкое энергопотребление и простоту эксплуатации. Благодаря встроенному нагревателю, монитору питания и гибкой настройке, он отлично подходит как для автономных устройств с батарейным питанием, так и для систем климат-контроля.
Эксперимент показал, что датчик требует особого внимание при первом включении. После длительного хранения чувствительный элемент гигрометра может накопить влагу в полимерном слое, что проявляется в занижении показаний и снижении чувствительности. Однако эта проблема решается либо встроенным нагревателем, либо аккуратной сушкой.
