Продолжаем тему датчиков температуры.
Сегодня у нас на повестке дня датчик, который измеряет не только температуру, но и влажность — это датчик 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 (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Почему-то в Workbench System этот проект работает неправильно, а в Keil всё хорошо.
Что-то со средой. У меня всё везде нормально работает.
Подключал только как библиотечную функцию к своему проекту, в Workbench System выдаются неправильные данные от DHT22.
Может плавающая запятая не включена?
Я использую целочисленную арифметику, мне такая точность не нужна.
И показания всегда постоянны не зависят от внешнего воздействия(
int16_t temper=0, hum=0;
uint8_t error_dht=0;
if(dht22_GetData(dt))
{
error_dht=0;
temper = ((*(uint16_t*)(dt+1)) & 0x3FFF) / 10;
if((*(uint16_t*)(dt+1)) & 0x8000) temper = -temper;
hum = (*(int16_t*)(dt+3)) / 10;
}
else
{
error_dht++;
if(error_dht>=10)
{
error_dht=10;
hum = -100;
temper = -100;
}
}
после прошивки STM32F103C8T6 в Keil в помещении выдаёт T=25 H=22 адекватно реагирует на изменения, после прошивки в Workbench System T=-1638 H=0(изредка 3276).
Та же самая проблема, есть решение?
Хотелось бы понять почему потому что я уже на грани 32кбайт.
Мне очень нравятся ваши уроки! Я пишу по 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;
}
Помогите пожалуйста разобраться.
Доброго времени суток.
Зачем так учить народ, почему на прерывании и таймере не написать ??
А почему бы так не написать?
Спасибо! Очень помог урок. Бился с DHT11, Помогла задержка в 2 секунды, правда мне хватило и 1,5 сек. и ногодрыганье с 100мсек. не использовал. Добился стабильной работы датчика. Ох уж этот китайский ширпотреб!
Возник вопрос, решил совместить 2 проекта: термодатчик DS18B20 и датчик влажности DHT22 (урок 92 и 107), т.е. делал не замену, а добавление. Функции и переменные проверил — ничего не пересекается. Проект собрался без ошибок. На порту PB11 — термодатчик, на PB10 — датчик влажности.
Сделал вывод значений обоих датчиков в терминал. Чтение через преобразователь USB-UART.
Почему в результате плата игнорирует датчик влажности DHT22, а видит только термодатчик DS18B20 ???
Доброго времени суток. У меня вопрос у кого-то получилось запилить DHT22 на другой контроллер, помимо 103-го?
И тут тоже самое.Если датчик передаёт а контроллер принимает.Порт настроить нужно как на вход.А так работать не будет.
При 3 оптимизации не работат, кто знает почему?