18 KiB
Описание компонента zh_aht
Общие сведения
zh_aht — это компонент для ESP-IDF, реализующий драйвер цифровых датчиков влажности и температуры серии AHT (AHT10/AHT15/AHT20/AHT21/AHT25/AHT30/AHT40) по интерфейсу I2C.
Компонент предоставляет высокоуровневый API для инициализации датчика, чтения влажности и температуры с проверкой целостности данных, а также для отслеживания статистики ошибок связи.
Основные возможности
- Поддержка всей серии AHT — один драйвер работает со всеми датчиками семейства без различий в модели.
- Новый драйвер I2C Master — использует API
i2c_master(driver/i2c_master.h) вместо устаревшего i2c_driver. - Автоматическая инициализация датчика — проба устройства и отправка команды инициализации, если датчик не откалиброван.
- Проверка целостности данных (CRC8) — каждый кадр данных проверяется алгоритмом CRC8 с полиномом
0x31. - Валидация состояния датчика — контроль битов калибровки и готовности в ответе датчика.
- Статистика ошибок — глобальный счётчик ошибок драйвера I2C с возможностью сброса.
- Внешнее управление шиной I2C — шина создаётся пользователем, что позволяет подключать несколько устройств на одну шину.
- Поддержка нескольких датчиков — через внешние расширители (например, мультиплексор
zh_pca9548a). - Скрытая реализация (opaque handle) — внутреннее состояние недоступно пользователю, работа только через публичный API.
Внутреннее устройство
Структура zh_aht_handle_t
Дескриптор датчика — непрозрачный тип (struct _zh_aht_handle_t), полный определитель которого скрыт в zh_aht.c. Пользователь оперирует только указателем на него.
| Поле | Тип | Описание |
|---|---|---|
dev_handle |
i2c_master_dev_handle_t |
Дескриптор I2C-устройства, через который выполняется вся связь с датчиком |
Структура zh_aht_init_config_t (публичная)
Конфигурация инициализации, заполняется пользователем. Для значений по умолчанию используется макрос ZH_AHT_INIT_CONFIG_DEFAULT() (частота 400000 Гц, адрес 0x38), при этом поле i2c_handle заполняется обязательно.
| Поле | Тип | Описание |
|---|---|---|
i2c_handle |
i2c_master_bus_handle_t |
Дескриптор I2C-шины, созданный пользователем |
i2c_address |
uint8_t |
Адрес устройства на шине: 0x38, 0x39 или 0x44 |
i2c_frequency |
uint32_t |
Тактовая частота SCL, от 1 до 400000 Гц |
Структура zh_aht_stats_t (публичная)
Глобальная статистика ошибок, доступна только на чтение через zh_aht_get_stats().
| Поле | Тип | Описание |
|---|---|---|
i2c_driver_error |
uint32_t |
Счётчик ошибок драйвера I2C (transmit/receive/remove device) |
Команды датчика
Вспомогательные константы протокола AHT, расположенные в zh_aht.c:
| Константа | Значение | Описание |
|---|---|---|
_aht_reset_command |
0xBA |
Команда программного сброса датчика |
_aht_read_command |
0xAC 0x33 0x00 |
Последовательность запуска измерения и чтения данных |
_aht_init_command |
0xBE 0x08 0x00 |
Команда инициализации (калибровки) для AHT20 и новее |
status_cmd |
0x71 |
Команда чтения байта статуса датчика |
Управление памятью
- Дескриптор выделяется одной операцией
heap_caps_calloc(1, sizeof(zh_aht_handle_t), MALLOC_CAP_8BIT)при инициализации, поэтому все поля гарантированно обнулены. - Освобождение — только через
heap_caps_free()внутриzh_aht_deinit(), после чего указатель в копии вызывающего кода обнуляется. - При любой ошибке инициализации уже выделенная память освобождается в макросах проверки ошибок (
ZH_ERROR_CHECKс блокомcleanup), утечки исключены. - Статистика ошибок
_stats— статическая глобальная переменная, живущая всё время работы программы.
Макросы обработки ошибок
Внутренние макросы ZH_ERROR_CHECK и ZH_ERROR_CHECK_CONT инкапсулируют типовую схему «проверка условия → логирование с именем функции, строкой и кодом ошибки → очистка ресурсов → возврат ошибки». Это устраняет дублирование кода обработки ошибок во всех функциях компонента.
API (интерфейс)
Инициализация и освобождение
| Функция | Описание |
|---|---|
zh_aht_init() |
Инициализация датчика: валидация конфигурации, выделение дескриптора, регистрация устройства на I2C-шине, проба датчика и отправка команды инициализации при необходимости. Указатель на дескриптор должен быть NULL. |
zh_aht_deinit() |
Удаление устройства с I2C-шины и освобождение дескриптора. Входной указатель обнуляется. |
Чтение данных
| Функция | Описание |
|---|---|
zh_aht_read() |
Запуск измерения (команда 0xAC), приём 7-байтного ответа, проверка статуса, CRC и преобразование сырых байтов во влажность (0–100 %) и температуру (−50…+150 °C). |
Сброс датчика
| Функция | Описание |
|---|---|
zh_aht_reset() |
Отправка команды сброса 0xBA, проверка статуса по 0x71 и повторная инициализация датчика, если бит калибровки сброшен. |
Получение информации
| Функция | Описание |
|---|---|
zh_aht_get_stats() |
Возвращает указатель на глобальную структуру статистики ошибок zh_aht_stats_t (только для чтения). |
Управление статистикой
| Функция | Описание |
|---|---|
zh_aht_reset_stats() |
Сбрасывает все счётчики ошибок статистики в ноль. |
Потокобезопасность
Компонент не является потокобезопасным: механизмы синхронизации (мьютексы, критические секции, атомарные операции) не используются.
- Отсутствие защиты дескриптора — одновременный вызов API-функций с одним дескриптором из нескольких задач может привести к некорректному обмену по I2C.
- Глобальная статистика
_stats— инкремент счётчикаi2c_driver_errorне атомарен. На практике на одной шине I2C операции передаются последовательно драйвером шины, но при многозадачном доступе к одному датчику синхронизацию должен обеспечить пользователь.
Алгоритмы
Преобразование сырых данных в инженерные единицы
Датчик возвращает 20-битные значения влажности и температуры в 7-байтном кадре:
- Влажность: байты
[1][2][3]конкатенируются в 24-битное слово, старшие 4 бита отбрасываются (>> 4), затемraw / 1048576 * 100— результат в процентах. - Температура: байты
[3][4][5]конкатенируются в 24-битное слово, маскируются младшие 20 бит (& 0xFFFFF), затемraw / 1048576 * 200 - 50— результат в °C. - Байт
[3]используется обоими значениями: его младшие 4 бита содержат старшие биты температуры.
Проверка состояния датчика в кадре
- Бит
0x08байта статуса[0]— признак откалиброванности датчика; если он сброшен, возвращаетсяESP_ERR_INVALID_STATE. - Бит
0x80байта статуса[0]— флаг занятости/таймаута измерения; если он установлен, возвращаетсяESP_ERR_TIMEOUT.
Алгоритм CRC8
- Начальное значение —
0xFF, полином —0x31, без инвертирования результата. - Для каждого байта данных: XOR с
crc, затем 8 итераций сдвига влево с условным XOR полинома при выходе единицы за старший бит. - Проверятся первые 6 байт кадра, 7-й байт — эталонная CRC от датчика.
Процедура инициализации I2C-подключения
- Регистрация устройства на шине (
i2c_master_bus_add_device) с 7-битным адресом и заданной частотой. - Задержка 100 мс, затем проба датчика (
i2c_master_probe) — при отсутствии ответа устройство удаляется с шины и возвращаетсяESP_ERR_NOT_FOUND. - Чтение байта статуса командой
0x71. - Если бит калибровки
0x08сброшен (характерно для AHT10/AHT15), отправляется команда инициализации0xBE 0x08 0x00и выдерживается 20 мс.
Примеры использования
Базовый пример
#include "zh_aht.h"
zh_aht_handle_t *aht_handle = NULL;
void app_main(void)
{
// Создаём I2C-шину (шину создаёт пользователь)
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_NUM_MAX - 1,
.scl_io_num = GPIO_NUM_22,
.sda_io_num = GPIO_NUM_21,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t i2c_bus_handle = NULL;
i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
// Инициализируем датчик конфигурацией по умолчанию
zh_aht_init_config_t config = ZH_AHT_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
zh_aht_init(&config, &aht_handle);
float humidity = 0.0;
float temperature = 0.0;
for (;;)
{
// Читаем влажность и температуру
if (zh_aht_read(&aht_handle, &humidity, &temperature) == ESP_OK)
{
printf("Humidity: %.2f%%, Temperature: %.2f°C\n", humidity, temperature);
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
// Освобождение ресурсов
zh_aht_deinit(&aht_handle);
}
Обработка ошибок и сброс датчика
#include "zh_aht.h"
static void read_sensor(zh_aht_handle_t **handle)
{
float humidity = 0.0;
float temperature = 0.0;
esp_err_t err = zh_aht_read(handle, &humidity, &temperature);
switch (err)
{
case ESP_OK:
printf("H: %.2f%%, T: %.2f°C\n", humidity, temperature);
break;
case ESP_ERR_INVALID_CRC:
// Повреждение данных — пробуем сбросить датчик
zh_aht_reset(handle);
break;
case ESP_ERR_TIMEOUT:
// Датчик не завершил измерение — читаем позже
break;
default:
printf("Read error: %s\n", esp_err_to_name(err));
break;
}
}
Мониторинг статистики ошибок
#include "zh_aht.h"
void monitor_errors(zh_aht_handle_t **handle)
{
// Получаем глобальную статистику (указатель всегда валиден)
const zh_aht_stats_t *stats = zh_aht_get_stats();
printf("I2C errors: %lu\n", (unsigned long)stats->i2c_driver_error);
// Пороговая реакция на накопленные ошибки
if (stats->i2c_driver_error > 10)
{
zh_aht_reset(handle); // сбрасываем датчик
zh_aht_reset_stats(); // обнуляем счётчики
}
}
Логирование
Компонент использует ESP-IDF логирование с тегом "zh_aht". Сообщения INFO сопровождаются ключевые операции, сообщения ERROR содержат имя файла, строку и имя кода ошибки.
#include "esp_log.h"
// Уменьшить verbosity компонента до уровня ошибок
esp_log_level_set("zh_aht", ESP_LOG_ERROR);
Ошибки
| Код | Описание |
|---|---|
ESP_OK |
Операция выполнена успешно |
ESP_ERR_INVALID_ARG |
Неверный аргумент: NULL-указатель, недопустимый адрес/частота I2C или конфигурация |
ESP_ERR_INVALID_STATE |
Дескриптор уже инициализирован, либо датчик не откалиброван (бит 0x08 статуса) |
ESP_ERR_NO_MEM |
Не удалось выделить память под дескриптор |
ESP_ERR_NOT_FOUND |
Датчик не обнаружен на шине (проба не ответила) |
ESP_ERR_TIMEOUT |
Датчик не завершил измерение (флаг занятости в байте статуса) |
ESP_ERR_INVALID_CRC |
Проверка CRC8 кадра данных не прошла |
ESP_FAIL |
Ошибка обмена по I2C (transmit/receive/remove device) |
Ограничения
- Потокобезопасность отсутствует — компонент не использует мьютексы; один дескриптор должен обслуживаться одной задачей либо синхронизироваться пользователем.
- Допустимые адреса I2C жёстко заданы — валидация принимает только
0x38,0x39,0x44, так как это аппаратные адреса серии AHT. - Частота I2C не выше 400 кГц — датчики серии AHT не поддерживают Fast-mode Plus.
- Шина I2C создаётся пользователем — компонент не управляет шиной,
i2c_handleв конфигурации обязателен; без негоzh_aht_init()вернётESP_ERR_INVALID_ARG. - Статистика глобальна —
_statsобщая для всех экземпляров датчиков: невозможно определить, какой именно датчик вызвал ошибку I2C. - Блокирующие задержки —
zh_aht_read()содержитvTaskDelay(80 мс)на время измерения,zh_aht_reset()— до 30 мс; функции нельзя вызывать из контекста, где недопусто переключение задач. - Необходим откалиброванный датчик — если после инициализации и сброса бит калибровки
0x08остаётся сброшенным,zh_aht_read()будет стабильно возвращатьESP_ERR_INVALID_STATE.
Требования для сборки
- ESP-IDF версии
>=5.0(используется новый APIdriver/i2c_master.h). - Компонент
driver(ESP-IDF 5.x) либоesp_driver_i2c(ESP-IDF 6.x) — выбирается автоматически вCMakeLists.txtпо версии фреймворка. - FreeRTOS (входит в состав ESP-IDF) — используется
vTaskDelay.
Установка
cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_aht
#include "zh_aht.h"