Skip to content

Commit 36d4563

Browse files
committed
feat(sensors): Add AHT30 sensor driver
1 parent 3936e75 commit 36d4563

8 files changed

Lines changed: 549 additions & 0 deletions

File tree

.github/workflows/upload_component.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ jobs:
103103
components/io_expander/esp_io_expander_ht8574
104104
components/io_expander/esp_io_expander_pi4ioe5v6408
105105
components/sensors/icm42670
106+
components/sensors/aht30
106107
namespace: "espressif"
107108
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
108109
dry_run: ${{ github.ref_name != 'master' || github.repository_owner != 'espressif' }}

components/.build-test-rules.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ components/sensors/icm42670:
4141
- if: IDF_VERSION < "5.2.0"
4242
reason: Requires I2C Driver-NG which was introduced in v5.2
4343

44+
components/sensors/aht30:
45+
depends_filepatterns:
46+
- "components/sensors/aht30/**"
47+
disable:
48+
- if: IDF_VERSION < "5.2.0"
49+
reason: Requires I2C Driver-NG which was introduced in v5.2
50+
4451
components/qma6100p:
4552
depends_filepatterns:
4653
- "components/qma6100p/**"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3")
2+
set(REQ esp_driver_i2c)
3+
else()
4+
set(REQ driver)
5+
endif()
6+
7+
idf_component_register(SRCS "aht30.c"
8+
INCLUDE_DIRS "include" REQUIRES ${REQ})
9+
10+
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u aht30_impl_init")

components/sensors/aht30/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# AHT30 Temperature and humidity sensor
2+
3+
[![Component Registry](https://components.espressif.com/components/espressif/aht30/badge.svg)](https://components.espressif.com/components/espressif/aht30)
4+
![maintenance-status](https://img.shields.io/badge/maintenance-passively--maintained-yellowgreen.svg)
5+
6+
C driver for Asair AHT30 temperature and humidity sensor.
7+
8+
## Features
9+
10+
- Get temperature and humidity readings as float values.
11+
- Check measurement status.
12+
13+
## Get Started
14+
15+
This driver, along with many other components from this repository, can be used as a package from [Espressif's IDF Component Registry](https://components.espressif.com). To include this driver in your project, run the following idf.py from the project's root directory:
16+
17+
```
18+
idf.py add-dependency "espressif/icm42670==*"
19+
```
20+
21+
Another option is to manually create a `idf_component.yml` file. You can find more about using .yml files for components from [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html).
22+
23+
## See Also
24+
* [AHT30 product page](https://asairsensors.com/product/aht30-integrated-temperature-and-humidity-sensor/)

components/sensors/aht30/aht30.c

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include "esp_check.h"
9+
#include "iot_sensor_hub.h"
10+
#include "aht30.h"
11+
12+
#define I2C_CLK_SPEED 400000
13+
14+
/*******************************************************************************
15+
* Types definitions
16+
*******************************************************************************/
17+
18+
typedef struct {
19+
i2c_master_dev_handle_t i2c_handle;
20+
} aht30_dev_t;
21+
22+
/*******************************************************************************
23+
* Function definitions
24+
*******************************************************************************/
25+
static esp_err_t aht30_write(aht30_handle_t sensor, const uint8_t *data_buf, const uint8_t data_len);
26+
static esp_err_t aht30_receive(const aht30_handle_t sensor, uint8_t *data_buf, const uint8_t data_len);
27+
static uint8_t aht30_calc_crc(uint8_t *data, uint8_t length);
28+
29+
/*******************************************************************************
30+
* Local variables
31+
*******************************************************************************/
32+
static const char *TAG = "AHT30";
33+
34+
/*******************************************************************************
35+
* Public API functions
36+
*******************************************************************************/
37+
38+
esp_err_t aht30_create(i2c_master_bus_handle_t i2c_bus, const uint8_t dev_addr, aht30_handle_t *handle_ret)
39+
{
40+
ESP_RETURN_ON_FALSE(i2c_bus != NULL, ESP_ERR_INVALID_ARG, TAG, "I2C handle can't be NULL");
41+
ESP_RETURN_ON_FALSE(handle_ret != NULL, ESP_ERR_INVALID_ARG, TAG, "AHT30 handle can't be NULL");
42+
43+
esp_err_t ret = ESP_OK;
44+
45+
// Allocate memory and initialize the driver object
46+
aht30_dev_t *sensor = (aht30_dev_t *) calloc(1, sizeof(aht30_dev_t));
47+
ESP_RETURN_ON_FALSE(sensor != NULL, ESP_ERR_NO_MEM, TAG, "Not enough memory");
48+
49+
const i2c_device_config_t i2c_dev_cfg = {
50+
.device_address = dev_addr,
51+
.scl_speed_hz = I2C_CLK_SPEED,
52+
};
53+
54+
ret = i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg, &sensor->i2c_handle);
55+
if (ret != ESP_OK) {
56+
ESP_LOGE(TAG, "Failed to add a new I2C device");
57+
aht30_delete(sensor);
58+
return ret;
59+
}
60+
assert(sensor->i2c_handle);
61+
62+
ESP_LOGD(TAG, "Successfully created a AHT30 device");
63+
*handle_ret = sensor;
64+
return ret;
65+
}
66+
67+
void aht30_delete(aht30_handle_t sensor)
68+
{
69+
aht30_dev_t *sens = (aht30_dev_t *) sensor;
70+
71+
if (sens->i2c_handle) {
72+
i2c_master_bus_rm_device(sens->i2c_handle);
73+
}
74+
75+
free(sens);
76+
}
77+
78+
esp_err_t aht30_get_temperature_humidity_value(aht30_handle_t sensor, float *temperature, float *humidity)
79+
{
80+
ESP_RETURN_ON_FALSE(sensor != NULL, ESP_ERR_INVALID_ARG, TAG, "AHT30 handle can't be NULL");
81+
ESP_RETURN_ON_FALSE(temperature != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the busy variable can't be NULL");
82+
ESP_RETURN_ON_FALSE(humidity != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the busy variable can't be NULL");
83+
84+
esp_err_t ret = ESP_OK;
85+
86+
// Start measurement command specified by the manufacturer
87+
uint8_t start_measurement[] = {0xAC, 0x33, 0x00};
88+
89+
ret = aht30_write(sensor, start_measurement, sizeof(start_measurement));
90+
if (ret != ESP_OK) {
91+
ESP_LOGE(TAG, "Failed to start AHT30 measurement");
92+
return ret;
93+
}
94+
95+
bool busy;
96+
uint8_t max_tries = 18;
97+
// Wait for the measurement to finish
98+
do {
99+
ret = aht30_get_busy(sensor, &busy);
100+
if (ret != ESP_OK) {
101+
ESP_LOGE(TAG, "Failed to read AHT30 busy status");
102+
return ret;
103+
}
104+
vTaskDelay(pdMS_TO_TICKS(10));
105+
} while (--max_tries && busy);
106+
107+
if (busy) {
108+
ESP_LOGE(TAG, "AHT30 timeout of measurement");
109+
return ESP_FAIL;
110+
}
111+
112+
uint8_t crc_result;
113+
uint8_t receive_buf[7];
114+
ESP_RETURN_ON_ERROR(aht30_receive(sensor, receive_buf, sizeof(receive_buf)), TAG, "Failed to receive measurement data");
115+
116+
crc_result = aht30_calc_crc(receive_buf, sizeof(receive_buf) - 1);
117+
ESP_RETURN_ON_FALSE(crc_result == receive_buf[sizeof(receive_buf) - 1], ESP_FAIL, TAG,
118+
"Result of CRC calculation does not match");
119+
120+
uint32_t raw_value;
121+
raw_value = (receive_buf[3] & 0x0F) << 16 | receive_buf[4] << 8 | receive_buf[5];
122+
*temperature = (float)raw_value * 200 / (1 << 20) - 50;
123+
124+
raw_value = receive_buf[1] << 12 | receive_buf[2] << 4 | receive_buf[3] >> 4;
125+
*humidity = (float)raw_value * 100 / (1 << 20) ;
126+
127+
return ret;
128+
}
129+
130+
esp_err_t aht30_get_busy(aht30_handle_t sensor, bool *busy)
131+
{
132+
ESP_RETURN_ON_FALSE(sensor != NULL, ESP_ERR_INVALID_ARG, TAG, "AHT30 handle can't be NULL");
133+
ESP_RETURN_ON_FALSE(busy != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the busy variable can't be NULL");
134+
uint8_t data = 0;
135+
136+
ESP_RETURN_ON_ERROR(aht30_receive(sensor, &data, 1), TAG, "Read sensor busy failed");
137+
138+
*busy = (data & BIT7);
139+
return ESP_OK;
140+
}
141+
142+
/*******************************************************************************
143+
* Private functions
144+
*******************************************************************************/
145+
static esp_err_t aht30_write(const aht30_handle_t sensor, const uint8_t *data_buf, const uint8_t data_len)
146+
{
147+
aht30_dev_t *sens = (aht30_dev_t *) sensor;
148+
assert(sens);
149+
150+
return i2c_master_transmit(sens->i2c_handle, data_buf, data_len, -1);
151+
}
152+
153+
static esp_err_t aht30_receive(const aht30_handle_t sensor, uint8_t *data_buf, const uint8_t data_len)
154+
{
155+
aht30_dev_t *sens = (aht30_dev_t *) sensor;
156+
assert(sens);
157+
158+
return i2c_master_receive(sens->i2c_handle, data_buf, data_len, -1);
159+
}
160+
161+
static uint8_t aht30_calc_crc(uint8_t *data, uint8_t length)
162+
{
163+
uint8_t i;
164+
uint8_t byte;
165+
uint8_t crc = 0xFF;
166+
for (byte = 0; byte < length; byte++) {
167+
crc ^= (data[byte]);
168+
for (i = 8; i > 0; --i) {
169+
if (crc & 0x80) {
170+
crc = (crc << 1) ^ 0x31;
171+
} else {
172+
crc = (crc << 1);
173+
}
174+
}
175+
}
176+
return crc;
177+
}
178+
179+
/*******************************************************************************
180+
* Private functions for sensor hub implementation
181+
*******************************************************************************/
182+
183+
static aht30_handle_t sensor_hub_aht30_handle;
184+
185+
esp_err_t aht30_impl_init(bus_handle_t bus_handle, uint8_t addr)
186+
{
187+
esp_err_t ret;
188+
189+
ret = aht30_create(bus_handle, addr, &sensor_hub_aht30_handle);
190+
if (ret != ESP_OK) {
191+
ESP_LOGE(TAG, "Failed to create AHT30 with error %s", esp_err_to_name(ret));
192+
}
193+
194+
return ret;
195+
}
196+
197+
esp_err_t aht30_impl_deinit(void)
198+
{
199+
aht30_delete(sensor_hub_aht30_handle);
200+
sensor_hub_aht30_handle = NULL;
201+
return ESP_OK;
202+
}
203+
204+
esp_err_t aht30_impl_test(void)
205+
{
206+
bool busy;
207+
return aht30_get_busy(sensor_hub_aht30_handle, &busy);
208+
}
209+
210+
esp_err_t aht30_impl_acquire_humidity(float *humidity)
211+
{
212+
float temperature;
213+
return aht30_get_temperature_humidity_value(sensor_hub_aht30_handle, &temperature, humidity);
214+
}
215+
216+
esp_err_t aht30_impl_acquire_temperature(float *temperature)
217+
{
218+
float humidity;
219+
return aht30_get_temperature_humidity_value(sensor_hub_aht30_handle, temperature, &humidity);
220+
}
221+
222+
static humiture_impl_t aht30_impl = {
223+
.init = aht30_impl_init,
224+
.deinit = aht30_impl_deinit,
225+
.test = aht30_impl_test,
226+
.acquire_humidity = aht30_impl_acquire_humidity,
227+
.acquire_temperature = aht30_impl_acquire_temperature,
228+
};
229+
230+
SENSOR_HUB_DETECT_FN(HUMITURE_ID, sensor_hub_aht30, &aht30_impl);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 1.0.0
2+
description: I2C driver for ATH30 temperature and humidity sensor
3+
url: https://github.com/espressif/esp-bsp/tree/master/components/aht30
4+
dependencies:
5+
idf: ">=5.2"
6+
sensor_hub:
7+
version: ^0.1.4
8+
public: true
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#pragma once
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
#endif
12+
13+
#include "driver/i2c_master.h"
14+
15+
#define AHT30_I2C_ADDRESS 0x38
16+
17+
typedef void *aht30_handle_t;
18+
19+
/**
20+
* @brief Create and init sensor object
21+
*
22+
* @param[in] i2c_bus I2C bus handle. Obtained from i2c_new_master_bus()
23+
* @param[in] dev_addr I2C device address of sensor.
24+
* @param[out] handle_ret Handle to created AHT30 driver object
25+
*
26+
* @return
27+
* - ESP_OK Success
28+
* - ESP_ERR_NO_MEM Not enough memory for the driver
29+
* - Others Error from underlying I2C driver
30+
*/
31+
esp_err_t aht30_create(i2c_master_bus_handle_t i2c_bus, const uint8_t dev_addr, aht30_handle_t *handle_ret);
32+
33+
/**
34+
* @brief Delete and release a sensor object
35+
*
36+
* @param sensor object handle of AHT30
37+
*/
38+
void aht30_delete(aht30_handle_t sensor);
39+
40+
/**
41+
* @brief Read temperature and humidity values
42+
*
43+
* @param sensor object handle of AHT30
44+
* @param temperature temperature measurement
45+
* @param humidity humidity measurement
46+
*
47+
* @return
48+
* - ESP_OK Success
49+
* - ESP_FAIL Fail
50+
*/
51+
esp_err_t aht30_get_temperature_humidity_value(aht30_handle_t sensor, float *temperature, float *humidity);
52+
53+
/**
54+
* @brief Read busy status
55+
*
56+
* @param sensor object handle of AHT30
57+
* @param busy busy status
58+
*
59+
* @return
60+
* - ESP_OK Success
61+
* - ESP_FAIL Fail
62+
*/
63+
esp_err_t aht30_get_busy(aht30_handle_t sensor, bool *busy);
64+
65+
#ifdef __cplusplus
66+
}
67+
#endif

0 commit comments

Comments
 (0)