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:
248
sgp40/sgp40.c
Normal file
248
sgp40/sgp40.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user