diff --git a/AHT40 datasheet.pdf b/AHT40 datasheet.pdf new file mode 100644 index 0000000..420847e Binary files /dev/null and b/AHT40 datasheet.pdf differ diff --git a/README.md b/README.md index 56585e7..07bbf94 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ## Features -1. Support of AHT10/AHT15/AHT20/AHT21/AHT25/AHT30. +1. Support of AHT10/AHT15/AHT20/AHT21/AHT25/AHT30/AHT40. 2. Support some sensors on one device with [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a). ## Attention @@ -41,109 +41,6 @@ In the application, add the component: #include "zh_aht.h" ``` -## Example +## Examples -Reading the sensor: - -```c -#include "zh_aht.h" - -#define I2C_PORT (I2C_NUM_MAX - 1) - -zh_aht_handle_t aht_handle = {0}; - -void app_main(void) -{ - esp_log_level_set("zh_aht", ESP_LOG_ERROR); - i2c_master_bus_config_t i2c_bus_config = { - .clk_source = I2C_CLK_SRC_DEFAULT, - .i2c_port = I2C_PORT, - .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 (;;) - { - zh_aht_read(&aht_handle, &humidity, &temperature); - printf("Humidity %0.2f\n", humidity); - printf("Temperature %0.2f\n", temperature); - const zh_aht_stats_t *stats = zh_aht_get_stats(); - printf("Number of i2c driver error: %ld.\n", stats->i2c_driver_error); - vTaskDelay(5000 / portTICK_PERIOD_MS); - } -} -``` - -3 sensor on one device: - -```c -#include "zh_pca9548a.h" -#include "zh_aht.h" - -#define I2C_PORT (I2C_NUM_MAX - 1) - -zh_pca9548a_handle_t pca9548a_handle = {0}; -zh_aht_handle_t aht_handle_chan_0 = {0}; -zh_aht_handle_t aht_handle_chan_1 = {0}; -zh_aht_handle_t aht_handle_chan_2 = {0}; - -void app_main(void) -{ - esp_log_level_set("zh_pca9548a", ESP_LOG_ERROR); - esp_log_level_set("zh_aht", ESP_LOG_ERROR); - i2c_master_bus_config_t i2c_bus_config = { - .clk_source = I2C_CLK_SRC_DEFAULT, - .i2c_port = I2C_PORT, - .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_pca9548a_init_config_t config = ZH_PCA9548A_INIT_CONFIG_DEFAULT(); - config.i2c_handle = i2c_bus_handle; - config.i2c_address = 0x70; - zh_pca9548a_init(&config, &pca9548a_handle); - zh_aht_init_config_t aht_config = ZH_AHT_INIT_CONFIG_DEFAULT(); - aht_config.i2c_handle = i2c_bus_handle; - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_0); - zh_aht_init(&aht_config, &aht_handle_chan_0); - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_1); - zh_aht_init(&aht_config, &aht_handle_chan_1); - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_2); - zh_aht_init(&aht_config, &aht_handle_chan_2); - float humidity = 0.0; - float temperature = 0.0; - for (;;) - { - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_0); - zh_aht_read(&aht_handle_chan_0, &humidity, &temperature); - printf("Sensor 1. Humidity %0.2f\n", humidity); - printf("Sensor 1. Temperature %0.2f\n", temperature); - humidity = 0.0; - temperature = 0.0; - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_1); - zh_aht_read(&aht_handle_chan_1, &humidity, &temperature); - printf("Sensor 2. Humidity %0.2f\n", humidity); - printf("Sensor 2. Temperature %0.2f\n", temperature); - humidity = 0.0; - temperature = 0.0; - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_2); - zh_aht_read(&aht_handle_chan_2, &humidity, &temperature); - printf("Sensor 3. Humidity %0.2f\n", humidity); - printf("Sensor 3. Temperature %0.2f\n", temperature); - humidity = 0.0; - temperature = 0.0; - vTaskDelay(5000 / portTICK_PERIOD_MS); - } -} -``` +See Wiki [EN](WIKI_EN.md#usage-examples) | [RU](WIKI_RU.md#примеры-использования) \ No newline at end of file diff --git a/WIKI_EN.md b/WIKI_EN.md index 79b279c..8df34db 100644 --- a/WIKI_EN.md +++ b/WIKI_EN.md @@ -16,7 +16,7 @@ ## Overview -`zh_aht` is a lightweight ESP-IDF component for AHT10/AHT15/AHT20/AHT21/AHT25/AHT30 humidity and temperature sensors. It provides a simple API to read humidity values from 0 to 100% and temperature values from -40 to +85°C. The component supports multiple sensors on a single I2C bus using the [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a) I2C multiplexer. +`zh_aht` is a lightweight ESP-IDF component for AHT10/AHT15/AHT20/AHT21/AHT25/AHT30/AHT40 humidity and temperature sensors. It provides a simple API to read humidity values from 0 to 100% and temperature values from -40 to +85°C. The component supports multiple sensors on a single I2C bus using the [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a) I2C multiplexer. The component is designed specifically for ESP32 microcontrollers and uses ESP-IDF v5.0+ I2C driver API. @@ -26,8 +26,8 @@ The component is designed specifically for ESP32 microcontrollers and uses ESP-I 1. **Humidity and Temperature Measurement**: Reads humidity from 0 to 100% and temperature from -40 to +85°C 2. **I2C Interface**: Uses standard I2C protocol (400 kHz max frequency) -3. **Configurable I2C Address**: Supports both 0x38 and 0x39 addresses -4. **Multiple Model Support**: AHT10, AHT15, AHT20, AHT21, AHT25, AHT30 +3. **Configurable I2C Address**: Supports 0x38, 0x39 and 0x44 addresses +4. **Multiple Model Support**: AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40 5. **Error Statistics**: Built-in error counter for I2C driver errors 6. **Low Power**: Works with ESP-IDF power management 7. **Thread-Safe**: Uses ESP-IDF I2C driver (thread-safe) @@ -75,13 +75,15 @@ To use multiple AHT sensors on the same I2C bus, also install the [zh_pca9548a]( ## API Reference +All functions in this library use double pointer (`zh_aht_handle_t **`) for the sensor handler to allow for proper memory management and thread-safe operations. + ### zh_aht_init_config_t Structure ```c typedef struct { i2c_master_bus_handle_t i2c_handle; // Unique I2C bus handle - uint8_t i2c_address; // Sensor I2C address (0x38 or 0x39) + uint8_t i2c_address; // Sensor I2C address (0x38, 0x39, or 0x44) uint32_t i2c_frequency; // Sensor I2C frequency (max 400000 Hz) } zh_aht_init_config_t; ``` @@ -95,13 +97,13 @@ Use `ZH_AHT_INIT_CONFIG_DEFAULT()` macro to initialize with default values: ### zh_aht_handle_t Structure -```c -typedef struct -{ - bool is_initialized; // Sensor initialization flag - i2c_master_dev_handle_t dev_handle; // Unique I2C device handle -} zh_aht_handle_t; -``` +The structure is declared as `typedef struct _zh_aht_handle_t zh_aht_handle_t;` and encapsulates internal implementation details. + +**Fields (internal):** + +| Field | Type | Description | +|-------|------|-------------| +| `i2c_master_dev_handle_t` | `dev_handle` | Pointer to I2C device handle. | --- @@ -123,18 +125,21 @@ Initializes the AHT sensor. **Parameters:** - `config` - Pointer to AHT initialization configuration structure -- `handle` - Pointer to unique AHT handle +- `handle` - Double pointer to unique AHT handle (`zh_aht_handle_t **`). If the pointer is NULL, memory will be allocated. **Returns:** - `ESP_OK` - Success - `ESP_ERR_INVALID_ARG` - Invalid argument (NULL config or handle) -- `ESP_FAIL` - Initialization failed (configuration check or I2C device addition) +- `ESP_ERR_INVALID_STATE` - Sensor is already initialized +- `ESP_ERR_NO_MEM` - Memory allocation failed +- `ESP_FAIL` - Initialization I2C device failed +- `ESP_ERR_NOT_FOUND` - Sensor did not respond **Example:** ```c -zh_aht_handle_t aht_handle = {0}; +zh_aht_handle_t *aht_handle = NULL; zh_aht_init_config_t config = ZH_AHT_INIT_CONFIG_DEFAULT(); config.i2c_handle = i2c_bus_handle; zh_aht_init(&config, &aht_handle); @@ -144,17 +149,16 @@ zh_aht_init(&config, &aht_handle); ### zh_aht_deinit() -Deinitializes the AHT sensor and removes it from the I2C bus. +Deinitializes the AHT sensor and removes it from the I2C bus. Memory for handle is automatically freed and the pointer is set to NULL. **Parameters:** -- `handle` - Pointer to unique AHT handle +- `handle` - Double pointer to unique AHT handle (`zh_aht_handle_t **`). Must not be NULL. **Returns:** - `ESP_OK` - Success -- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle) -- `ESP_ERR_INVALID_STATE` - Sensor not initialized +- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle pointer) - `ESP_FAIL` - I2C device removal failed --- @@ -165,18 +169,17 @@ Reads humidity and temperature values from the sensor. **Parameters:** -- `handle` - Pointer to unique AHT handle +- `handle` - Double pointer to unique AHT handle (`zh_aht_handle_t **`). Must not be NULL. - `humidity` - Pointer to store humidity value (in %) - `temperature` - Pointer to store temperature value (in °C) **Returns:** - `ESP_OK` - Success -- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle, humidity or temperature) -- `ESP_ERR_NOT_FOUND` - Sensor not initialized +- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle pointer or humidity pointer or temperature pointer) +- `ESP_FAIL` - I2C communication error - `ESP_ERR_TIMEOUT` - Timeout waiting for data ready - `ESP_ERR_INVALID_CRC` - Invalid CRC checksum -- `ESP_FAIL` - I2C communication error **Note:** The function performs measurement and waits ~80ms for data ready. @@ -188,13 +191,12 @@ Resets the AHT sensor. **Parameters:** -- `handle` - Pointer to unique AHT handle +- `handle` - Double pointer to unique AHT handle (`zh_aht_handle_t **`). Must not be NULL. **Returns:** - `ESP_OK` - Success -- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle) -- `ESP_ERR_NOT_FOUND` - Sensor not initialized +- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle pointer) - `ESP_FAIL` - I2C communication error --- @@ -207,25 +209,12 @@ Gets error statistics since last reset. - Pointer to the statistics structure -**Example:** - -```c -const zh_aht_stats_t *stats = zh_aht_get_stats(); -printf("I2C errors: %ld\n", stats->i2c_driver_error); -``` - --- ### zh_aht_reset_stats() Resets error statistics counter. -**Example:** - -```c -zh_aht_reset_stats(); -``` - --- ## Usage Examples @@ -237,7 +226,7 @@ zh_aht_reset_stats(); #define I2C_PORT (I2C_NUM_MAX - 1) -zh_aht_handle_t aht_handle = {0}; +zh_aht_handle_t *aht_handle = NULL; void app_main(void) { @@ -280,9 +269,9 @@ void app_main(void) #define I2C_PORT (I2C_NUM_MAX - 1) zh_pca9548a_handle_t pca9548a_handle = {0}; -zh_aht_handle_t aht_handle_chan_0 = {0}; -zh_aht_handle_t aht_handle_chan_1 = {0}; -zh_aht_handle_t aht_handle_chan_2 = {0}; +zh_aht_handle_t *aht_handle_chan_0 = NULL; +zh_aht_handle_t *aht_handle_chan_1 = NULL; +zh_aht_handle_t *aht_handle_chan_2 = NULL; void app_main(void) { @@ -346,10 +335,10 @@ void app_main(void) | **Temperature Range** | -40 - +85°C | | **Humidity Resolution** | 0.01% | | **Temperature Resolution** | 0.01°C | -| **I2C Address** | 0x38, 0x39 | +| **I2C Address** | 0x38, 0x39, 0x44 | | **I2C Frequency** | Up to 400 kHz | | **Measurement Time** | ~80 ms | -| **Supported Models** | AHT10, AHT15, AHT20, AHT21, AHT25, AHT30 | +| **Supported Models** | AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40 | | **ESP-IDF Version** | >= 5.0 | | **Platform** | ESP32 series | | **Language** | C (C99) | @@ -362,8 +351,9 @@ void app_main(void) |------------|-------------| | `ESP_OK` | Operation successful | | `ESP_ERR_INVALID_ARG` | Invalid argument (NULL pointer or invalid configuration) | -| `ESP_ERR_INVALID_STATE` | Sensor not initialized | -| `ESP_ERR_NOT_FOUND` | Sensor not initialized or not responding | +| `ESP_ERR_INVALID_STATE` | Sensor is already initialized | +| `ESP_ERR_NO_MEM` | Memory allocation failed | +| `ESP_ERR_NOT_FOUND` | Sensor did not respond | | `ESP_ERR_TIMEOUT` | Timeout waiting for data ready | | `ESP_ERR_INVALID_CRC` | Invalid CRC checksum | | `ESP_FAIL` | General failure (I2C communication error) | @@ -412,7 +402,8 @@ limitations under the License. - **Measurement Time**: The sensor requires ~80ms for each measurement - **I2C_ISR_IRAM_SAFE**: For correct operation, enable `I2C_ISR_IRAM_SAFE` and `I2C_MASTER_ISR_HANDLER_IN_IRAM` in menuconfig - **Thread Safety**: The component uses ESP-IDF I2C driver which is thread-safe +- **Memory Management**: All API functions now use double pointers for handle, which allows automatic memory management (freeing on deinitialization, setting to NULL) --- -*Generated for zh_aht v3.4.0* +*Generated for zh_aht v4.0.0* diff --git a/WIKI_RU.md b/WIKI_RU.md index 8dfb678..a359d67 100644 --- a/WIKI_RU.md +++ b/WIKI_RU.md @@ -16,7 +16,7 @@ ## Обзор -`zh_aht` - это легковесный компонент ESP-IDF для датчиков влажности и температуры AHT10/AHT15/AHT20/AHT21/AHT25/AHT30. Он предоставляет простой API для считывания значений влажности от 0 до 100% и температуры от -40 до +85°C. Компонент поддерживает несколько датчиков на одной шине I2C с использованием I2C мультиплексатора [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a). +`zh_aht` - это легковесный компонент ESP-IDF для датчиков влажности и температуры AHT10/AHT15/AHT20/AHT21/AHT25/AHT30/AHT40. Он предоставляет простой API для считывания значений влажности от 0 до 100% и температуры от -40 до +85°C. Компонент поддерживает несколько датчиков на одной шине I2C с использованием I2C мультиплексатора [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a). Компонент разработан специально для микроконтроллеров ESP32 и использует API драйвера I2C ESP-IDF v5.0+. @@ -26,8 +26,8 @@ 1. **Измерение влажности и температуры**: Считывание значений влажности от 0 до 100% и температуры от -40 до +85°C 2. **Интерфейс I2C**: Использует стандартный протокол I2C (макс. частота 400 кГц) -3. **Настраиваемый адрес I2C**: Поддерживает адреса 0x38 и 0x39 -4. **Поддержка нескольких моделей**: AHT10, AHT15, AHT20, AHT21, AHT25, AHT30 +3. **Настраиваемый адрес I2C**: Поддерживает адреса 0x38, 0x39 и 0x44 +4. **Поддержка нескольких моделей**: AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40 5. **Статистика ошибок**: Встроенный счетчик ошибок драйвера I2C 6. **Низкое энергопотребление**: Работает с системой управления питанием ESP-IDF 7. **Потокобезопасность**: Использует драйвер I2C ESP-IDF (потокобезопасен) @@ -75,13 +75,15 @@ I2C_MASTER_ISR_HANDLER_IN_IRAM ## Справочник API +Все функции в этой библиотеке используют двойной указатель (`zh_aht_handle_t **`) для дескриптора датчика, чтобы обеспечить правильное управление памятью и потокобезопасность. + ### Структура zh_aht_init_config_t ```c typedef struct { i2c_master_bus_handle_t i2c_handle; // Уникальный дескриптор шины I2C - uint8_t i2c_address; // Адрес устройства датчика I2C (0x38 или 0x39) + uint8_t i2c_address; // Адрес устройства датчика I2C (0x38, 0x39 или 0x44) uint32_t i2c_frequency; // Частота I2C датчика (макс. 400000 Гц) } zh_aht_init_config_t; ``` @@ -95,13 +97,13 @@ typedef struct ### Структура zh_aht_handle_t -```c -typedef struct -{ - bool is_initialized; // Флаг инициализации датчика - i2c_master_dev_handle_t dev_handle; // Уникальный дескриптор устройства I2C -} zh_aht_handle_t; -``` +Структура объявляется как `typedef struct _zh_aht_handle_t zh_aht_handle_t;` и инкапсулирует детали внутренней реализации. + +**Поля (внутренние):** + +| Поле | Тип | Описание | +|------|-----|----------| +| `i2c_master_dev_handle_t` | `dev_handle` | Указатель на дескриптор устройства I2C. | --- @@ -123,18 +125,21 @@ typedef struct **Параметры:** - `config` - Указатель на структуру конфигурации инициализации AHT -- `handle` - Указатель на уникальный дескриптор AHT +- `handle` - Двойной указатель на уникальный дескриптор AHT (`zh_aht_handle_t **`). Если указатель NULL, память будет выделена. **Возвращает:** - `ESP_OK` - Успех - `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL config или handle) -- `ESP_FAIL` - Ошибка инициализации (проверка конфигурации или добавление устройства I2C) +- `ESP_ERR_INVALID_STATE` - Датчик уже инициализирован +- `ESP_ERR_NO_MEM` - Ошибка выделения памяти +- `ESP_FAIL` - Ошибка инициализации устройства I2C +- `ESP_ERR_NOT_FOUND` - Датчик не отвечает **Пример:** ```c -zh_aht_handle_t aht_handle = {0}; +zh_aht_handle_t *aht_handle = NULL; zh_aht_init_config_t config = ZH_AHT_INIT_CONFIG_DEFAULT(); config.i2c_handle = i2c_bus_handle; zh_aht_init(&config, &aht_handle); @@ -144,17 +149,16 @@ zh_aht_init(&config, &aht_handle); ### zh_aht_deinit() -Деинициализирует датчик AHT и удаляет его из шины I2C. +Деинициализирует датчик AHT и удаляет его из шины I2C. Память для handle автоматически освобождается, а указатель устанавливается в NULL. **Параметры:** -- `handle` - Указатель на уникальный дескриптор AHT +- `handle` - Двойной указатель на уникальный дескриптор AHT (`zh_aht_handle_t **`). Не должен быть NULL. **Возвращает:** - `ESP_OK` - Успех - `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle) -- `ESP_ERR_INVALID_STATE` - Датчик не инициализирован - `ESP_FAIL` - Ошибка удаления устройства I2C --- @@ -165,7 +169,7 @@ zh_aht_init(&config, &aht_handle); **Параметры:** -- `handle` - Указатель на уникальный дескриптор AHT +- `handle` - Двойной указатель на уникальный дескриптор AHT (`zh_aht_handle_t **`). Не должен быть NULL. - `humidity` - Указатель для хранения значения влажности (в %) - `temperature` - Указатель для хранения значения температуры (в °C) @@ -173,10 +177,9 @@ zh_aht_init(&config, &aht_handle); - `ESP_OK` - Успех - `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle, humidity или temperature) -- `ESP_ERR_NOT_FOUND` - Датчик не инициализирован +- `ESP_FAIL` - Ошибка связи I2C - `ESP_ERR_TIMEOUT` - Превышено время ожидания готовности данных - `ESP_ERR_INVALID_CRC` - Неверная CRC контрольная сумма -- `ESP_FAIL` - Ошибка связи I2C **Примечание:** Функция выполняет измерение и ожидает ~80 мс готовности данных. @@ -188,13 +191,12 @@ zh_aht_init(&config, &aht_handle); **Параметры:** -- `handle` - Указатель на уникальный дескриптор AHT +- `handle` - Двойной указатель на уникальный дескриптор AHT (`zh_aht_handle_t **`). Не должен быть NULL. **Возвращает:** - `ESP_OK` - Успех - `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle) -- `ESP_ERR_NOT_FOUND` - Датчик не инициализирован - `ESP_FAIL` - Ошибка связи I2C --- @@ -207,25 +209,12 @@ zh_aht_init(&config, &aht_handle); - Указатель на структуру статистики -**Пример:** - -```c -const zh_aht_stats_t *stats = zh_aht_get_stats(); -printf("Ошибки I2C: %ld\n", stats->i2c_driver_error); -``` - --- ### zh_aht_reset_stats() Сбрасывает счетчик статистики ошибок. -**Пример:** - -```c -zh_aht_reset_stats(); -``` - --- ## Примеры использования @@ -237,7 +226,7 @@ zh_aht_reset_stats(); #define I2C_PORT (I2C_NUM_MAX - 1) -zh_aht_handle_t aht_handle = {0}; +zh_aht_handle_t *aht_handle = NULL; void app_main(void) { @@ -280,9 +269,9 @@ void app_main(void) #define I2C_PORT (I2C_NUM_MAX - 1) zh_pca9548a_handle_t pca9548a_handle = {0}; -zh_aht_handle_t aht_handle_chan_0 = {0}; -zh_aht_handle_t aht_handle_chan_1 = {0}; -zh_aht_handle_t aht_handle_chan_2 = {0}; +zh_aht_handle_t *aht_handle_chan_0 = NULL; +zh_aht_handle_t *aht_handle_chan_1 = NULL; +zh_aht_handle_t *aht_handle_chan_2 = NULL; void app_main(void) { @@ -346,10 +335,10 @@ void app_main(void) | **Диапазон температуры** | -40 - +85°C | | **Разрешение измерения влажности** | 0.01% | | **Разрешение измерения температуры** | 0.01°C | -| **Адрес I2C** | 0x38, 0x39 | +| **Адрес I2C** | 0x38, 0x39, 0x44 | | **Частота I2C** | До 400 кГц | | **Время измерения** | ~80 мс | -| **Поддерживаемые модели** | AHT10, AHT15, AHT20, AHT21, AHT25, AHT30 | +| **Поддерживаемые модели** | AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40 | | **Версия ESP-IDF** | >= 5.0 | | **Платформа** | Семейство ESP32 | | **Язык** | C (C99) | @@ -362,8 +351,9 @@ void app_main(void) |------------|----------| | `ESP_OK` | Операция выполнена успешно | | `ESP_ERR_INVALID_ARG` | Неверный аргумент (NULL указатель или неверная конфигурация) | -| `ESP_ERR_INVALID_STATE` | Датчик не инициализирован | -| `ESP_ERR_NOT_FOUND` | Датчик не инициализирован или не отвечает | +| `ESP_ERR_INVALID_STATE` | Датчик уже инициализирован | +| `ESP_ERR_NO_MEM` | Ошибка выделения памяти | +| `ESP_ERR_NOT_FOUND` | Датчик не отвечает | | `ESP_ERR_TIMEOUT` | Превышено время ожидания готовности данных | | `ESP_ERR_INVALID_CRC` | Неверная CRC контрольная сумма | | `ESP_FAIL` | Общая ошибка (ошибка связи I2C) | @@ -412,7 +402,8 @@ void app_main(void) - **Время измерения**: Датчик требует ~80 мс для каждого измерения - **I2C_ISR_IRAM_SAFE**: Для правильной работы включите `I2C_ISR_IRAM_SAFE` и `I2C_MASTER_ISR_HANDLER_IN_IRAM` в menuconfig - **Потокобезопасность**: Компонент использует драйвер I2C ESP-IDF, который является потокобезопасным +- **Управление памятью**: Все функции API теперь используют двойные указатели для handle, что позволяет автоматически управлять памятью (освобождение при деинициализации, установка в NULL) --- -*Сгенерировано для zh_aht v3.4.0* +*Сгенерировано для zh_aht v4.0.0* diff --git a/idf_component.yml b/idf_component.yml index 7cbca95..78c84b5 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,6 +1,6 @@ description: ESP32 ESP-IDF component for AHT10/AHT15/AHT20/AHT21/AHT25/AHT30 humidity & temperature sensor url: https://github.com/aZholtikov/zh_aht -version: "3.4.0" +version: "4.0.0" license: "Apache-2.0" maintainers: - "Alexey Zholtikov " diff --git a/include/zh_aht.h b/include/zh_aht.h index 3ddb55e..3e0fee9 100644 --- a/include/zh_aht.h +++ b/include/zh_aht.h @@ -1,5 +1,14 @@ /** * @file zh_aht.h + * + * @brief ESP-IDF driver for AHT family sensors (AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40). + * + * This module provides a high-level interface to control AHT family I2C temperature & humidity sensors. + * All devices (AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40) share the same command set, data format, and CRC algorithm. + * + * @note Supported devices: AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40, and compatible clones. + * @note Default I2C address: 0x38 (ADR tied to GND). Some variants (e.g., AHT20/30/40 on certain boards) may use 0x39 (ADR tied to VDD). + * @note Measurement range and accuracy differ between variants, but communication protocol is identical. */ #pragma once @@ -9,100 +18,118 @@ #include "driver/i2c_master.h" #include "freertos/FreeRTOS.h" +#ifdef __cplusplus +extern "C" +{ +#endif + /** - * @brief AHT sensor initial default values. + * @brief Default AHT initialization configuration. + * + * @note I2C frequency: 400 kHz (fast mode — supported by all AHT variants). + * @note I2C address: 0x38 (ADR = GND). If ADR is pulled high (VDD), use 0x39. For AHT40 use 0x44. + * + * @warning AHT20/30 may be configured to 0x39 by hardware — check your board! */ #define ZH_AHT_INIT_CONFIG_DEFAULT() \ { \ .i2c_frequency = 400000, \ .i2c_address = 0x38} -#ifdef __cplusplus -extern "C" -{ -#endif + /** + * @brief Opaque handle type for AHT family sensor instance. + */ + typedef struct _zh_aht_handle_t zh_aht_handle_t; /** - * @brief Structure for initial initialization of AHT sensor. + * @brief AHT sensor initialization configuration structure. */ typedef struct { - i2c_master_bus_handle_t i2c_handle; /*!< Unique I2C bus handle. */ - uint8_t i2c_address; /*!< Sensor I2C address. */ - uint32_t i2c_frequency; /*!< Sensor I2C frequency. */ + i2c_master_bus_handle_t i2c_handle; /*!< I2C bus handle (created via i2c_master_bus_create() or i2c_master_bus_alloc_handle()). */ + uint8_t i2c_address; /*!< I2C address: 0x38 (ADR=GND) or 0x39 (ADR=VDD) or 0x44 (AHT40). */ + uint32_t i2c_frequency; /*!< I2C clock frequency in Hz (≤ 400000). */ } zh_aht_init_config_t; /** - * @brief AHT sensor handle. + * @brief Error statistics structure. */ typedef struct { - bool is_initialized; /*!< Sensor initialization flag. */ - i2c_master_dev_handle_t dev_handle; /*!< Unique I2C device handle. */ - } zh_aht_handle_t; - - /** - * @brief Structure for error statistics storage. - */ - typedef struct - { - uint32_t i2c_driver_error; /*!< Number of i2c driver error. */ + uint32_t i2c_driver_error; /*!< Number of I2C errors in zh_aht_read()/zh_aht_reset(). */ } zh_aht_stats_t; /** - * @brief Initialize AHT sensor. + * @brief Initialize an AHT family sensor and allocate handle. * - * @param[in] config Pointer to AHT initialized configuration structure. Can point to a temporary variable. - * @param[out] handle Pointer to unique AHT handle. + * Validates configuration, allocates handle, registers device, probes sensor, and triggers calibration if needed. * - * @attention I2C driver must be initialized first. + * @param[in] config Pointer to initialized configuration (must be non `NULL`, valid). + * @param[out] handle Double pointer to handle structure (`zh_aht_handle_t **`). On success, `*handle` points to newly allocated structure. * - * @note Before initialize the sensor recommend initialize zh_aht_init_config_t structure with default values. - * - * @code zh_aht_init_config_t config = ZH_AHT_INIT_CONFIG_DEFAULT() @endcode - * - * @return ESP_OK if success or an error code otherwise. + * @return ESP_OK on success. + * @return ESP_ERR_INVALID_ARG if `config == NULL` or `handle == NULL`. + * @return ESP_ERR_INVALID_STATE if `*handle != NULL` (already initialized). + * @return ESP_ERR_NO_MEM if handle allocation failed. + * @return ESP_FAIL if I2C registration failed. + * @return ESP_ERR_NOT_FOUND if sensor did not respond. */ - esp_err_t zh_aht_init(const zh_aht_init_config_t *config, zh_aht_handle_t *handle); + esp_err_t zh_aht_init(const zh_aht_init_config_t *config, zh_aht_handle_t **handle); /** - * @brief Deinitialize AHT sensor. + * @brief Deinitialize the AHT sensor and release resources. * - * @param[in] handle Pointer to unique AHT handle. + * Removes I2C device, frees handle, sets `*handle = NULL`. * - * @return ESP_OK if success or an error code otherwise. + * @param[in,out] handle Double pointer to handle structure (`zh_aht_handle_t **`). Must not be `NULL`. Set to `NULL` on exit. + * + * @return ESP_OK on success. + * @return ESP_ERR_INVALID_ARG if `handle == NULL` or `*handle == NULL` (not initialized). + * @return ESP_FAIL if failed to remove I2C device. */ - esp_err_t zh_aht_deinit(zh_aht_handle_t *handle); + esp_err_t zh_aht_deinit(zh_aht_handle_t **handle); /** - * @brief Read AHT sensor. + * @brief Trigger measurement and read temperature & humidity. * - * @param[in] handle Pointer to unique AHT handle. - * @param[out] humidity Pointer for AHT sensor reading data of humidity. - * @param[out] temperature Pointer for AHT sensor reading data of temperature. + * Sends read command, waits 80 ms, validates CRC/status, and converts to physical units. * - * @return ESP_OK if success or an error code otherwise. + * @param[in] handle Double pointer to handle structure (`zh_aht_handle_t **`). Must not be `NULL`. + * @param[out] humidity Pointer to store humidity (%RH). + * @param[out] temperature Pointer to store temperature (°C). + * + * @return ESP_OK on success. + * @return ESP_ERR_INVALID_ARG if `handle == NULL` or `*handle == NULL` (not initialized) or `humidity == NULL` or `temperature == NULL`. + * @return ESP_FAIL if I2C error. + * @return ESP_ERR_TIMEOUT if sensor still busy. + * @return ESP_ERR_INVALID_CRC if CRC mismatch. */ - esp_err_t zh_aht_read(zh_aht_handle_t *handle, float *humidity, float *temperature); + esp_err_t zh_aht_read(zh_aht_handle_t **handle, float *humidity, float *temperature); /** - * @brief Reset AHT sensor. + * @brief Soft reset the AHT sensor. * - * @param[in] handle Pointer to unique AHT handle. + * Sends reset command and waits 20 ms for recovery. * - * @return ESP_OK if success or an error code otherwise. + * @param[in] handle Double pointer to handle structure (`zh_aht_handle_t **`). Must not be `NULL`. + * + * @return ESP_OK on success. + * @return ESP_ERR_INVALID_ARG if `handle == NULL` or `*handle == NULL` (not initialized). + * @return ESP_FAIL if I2C error. */ - esp_err_t zh_aht_reset(zh_aht_handle_t *handle); + esp_err_t zh_aht_reset(zh_aht_handle_t **handle); /** - * @brief Get error statistics. + * @brief Get pointer to internal error statistics. * - * @return Pointer to the statistics structure. + * @return Pointer to static statistics structure. */ const zh_aht_stats_t *zh_aht_get_stats(void); /** - * @brief Reset error statistics. + * @brief Reset error statistics counter. + * + * Sets `i2c_driver_error = 0`. */ void zh_aht_reset_stats(void); diff --git a/version.txt b/version.txt index fbcbf73..0c89fc9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.4.0 \ No newline at end of file +4.0.0 \ No newline at end of file diff --git a/zh_aht.c b/zh_aht.c index a1ea116..1892797 100644 --- a/zh_aht.c +++ b/zh_aht.c @@ -13,47 +13,75 @@ static const char *TAG = "zh_aht"; return err; \ } -static const uint8_t _aht_reset_command = 0xBA; -static const uint8_t _aht_read_command[] = {0xAC, 0x33, 0x00}; -static const uint8_t _aht_init_command[] = {0xBE, 0x08, 0x00}; +/** + * @brief Opaque handle structure for the AHT family sensor (AHT10/AHT15/AHT20/AHT21/AHT25/AHT30/AHT40). + * + * This structure encapsulates the I2C device handle required to communicate with any sensor + * in the AHT family. All AHT sensors share the same I2C protocol, register map, and CRC algorithm. + * + * @note Supported devices: AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40, and compatible clones. + * @note Default I2C addresses: + * - AHT10: 0x38 (ADR tied to GND) + * - AHT20/30: 0x38 (ADR tied to GND), 0x39 (ADR tied to VDD) + * - AHT40: 0x44 + * @note Some variants may differ in measurement range and accuracy, but not in communication protocol. + * + * @note Do not instantiate or modify this structure directly. All interactions must go through + * the public API functions: zh_aht_init(), zh_aht_read(), zh_aht_deinit(), etc. + */ +struct _zh_aht_handle_t +{ + i2c_master_dev_handle_t dev_handle; /*!< I2C device handle (ESP-IDF i2c_master API) used for all bus transactions. */ +}; + +/** + * @brief AHT family command constants. + * + * @note All AHT family members (AHT10, AHT15, AHT20, AHT21, AHT25, AHT30, AHT40) support these commands. + * @note CRC algorithm, data format, and command structure are identical across family. + */ +static const uint8_t _aht_reset_command = 0xBA; /*!< Soft reset command (AHT family common). */ +static const uint8_t _aht_read_command[] = {0xAC, 0x33, 0x00}; /*!< Trigger measurement and read data command. Format: 0xAC (measurement trigger) + 0x33 (normal mode) + 0x00 (unused byte). */ +static const uint8_t _aht_init_command[] = {0xBE, 0x08, 0x00}; /*!< Self-calibration/initialization command (must be sent if CALIBRATED bit (status[3]) = 0). */ static zh_aht_stats_t _stats = {0}; static esp_err_t _zh_aht_validate_config(const zh_aht_init_config_t *config); -static esp_err_t _zh_aht_i2c_init(const zh_aht_init_config_t *config, zh_aht_handle_t *handle); +static esp_err_t _zh_aht_i2c_init(const zh_aht_init_config_t *config, zh_aht_handle_t **handle); static uint8_t _zh_aht_calc_crc(const uint8_t *buf, size_t len); -esp_err_t zh_aht_init(const zh_aht_init_config_t *config, zh_aht_handle_t *handle) +esp_err_t zh_aht_init(const zh_aht_init_config_t *config, zh_aht_handle_t **handle) // -V2008 { ZH_LOGI("AHT initialization begin."); ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT initialization failed. Invalid argument."); - ZH_ERROR_CHECK(_zh_aht_validate_config(config) == ESP_OK, ESP_FAIL, NULL, "AHT initialization failed. Initial configuration check failed."); - ZH_ERROR_CHECK(_zh_aht_i2c_init(config, handle) == ESP_OK, ESP_FAIL, NULL, "AHT initialization failed. Failed to add I2C device."); - handle->is_initialized = true; + ZH_ERROR_CHECK(_zh_aht_validate_config(config) == ESP_OK, ESP_ERR_INVALID_ARG, NULL, "AHT initialization failed. Invalid config."); + ZH_ERROR_CHECK(*handle == NULL, ESP_ERR_INVALID_STATE, NULL, "AHT initialization failed. AHT is already initialized."); + *handle = heap_caps_calloc(1, sizeof(zh_aht_handle_t), MALLOC_CAP_8BIT); + ZH_ERROR_CHECK(*handle != NULL, ESP_ERR_NO_MEM, NULL, "AHT initialization failed. Failed to allocate AHT handle."); + ZH_ERROR_CHECK(_zh_aht_i2c_init(config, handle) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "AHT initialization failed. I2C init failed."); ZH_LOGI("AHT initialization completed successfully."); return ESP_OK; } -esp_err_t zh_aht_deinit(zh_aht_handle_t *handle) +esp_err_t zh_aht_deinit(zh_aht_handle_t **handle) { ZH_LOGI("AHT deinitialization started."); - ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT deinitialization failed. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "AHT deinitialization failed. AHT not initialized."); - ZH_ERROR_CHECK(i2c_master_bus_rm_device(handle->dev_handle) == ESP_OK, ESP_FAIL, NULL, "AHT deinitialization failed. I2C remove device failed."); - handle->is_initialized = false; + ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT deinitialization failed. Invalid argument."); + ZH_ERROR_CHECK(i2c_master_bus_rm_device((*handle)->dev_handle) == ESP_OK, ESP_FAIL, NULL, "AHT deinitialization failed. I2C remove device failed."); + heap_caps_free(*handle); + *handle = NULL; ZH_LOGI("AHT deinitialization completed successfully."); return ESP_OK; } -esp_err_t zh_aht_read(zh_aht_handle_t *handle, float *humidity, float *temperature) // -V2008 +esp_err_t zh_aht_read(zh_aht_handle_t **handle, float *humidity, float *temperature) // -V2008 { ZH_LOGI("AHT read begin."); - ZH_ERROR_CHECK(humidity != NULL && temperature != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT read fail. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_NOT_FOUND, NULL, "AHT read fail. AHT not initialized."); + ZH_ERROR_CHECK(handle != NULL && *handle != NULL && humidity != NULL && temperature != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT read fail. Invalid argument."); uint8_t sensor_data[7] = {0}; - ZH_ERROR_CHECK(i2c_master_transmit(handle->dev_handle, _aht_read_command, sizeof(_aht_read_command), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, + ZH_ERROR_CHECK(i2c_master_transmit((*handle)->dev_handle, _aht_read_command, sizeof(_aht_read_command), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, ++_stats.i2c_driver_error, "AHT read fail. I2C driver error."); vTaskDelay(80 / portTICK_PERIOD_MS); - ZH_ERROR_CHECK(i2c_master_receive(handle->dev_handle, sensor_data, sizeof(sensor_data), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, + ZH_ERROR_CHECK(i2c_master_receive((*handle)->dev_handle, sensor_data, sizeof(sensor_data), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, ++_stats.i2c_driver_error, "AHT read fail. I2C driver error."); ZH_ERROR_CHECK((sensor_data[0] & 0x80) == 0, ESP_ERR_TIMEOUT, NULL, "AHT read fail. Timeout exceeded."); ZH_ERROR_CHECK(_zh_aht_calc_crc(sensor_data, 6) == sensor_data[6], ESP_ERR_INVALID_CRC, NULL, "AHT read fail. Invalid CRC."); @@ -63,12 +91,11 @@ esp_err_t zh_aht_read(zh_aht_handle_t *handle, float *humidity, float *temperatu return ESP_OK; } -esp_err_t zh_aht_reset(zh_aht_handle_t *handle) +esp_err_t zh_aht_reset(zh_aht_handle_t **handle) { ZH_LOGI("AHT reset begin."); - ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT reset fail. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_NOT_FOUND, NULL, "AHT reset fail. AHT not initialized."); - ZH_ERROR_CHECK(i2c_master_transmit(handle->dev_handle, &_aht_reset_command, sizeof(_aht_reset_command), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, + ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "AHT reset fail. Invalid argument."); + ZH_ERROR_CHECK(i2c_master_transmit((*handle)->dev_handle, &_aht_reset_command, sizeof(_aht_reset_command), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, ++_stats.i2c_driver_error, "AHT reset fail. I2C driver error."); vTaskDelay(20 / portTICK_PERIOD_MS); ZH_LOGI("AHT reset completed successfully."); @@ -89,13 +116,13 @@ void zh_aht_reset_stats(void) static esp_err_t _zh_aht_validate_config(const zh_aht_init_config_t *config) { - ZH_ERROR_CHECK(config->i2c_address == 0x38 || config->i2c_address == 0x39, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C address."); + ZH_ERROR_CHECK(config->i2c_address == 0x38 || config->i2c_address == 0x39 || config->i2c_address == 0x44, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C address."); ZH_ERROR_CHECK(config->i2c_frequency <= 400000, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C frequency."); ZH_ERROR_CHECK(config->i2c_handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C handle."); return ESP_OK; } -static esp_err_t _zh_aht_i2c_init(const zh_aht_init_config_t *config, zh_aht_handle_t *handle) // -V2008 +static esp_err_t _zh_aht_i2c_init(const zh_aht_init_config_t *config, zh_aht_handle_t **handle) // -V2008 { i2c_device_config_t aht_config = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, @@ -106,17 +133,17 @@ static esp_err_t _zh_aht_i2c_init(const zh_aht_init_config_t *config, zh_aht_han ZH_ERROR_CHECK(i2c_master_bus_add_device(config->i2c_handle, &aht_config, &_dev_handle) == ESP_OK, ESP_FAIL, NULL, "Add I2C device failed."); vTaskDelay(100 / portTICK_PERIOD_MS); ZH_ERROR_CHECK(i2c_master_probe(config->i2c_handle, config->i2c_address, 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_ERR_NOT_FOUND, - ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed."), "Sensor not connected or not responded."); + {ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "Sensor not connected or not responded."); uint8_t sensor_data = 0; ZH_ERROR_CHECK(i2c_master_receive(_dev_handle, &sensor_data, sizeof(sensor_data), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed."), "I2C driver error."); + {ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "I2C driver error."); if ((sensor_data & 0x08) == 0) { ZH_ERROR_CHECK(i2c_master_transmit(_dev_handle, _aht_init_command, sizeof(_aht_init_command), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed."), "I2C driver error."); + {ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "I2C driver error."); vTaskDelay(20 / portTICK_PERIOD_MS); } - handle->dev_handle = _dev_handle; + (*handle)->dev_handle = _dev_handle; return ESP_OK; }