284 lines
18 KiB
Markdown
284 lines
18 KiB
Markdown
# Описание компонента 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"
|
||
```
|