Чего только не реализовано на ESP32?

На этом микропоцессоре создано множество интернет и не только вещей. Вот и я уже очень давно хочу приобщиться к IOT, а также дополнительно прокачать себя в низкоуровневом программировании.

Однако ванильные Arduino‑скетчи, коими завалены все туториалы, меня не интересуют. Хочется «настоящего» программирования, сложностей, бессонных ночей, разборов документации и тому подобное...
Потому я выбрал путь изучения ESP32 с помощью C и ESP‑IDF.

С чего же начать? — конечно же метеостанция!

Это небольшой проект, которые покажет насколько ты вообще DIYщик, научит основам работы с ESP и её функциям.
Для своей метеостации я выбрал только основные датчики: давления, освещенности, температуры, влажности. Вот о последнем сегодня и пойдёт речь.

В качестве подопытного был приобретён популярный DHT22. Приобретался он сразу на плате с обвязкой (подтягивающий резистор, конденсатор и удобные пины для подключения).

Первый этап - подключение

Здесь всё просто: плюс к плюсу, минус к минусу, дату на любой цифровой GPIO. Главное чтобы GPIO умел работать как на ввод, так и на вывод. Питание можно брать как 3.3 В, так и 5 В.

Подключение DHT22 к ESP32
Подключение DHT22 к ESP32

Программирование

Опустим вопросы настройки 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)

А вот дальше начинается веселье... Нужно разобраться как работает датчик и реализовать это в коде.
Открываем документацию на датчик и видим следующее:

Принцип работы обмена данными по 1 проводу с DHT22
Принцип работы обмена данными по 1 проводу с DHT22

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

Диаграмма работы и таблица таймингов
Диаграмма работы и таблица таймингов

Чтож, приступаем к реализации!
Для работы с микросекундами самым простым вариантом будет использование стандартной функции задержки ets_delay_us(uint32_t us), которая входит в библиотеку rom/ets_sys.h.

А дальше всё по порядку:

  1. Инициализация датчика (строки 17-30)

  2. Подсчёт импульсов/бит (строки 32-47)

  3. Проверка чексуммы (строки 50-53)

  4. Перевод в человеческие значения (строки 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 сделан! Дальше будет только интереснее!