97. Влажность: DHT22, SHT31 и AHT20
Три самых ходовых датчика влажности и два способа с ними говорить. DHT22 — свой однопроводный протокол, где бит кодируется длительностью импульса, и его приходится мерить таймером. SHT31 и AHT20 — шина I²C и ответ, защищённый CRC. Все трое заодно меряют температуру, а из температуры и влажности получается точка росы — когда запотеет корпус.
Подтяжки и провода шины I²C разобраны в разделе 95. Однопроводный протокол DHT — не 1-Wire из раздела 83.2: у него нет адресов, а биты различаются не моментом чтения, а длительностью.
Как это называется в документации
Оригинал Перевод relative humidity (RH) относительная влажность dew point точка росы single-bus (proprietary) однопроводный протокол DHT — не 1-Wire pulse width длительность импульса checksum контрольная сумма — сумма байт CRC-8, polynomial 0x31 CRC-8 с полиномом 0x31 single shot, repeatability однократное измерение; повторяемость clock stretching растягивание такта: ведомый держит SCL sign-magnitude знак и модуль — не дополнительный код status byte, busy байт состояния; «ещё измеряю»
97.1 Три датчика
| DHT22 | SHT31 | AHT20 | |
|---|---|---|---|
| интерфейс | свой однопроводный | I²C, адрес 0x44 или 0x45 | I²C, адрес 0x38 |
| формат | влажность и температура по 16 бит, десятые доли | два кода по 16 бит | два кода по 20 бит |
| защита | сумма четырёх байт | CRC-8 на каждое слово | CRC-8 на весь ответ |
| особенность | бит — длительностью импульса, опрос не чаще раза в 2 с | команды по 16 бит | байт состояния: «ещё измеряю» |
97.2 DHT22: бит — это длительность
STM32 прижимает линию к земле на 2 мс — «начни» (DHT11 нужно не меньше 18 мс) — и отпускает. Датчик отвечает: 80 мкс ноль, 80 мкс единица, затем 40 бит. Перед каждым битом — 50 мкс ноля, а сам бит — это длительность следующей единицы: 26–28 мкс — ноль, 70 мкс — единица. Значит, мерить надо высокие импульсы, и порог — посередине, 49 мкс.
DHT11 отличается не только стартом: в его кадре байты — целая и дробная части
(влажность 2A 00 — 42 %, температура 17 05 — 23,5 °C), а не 16-битные числа
в десятых долях. dht_decode ниже выдаст для него 1075,2 % — нужен свой разбор.
Удобнее всего — прерывание на оба фронта и таймер, считающий микросекунды:
на фронте запомнить время, на спаде записать длительность. TIM2 тактируется
72 МГц у F103 и 84 МГц у F407 — делитель PSC 71 и 83 даёт 1 МГц. Вывод
PA0 — выход с открытым стоком: 0 прижимает линию, 1 отпускает её к подтяжке,
а IDR читает настоящий уровень, так что переключать направление не нужно.
void dht_start(void)
{
pa0_open_drain();
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
TIM2->PSC = TIM2_PSC;
TIM2->ARR = 0xFFFFu;
TIM2->EGR = TIM_EGR_UG;
TIM2->CR1 = TIM_CR1_CEN;
n_highs = 0;
GPIOA->BSRR = GPIO_BSRR_BR0; /* прижать линию: «начни» */
uint16_t t0 = (uint16_t)TIM2->CNT;
while ((uint16_t)(TIM2->CNT - t0) < 2000u) { } /* 2 мс; DHT11 нужно не меньше 18 */
EXTI->RTSR |= EXTI_RTSR_TR0;
EXTI->FTSR |= EXTI_FTSR_TR0;
EXTI->PR = EXTI_PR_PR0;
EXTI->IMR |= EXTI_IMR_MR0;
NVIC_EnableIRQ(EXTI0_IRQn);
GPIOA->BSRR = GPIO_BSRR_BS0; /* отпустить: дальше говорит датчик */
}
void EXTI0_IRQHandler(void)
{
uint16_t now = (uint16_t)TIM2->CNT;
if (GPIOA->IDR & 1u)
rise_at = now; /* фронт — начало единицы */
else if (n_highs < DHT_HIGHS)
dht_high_us[n_highs++] = (uint16_t)(now - rise_at); /* спад — её длительность */
EXTI->PR = EXTI_PR_PR0;
}
Спадов будет 42: первая единица — это отпущенная линия до ответа датчика, вторая — ответ 80 мкс, и только с третьей идут 40 бит.
97.3 Разбор 40 бит и сумма
int dht_decode(const uint16_t hi_us[40], int16_t *t_dc, uint16_t *rh_dm)
{
uint8_t b[5] = { 0 };
for (unsigned i = 0; i < 40; i++)
b[i / 8] = (uint8_t)((b[i / 8] << 1) | (hi_us[i] > DHT_BIT1_US));
if ((uint8_t)(b[0] + b[1] + b[2] + b[3]) != b[4])
return 0; /* сумма не сошлась */
*rh_dm = (uint16_t)(b[0] << 8 | b[1]);
int16_t t = (int16_t)((b[2] & 0x7F) << 8 | b[3]);
*t_dc = (b[2] & 0x80) ? (int16_t)-t : t; /* старший бит — знак, а не дополнительный код */
return 1;
}
Пять байт кадра 02 92 01 0D A2: влажность 0x0292 = 658 — 65,8 %,
температура 0x010D = 269 — 26,9 °C, сумма 0x02 + 0x92 + 0x01 + 0x0D = 0xA2.
Программа на компьютере собирала кадры из импульсов с разбросом и отдавала
их этой функции: кадр разобран верно даже с импульсами на краях допуска —
36…38 и 58…60 мкс, а кадр с испорченной суммой отвергнут.
Знак — отдельным битом. Отрицательная температура у DHT22 — это
старший бит плюс модуль: код 0x8065 — это −10,1 °C (сумма такого кадра —
0x79). Прочитанный как int16_t, тот же код дал бы −32 667, то есть
−3266,7 °C. У DS18B20 (раздел 96) — наоборот, дополнительный код: одна
и та же ошибка «не тот формат» у двух датчиков даёт разную чушь.
97.4 SHT31 и AHT20: CRC с полиномом 0x31
Оба датчика прикладывают к данным CRC-8 — с полиномом 0x31 и начальным значением 0xFF, старшим битом вперёд:
uint8_t crc8_31(const uint8_t *p, size_t n)
{
uint8_t crc = 0xFF;
while (n--) {
crc ^= *p++;
for (int i = 0; i < 8; i++)
crc = (uint8_t)((crc & 0x80) ? (crc << 1) ^ 0x31 : crc << 1);
}
return crc;
}
Это тот же многочлен x⁸ + x⁵ + x⁴ + 1, что у 1-Wire (раздел 83.2): 0x8C там — это 0x31, записанное задом наперёд. Но там биты идут младшим вперёд и счёт начинается с нуля, поэтому результат другой, и чужая функция CRC здесь не подойдёт. Контрольное значение из документации: для байт 0xBE 0xEF — 0x92; функция его даёт.
Адреса и команды:
#define SHT31_ADDR 0x44u /* 0x45, если вывод ADDR — к питанию */
#define SHT31_MEASURE 0x2400u /* однократно, высокая повторяемость, без растягивания такта; до 15 мс */
#define AHT20_ADDR 0x38u
#define AHT20_TRIGGER { 0xAC, 0x33, 0x00 } /* начать измерение; готово через 80 мс */
Чтение и запись по шине — как в разделе 17: команда, пауза на измерение,
чтение 6 байт у SHT31 и 7 байт у AHT20. Перед первым измерением AHT20 через
40 мс после включения читают байт состояния: если бит 3 («откалиброван») не
поднят, посылают 0xBE 0x08 0x00 и ждут 10 мс.
97.5 Перевод кодов
У SHT31 два 16-битных кода, каждый со своей CRC. Температура — −45 + 175 · S / 65535 °C, влажность — 100 · S / 65535 %:
int sht31_convert(const uint8_t d[6], int16_t *t_c100, uint16_t *rh_c100)
{
if (crc8_31(d, 2) != d[2] || crc8_31(d + 3, 2) != d[5])
return 0;
uint32_t st = (uint32_t)d[0] << 8 | d[1], srh = (uint32_t)d[3] << 8 | d[4];
*t_c100 = (int16_t)((int32_t)((17500u * st + 32767u) / 65535u) - 4500);
*rh_c100 = (uint16_t)((10000u * srh + 32767u) / 65535u);
return 1;
}
Код температуры 0x6666 даёт 25,00 °C, код влажности 0x8000 — 50,00 %; их CRC — 0x93 и 0xA2. Один сбитый бит — и функция отвергает ответ.
У AHT20 коды по 20 бит, упакованные в пять байт после байта состояния, и одна CRC на все шесть. Влажность — S / 2²⁰ · 100 %, температура — S / 2²⁰ · 200 − 50 °C:
int aht20_convert(const uint8_t d[7], int16_t *t_c100, uint16_t *rh_c100)
{
if (d[0] & 0x80)
return 0; /* ещё измеряет */
if (crc8_31(d, 6) != d[6])
return 0;
uint32_t srh = (uint32_t)d[1] << 12 | (uint32_t)d[2] << 4 | d[3] >> 4;
uint32_t st = ((uint32_t)d[3] & 0x0F) << 16 | (uint32_t)d[4] << 8 | d[5];
*rh_c100 = (uint16_t)(((uint64_t)srh * 10000u + (1u << 19)) >> 20);
*t_c100 = (int16_t)((int32_t)(((uint64_t)st * 20000u + (1u << 19)) >> 20) - 5000);
return 1;
}
Код влажности 0x80000 — 50,00 %, код температуры 0x60000 — 25,00 °C, CRC ответа — 0x4E. Старший бит байта состояния — «ещё измеряю»: такой ответ отвергается, его надо перечитать позже.
97.6 Точка росы
Относительная влажность сама по себе мало говорит конструктору. Важнее точка росы — температура, при которой этот воздух начнёт отдавать воду: если корпус или плата холоднее, на них выпадет конденсат. Формула Магнуса над водой:
double dew_point(double t_c, double rh)
{
const double b = 17.62, c = 243.12;
double g = log(rh / 100.0) + b * t_c / (c + t_c);
return c * g / (b - g);
}
При 25 °C и 60 % точка росы — 16,69 °C, при 20 °C и 50 % — 9,26 °C.
У F407 блок плавающей точки — одинарной точности, и double там всё равно
считается программно; для раза в секунду это неважно, а при частом расчёте
лучше float и logf.
97.7 Грабли
DHT22 молчит. Стартовый импульс короче нужного (DHT11 нужно не меньше 18 мс) или датчик опрашивают чаще раза в 2 с.
На морозе DHT22 показывает тысячи градусов. Знак и модуль прочитаны как дополнительный код. Старший бит — знак.
Сумма DHT22 иногда не сходится. Обработчик фронтов задерживают другие прерывания, и длительности плывут. Дать EXTI0 высокий приоритет или захватывать фронты таймером.
CRC SHT31 не сходится никогда. Взята функция CRC от 1-Wire: многочлен тот же, но порядок бит и начальное значение другие.
AHT20 отдаёт одно и то же. Прочли раньше конца измерения: старший бит байта состояния — «ещё измеряю».
Влажность у горячей платы занижена. Относительная влажность падает с нагревом: датчик выносят подальше от стабилизатора и процессора.
97.8 Итоги
- DHT22: бит — длительность единицы (26–28 мкс — ноль, 70 мкс — единица), порог 49 мкс; мерят прерыванием на оба фронта и таймером на 1 МГц.
- 40 бит — влажность, температура и сумма; температура — знак и модуль: 0x8065 — это −10,1 °C.
- SHT31 и AHT20 защищены CRC-8 с полиномом 0x31 и началом 0xFF: 0xBE 0xEF даёт 0x92; у 1-Wire многочлен тот же, но другой порядок бит.
- SHT31: −45 + 175 · S / 65535 °C; AHT20 — 20-битные коды, S / 2²⁰ · 200 − 50 °C; бит «ещё измеряю» проверять.
- Точка росы при 25 °C и 60 % — 16,69 °C: холоднее этого корпус запотеет.
Дальше: раздел 98 «Давление и высота: BMP280 и BME280» — калибровочные коэффициенты в самом датчике и формулы компенсации.