doc: added wiki

This commit is contained in:
aZholtikov committed 2026-10-17 21:36:38 +03:00
1 parent 938eb7735d
commit 1bc908eb50
1 file changed
+283
+283
View File
@@ -0,0 +1,283 @@
# Описание компонента 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"
```