Add bh1750, aht20, sgp40 drivers unified on native i2c_master API

Forked from managed_components (espressif/bh1750, espressif/aht20,
esp-idf-lib/sgp40) and rewrote aht20/sgp40 internals to use
driver/i2c_master.h directly instead of the i2c_bus and i2cdev
wrapper components, so all three sensors share one create/delete
handle pattern over a single i2c_master_bus_handle_t.
This commit is contained in:
Julien Lazarewicz
2026-07-05 23:27:50 +02:00
commit e940c767a6
19 changed files with 2441 additions and 0 deletions

248
sgp40/sgp40.c Normal file
View File

@@ -0,0 +1,248 @@
/**
* @file sgp40.c
*
* ESP-IDF driver for SGP40 Indoor Air Quality Sensor for VOC Measurements
*
* Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>
*
* BSD Licensed as described in the file LICENSE
*/
#include <stdlib.h>
#include <math.h>
#include <esp_err.h>
#include <esp_log.h>
#include "sgp40.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define I2C_FREQ_HZ 400000
static const char *TAG = "sgp40";
#define CMD_SOFT_RESET 0x0006
#define CMD_FEATURESET 0x202f
#define CMD_MEASURE_RAW 0x260f
#define CMD_SELF_TEST 0x280e
#define CMD_SERIAL 0x3682
#define CMD_HEATER_OFF 0x3615
#define TIME_SOFT_RESET 10
#define TIME_FEATURESET 10
#define TIME_MEASURE_RAW 30
#define TIME_SELF_TEST 250
#define TIME_HEATER_OFF 10
#define TIME_SERIAL 10
#define SELF_TEST_OK 0xd400
#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0)
typedef struct
{
i2c_master_dev_handle_t i2c_handle;
uint16_t serial[3];
uint16_t featureset;
VocAlgorithmParams voc;
} sgp40_dev_t;
static uint8_t crc8(const uint8_t *data, size_t count)
{
uint8_t res = 0xff;
for (size_t i = 0; i < count; ++i)
{
res ^= data[i];
for (uint8_t bit = 8; bit > 0; --bit)
{
if (res & 0x80)
res = (res << 1) ^ 0x31;
else
res = (res << 1);
}
}
return res;
}
static inline uint16_t swap(uint16_t v)
{
return (v << 8) | (v >> 8);
}
static esp_err_t send_cmd(i2c_master_dev_handle_t dev_handle, uint16_t cmd, uint16_t *data, size_t words)
{
uint8_t buf[2 + words * 3];
// add command
*(uint16_t *)buf = swap(cmd);
if (data && words)
// add arguments
for (size_t i = 0; i < words; i++)
{
uint8_t *p = buf + 2 + i * 3;
*(uint16_t *)p = swap(data[i]);
*(p + 2) = crc8(p, 2);
}
ESP_LOGV(TAG, "Sending buffer:");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
return i2c_master_transmit(dev_handle, buf, sizeof(buf), pdMS_TO_TICKS(1000));
}
static esp_err_t read_resp(i2c_master_dev_handle_t dev_handle, uint16_t *data, size_t words)
{
uint8_t buf[words * 3];
CHECK(i2c_master_receive(dev_handle, buf, sizeof(buf), pdMS_TO_TICKS(1000)));
ESP_LOGV(TAG, "Received buffer:");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE);
for (size_t i = 0; i < words; i++)
{
uint8_t *p = buf + i * 3;
uint8_t crc = crc8(p, 2);
if (crc != *(p + 2))
{
ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2));
return ESP_ERR_INVALID_CRC;
}
data[i] = swap(*(uint16_t *)p);
}
return ESP_OK;
}
static esp_err_t execute_cmd(sgp40_dev_t *dev, uint16_t cmd, uint32_t timeout_ms,
uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words)
{
CHECK_ARG(dev);
CHECK(send_cmd(dev->i2c_handle, cmd, out_data, out_words));
if (timeout_ms)
vTaskDelay(pdMS_TO_TICKS(timeout_ms));
if (in_data && in_words)
CHECK(read_resp(dev->i2c_handle, in_data, in_words));
return ESP_OK;
}
////////////////////////////////////////////////////////////////////////////////
esp_err_t sgp40_create(i2c_master_bus_handle_t bus_handle, const uint8_t dev_addr, sgp40_handle_t *handle_ret)
{
CHECK_ARG(bus_handle && handle_ret);
sgp40_dev_t *dev = calloc(1, sizeof(sgp40_dev_t));
if (!dev)
return ESP_ERR_NO_MEM;
const i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = dev_addr,
.scl_speed_hz = I2C_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev->i2c_handle);
if (ret != ESP_OK)
{
free(dev);
return ret;
}
ret = execute_cmd(dev, CMD_SERIAL, TIME_SERIAL, NULL, 0, dev->serial, 3);
if (ret == ESP_OK)
ret = execute_cmd(dev, CMD_FEATURESET, TIME_FEATURESET, NULL, 0, &dev->featureset, 1);
if (ret != ESP_OK)
{
i2c_master_bus_rm_device(dev->i2c_handle);
free(dev);
return ret;
}
ESP_LOGD(TAG, "Device found. S/N: 0x%04x%04x%04x, featureset 0x%04x",
dev->serial[0], dev->serial[1], dev->serial[2], dev->featureset);
VocAlgorithm_init(&dev->voc);
*handle_ret = (sgp40_handle_t)dev;
return ESP_OK;
}
esp_err_t sgp40_delete(sgp40_handle_t sensor)
{
CHECK_ARG(sensor);
sgp40_dev_t *dev = (sgp40_dev_t *)sensor;
esp_err_t ret = i2c_master_bus_rm_device(dev->i2c_handle);
free(dev);
return ret;
}
esp_err_t sgp40_soft_reset(sgp40_handle_t sensor)
{
CHECK_ARG(sensor);
return execute_cmd((sgp40_dev_t *)sensor, CMD_SOFT_RESET, TIME_SOFT_RESET, NULL, 0, NULL, 0);
}
esp_err_t sgp40_self_test(sgp40_handle_t sensor)
{
CHECK_ARG(sensor);
uint16_t res;
CHECK(execute_cmd((sgp40_dev_t *)sensor, CMD_SELF_TEST, TIME_SELF_TEST, NULL, 0, &res, 1));
return res == SELF_TEST_OK ? ESP_OK : ESP_FAIL;
}
esp_err_t sgp40_heater_off(sgp40_handle_t sensor)
{
CHECK_ARG(sensor);
return execute_cmd((sgp40_dev_t *)sensor, CMD_HEATER_OFF, TIME_HEATER_OFF, NULL, 0, NULL, 0);
}
esp_err_t sgp40_measure_raw(sgp40_handle_t sensor, float humidity, float temperature, uint16_t *raw)
{
CHECK_ARG(sensor && raw);
uint16_t params[2];
if (isnan(humidity) || isnan(temperature))
{
params[0] = 0x8000;
params[1] = 0x6666;
ESP_LOGW(TAG, "Uncompensated measurement");
}
else
{
if (humidity < 0)
humidity = 0;
else if (humidity > 100)
humidity = 100;
if (temperature < -45)
temperature = -45;
else if (temperature > 129.76)
temperature = 129.76;
params[0] = (uint16_t)(humidity / 100.0 * 65536);
params[1] = (uint16_t)((temperature + 45) / 175.0 * 65535);
}
return execute_cmd((sgp40_dev_t *)sensor, CMD_MEASURE_RAW, TIME_MEASURE_RAW, params, 2, raw, 1);
}
esp_err_t sgp40_measure_voc(sgp40_handle_t sensor, float humidity, float temperature, int32_t *voc_index)
{
CHECK_ARG(sensor && voc_index);
sgp40_dev_t *dev = (sgp40_dev_t *)sensor;
uint16_t raw;
CHECK(sgp40_measure_raw(sensor, humidity, temperature, &raw));
VocAlgorithm_process(&dev->voc, raw, voc_index);
return ESP_OK;
}