Files
2026-10-17 21:36:38 +03:00

284 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Описание компонента 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"
```