Подключение DHT11 и DHT22 к STM32F103C8T6

Подключение DHT11 и DHT22 к STM32F103C8T6

Программная реализация опроса датчика температуры DHT11 (DHT22) с помощью микроконтроллера STM32F103.

Описание датчиков DHT11 и DHT22.

Датчики температуры DHT11 измеряет температуру в диапазоне от 0 до 50 °С с погрешностью ±2 градуса и влажность от 20 до 90% с точностью ±5%.

Датчик DHT22 имеет диапазон измерения температуры от -40 до 80 °С с погрешностью 0.5 и влажности от 0 до 100% с точностью ±2% (максимум 5%).

Оба датчика имеют одинаковую распиновку и схожий протокол передачи данных, по этому я решил их объединить в одну статью.

Вариант подкючения с DMA (ПДП).

Типичная схема подключения из datasheet DHT11.

DHT11 DHT22 схема подключенияdht11 dht22 pinout

Датчики имеют 4 вывода:
1 питание 3.3-5.5V (в DHT11 заявлено минимум 3.5V но у меня он работает от 3.3)
2 данные
3 не используется
4 земля

Процесс взаимодействия с DHT11 и DHT22

Всего датчик передает 40 бит данных старшим битом вперед. 8 бит - целая часть влажности, 8 бит дробная часть влажности, 8 бит целая часть температуры, 8 бит дробная часть температуры и 8 бит контрольная сумма.

Контрольная сумма это сумма всех 4 байт. Для проверки правильности переданных данных нужно сложить 4 первых байта в переменной типа uint8 и сравнить их с 5 байтом. Я написал в uint8  потому, что может получится число больше чем 1 байт, и что бы не делать лишних манипуляций и не расходовать попросту память лучше сразу использовать uint8.

Ниже приведена диаграмма таймингов.

dht11 dht22 data protoloc

Я нашел отличие лишь в стартовом сигнале. Для DHT11 минимальное время прижатия к 0 заявлено 18 мс., а для DHT22 время 1 мс. Инициализация датчика от МК исчисляется в миллисекундах, а передача сигнала присутствия и сами данные от датчика в микросекундах.

И так, как же получить данные от датчика? Очень просто.

На линию данных (2 пин) датчика подается питание.
Далее, для того что бы датчик начал передавать данные, нужно прижать линию данных к земле минимум 18 мс для DHT11 и 1 мс для DHT22. После этого нужно отпустить питание и ждать, когда датчик сообщит о своем присутствии.

Если датчик подключен и все хорошо, то через несколько мкс линия данных будет прижата к нулю на 80 мкс, потом отпущена еще на 80 мкс. После этого датчик будет передавать данные по 1 биту.

Передача бит данных состоит из прижатия линии к земле на 50 мкс и отпускании ее либо на 26-28 мкс для 0 либо на 70 мкс для 1.

dht11 dht22 Data Timing Diagram

Для того чтобы определить 0 или 1 мы получили можно засекать время прижатия линии к 0, это будет 50 мкс, а дальше, если линия отпущена на время меньше этого - значит мы получили 0, если же линия отпущена на время больше чем 50 мкс - мы получили 1.

Пример для STM32F103C8T6 получения температуры с DHT11.

Я подключил датчик к PB6 так как пин толерантен к 5В, а так же для демонстрации варианта получения температуры как полностью программной реализацией так и с помощью таймера и DMA (но об этом в другой статье).

Пин 2 датчика подтянут к питанию резистором 10 кОм.

Я создал несколько макросов для упрощения переноса на другой пин, в случае необходимости.

#define DHT_SIZE 5 // size of temp_s
#define DHT_MAX_US 200
#define DHT_GPIO GPIOB
#define DHT_PIN GPIO_Pin_6

Настраиваем кварц на частоту 72 МГц.

Ногу настраиваем на выход с открытым стоком (open drain) GPIO_Mode_Out_OD. Это позволит нам прижимать ногу к земле для инициализации датчика, а когда нужно - слушать датчик.

void initGPIO() { 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); 

  GPIO_InitTypeDef port; 
  GPIO_StructInit(&port); 

  port.GPIO_Mode = GPIO_Mode_Out_OD; 

  port.GPIO_Pin = DHT_PIN;
  port.GPIO_Speed = GPIO_Speed_50MHz; 
  GPIO_Init(DHT_GPIO, &port);

Думаю нет смысла комментировать каждую строку.

Нам нужно считать как долго нога прижата к земле или отпущена, я вынес это в отдельную функцию

// state - какой уровень считаем
uint8_t DHTGetUs(uint8_t state){ 
  uint8_t cnt = 0;
  // считаем сколько us продержался наш уровень, и выходим если что то пошло не так
  while (GPIO_ReadInputDataBit(DHT_GPIO, DHT_PIN) == state && (cnt++ < DHT_MAX_US)){ 
    delay_us(1); 
  } 

  return cnt; 
} 

Для удобства использования полученных 40 бит я набросал вспомогательную структуру

typedef struct { 
  uint8_t HI; // целая часть влажности
  uint8_t HD; // дробная часть влажности
  uint8_t TI; // целая часть температуры
  uint8_t TD; // дробная часть температуры
  uint8_t crc; // контрольная сумма
} temp_s; 

Функция, опроса датчика

uint8_t DHTRecv(temp_s *data){ 
  // для удобства получения данных в цикле
  // представим нашу структуру как указатель на массив байт
  uint8_t *buf = data;
  uint8_t cnt, i, b, k, n = 0;

  // Поднимем ногу датчика к питанию на некоторое время, что бы датчик начал работать
  GPIO_WriteBit(DHT_GPIO, DHT_PIN, Bit_SET);
  delay_ms(100);
  // прижмем к питанию минимум на 18 мкс для DHT 11
  GPIO_WriteBit(DHT_GPIO, DHT_PIN, Bit_RESET);
  delay_ms(30);
  // отпустим ногу
  GPIO_WriteBit(DHT_GPIO, DHT_PIN, Bit_SET);

  // датчик не сразу прижимает ногу в 0
  // по этому ждем некоторое время
  DHTGetUs(1);
  // теперь, когда нога прижата, ждем пока она прижата
  // 5 тут просто для того что бы убедится что прошло минимум 5 мкс
  if (DHTGetUs(0) < 5) return ERROR;
  // теперь ждем пока нога поднята
  if (DHTGetUs(1) < 5) return ERROR;

  // импульс присутствия датчика получен
  // значит нужно начинать получать наши биты
  // цикл по размеру структуры, в нашем случае 5 байт
  for (b = 0; b < DHT_SIZE; ++b) {
    // обнулим предыдущие показания
    *buf = 0;
    // получаем 8 бит в первый байт
    for (i = 0; i < 8; ++i) {
      получаем время на сколько нога была прижата к 0
      cnt = DHTGetUs(0);
      // теперь получаем сколько нога прижата к 1 и сравниваем
      // если к 1 прижата дольше (>) значит пришла единица
      // если нет то пришел 0
      // и пишем это в нашу структуру
      *buf |= (DHTGetUs(1) > cnt) << (7 - i);
    }
    // сдвигаем указатель на следущий байт структуры
    buf++;
  } 

  // проверяем контрольную сумму
  if (data->crc != data->HD + data->HI + data->TD + data->TI) return ERROR; 

  return SUCCESS; 
}

Думаю я расписал достаточно подробно

Вызывать можно как то так

temp_s data; // переменная где будем зранить нашу температуру
SetSysClockTo72();
initGPIO();

while(1) {
  if (DHTRecv(&data) == SUCCESS) {
    // тут у нас данные в data
    // например выводим на дисплей 1602a
    ClearLCDScreen();
    snprintf(buf, 20, "Temp: %d.%d", data.TI, data.TD);
    PrintStr(buf);
    Cursor(1, 0);
    snprintf(buf, 20, "H: %d.%d", data.HI, data.HD);
    PrintStr(buf);
  } else {
    // что то пошло не так
    ClearLCDScreen(); 
    PrintStr("DHT Error"); 
  }
}
При копировании материалов ссылка на https://terraideas.ru/ обязательна

Комментарии к статье: Подключение DHT11 и DHT22 к STM32F103C8T6

Влад 3 месяца назад

Не тратьте свое время на этот код. Лучше найдите проще и без ошибок. Плюс вы задолбаетесь его исправлять, т.к. после копирования текста он вставляется в одну строку и с постоянной припиской

DrobyshevAlex 3 месяца назад

Этот код уже два года работал в инкубаторе, прекрасно работает :) Вывело 48 из 56 яичек :) Кому не нравиться, можете не использовать))

DrobyshevAlex 3 месяца назад

По поводу копирования, убрал скрипт который убивал переносы строк. Я от чистого сердца пытался помочь людям типа меня (новичкам св мк), писал код с комментариями, а кому лень даже переносы строк расставить может не тратить время.

Ашот Клекчян около 1 месяца назад

Расчет времени поправил по моему кварцу, все отлично работает, спасибо.