STM Урок 107. Датчик влажности и температуры DHT22. Часть 1



Продолжаем тему датчиков температуры.

Сегодня у нас на повестке дня датчик, который измеряет не только температуру, но и влажность — это датчик DHT22. Он также может встречаться под маркой AM2302.

Также существует датчик DHT11, урок по которому меня неоднократно просили сделать, он у меня тоже есть. Но таких уроков, во-первых, уже немало, а во-вторых, думаю более актуальным и востребованным будет урок именно по DHT22. Обусловлено это тем, что DHT11 не умеет измерять отрицательную температуру, а также точность измерений у него ниже. Тем более что основная работа по написанию кода для датчика DHT22 будет применительна и к DHT11 за исключением некоторых деталей расположения значений в информационных байтах.

Выглядит датчик следующим образом

 

 

Корпус датчика сравнительно большой. Из корпуса выходят 4 вывода и они имеют следующее назначение (слева направо)

 

1 — VDD-power supply (питание)

2 — DATA-signal (информационный выход)

3 — NULL (не используется)

4 — GND (общий провод)

 

Теперь кратко о характеристиках датчика.

Напряжение питания — 3.3-6 В постоянного тока;

Потребляемый ток в момент преобразования (от запроса окончания преобразования) —   1 милиампер при 3,3 В, 1.5 милиампера при 6 В;

Потребляемый ток в дежурном режие (режиме сна) — 40 микроампер при 3,3 В, 50 микроампер при 6 В.

Тип передачи информации — digital signal via single-bus (цифровой сигнал через одну шину, только он не имеет ничего общего с 1-WIRE);

Измеряющий элемент — полимерный конденсатор;

Диапазон измерений: влажность — 0-100 %RH; температура — -40 ~ +80 ?C;

Точность измерений: влажность — +-2 %RH (Max +-5 %RH); температура — менее +-0.5 ?C;

Разрешение или чувствительность: влажность — 0.1 %RH; температура — 0.1 ?C;

Стабильность измерений: влажность — +-1 %RH; температура — +-0.2 ?C;

Гистерезис влажности — +-0.3%RH;

Долгосрочная стабильность — +-0.5 %RH/год;

Период измерения данных: 2 секунды;

Размеры малый корпус 14x18x5.5 мм; большой корпус 22x28x5 мм.

 

Теперь по передаче данных и состояниям датчика.

Со времени подачи питания на датчик его неустойчивое состояние длится около секунды, поэтому данное время лучше переждать и не обращаться к датчику.

Управление датчиком происходит путём подачи логического 0 способом притягивания его информационной ножки к земле и подачи логической 1 путём её отпускания. Но чтобы в данный момент появилась логическая 0, необходимо подключить с информационной ножки на шину питания подтягивающий резистор 4,7 — 10 килоом. Где-то я видел информацию, что в корпусном датчике уже встроен такой резистор, но я его сквозь щели корпуса явно не увидел, поэтому я всё-таки подключил резистор на 10 килоом, мало того — я использовал ту же маленькую макетную платку, что и для DS18B20, а у меня на ней уже был резистор. Да и чтобы адаптировать её под датчик DHT22, мне пришлось только переставить на 1 шаг перемычку

 

 

Также должен быть установлен конденсатор на 100 нанофарад между VCC и GND, но он точно уже есть в корпусных вариантах, поэтому перед нами такая задача уже не стоит.

Сначала мы держим логическую 1 на информационной ножке датчика. В это время датчик находится в состоянии сна.

Чтобы начать работу с датчиком, так сказать «разбудить его» мы должны отправить условие старта. Для этого мы должны подать на него логический 0 способом прижатия информационной ножки к земле, подождать 18 милисекунд (можно меньше, но не менее 1 милисекунды, мы подождём 18), а затем подать логическую 1. После этого, если датчик присутствует на шине и готов с нами «сотрудничать», то он спустя 20-40 микросекунд обязан будет откликнуться логическим 0, что мы заметим по прижатию ножки к земле без нашего участия. Затем мы ждём ещё 80 микросекунд, после чего датчик должен будет отпустить ножку, затем ждём ещё 80 микросекунд, после чего датчик начнёт передавать информацию в виде 5 информационных байт (нажмите на картинку для увеличения изображения)

 

 

Каким образом передаются эти байты, мы узнаем чуть позже ближе к моменту написания кода, иначе мы рискуем забыть, здесь же всё-таки точные цифры и нам придется отматывать страничку назад.

Ну вот такое вот краткое знакомство.

Подключать мы данный датчик будем к микроконтроллеру STM32F103C8T6, расположенному на недорогой отладочной плате, с которой мы постоянно работаем. Мало того, датчик мы подключим к той же ножке PB11, что упростит написание кода. Также ещё мы к данной плате подключим восьмиразрядный светодиодный индикатор, собранный в виде модуля на микросхеме MAX7219, с которой мы также работали неоднократно.

Вот так будет выглядеть наша тестовая схема

 

 

Проект мы также создадим из проекта урока 92, в котором мы работали с датчиком DS18B20, и назовём его по имени датчика — DHT22.

 

 

В соответствующих папках проекта также исправим имена файлов библиотеки ds18b20.c и ds18b20.h на dht22.c и dht22.h.

Откроем наш проект в Cube MX, включим в нём ещё шину SPI2 (SPI1 мы прибережём на будущее)

 

Ножку PB12 включим на выход, она будет у нас выполнять функции ножки Chip Select для SPI2

 

 

Перейдём в Configuration и настроим SPI2

 

 

Сгенерируем проект, откроем его в Keil, подключим файл dht22.c, настроим программатор на авторезет, установим уровень оптимизации в 1 и попробуем собрать проект, который у нас скорей всего пока не соберётся, так как мы с вами только переименовали файлы библиотеки, не внеся в них соответствующие изменения.

Перейдём в файл main.h и исправим там сначала подключение хедер-файла

 

#include "dht22.h"

 

Затем перейдём в файл dht22.c и исправим это и там.

Также из этого файла удалим функции вместе с телами ds18b20_Reset, ds18b20_ReadBit, ds18b20_ReadByte, ds18b20_WriteBit, ds18b20_WriteByte, ds18b20_MeasureTemperCmd, ds18b20_ReadStratcpad и ds18b20_Convert.

Функцию ds18b20_init переименуем в dht22_init, а у функции ds18b20_GetSign исправим не только имя, но и параметр

 

uint8_t dht22_GetData(uint8_t *data)

 

В функции инициализации сначала исправим входящие аргументы. У нас их не будет

 

uint8_t dht22_init(void)

 

В функции инициализации исправим тело, кроме возврата нуля

 

uint8_t dht22_init(uint8_t mode)

{

  HAL_Delay(2000);

  GPIOB->ODR |= GPIO_ODR_ODR11;//высокий уровень

  return 0;

 

Мы выставили логический 1, подождав перед этим 2 секунду, дав датчику перейти в устойчивое состояние. Хоть для этого нужна и одна секунда. Но нам же надо ещё, чтобы датчик сконвертировал данные, а на это надо 2 секунды. По идее мы тогда должны добавлять 3 секунды, но хватит и двух. Если первое показание будет неточным — это не страшно.

Теперь займёмся функцией dht22_GetData, в которой, думаю следует вообще удалить всё тело и потихоньку его наполнить, следуя рекомендациям технической документации.

Сначала добавим парочку локальных переменных

 

uint8_t dht22_GetData(uint8_t *data)

{

  uint8_t i, j = 0;

 

 

Перезагрузим ножку порта, то есть сначала опустим её в ноль, затем вернём обратно в единицу и подождём 100 милисекунд

 

uint8_t i, j = 0;

//reset port

GPIOB->ODR &= ~GPIO_ODR_ODR11;//низкий уровень

GPIOB->ODR |= GPIO_ODR_ODR11;//высокий уровень

DelayMicro(100000);

 

Я это увидел в каком-то исходнике, в тех. документации почему-то не нашёл, а без этого иногда бывает, что датчик не откликается, поэтому я эту часть кода решил оставить. Если у вас будет работать и без этого, то можете данный участок пропустить.

Далее передадим условие СТАРТ

 

DelayMicro(100000);

//передадим условие СТАРТ

GPIOB->ODR &= ~GPIO_ODR_ODR11;//низкий уровень

DelayMicro(18000);

GPIOB->ODR |= GPIO_ODR_ODR11;//высокий уровень

 

Затем дождёмся ответа датчика

 

GPIOB->ODR |= GPIO_ODR_ODR11;//высокий уровень

//дождемся ответа датчика

DelayMicro(39);//20-40 мкс

 

Уходим с ошибкой из функции, если датчик не ответит

 

DelayMicro(39);//20-40 мкс

//если датчик нам не ответил притягиванием шины, то ошибка

if(GPIOB->IDR & GPIO_IDR_IDR11) {

  return 0;

}

 

Подождём ещё 80 микросекунд

 

  return 0;

}

DelayMicro(80);

 

Проверим, отпустил ли датчик шину

 

DelayMicro(80);

//если датчик не отпустил шину, то ошибка

if(!(GPIOB->IDR & GPIO_IDR_IDR11)) {

  return 0;

}

 

Подождём ещё 80 микросекунд

 

  return 0;

}

DelayMicro(80);

 

Дальше уже начнётся чтение байтов.

Во-первых, нам нужно знать что за байты, то есть с какой именно информацией, датчик нам передаст.

Байты передаются от старшего к более младшим, биты тоже.

В двух старших байтах находится информация о влажности, увеличенная в 10 раз, чтобы не использовать дроби, а в двух следующих (вернее в младших их 15 битах) — информация о температуре, также увеличенная в 10 раз. В самом старшем бите данных двух байтов — информация о знаке. Если плюс — то 0, если минус — то 1.

Давайте заглянем в техническую документацию и найдём там сначала, как передаёт датчик биты, сначала как передаётся 0

 

 

Здесь видно, что при передаче 0 датчик после притягивания шины через 50 микросекунд вновь её отпускает, затем по прошествии 26-28 микросекунд её притянет к земле.

Теперь посмотрим, как датчик передаёт 1

 

 

При передаче 1 датчик после притягивания шины через 50 микросекунд также её отпускает, как и при передаче 0, а затем уже датчик её притянет к земле только через 70 микросекунд.

Ну и руководствуясь данной информацией напишем цикл приёма 5 байтов от датчика

 

DelayMicro(80);

//читаем данные (записываем байты в массив наоборот,

//так как сначала передаётся старший, чтобы потом

//не переворачивать двухбайтовый результат)

for (j=0; j<5; j++)

{

}

 

Здесь обычный цикл из 5 повторений.

Теперь начнём получать наши байты в цикле. Полученные байты мы будем записывать в массив из 5 элементов, причём лучше их писать наоборот, начиная от последнего элемента, чтобы байты расположились правильно по старшинству и не пришлось потом переворачивать результат двухбайтовых величин. Для начала обнулим элемент, чтобы нам писать в него только единицы, а нули там уже будут

 

for (j=0; j<5; j++)

{

  data[4-j]=0;

}

 

Далее получаем каждый бит, поэтому ещё один цикл

 

data[4-j]=0;

for(i=0; i<8; i++)

{

}

 

В данном цикле мы сначала ждём, когда датчик отпустит шину

 

for(i=0; i<8; i++)

{

  while(!(GPIOB->IDR & GPIO_IDR_IDR11)); //ждём отпускания шины

 

Затем после того как условие цикла не будет выполняться, то есть на проводе воцарится логическая единица, мы подождём 30 микросекунд, в это время уже точно будет ясной ситуация

 

while(!(GPIOB->IDR & GPIO_IDR_IDR11)); //ждём отпускания шины

DelayMicro(30);

 

Теперь узнаем состояние шины

 

DelayMicro(30);

if(GPIOB->IDR & GPIO_IDR_IDR11) //читаем результат по прошествии 30 микросекунд

 

И, если шина за это время к земле обратно не притянулась, значит у нас единица. Запишем её в нужный разряд.

 

if(GPIOB->IDR & GPIO_IDR_IDR11) //читаем результат по прошествии 30 микросекунд

  //если шина за это время не притянулась к земле, то значит это единица, а если притянулась, то ноль

  data[4-j] |= (1<<(7-i));

 

А если мы не попали в это условие, то значит ничего не запишем и у нас останется 0 в разряде.

Теперь подождём, кода датчик обратно притянет ножку к земле, и наш цикл повторится для следующего бита

 

  data[4-j] |= (1<<(7-i));

while(GPIOB->IDR & GPIO_IDR_IDR11); //ждём, пока датчик притянет шину (в случае единицы)

 

Выходим из всех циклов и вернём единицу

 

      while(GPIOB->IDR & GPIO_IDR_IDR11); //ждём, пока датчик притянет шину (в случае единицы)

    }

  }

  return 1;

}

 

То есть если мы сюда дойдём, значит всё нормально и тогда функция возвращает 1, что мы будем расценивать как TRUE.

Теперь перейдём в заголовочный файл dht22.h и поправим его, исправив прототипы и удалив ненужные, а также удалим ненужные макросы, также приведём в соответствие макросы подключения данного хедера.

После всего этого файл примет следующий вид

 

#ifndef DHT22_H_

#define DHT22_H_

//--------------------------------------------------

#include "stm32f1xx_hal.h"

#include <string.h>

#include <stdlib.h>

#include <stdint.h>

//--------------------------------------------------

void port_init(void);

uint8_t dht22_init(void);

uint8_t dht22_GetData(uint8_t *data);

//--------------------------------------------------

#endif /* DHT22_H_ */

 

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

 

 

Предыдущий урок Программирование МК STM32 Следующая часть

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Датчик температуры и влажности можно приобрести здесь DHT22

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)

STM Датчик влажности и температуры DHT22

 

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

STM Датчик влажности и температуры DHT22

18 комментариев на “STM Урок 107. Датчик влажности и температуры DHT22. Часть 1
  1. Анас:

    Почему-то в Workbench System этот проект работает неправильно, а в Keil всё хорошо.

  2. Анас:

    Подключал только как библиотечную функцию к своему проекту, в Workbench System выдаются неправильные данные от DHT22.

  3. Анас:

    после прошивки STM32F103C8T6 в Keil в помещении выдаёт T=25 H=22 адекватно реагирует на изменения, после прошивки в Workbench System T=-1638 H=0(изредка 3276).

  4. Анас:

    Хотелось бы понять почему потому что я уже на грани 32кбайт.

  5. Taras01:

    Мне очень нравятся ваши уроки! Я пишу по Nucleo F411RE…..Я не могу правильно переписать dht22.c. У меня проблема с регистрами. Датчик висит на PB12.
    Я их как то так инициализирую:

    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_12);
    GPIOB->MODER |= GPIO_MODER_MODE11_0; // MODER12[0:1]
    GPIOB->OTYPER |= GPIO_OTYPER_OT11; //open drain
    GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED11; //high speed

    uint8_t dht22_init(void)
    {
    HAL_Delay(2000);
    GPIOB->ODR |= GPIO_ODR_OD11;
    return 0;
    }
    Помогите пожалуйста разобраться.

  6. VD:

    Доброго времени суток.
    Зачем так учить народ, почему на прерывании и таймере не написать ??

  7. Дмитрий:

    Спасибо! Очень помог урок. Бился с DHT11, Помогла задержка в 2 секунды, правда мне хватило и 1,5 сек. и ногодрыганье с 100мсек. не использовал. Добился стабильной работы датчика. Ох уж этот китайский ширпотреб!

  8. Denis:

    Возник вопрос, решил совместить 2 проекта: термодатчик DS18B20 и датчик влажности DHT22 (урок 92 и 107), т.е. делал не замену, а добавление. Функции и переменные проверил — ничего не пересекается. Проект собрался без ошибок. На порту PB11 — термодатчик, на PB10 — датчик влажности.
    Сделал вывод значений обоих датчиков в терминал. Чтение через преобразователь USB-UART.
    Почему в результате плата игнорирует датчик влажности DHT22, а видит только термодатчик DS18B20 ???

  9. zhenya:

    Доброго времени суток. У меня вопрос у кого-то получилось запилить DHT22 на другой контроллер, помимо 103-го?

  10. И тут тоже самое.Если датчик передаёт а контроллер принимает.Порт настроить нужно как на вход.А так работать не будет.

  11. Влад:

    При 3 оптимизации не работат, кто знает почему?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*