# Описание компонента zh_aht ## Общие сведения **zh_aht** — это компонент для ESP-IDF, реализующий драйвер цифровых датчиков влажности и температуры серии AHT (AHT10/AHT15/AHT20/AHT21/AHT25/AHT30/AHT40) по интерфейсу I2C. Компонент предоставляет высокоуровневый API для инициализации датчика, чтения влажности и температуры с проверкой целостности данных, а также для отслеживания статистики ошибок связи. ## Основные возможности 1. **Поддержка всей серии AHT** — один драйвер работает со всеми датчиками семейства без различий в модели. 2. **Новый драйвер I2C Master** — использует API `i2c_master` (driver/i2c_master.h) вместо устаревшего i2c_driver. 3. **Автоматическая инициализация датчика** — проба устройства и отправка команды инициализации, если датчик не откалиброван. 4. **Проверка целостности данных (CRC8)** — каждый кадр данных проверяется алгоритмом CRC8 с полиномом `0x31`. 5. **Валидация состояния датчика** — контроль битов калибровки и готовности в ответе датчика. 6. **Статистика ошибок** — глобальный счётчик ошибок драйвера I2C с возможностью сброса. 7. **Внешнее управление шиной I2C** — шина создаётся пользователем, что позволяет подключать несколько устройств на одну шину. 8. **Поддержка нескольких датчиков** — через внешние расширители (например, мультиплексор `zh_pca9548a`). 9. **Скрытая реализация (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()` | Сбрасывает все счётчики ошибок статистики в ноль. | ## Потокобезопасность Компонент **не является потокобезопасным**: механизмы синхронизации (мьютексы, критические секции, атомарные операции) не используются. 1. **Отсутствие защиты дескриптора** — одновременный вызов API-функций с одним дескриптором из нескольких задач может привести к некорректному обмену по I2C. 2. **Глобальная статистика `_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-подключения 1. Регистрация устройства на шине (`i2c_master_bus_add_device`) с 7-битным адресом и заданной частотой. 2. Задержка 100 мс, затем проба датчика (`i2c_master_probe`) — при отсутствии ответа устройство удаляется с шины и возвращается `ESP_ERR_NOT_FOUND`. 3. Чтение байта статуса командой `0x71`. 4. Если бит калибровки `0x08` сброшен (характерно для AHT10/AHT15), отправляется команда инициализации `0xBE 0x08 0x00` и выдерживается 20 мс. ## Примеры использования ### Базовый пример ```c #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); } ``` ### Обработка ошибок и сброс датчика ```c #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; } } ``` ### Мониторинг статистики ошибок ```c #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` содержат имя файла, строку и имя кода ошибки. ```c #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) | ## Ограничения 1. **Потокобезопасность отсутствует** — компонент не использует мьютексы; один дескриптор должен обслуживаться одной задачей либо синхронизироваться пользователем. 2. **Допустимые адреса I2C жёстко заданы** — валидация принимает только `0x38`, `0x39`, `0x44`, так как это аппаратные адреса серии AHT. 3. **Частота I2C не выше 400 кГц** — датчики серии AHT не поддерживают Fast-mode Plus. 4. **Шина I2C создаётся пользователем** — компонент не управляет шиной, `i2c_handle` в конфигурации обязателен; без него `zh_aht_init()` вернёт `ESP_ERR_INVALID_ARG`. 5. **Статистика глобальна** — `_stats` общая для всех экземпляров датчиков: невозможно определить, какой именно датчик вызвал ошибку I2C. 6. **Блокирующие задержки** — `zh_aht_read()` содержит `vTaskDelay(80 мс)` на время измерения, `zh_aht_reset()` — до 30 мс; функции нельзя вызывать из контекста, где недопусто переключение задач. 7. **Необходим откалиброванный датчик** — если после инициализации и сброса бит калибровки `0x08` остаётся сброшенным, `zh_aht_read()` будет стабильно возвращать `ESP_ERR_INVALID_STATE`. ## Требования для сборки - ESP-IDF версии `>=5.0` (используется новый API `driver/i2c_master.h`). - Компонент `driver` (ESP-IDF 5.x) либо `esp_driver_i2c` (ESP-IDF 6.x) — выбирается автоматически в `CMakeLists.txt` по версии фреймворка. - FreeRTOS (входит в состав ESP-IDF) — используется `vTaskDelay`. ## Установка ```bash cd ваш_проект/components git clone https://github.com/aZholtikov/zh_aht ``` ```c #include "zh_aht.h" ```