Чего только не реализовано на ESP32?
На этом микропоцессоре создано множество интернет и не только вещей. Вот и я уже очень давно хочу приобщиться к IOT, а также дополнительно прокачать себя в низкоуровневом программировании.
Однако ванильные Arduino‑скетчи, коими завалены все туториалы, меня не интересуют. Хочется «настоящего» программирования, сложностей, бессонных ночей, разборов документации и тому подобное...
Потому я выбрал путь изучения ESP32 с помощью C и ESP‑IDF.
С чего же начать? — конечно же метеостанция!
Это небольшой проект, которые покажет насколько ты вообще DIYщик, научит основам работы с ESP и её функциям.
Для своей метеостации я выбрал только основные датчики: давления, освещенности, температуры, влажности. Вот о последнем сегодня и пойдёт речь.
В качестве подопытного был приобретён популярный DHT22. Приобретался он сразу на плате с обвязкой (подтягивающий резистор, конденсатор и удобные пины для подключения).
Первый этап - подключение
Здесь всё просто: плюс к плюсу, минус к минусу, дату на любой цифровой GPIO. Главное чтобы GPIO умел работать как на ввод, так и на вывод. Питание можно брать как 3.3 В, так и 5 В.

Программирование
Опустим вопросы настройки IDE (в моём случае VS Code) и ESP-IDF. Перейдём сразу к сути и создадим новый пустой проект с благозвучным именем weather-station
Для работы с датчиком необходимо создать компонент, удобнее всего это делать через консоль:
idf.py create-component -C components dht22У нас появится папка компонентов со следующей структурой:
components/dht22/
├── include/
│ └── dht22.h
├── CMakeLists.txt
└── dht22.cДля начала определим заголовочный файл dht22.h
От датчика нам нужна только одна функция, которая получит на вход номер GPIO и «вернёт» нам значения влажности и температуры.
#pragma once
#include <driver/gpio.h>
#include <esp_log.h>
esp_err_t dht22_read(gpio_num_t pin, float *temperature, float *humidity);Можно было сделать возвращаемое значение void, но с ESP удобно возвращать стандартный тип ошибки esp_err_t. Это помогает потом понять что пошло не так, или наоборот — что всё работает отлично.
Для использования GPIO необходимо в CMakeList.txt подключить компонент esp_driver_gpio:
idf_component_register(SRCS "dht22.c"
INCLUDE_DIRS "include"
REQUIRES esp_driver_gpio)А вот дальше начинается веселье... Нужно разобраться как работает датчик и реализовать это в коде.
Открываем документацию на датчик и видим следующее:

И чуть ниже расписано как это выглядит непосредственно на железе с указанием всех таймингов.

Чтож, приступаем к реализации!
Для работы с микросекундами самым простым вариантом будет использование стандартной функции задержки ets_delay_us(uint32_t us), которая входит в библиотеку rom/ets_sys.h.
А дальше всё по порядку:
Инициализация датчика (строки 17-30)
Подсчёт импульсов/бит (строки 32-47)
Проверка чексуммы (строки 50-53)
Перевод в человеческие значения (строки 55-67)
Получившийся код dht22.c приведён ниже:
#include "dht22.h"
#include <driver/gpio.h>
#include <rom/ets_sys.h>
#include <esp_log.h>
static int dht_wait_level(gpio_num_t pin, int level, uint32_t timeout_us)
{
while (gpio_get_level(pin) == level) {
if (!timeout_us--) return -1;
ets_delay_us(1);
}
return 0;
}
esp_err_t dht22_read(gpio_num_t pin, float *temperature, float *humidity)
{
uint8_t data[5] = {0};
// Start signal
gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT_OD);
gpio_set_level(pin, 0);
ets_delay_us(2000); // 2 ms low (0.8-20 ms)
gpio_set_level(pin, 1);
ets_delay_us(40); // 40 us high (20-200 us)
// Response
if (dht_wait_level(pin, 0, 80) < 0) return ESP_ERR_TIMEOUT;
if (dht_wait_level(pin, 1, 80) < 0) return ESP_ERR_TIMEOUT;
// Read 40 bits
for (int i = 0; i < 40; i++) {
// wait for low to high edge
if (dht_wait_level(pin, 0, 60) < 0) return ESP_ERR_TIMEOUT;
// measure high pulse
uint32_t t = 0;
while (gpio_get_level(pin)) {
if (++t > 100) return ESP_ERR_TIMEOUT;
ets_delay_us(1);
}
int byte = i / 8;
data[byte] <<= 1;
if (t > 40) // >40 us means bit = 1
data[byte] |= 1;
}
// checksum
uint8_t sum = data[0] + data[1] + data[2] + data[3];
if ((sum & 0xFF) != data[4]) {
return ESP_ERR_INVALID_CRC;
}
uint16_t raw_h = (data[0] << 8) | data[1];
uint16_t raw_t = (data[2] << 8) | data[3];
*humidity = raw_h / 10.0f;
if (raw_t & 0x8000) {
raw_t &= 0x7FFF;
*temperature = -((float)raw_t / 10.0f);
} else {
*temperature = raw_t / 10.0f;
}
return ESP_OK;
}
Теперь остаётся вызывать эту функцию из основного кода.
Для этого в main.c
определяем порт (я подключил к 23)
добавляем бесконечный цикл с вызовом функции
добавляем задержку между вызовами, чтобы дёргать датчик только раз в несколько секунд
выводим информацию — я сделал в консоль с помощью функции ESP_LOGI(tag, format,...)
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include "dht22.h"
#define DHT22_GPIO 23
void app_main(void)
{
float temperature, humidity = 0;
while (true){
if (dht22_read(DHT22_GPIO, &temperature, &humidity) == ESP_OK)
ESP_LOGI("main", "Temperature = %0.1f, Humidity = %0.1f", temperature, humidity);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}Билдим, прошиваем и смотрим на вывод:
I (2275) main: Temperature = 22.6, Humidity = 28.3
I (4275) main: Temperature = 22.6, Humidity = 28.4
I (6275) main: Temperature = 22.6, Humidity = 28.3Всё заработало!
Датчик исправно работает и показывает температур и влажность в помещении. Сейчас зима, поэтому влажность низкая.
Первый шаг в IOT сделан! Дальше будет только интереснее!
