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:
47
README.md
Normal file
47
README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# espidf_i2c
|
||||
|
||||
Forked/rewritten ESP-IDF I2C sensor drivers, unified onto the native
|
||||
`driver/i2c_master.h` API (ESP-IDF >= 5.3) so every sensor shares the same
|
||||
handle lifecycle instead of each vendor bringing its own I2C abstraction
|
||||
(`i2c_bus`, `i2cdev`, etc.).
|
||||
|
||||
## Common API convention
|
||||
|
||||
Every driver in this repo follows the same shape:
|
||||
|
||||
```c
|
||||
typedef void *xxx_handle_t;
|
||||
|
||||
esp_err_t xxx_create(i2c_master_bus_handle_t bus_handle, uint8_t dev_addr, xxx_handle_t *handle_ret);
|
||||
esp_err_t xxx_delete(xxx_handle_t handle);
|
||||
// ... device-specific read/measure functions taking xxx_handle_t
|
||||
```
|
||||
|
||||
Callers create one `i2c_master_bus_handle_t` via `i2c_new_master_bus()` and
|
||||
pass it to each sensor's `_create()`, which internally calls
|
||||
`i2c_master_bus_add_device()`. No sensor wraps the bus a second time.
|
||||
|
||||
## Components
|
||||
|
||||
- `bh1750/` — ambient light sensor. Already used the native API upstream;
|
||||
relocated here unchanged.
|
||||
- `aht20/` — temperature/humidity sensor. Rewritten from Espressif's
|
||||
`i2c_bus` component onto `i2c_master_transmit`/`i2c_master_receive`.
|
||||
- `sgp40/` — VOC index sensor. Rewritten from esp-idf-lib's `i2cdev`
|
||||
component onto `i2c_master_transmit`/`i2c_master_receive`; the Sensirion
|
||||
VOC algorithm files are carried over unmodified (no I2C dependency).
|
||||
|
||||
## Using from a project
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
laz/bh1750:
|
||||
git: https://git.jeese.fr/laz/espidf_i2c.git
|
||||
path: bh1750
|
||||
laz/aht20:
|
||||
git: https://git.jeese.fr/laz/espidf_i2c.git
|
||||
path: aht20
|
||||
laz/sgp40:
|
||||
git: https://git.jeese.fr/laz/espidf_i2c.git
|
||||
path: sgp40
|
||||
```
|
||||
6
aht20/CMakeLists.txt
Normal file
6
aht20/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "aht20.c"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS "priv_include"
|
||||
REQUIRES "esp_driver_i2c"
|
||||
)
|
||||
202
aht20/LICENSE
Normal file
202
aht20/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
134
aht20/aht20.c
Normal file
134
aht20/aht20.c
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "aht20.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#include "aht20_reg.h"
|
||||
|
||||
#define I2C_CLK_SPEED 100000
|
||||
|
||||
const static char *TAG = "AHT20";
|
||||
|
||||
static esp_err_t aht20_write_reg(aht20_handle_t handle, uint8_t reg_addr, uint8_t *data, uint8_t len)
|
||||
{
|
||||
i2c_master_dev_handle_t dev_handle = (i2c_master_dev_handle_t)handle;
|
||||
uint8_t buf[1 + len];
|
||||
buf[0] = reg_addr;
|
||||
memcpy(&buf[1], data, len);
|
||||
return i2c_master_transmit(dev_handle, buf, sizeof(buf), pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
static esp_err_t aht20_read_reg(aht20_handle_t handle, uint8_t *data, size_t len)
|
||||
{
|
||||
i2c_master_dev_handle_t dev_handle = (i2c_master_dev_handle_t)handle;
|
||||
return i2c_master_receive(dev_handle, data, len, pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
static uint8_t aht20_calc_crc(uint8_t *data, uint8_t len)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t byte;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
for (byte = 0; byte < len; byte++) {
|
||||
crc ^= data[byte];
|
||||
for (i = 8; i > 0; --i) {
|
||||
if ((crc & 0x80) != 0) {
|
||||
crc = (crc << 1) ^ 0x31;
|
||||
} else {
|
||||
crc = crc << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
esp_err_t aht20_read_temperature_humidity(aht20_handle_t handle,
|
||||
uint32_t *temperature_raw, float *temperature,
|
||||
uint32_t *humidity_raw, float *humidity)
|
||||
{
|
||||
uint8_t status;
|
||||
uint8_t buf[7];
|
||||
uint32_t raw_data;
|
||||
|
||||
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
|
||||
|
||||
buf[0] = 0x33;
|
||||
buf[1] = 0x00;
|
||||
ESP_RETURN_ON_ERROR(aht20_write_reg(handle, AHT20_START_MEASURMENT_CMD, buf, 2), TAG, "I2C write error");
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
ESP_RETURN_ON_ERROR(aht20_read_reg(handle, &status, 1), TAG, "I2C read error");
|
||||
|
||||
if ((status & BIT(AT581X_STATUS_Calibration_Enable)) &&
|
||||
(status & BIT(AT581X_STATUS_CRC_FLAG)) &&
|
||||
((status & BIT(AT581X_STATUS_BUSY_INDICATION)) == 0)) {
|
||||
ESP_RETURN_ON_ERROR(aht20_read_reg(handle, buf, 7), TAG, "I2C read error");
|
||||
ESP_RETURN_ON_ERROR((aht20_calc_crc(buf, 6) != buf[6]), TAG, "crc is error");
|
||||
|
||||
raw_data = buf[1];
|
||||
raw_data = raw_data << 8;
|
||||
raw_data += buf[2];
|
||||
raw_data = raw_data << 8;
|
||||
raw_data += buf[3];
|
||||
raw_data = raw_data >> 4;
|
||||
*humidity_raw = raw_data;
|
||||
*humidity = (float)raw_data * 100 / 1048576;
|
||||
|
||||
raw_data = buf[3] & 0x0F;
|
||||
raw_data = raw_data << 8;
|
||||
raw_data += buf[4];
|
||||
raw_data = raw_data << 8;
|
||||
raw_data += buf[5];
|
||||
*temperature_raw = raw_data;
|
||||
*temperature = (float)raw_data * 200 / 1048576 - 50;
|
||||
return ESP_OK;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "data is not ready");
|
||||
return ESP_ERR_NOT_FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t aht20_create(i2c_master_bus_handle_t bus_handle, const uint8_t dev_addr, aht20_handle_t *handle_ret)
|
||||
{
|
||||
ESP_LOGI(TAG, "%-15s: %d.%d.%d", CHIP_NAME, AHT20_VER_MAJOR, AHT20_VER_MINOR, AHT20_VER_PATCH);
|
||||
ESP_LOGI(TAG, "%-15s: %1.1f - %1.1fV", "SUPPLY_VOLTAGE", SUPPLY_VOLTAGE_MIN, SUPPLY_VOLTAGE_MAX);
|
||||
ESP_LOGI(TAG, "%-15s: %.2f - %.2f degC", "TEMPERATURE", TEMPERATURE_MIN, TEMPERATURE_MAX);
|
||||
|
||||
ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "invalid bus handle");
|
||||
ESP_RETURN_ON_FALSE(handle_ret, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
|
||||
|
||||
const i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = dev_addr,
|
||||
.scl_speed_hz = I2C_CLK_SPEED,
|
||||
};
|
||||
|
||||
i2c_master_dev_handle_t dev_handle;
|
||||
esp_err_t ret = i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c_master_bus_add_device failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*handle_ret = (aht20_handle_t)dev_handle;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t aht20_delete(aht20_handle_t handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
|
||||
return i2c_master_bus_rm_device((i2c_master_dev_handle_t)handle);
|
||||
}
|
||||
4
aht20/idf_component.yml
Normal file
4
aht20/idf_component.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
idf: '>=5.3'
|
||||
description: I2C driver for AHT20 temperature/humidity sensor (native driver/i2c_master.h API)
|
||||
version: 2.0.0
|
||||
70
aht20/include/aht20.h
Normal file
70
aht20/include/aht20.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_types.h"
|
||||
#include "esp_err.h"
|
||||
#include "driver/i2c_master.h"
|
||||
|
||||
/* AHT20 address: CE pin low - 0x38, CE pin high - 0x39 */
|
||||
#define AHT20_ADDRRES_0 (0x38)
|
||||
#define AHT20_ADDRESS_1 (0x39)
|
||||
|
||||
/**
|
||||
* @brief Type of AHT20 device handle
|
||||
*/
|
||||
typedef void *aht20_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Create and init sensor object and return a sensor handle
|
||||
*
|
||||
* @param[in] bus_handle I2C bus handle. Obtained from i2c_new_master_bus().
|
||||
* @param[in] dev_addr I2C address of sensor. Use AHT20_ADDRRES_0 for default address.
|
||||
* @param[out] handle_ret Handle to created AHT20 driver object.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Not enough memory for the driver
|
||||
* - Others Error from underlying I2C driver
|
||||
*/
|
||||
esp_err_t aht20_create(i2c_master_bus_handle_t bus_handle, const uint8_t dev_addr, aht20_handle_t *handle_ret);
|
||||
|
||||
/**
|
||||
* @brief Delete and release a sensor object
|
||||
*
|
||||
* @param sensor object handle of aht20
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t aht20_delete(aht20_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief read the temperature and humidity data
|
||||
*
|
||||
* @param[in] sensor object handle of aht20
|
||||
* @param[out] temperature_raw points to a raw temperature buffer
|
||||
* @param[out] temperature points to a converted temperature buffer
|
||||
* @param[out] humidity_raw points to a raw humidity buffer
|
||||
* @param[out] humidity points to a converted humidity buffer
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NOT_FINISHED Measurement not ready yet
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t aht20_read_temperature_humidity(aht20_handle_t sensor,
|
||||
uint32_t *temperature_raw, float *temperature,
|
||||
uint32_t *humidity_raw, float *humidity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
24
aht20/priv_include/aht20_reg.h
Normal file
24
aht20/priv_include/aht20_reg.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @brief chip information definition
|
||||
*/
|
||||
#define CHIP_NAME "ASAIR AHT20" /**< chip name */
|
||||
#define SUPPLY_VOLTAGE_MIN (2.2f) /**< chip min supply voltage */
|
||||
#define SUPPLY_VOLTAGE_MAX (5.5f) /**< chip max supply voltage */
|
||||
#define TEMPERATURE_MIN (-40.0f) /**< chip min operating temperature */
|
||||
#define TEMPERATURE_MAX (125.0f) /**< chip max operating temperature */
|
||||
|
||||
#define AHT20_START_MEASURMENT_CMD 0xAC /* start measurement command */
|
||||
|
||||
#define AT581X_STATUS_CMP_INT (2) /* 1 --Out threshold range; 0 --In threshold range */
|
||||
#define AT581X_STATUS_Calibration_Enable (3) /* 1 --Calibration enable; 0 --Calibration disable */
|
||||
#define AT581X_STATUS_CRC_FLAG (4) /* 1 --CRC ok; 0 --CRC failed */
|
||||
#define AT581X_STATUS_MODE_STATUS (5) /* 00 -NOR mode; 01 -CYC mode; 1x --CMD mode */
|
||||
#define AT581X_STATUS_BUSY_INDICATION (7) /* 1 --Equipment is busy; 0 --Equipment is idle */
|
||||
5
bh1750/CMakeLists.txt
Normal file
5
bh1750/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "bh1750.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "esp_driver_i2c"
|
||||
)
|
||||
202
bh1750/LICENSE
Normal file
202
bh1750/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
116
bh1750/bh1750.c
Normal file
116
bh1750/bh1750.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "bh1750.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/projdefs.h" // for pdMS_TO_TICKS
|
||||
|
||||
#define BH_1750_MEASUREMENT_ACCURACY \
|
||||
1.2 /*!< the typical measurement accuracy of BH1750 sensor \
|
||||
*/
|
||||
|
||||
#define BH1750_POWER_DOWN 0x00 /*!< Command to set Power Down*/
|
||||
#define BH1750_POWER_ON 0x01 /*!< Command to set Power On*/
|
||||
#define I2C_CLK_SPEED 400000
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
i2c_master_dev_handle_t i2c_handle;
|
||||
} bh1750_dev_t;
|
||||
|
||||
static esp_err_t bh1750_write_byte(const bh1750_dev_t* const sens, const uint8_t byte)
|
||||
{ return i2c_master_transmit(sens->i2c_handle, &byte, 1, pdMS_TO_TICKS(1000)); }
|
||||
|
||||
esp_err_t bh1750_create(i2c_master_bus_handle_t i2c_bus,
|
||||
const uint8_t dev_addr,
|
||||
bh1750_handle_t* handle_ret)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
bh1750_dev_t* sensor = (bh1750_dev_t*) calloc(1, sizeof(bh1750_dev_t));
|
||||
if (!sensor)
|
||||
{
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Add new I2C device
|
||||
const i2c_device_config_t i2c_dev_cfg = {
|
||||
.device_address = dev_addr,
|
||||
.scl_speed_hz = I2C_CLK_SPEED,
|
||||
};
|
||||
ret = i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg, &sensor->i2c_handle);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
free(sensor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
assert(sensor->i2c_handle);
|
||||
*handle_ret = sensor;
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t bh1750_delete(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t* sens = (bh1750_dev_t*) sensor;
|
||||
if (sens->i2c_handle)
|
||||
{
|
||||
i2c_master_bus_rm_device(sens->i2c_handle);
|
||||
}
|
||||
free(sens);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bh1750_power_down(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t* sens = (bh1750_dev_t*) sensor;
|
||||
return bh1750_write_byte(sens, BH1750_POWER_DOWN);
|
||||
}
|
||||
|
||||
esp_err_t bh1750_power_on(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t* sens = (bh1750_dev_t*) sensor;
|
||||
return bh1750_write_byte(sens, BH1750_POWER_ON);
|
||||
}
|
||||
|
||||
esp_err_t bh1750_set_measure_time(bh1750_handle_t sensor, const uint8_t measure_time)
|
||||
{
|
||||
bh1750_dev_t* sens = (bh1750_dev_t*) sensor;
|
||||
uint32_t i = 0;
|
||||
uint8_t buf[2] = { 0x40, 0x60 }; // constant part of the the MTreg
|
||||
buf[0] |= measure_time >> 5;
|
||||
buf[1] |= measure_time & 0x1F;
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
esp_err_t ret = bh1750_write_byte(sens, buf[i]);
|
||||
if (ESP_OK != ret)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bh1750_set_measure_mode(bh1750_handle_t sensor, const bh1750_measure_mode_t cmd_measure)
|
||||
{
|
||||
bh1750_dev_t* sens = (bh1750_dev_t*) sensor;
|
||||
return bh1750_write_byte(sens, (uint8_t) cmd_measure);
|
||||
}
|
||||
|
||||
esp_err_t bh1750_get_data(bh1750_handle_t sensor, float* const data)
|
||||
{
|
||||
bh1750_dev_t* sens = (bh1750_dev_t*) sensor;
|
||||
uint8_t read_buffer[2];
|
||||
esp_err_t ret = i2c_master_receive(
|
||||
sens->i2c_handle, read_buffer, sizeof(read_buffer), pdMS_TO_TICKS(1000));
|
||||
if (ESP_OK != ret)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
*data = ((read_buffer[0] << 8 | read_buffer[1]) / BH_1750_MEASUREMENT_ACCURACY);
|
||||
return ESP_OK;
|
||||
}
|
||||
4
bh1750/idf_component.yml
Normal file
4
bh1750/idf_component.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
idf: '>=5.3'
|
||||
description: I2C driver for BH1750 light sensor (native driver/i2c_master.h API)
|
||||
version: 2.0.0
|
||||
136
bh1750/include/bh1750.h
Normal file
136
bh1750/include/bh1750.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief BH1750 driver
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "driver/i2c_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef enum {
|
||||
BH1750_CONTINUE_1LX_RES = 0x10, /*!< Command to set measure mode as Continuously H-Resolution mode*/
|
||||
BH1750_CONTINUE_HALFLX_RES = 0x11, /*!< Command to set measure mode as Continuously H-Resolution mode2*/
|
||||
BH1750_CONTINUE_4LX_RES = 0x13, /*!< Command to set measure mode as Continuously L-Resolution mode*/
|
||||
BH1750_ONETIME_1LX_RES = 0x20, /*!< Command to set measure mode as One Time H-Resolution mode*/
|
||||
BH1750_ONETIME_HALFLX_RES = 0x21, /*!< Command to set measure mode as One Time H-Resolution mode2*/
|
||||
BH1750_ONETIME_4LX_RES = 0x23, /*!< Command to set measure mode as One Time L-Resolution mode*/
|
||||
} bh1750_measure_mode_t;
|
||||
|
||||
#define BH1750_I2C_ADDRESS_DEFAULT (0x23)
|
||||
typedef void *bh1750_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Set bh1750 as power down mode (low current)
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_power_down(bh1750_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Set bh1750 as power on mode
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_power_on(bh1750_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Get light intensity from bh1750
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
* @param[in] cmd_measure the instruction to set measurement mode
|
||||
*
|
||||
* @note
|
||||
* You should call this funtion to set measurement mode before call bh1750_get_data() to acquire data.
|
||||
* If you set onetime mode, you just can get one measurement result.
|
||||
* If you set continuous mode, you can call bh1750_get_data() to acquire data repeatedly.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_mode(bh1750_handle_t sensor, const bh1750_measure_mode_t cmd_measure);
|
||||
|
||||
/**
|
||||
* @brief Get light intensity from BH1750
|
||||
*
|
||||
* Returns light intensity in [lx] corrected by typical BH1750 Measurement Accuracy (= 1.2).
|
||||
*
|
||||
* @see BH1750 datasheet Rev. D page 2
|
||||
*
|
||||
* @note
|
||||
* You should acquire data from the sensor after the measurement time is over,
|
||||
* so take care of measurement time in different modes.
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
* @param[out] data light intensity value got from bh1750 in [lx]
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_get_data(bh1750_handle_t sensor, float *const data);
|
||||
|
||||
/**
|
||||
* @brief Set measurement time
|
||||
*
|
||||
* This function is used to adjust BH1750 sensitivity, i.e. compensating influence from optical window.
|
||||
*
|
||||
* @see BH1750 datasheet Rev. D page 11
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
* @param[in] measure_time measurement time
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_time(bh1750_handle_t sensor, const uint8_t measure_time);
|
||||
|
||||
/**
|
||||
* @brief Create and init sensor object and return a sensor handle
|
||||
*
|
||||
* @param[in] i2c_bus I2C bus handle. Obtained from i2c_new_master_bus().s
|
||||
* @param[in] dev_addr I2C device address of sensor. Use BH1750_I2C_ADDRESS_DEFAULT for default address.
|
||||
* @param[out] handle_ret Handle to created BH1750 driver object.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Not enough memory for the driver
|
||||
* - ESP_ERR_NOT_FOUND Sensor not found on the I2C bus
|
||||
* - Others Error from underlying I2C driver
|
||||
*/
|
||||
esp_err_t bh1750_create(i2c_master_bus_handle_t i2c_bus, const uint8_t dev_addr, bh1750_handle_t *handle_ret);
|
||||
|
||||
/**
|
||||
* @brief Delete and release a sensor object
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_delete(bh1750_handle_t sensor);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
sgp40/CMakeLists.txt
Normal file
5
sgp40/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "sgp40.c" "sensirion_voc_algorithm.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "esp_driver_i2c"
|
||||
)
|
||||
27
sgp40/LICENSE
Normal file
27
sgp40/LICENSE
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright 2020 Sensirion AG
|
||||
Copyright 2020 Ruslan V. Uss <unclerus@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
4
sgp40/idf_component.yml
Normal file
4
sgp40/idf_component.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
idf: '>=5.3'
|
||||
description: I2C driver for SGP40 VOC sensor (native driver/i2c_master.h API)
|
||||
version: 2.0.0
|
||||
203
sgp40/include/sensirion_voc_algorithm.h
Normal file
203
sgp40/include/sensirion_voc_algorithm.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file sensirion_voc_algorithm.h
|
||||
* @defgroup sgp40 sgp40
|
||||
* @{
|
||||
*
|
||||
* Library for calculating concentration of volatile organic compounds
|
||||
*/
|
||||
|
||||
#ifndef VOCALGORITHM_H_
|
||||
#define VOCALGORITHM_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* The fixed point arithmetic parts of this code were originally created by
|
||||
* https://github.com/PetteriAimonen/libfixmath
|
||||
*/
|
||||
|
||||
typedef int32_t fix16_t;
|
||||
|
||||
#define F16(x) \
|
||||
((fix16_t)(((x) >= 0) ? ((x)*65536.0 + 0.5) : ((x)*65536.0 - 0.5)))
|
||||
|
||||
#define VocAlgorithm_SAMPLING_INTERVAL (1.)
|
||||
#define VocAlgorithm_INITIAL_BLACKOUT (45.)
|
||||
#define VocAlgorithm_VOC_INDEX_GAIN (230.)
|
||||
#define VocAlgorithm_SRAW_STD_INITIAL (50.)
|
||||
#define VocAlgorithm_SRAW_STD_BONUS (220.)
|
||||
#define VocAlgorithm_TAU_MEAN_VARIANCE_HOURS (12.)
|
||||
#define VocAlgorithm_TAU_INITIAL_MEAN (20.)
|
||||
#define VocAlgorithm_INIT_DURATION_MEAN ((3600. * 0.75))
|
||||
#define VocAlgorithm_INIT_TRANSITION_MEAN (0.01)
|
||||
#define VocAlgorithm_TAU_INITIAL_VARIANCE (2500.)
|
||||
#define VocAlgorithm_INIT_DURATION_VARIANCE ((3600. * 1.45))
|
||||
#define VocAlgorithm_INIT_TRANSITION_VARIANCE (0.01)
|
||||
#define VocAlgorithm_GATING_THRESHOLD (340.)
|
||||
#define VocAlgorithm_GATING_THRESHOLD_INITIAL (510.)
|
||||
#define VocAlgorithm_GATING_THRESHOLD_TRANSITION (0.09)
|
||||
#define VocAlgorithm_GATING_MAX_DURATION_MINUTES ((60. * 3.))
|
||||
#define VocAlgorithm_GATING_MAX_RATIO (0.3)
|
||||
#define VocAlgorithm_SIGMOID_L (500.)
|
||||
#define VocAlgorithm_SIGMOID_K (-0.0065)
|
||||
#define VocAlgorithm_SIGMOID_X0 (213.)
|
||||
#define VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.)
|
||||
#define VocAlgorithm_LP_TAU_FAST (20.0)
|
||||
#define VocAlgorithm_LP_TAU_SLOW (500.0)
|
||||
#define VocAlgorithm_LP_ALPHA (-0.2)
|
||||
#define VocAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.))
|
||||
#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.)
|
||||
#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.)
|
||||
|
||||
/**
|
||||
* Struct to hold all the states of the VOC algorithm.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
fix16_t mVoc_Index_Offset;
|
||||
fix16_t mTau_Mean_Variance_Hours;
|
||||
fix16_t mGating_Max_Duration_Minutes;
|
||||
fix16_t mSraw_Std_Initial;
|
||||
fix16_t mUptime;
|
||||
fix16_t mSraw;
|
||||
fix16_t mVoc_Index;
|
||||
fix16_t m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes;
|
||||
bool m_Mean_Variance_Estimator___Initialized;
|
||||
fix16_t m_Mean_Variance_Estimator___Mean;
|
||||
fix16_t m_Mean_Variance_Estimator___Sraw_Offset;
|
||||
fix16_t m_Mean_Variance_Estimator___Std;
|
||||
fix16_t m_Mean_Variance_Estimator___Gamma;
|
||||
fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Mean;
|
||||
fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Variance;
|
||||
fix16_t m_Mean_Variance_Estimator__Gamma_Mean;
|
||||
fix16_t m_Mean_Variance_Estimator__Gamma_Variance;
|
||||
fix16_t m_Mean_Variance_Estimator___Uptime_Gamma;
|
||||
fix16_t m_Mean_Variance_Estimator___Uptime_Gating;
|
||||
fix16_t m_Mean_Variance_Estimator___Gating_Duration_Minutes;
|
||||
fix16_t m_Mean_Variance_Estimator___Sigmoid__L;
|
||||
fix16_t m_Mean_Variance_Estimator___Sigmoid__K;
|
||||
fix16_t m_Mean_Variance_Estimator___Sigmoid__X0;
|
||||
fix16_t m_Mox_Model__Sraw_Std;
|
||||
fix16_t m_Mox_Model__Sraw_Mean;
|
||||
fix16_t m_Sigmoid_Scaled__Offset;
|
||||
fix16_t m_Adaptive_Lowpass__A1;
|
||||
fix16_t m_Adaptive_Lowpass__A2;
|
||||
bool m_Adaptive_Lowpass___Initialized;
|
||||
fix16_t m_Adaptive_Lowpass___X1;
|
||||
fix16_t m_Adaptive_Lowpass___X2;
|
||||
fix16_t m_Adaptive_Lowpass___X3;
|
||||
} VocAlgorithmParams;
|
||||
|
||||
/**
|
||||
* Initialize the VOC algorithm parameters. Call this once at the beginning or
|
||||
* whenever the sensor stopped measurements.
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
*/
|
||||
void VocAlgorithm_init(VocAlgorithmParams *params);
|
||||
|
||||
/**
|
||||
* Get current algorithm states. Retrieved values can be used in
|
||||
* VocAlgorithm_set_states() to resume operation after a short interruption,
|
||||
* skipping initial learning phase. This feature can only be used after at least
|
||||
* 3 hours of continuous operation.
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param state0 State0 to be stored
|
||||
* @param state1 State1 to be stored
|
||||
*/
|
||||
void VocAlgorithm_get_states(VocAlgorithmParams *params, int32_t *state0,
|
||||
int32_t *state1);
|
||||
|
||||
/**
|
||||
* Set previously retrieved algorithm states to resume operation after a short
|
||||
* interruption, skipping initial learning phase. This feature should not be
|
||||
* used after inerruptions of more than 10 minutes. Call this once after
|
||||
* VocAlgorithm_init() and the optional VocAlgorithm_set_tuning_parameters(), if
|
||||
* desired. Otherwise, the algorithm will start with initial learning phase.
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param state0 State0 to be restored
|
||||
* @param state1 State1 to be restored
|
||||
*/
|
||||
void VocAlgorithm_set_states(VocAlgorithmParams *params, int32_t state0,
|
||||
int32_t state1);
|
||||
|
||||
/**
|
||||
* Set parameters to customize the VOC algorithm. Call this once after
|
||||
* VocAlgorithm_init(), if desired. Otherwise, the default values will be used.
|
||||
*
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param voc_index_offset VOC index representing typical (average)
|
||||
* conditions. Range 1..250, default 100
|
||||
* @param learning_time_hours Time constant of long-term estimator.
|
||||
* Past events will be forgotten after about
|
||||
* twice the learning time.
|
||||
* Range 1..72 [hours], default 12 [hours]
|
||||
* @param gating_max_duration_minutes Maximum duration of gating (freeze of
|
||||
* estimator during high VOC index signal).
|
||||
* 0 (no gating) or range 1..720 [minutes],
|
||||
* default 180 [minutes]
|
||||
* @param std_initial Initial estimate for standard deviation.
|
||||
* Lower value boosts events during initial
|
||||
* learning period, but may result in larger
|
||||
* device-to-device variations.
|
||||
* Range 10..500, default 50
|
||||
*/
|
||||
void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams *params,
|
||||
int32_t voc_index_offset,
|
||||
int32_t learning_time_hours,
|
||||
int32_t gating_max_duration_minutes,
|
||||
int32_t std_initial);
|
||||
|
||||
/**
|
||||
* Calculate the VOC index value from the raw sensor value.
|
||||
*
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param sraw Raw value from the SGP40 sensor
|
||||
* @param voc_index Calculated VOC index value from the raw sensor value. Zero
|
||||
* during initial blackout period and 1..500 afterwards
|
||||
*/
|
||||
void VocAlgorithm_process(VocAlgorithmParams *params, int32_t sraw,
|
||||
int32_t *voc_index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**@}*/
|
||||
|
||||
#endif /* VOCALGORITHM_H_ */
|
||||
106
sgp40/include/sgp40.h
Normal file
106
sgp40/include/sgp40.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @file sgp40.h
|
||||
* @defgroup sgp40 sgp40
|
||||
* @{
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
#ifndef __SGP40_H__
|
||||
#define __SGP40_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <esp_err.h>
|
||||
#include "driver/i2c_master.h"
|
||||
#include "sensirion_voc_algorithm.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SGP40_ADDR 0x59 //!< I2C address
|
||||
|
||||
/**
|
||||
* @brief Type of SGP40 device handle
|
||||
*/
|
||||
typedef void *sgp40_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Create and init sensor object, read device information, initialize the VOC algorithm
|
||||
*
|
||||
* @param[in] bus_handle I2C bus handle. Obtained from i2c_new_master_bus().
|
||||
* @param[in] dev_addr I2C address of sensor. Use SGP40_ADDR for default address.
|
||||
* @param[out] handle_ret Handle to created SGP40 driver object.
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_create(i2c_master_bus_handle_t bus_handle, const uint8_t dev_addr, sgp40_handle_t *handle_ret);
|
||||
|
||||
/**
|
||||
* @brief Delete and release a sensor object
|
||||
*
|
||||
* @param sensor object handle of sgp40
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_delete(sgp40_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Reset device, than put it to idle mode
|
||||
*
|
||||
* @param sensor object handle of sgp40
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_soft_reset(sgp40_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Perform a self-test
|
||||
*
|
||||
* @param sensor object handle of sgp40
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_self_test(sgp40_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Turn hotplate off, stop measurement and put device to idle mode
|
||||
*
|
||||
* @param sensor object handle of sgp40
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_heater_off(sgp40_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Perform a measurement
|
||||
*
|
||||
* @param sensor object handle of sgp40
|
||||
* @param humidity Relative humidity, percents. Use NaN if
|
||||
* you want uncompensated measurement
|
||||
* @param temperature Temperature, degrees Celsius. Use NaN if
|
||||
* you want uncompensated measurement
|
||||
* @param[out] raw Raw value, proportional to the logarithm
|
||||
* of the resistance of the sensing element
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_measure_raw(sgp40_handle_t sensor, float humidity, float temperature, uint16_t *raw);
|
||||
|
||||
/**
|
||||
* @brief Perform a measurement and update VOC index
|
||||
*
|
||||
* @param sensor object handle of sgp40
|
||||
* @param humidity Relative humidity, percents. Use NaN if
|
||||
* you want uncompensated measurement
|
||||
* @param temperature Temperature, degrees Celsius. Use NaN if
|
||||
* you want uncompensated measurement
|
||||
* @param[out] voc_index Calculated VOC index
|
||||
* @return `ESP_OK` on success
|
||||
*/
|
||||
esp_err_t sgp40_measure_voc(sgp40_handle_t sensor, float humidity, float temperature, int32_t *voc_index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**@}*/
|
||||
|
||||
#endif /* __SGP40_H__ */
|
||||
898
sgp40/sensirion_voc_algorithm.c
Normal file
898
sgp40/sensirion_voc_algorithm.c
Normal file
@@ -0,0 +1,898 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "sensirion_voc_algorithm.h"
|
||||
|
||||
/* The fixed point arithmetic parts of this code were originally created by
|
||||
* https://github.com/PetteriAimonen/libfixmath
|
||||
*/
|
||||
|
||||
/*!< the maximum value of fix16_t */
|
||||
#define FIX16_MAXIMUM 0x7FFFFFFF
|
||||
/*!< the minimum value of fix16_t */
|
||||
#define FIX16_MINIMUM 0x80000000
|
||||
/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not
|
||||
* specified */
|
||||
#define FIX16_OVERFLOW 0x80000000
|
||||
/*!< fix16_t value of 1 */
|
||||
#define FIX16_ONE 0x00010000
|
||||
|
||||
inline fix16_t fix16_from_int(int32_t a)
|
||||
{
|
||||
return a * FIX16_ONE;
|
||||
}
|
||||
|
||||
inline int32_t fix16_cast_to_int(fix16_t a)
|
||||
{
|
||||
return (a >> 16);
|
||||
}
|
||||
|
||||
/*! Multiplies the two given fix16_t's and returns the result. */
|
||||
static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1);
|
||||
|
||||
/*! Divides the first given fix16_t by the second and returns the result. */
|
||||
static fix16_t fix16_div(fix16_t inArg0, fix16_t inArg1);
|
||||
|
||||
/*! Returns the square root of the given fix16_t. */
|
||||
static fix16_t fix16_sqrt(fix16_t inValue);
|
||||
|
||||
/*! Returns the exponent (e^) of the given fix16_t. */
|
||||
static fix16_t fix16_exp(fix16_t inValue);
|
||||
|
||||
static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
|
||||
{
|
||||
// Each argument is divided to 16-bit parts.
|
||||
// AB
|
||||
// * CD
|
||||
// -----------
|
||||
// BD 16 * 16 -> 32 bit products
|
||||
// CB
|
||||
// AD
|
||||
// AC
|
||||
// |----| 64 bit product
|
||||
int32_t A = (inArg0 >> 16), C = (inArg1 >> 16);
|
||||
uint32_t B = (inArg0 & 0xFFFF), D = (inArg1 & 0xFFFF);
|
||||
|
||||
int32_t AC = A * C;
|
||||
int32_t AD_CB = A * D + C * B;
|
||||
uint32_t BD = B * D;
|
||||
|
||||
int32_t product_hi = AC + (AD_CB >> 16);
|
||||
|
||||
// Handle carry from lower 32 bits to upper part of result.
|
||||
uint32_t ad_cb_temp = AD_CB << 16;
|
||||
uint32_t product_lo = BD + ad_cb_temp;
|
||||
if (product_lo < BD)
|
||||
product_hi++;
|
||||
|
||||
#ifndef FIXMATH_NO_OVERFLOW
|
||||
// The upper 17 bits should all be the same (the sign).
|
||||
if (product_hi >> 31 != product_hi >> 15)
|
||||
return FIX16_OVERFLOW;
|
||||
#endif
|
||||
|
||||
#ifdef FIXMATH_NO_ROUNDING
|
||||
return (product_hi << 16) | (product_lo >> 16);
|
||||
#else
|
||||
// Subtracting 0x8000 (= 0.5) and then using signed right shift
|
||||
// achieves proper rounding to result-1, except in the corner
|
||||
// case of negative numbers and lowest word = 0x8000.
|
||||
// To handle that, we also have to subtract 1 for negative numbers.
|
||||
uint32_t product_lo_tmp = product_lo;
|
||||
product_lo -= 0x8000;
|
||||
product_lo -= (uint32_t)product_hi >> 31;
|
||||
if (product_lo > product_lo_tmp)
|
||||
product_hi--;
|
||||
|
||||
// Discard the lowest 16 bits. Note that this is not exactly the same
|
||||
// as dividing by 0x10000. For example if product = -1, result will
|
||||
// also be -1 and not 0. This is compensated by adding +1 to the result
|
||||
// and compensating this in turn in the rounding above.
|
||||
fix16_t result = (product_hi << 16) | (product_lo >> 16);
|
||||
result += 1;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
static fix16_t fix16_div(fix16_t a, fix16_t b)
|
||||
{
|
||||
// This uses the basic binary restoring division algorithm.
|
||||
// It appears to be faster to do the whole division manually than
|
||||
// trying to compose a 64-bit divide out of 32-bit divisions on
|
||||
// platforms without hardware divide.
|
||||
|
||||
if (b == 0)
|
||||
return FIX16_MINIMUM;
|
||||
|
||||
uint32_t remainder = (a >= 0) ? a : (-a);
|
||||
uint32_t divider = (b >= 0) ? b : (-b);
|
||||
|
||||
uint32_t quotient = 0;
|
||||
uint32_t bit = 0x10000;
|
||||
|
||||
/* The algorithm requires D >= R */
|
||||
while (divider < remainder)
|
||||
{
|
||||
divider <<= 1;
|
||||
bit <<= 1;
|
||||
}
|
||||
|
||||
#ifndef FIXMATH_NO_OVERFLOW
|
||||
if (!bit)
|
||||
return FIX16_OVERFLOW;
|
||||
#endif
|
||||
|
||||
if (divider & 0x80000000)
|
||||
{
|
||||
// Perform one step manually to avoid overflows later.
|
||||
// We know that divider's bottom bit is 0 here.
|
||||
if (remainder >= divider)
|
||||
{
|
||||
quotient |= bit;
|
||||
remainder -= divider;
|
||||
}
|
||||
divider >>= 1;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
/* Main division loop */
|
||||
while (bit && remainder)
|
||||
{
|
||||
if (remainder >= divider)
|
||||
{
|
||||
quotient |= bit;
|
||||
remainder -= divider;
|
||||
}
|
||||
|
||||
remainder <<= 1;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
#ifndef FIXMATH_NO_ROUNDING
|
||||
if (remainder >= divider)
|
||||
{
|
||||
quotient++;
|
||||
}
|
||||
#endif
|
||||
|
||||
fix16_t result = quotient;
|
||||
|
||||
/* Figure out the sign of result */
|
||||
if ((a ^ b) & 0x80000000)
|
||||
{
|
||||
#ifndef FIXMATH_NO_OVERFLOW
|
||||
if (result == FIX16_MINIMUM)
|
||||
return FIX16_OVERFLOW;
|
||||
#endif
|
||||
|
||||
result = -result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static fix16_t fix16_sqrt(fix16_t x)
|
||||
{
|
||||
// It is assumed that x is not negative
|
||||
|
||||
uint32_t num = x;
|
||||
uint32_t result = 0;
|
||||
uint32_t bit;
|
||||
uint8_t n;
|
||||
|
||||
bit = (uint32_t)1 << 30;
|
||||
while (bit > num)
|
||||
bit >>= 2;
|
||||
|
||||
// The main part is executed twice, in order to avoid
|
||||
// using 64 bit values in computations.
|
||||
for (n = 0; n < 2; n++)
|
||||
{
|
||||
// First we get the top 24 bits of the answer.
|
||||
while (bit)
|
||||
{
|
||||
if (num >= result + bit)
|
||||
{
|
||||
num -= result + bit;
|
||||
result = (result >> 1) + bit;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (result >> 1);
|
||||
}
|
||||
bit >>= 2;
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
// Then process it again to get the lowest 8 bits.
|
||||
if (num > 65535)
|
||||
{
|
||||
// The remainder 'num' is too large to be shifted left
|
||||
// by 16, so we have to add 1 to result manually and
|
||||
// adjust 'num' accordingly.
|
||||
// num = a - (result + 0.5)^2
|
||||
// = num + result^2 - (result + 0.5)^2
|
||||
// = num - result - 0.5
|
||||
num -= result;
|
||||
num = (num << 16) - 0x8000;
|
||||
result = (result << 16) + 0x8000;
|
||||
}
|
||||
else
|
||||
{
|
||||
num <<= 16;
|
||||
result <<= 16;
|
||||
}
|
||||
|
||||
bit = 1 << 14;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FIXMATH_NO_ROUNDING
|
||||
// Finally, if next bit would have been 1, round the result upwards.
|
||||
if (num > result)
|
||||
{
|
||||
result++;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (fix16_t)result;
|
||||
}
|
||||
|
||||
static fix16_t fix16_exp(fix16_t x)
|
||||
{
|
||||
// Function to approximate exp(); optimized more for code size than speed
|
||||
|
||||
// exp(x) for x = +/- {1, 1/8, 1/64, 1/512}
|
||||
#define NUM_EXP_VALUES 4
|
||||
static const fix16_t exp_pos_values[NUM_EXP_VALUES] =
|
||||
{
|
||||
F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)
|
||||
};
|
||||
static const fix16_t exp_neg_values[NUM_EXP_VALUES] =
|
||||
{
|
||||
F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)
|
||||
};
|
||||
const fix16_t* exp_values;
|
||||
|
||||
fix16_t res, arg;
|
||||
uint16_t i;
|
||||
|
||||
if (x >= F16(10.3972))
|
||||
return FIX16_MAXIMUM;
|
||||
if (x <= F16(-11.7835))
|
||||
return 0;
|
||||
|
||||
if (x < 0)
|
||||
{
|
||||
x = -x;
|
||||
exp_values = exp_neg_values;
|
||||
}
|
||||
else
|
||||
{
|
||||
exp_values = exp_pos_values;
|
||||
}
|
||||
|
||||
res = FIX16_ONE;
|
||||
arg = FIX16_ONE;
|
||||
for (i = 0; i < NUM_EXP_VALUES; i++)
|
||||
{
|
||||
while (x >= arg)
|
||||
{
|
||||
res = fix16_mul(res, exp_values[i]);
|
||||
x -= arg;
|
||||
}
|
||||
arg >>= 3;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void VocAlgorithm__init_instances(VocAlgorithmParams* params);
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator___init_instances(
|
||||
VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t std_initial,
|
||||
fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes);
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
|
||||
fix16_t mean, fix16_t std,
|
||||
fix16_t uptime_gamma);
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params);
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
|
||||
VocAlgorithmParams* params, fix16_t voc_index_from_prior);
|
||||
static void VocAlgorithm__mean_variance_estimator__process(
|
||||
VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior);
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
|
||||
VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K);
|
||||
static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
VocAlgorithmParams* params, fix16_t sample);
|
||||
static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t SRAW_STD,
|
||||
fix16_t SRAW_MEAN);
|
||||
static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
|
||||
fix16_t sraw);
|
||||
static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params);
|
||||
static void
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t offset);
|
||||
static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
|
||||
fix16_t sample);
|
||||
static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params);
|
||||
static void
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params);
|
||||
static fix16_t
|
||||
VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
|
||||
fix16_t sample);
|
||||
|
||||
void VocAlgorithm_init(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
params->mVoc_Index_Offset = F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
|
||||
params->mTau_Mean_Variance_Hours =
|
||||
F16(VocAlgorithm_TAU_MEAN_VARIANCE_HOURS);
|
||||
params->mGating_Max_Duration_Minutes =
|
||||
F16(VocAlgorithm_GATING_MAX_DURATION_MINUTES);
|
||||
params->mSraw_Std_Initial = F16(VocAlgorithm_SRAW_STD_INITIAL);
|
||||
params->mUptime = F16(0.);
|
||||
params->mSraw = F16(0.);
|
||||
params->mVoc_Index = 0;
|
||||
VocAlgorithm__init_instances(params);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__init_instances(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__mean_variance_estimator__init(params);
|
||||
VocAlgorithm__mean_variance_estimator__set_parameters(
|
||||
params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours,
|
||||
params->mGating_Max_Duration_Minutes);
|
||||
VocAlgorithm__mox_model__init(params);
|
||||
VocAlgorithm__mox_model__set_parameters(
|
||||
params, VocAlgorithm__mean_variance_estimator__get_std(params),
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(params));
|
||||
VocAlgorithm__sigmoid_scaled__init(params);
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(params,
|
||||
params->mVoc_Index_Offset);
|
||||
VocAlgorithm__adaptive_lowpass__init(params);
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(params);
|
||||
}
|
||||
|
||||
void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
|
||||
int32_t* state1)
|
||||
{
|
||||
|
||||
*state0 = VocAlgorithm__mean_variance_estimator__get_mean(params);
|
||||
*state1 = VocAlgorithm__mean_variance_estimator__get_std(params);
|
||||
return;
|
||||
}
|
||||
|
||||
void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
|
||||
int32_t state1)
|
||||
{
|
||||
|
||||
VocAlgorithm__mean_variance_estimator__set_states(
|
||||
params, state0, state1, F16(VocAlgorithm_PERSISTENCE_UPTIME_GAMMA));
|
||||
params->mSraw = state0;
|
||||
}
|
||||
|
||||
void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
|
||||
int32_t voc_index_offset,
|
||||
int32_t learning_time_hours,
|
||||
int32_t gating_max_duration_minutes,
|
||||
int32_t std_initial)
|
||||
{
|
||||
|
||||
params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset));
|
||||
params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours));
|
||||
params->mGating_Max_Duration_Minutes =
|
||||
(fix16_from_int(gating_max_duration_minutes));
|
||||
params->mSraw_Std_Initial = (fix16_from_int(std_initial));
|
||||
VocAlgorithm__init_instances(params);
|
||||
}
|
||||
|
||||
void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
|
||||
int32_t* voc_index)
|
||||
{
|
||||
|
||||
if ((params->mUptime <= F16(VocAlgorithm_INITIAL_BLACKOUT)))
|
||||
{
|
||||
params->mUptime =
|
||||
(params->mUptime + F16(VocAlgorithm_SAMPLING_INTERVAL));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (((sraw > 0) && (sraw < 65000)))
|
||||
{
|
||||
if ((sraw < 20001))
|
||||
{
|
||||
sraw = 20001;
|
||||
}
|
||||
else if ((sraw > 52767))
|
||||
{
|
||||
sraw = 52767;
|
||||
}
|
||||
params->mSraw = (fix16_from_int((sraw - 20000)));
|
||||
}
|
||||
params->mVoc_Index =
|
||||
VocAlgorithm__mox_model__process(params, params->mSraw);
|
||||
params->mVoc_Index =
|
||||
VocAlgorithm__sigmoid_scaled__process(params, params->mVoc_Index);
|
||||
params->mVoc_Index =
|
||||
VocAlgorithm__adaptive_lowpass__process(params, params->mVoc_Index);
|
||||
if ((params->mVoc_Index < F16(0.5)))
|
||||
{
|
||||
params->mVoc_Index = F16(0.5);
|
||||
}
|
||||
if ((params->mSraw > F16(0.)))
|
||||
{
|
||||
VocAlgorithm__mean_variance_estimator__process(
|
||||
params, params->mSraw, params->mVoc_Index);
|
||||
VocAlgorithm__mox_model__set_parameters(
|
||||
params, VocAlgorithm__mean_variance_estimator__get_std(params),
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(params));
|
||||
}
|
||||
}
|
||||
*voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5))));
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__mean_variance_estimator__set_parameters(params, F16(0.),
|
||||
F16(0.), F16(0.));
|
||||
VocAlgorithm__mean_variance_estimator___init_instances(params);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___init_instances(
|
||||
VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__init(params);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t std_initial,
|
||||
fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes)
|
||||
{
|
||||
|
||||
params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes =
|
||||
gating_max_duration_minutes;
|
||||
params->m_Mean_Variance_Estimator___Initialized = false;
|
||||
params->m_Mean_Variance_Estimator___Mean = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Sraw_Offset = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Std = std_initial;
|
||||
params->m_Mean_Variance_Estimator___Gamma =
|
||||
(fix16_div(F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
|
||||
(VocAlgorithm_SAMPLING_INTERVAL / 3600.))),
|
||||
(tau_mean_variance_hours +
|
||||
F16((VocAlgorithm_SAMPLING_INTERVAL / 3600.)))));
|
||||
params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
|
||||
F16(((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
|
||||
VocAlgorithm_SAMPLING_INTERVAL) /
|
||||
(VocAlgorithm_TAU_INITIAL_MEAN + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = F16(
|
||||
((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
|
||||
VocAlgorithm_SAMPLING_INTERVAL) /
|
||||
(VocAlgorithm_TAU_INITIAL_VARIANCE + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Mean_Variance_Estimator__Gamma_Mean = F16(0.);
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gamma = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
|
||||
fix16_t mean, fix16_t std,
|
||||
fix16_t uptime_gamma)
|
||||
{
|
||||
|
||||
params->m_Mean_Variance_Estimator___Mean = mean;
|
||||
params->m_Mean_Variance_Estimator___Std = std;
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma;
|
||||
params->m_Mean_Variance_Estimator___Initialized = true;
|
||||
}
|
||||
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
return params->m_Mean_Variance_Estimator___Std;
|
||||
}
|
||||
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
return (params->m_Mean_Variance_Estimator___Mean +
|
||||
params->m_Mean_Variance_Estimator___Sraw_Offset);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
|
||||
VocAlgorithmParams* params, fix16_t voc_index_from_prior)
|
||||
{
|
||||
|
||||
fix16_t uptime_limit;
|
||||
fix16_t sigmoid_gamma_mean;
|
||||
fix16_t gamma_mean;
|
||||
fix16_t gating_threshold_mean;
|
||||
fix16_t sigmoid_gating_mean;
|
||||
fix16_t sigmoid_gamma_variance;
|
||||
fix16_t gamma_variance;
|
||||
fix16_t gating_threshold_variance;
|
||||
fix16_t sigmoid_gating_variance;
|
||||
|
||||
uptime_limit = F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
|
||||
VocAlgorithm_SAMPLING_INTERVAL));
|
||||
if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit))
|
||||
{
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gamma =
|
||||
(params->m_Mean_Variance_Estimator___Uptime_Gamma +
|
||||
F16(VocAlgorithm_SAMPLING_INTERVAL));
|
||||
}
|
||||
if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit))
|
||||
{
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gating =
|
||||
(params->m_Mean_Variance_Estimator___Uptime_Gating +
|
||||
F16(VocAlgorithm_SAMPLING_INTERVAL));
|
||||
}
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), F16(VocAlgorithm_INIT_DURATION_MEAN),
|
||||
F16(VocAlgorithm_INIT_TRANSITION_MEAN));
|
||||
sigmoid_gamma_mean =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
|
||||
gamma_mean =
|
||||
(params->m_Mean_Variance_Estimator___Gamma +
|
||||
(fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
|
||||
params->m_Mean_Variance_Estimator___Gamma),
|
||||
sigmoid_gamma_mean)));
|
||||
gating_threshold_mean =
|
||||
(F16(VocAlgorithm_GATING_THRESHOLD) +
|
||||
(fix16_mul(
|
||||
F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
|
||||
VocAlgorithm_GATING_THRESHOLD)),
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), gating_threshold_mean,
|
||||
F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
|
||||
sigmoid_gating_mean =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, voc_index_from_prior);
|
||||
params->m_Mean_Variance_Estimator__Gamma_Mean =
|
||||
(fix16_mul(sigmoid_gating_mean, gamma_mean));
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), F16(VocAlgorithm_INIT_DURATION_VARIANCE),
|
||||
F16(VocAlgorithm_INIT_TRANSITION_VARIANCE));
|
||||
sigmoid_gamma_variance =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
|
||||
gamma_variance =
|
||||
(params->m_Mean_Variance_Estimator___Gamma +
|
||||
(fix16_mul(
|
||||
(params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
|
||||
params->m_Mean_Variance_Estimator___Gamma),
|
||||
(sigmoid_gamma_variance - sigmoid_gamma_mean))));
|
||||
gating_threshold_variance =
|
||||
(F16(VocAlgorithm_GATING_THRESHOLD) +
|
||||
(fix16_mul(
|
||||
F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
|
||||
VocAlgorithm_GATING_THRESHOLD)),
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), gating_threshold_variance,
|
||||
F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
|
||||
sigmoid_gating_variance =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, voc_index_from_prior);
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance =
|
||||
(fix16_mul(sigmoid_gating_variance, gamma_variance));
|
||||
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
|
||||
(params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
|
||||
(fix16_mul(F16((VocAlgorithm_SAMPLING_INTERVAL / 60.)),
|
||||
((fix16_mul((F16(1.) - sigmoid_gating_mean),
|
||||
F16((1. + VocAlgorithm_GATING_MAX_RATIO)))) -
|
||||
F16(VocAlgorithm_GATING_MAX_RATIO)))));
|
||||
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes <
|
||||
F16(0.)))
|
||||
{
|
||||
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
|
||||
}
|
||||
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
|
||||
params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes))
|
||||
{
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator__process(
|
||||
VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior)
|
||||
{
|
||||
|
||||
fix16_t delta_sgp;
|
||||
fix16_t c;
|
||||
fix16_t additional_scaling;
|
||||
|
||||
if ((params->m_Mean_Variance_Estimator___Initialized == false))
|
||||
{
|
||||
params->m_Mean_Variance_Estimator___Initialized = true;
|
||||
params->m_Mean_Variance_Estimator___Sraw_Offset = sraw;
|
||||
params->m_Mean_Variance_Estimator___Mean = F16(0.);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (((params->m_Mean_Variance_Estimator___Mean >= F16(100.)) ||
|
||||
(params->m_Mean_Variance_Estimator___Mean <= F16(-100.))))
|
||||
{
|
||||
params->m_Mean_Variance_Estimator___Sraw_Offset =
|
||||
(params->m_Mean_Variance_Estimator___Sraw_Offset +
|
||||
params->m_Mean_Variance_Estimator___Mean);
|
||||
params->m_Mean_Variance_Estimator___Mean = F16(0.);
|
||||
}
|
||||
sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
|
||||
VocAlgorithm__mean_variance_estimator___calculate_gamma(
|
||||
params, voc_index_from_prior);
|
||||
delta_sgp = (fix16_div(
|
||||
(sraw - params->m_Mean_Variance_Estimator___Mean),
|
||||
F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING)));
|
||||
if ((delta_sgp < F16(0.)))
|
||||
{
|
||||
c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
|
||||
}
|
||||
else
|
||||
{
|
||||
c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
|
||||
}
|
||||
additional_scaling = F16(1.);
|
||||
if ((c > F16(1440.)))
|
||||
{
|
||||
additional_scaling = F16(4.);
|
||||
}
|
||||
params->m_Mean_Variance_Estimator___Std = (fix16_mul(
|
||||
fix16_sqrt((fix16_mul(
|
||||
additional_scaling,
|
||||
(F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) -
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance)))),
|
||||
fix16_sqrt((
|
||||
(fix16_mul(
|
||||
params->m_Mean_Variance_Estimator___Std,
|
||||
(fix16_div(
|
||||
params->m_Mean_Variance_Estimator___Std,
|
||||
(fix16_mul(
|
||||
F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING),
|
||||
additional_scaling)))))) +
|
||||
(fix16_mul(
|
||||
(fix16_div(
|
||||
(fix16_mul(
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance,
|
||||
delta_sgp)),
|
||||
additional_scaling)),
|
||||
delta_sgp))))));
|
||||
params->m_Mean_Variance_Estimator___Mean =
|
||||
(params->m_Mean_Variance_Estimator___Mean +
|
||||
(fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean,
|
||||
delta_sgp)));
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
|
||||
VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(0.), F16(0.), F16(0.));
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K)
|
||||
{
|
||||
|
||||
params->m_Mean_Variance_Estimator___Sigmoid__L = L;
|
||||
params->m_Mean_Variance_Estimator___Sigmoid__K = K;
|
||||
params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
|
||||
}
|
||||
|
||||
static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
VocAlgorithmParams* params, fix16_t sample)
|
||||
{
|
||||
|
||||
fix16_t x;
|
||||
|
||||
x = (fix16_mul(params->m_Mean_Variance_Estimator___Sigmoid__K,
|
||||
(sample - params->m_Mean_Variance_Estimator___Sigmoid__X0)));
|
||||
if ((x < F16(-50.)))
|
||||
{
|
||||
return params->m_Mean_Variance_Estimator___Sigmoid__L;
|
||||
}
|
||||
else if ((x > F16(50.)))
|
||||
{
|
||||
return F16(0.);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (fix16_div(params->m_Mean_Variance_Estimator___Sigmoid__L,
|
||||
(F16(1.) + fix16_exp(x))));
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__mox_model__set_parameters(params, F16(1.), F16(0.));
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t SRAW_STD,
|
||||
fix16_t SRAW_MEAN)
|
||||
{
|
||||
|
||||
params->m_Mox_Model__Sraw_Std = SRAW_STD;
|
||||
params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
|
||||
}
|
||||
|
||||
static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
|
||||
fix16_t sraw)
|
||||
{
|
||||
|
||||
return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean),
|
||||
(-(params->m_Mox_Model__Sraw_Std +
|
||||
F16(VocAlgorithm_SRAW_STD_BONUS))))),
|
||||
F16(VocAlgorithm_VOC_INDEX_GAIN)));
|
||||
}
|
||||
|
||||
static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(params, F16(0.));
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t offset)
|
||||
{
|
||||
|
||||
params->m_Sigmoid_Scaled__Offset = offset;
|
||||
}
|
||||
|
||||
static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
|
||||
fix16_t sample)
|
||||
{
|
||||
|
||||
fix16_t x;
|
||||
fix16_t shift;
|
||||
|
||||
x = (fix16_mul(F16(VocAlgorithm_SIGMOID_K),
|
||||
(sample - F16(VocAlgorithm_SIGMOID_X0))));
|
||||
if ((x < F16(-50.)))
|
||||
{
|
||||
return F16(VocAlgorithm_SIGMOID_L);
|
||||
}
|
||||
else if ((x > F16(50.)))
|
||||
{
|
||||
return F16(0.);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((sample >= F16(0.)))
|
||||
{
|
||||
shift = (fix16_div(
|
||||
(F16(VocAlgorithm_SIGMOID_L) -
|
||||
(fix16_mul(F16(5.), params->m_Sigmoid_Scaled__Offset))),
|
||||
F16(4.)));
|
||||
return ((fix16_div((F16(VocAlgorithm_SIGMOID_L) + shift),
|
||||
(F16(1.) + fix16_exp(x)))) -
|
||||
shift);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (fix16_mul(
|
||||
(fix16_div(params->m_Sigmoid_Scaled__Offset,
|
||||
F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT))),
|
||||
(fix16_div(F16(VocAlgorithm_SIGMOID_L),
|
||||
(F16(1.) + fix16_exp(x))))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(params);
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params)
|
||||
{
|
||||
|
||||
params->m_Adaptive_Lowpass__A1 =
|
||||
F16((VocAlgorithm_SAMPLING_INTERVAL /
|
||||
(VocAlgorithm_LP_TAU_FAST + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Adaptive_Lowpass__A2 =
|
||||
F16((VocAlgorithm_SAMPLING_INTERVAL /
|
||||
(VocAlgorithm_LP_TAU_SLOW + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Adaptive_Lowpass___Initialized = false;
|
||||
}
|
||||
|
||||
static fix16_t
|
||||
VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
|
||||
fix16_t sample)
|
||||
{
|
||||
|
||||
fix16_t abs_delta;
|
||||
fix16_t F1;
|
||||
fix16_t tau_a;
|
||||
fix16_t a3;
|
||||
|
||||
if ((params->m_Adaptive_Lowpass___Initialized == false))
|
||||
{
|
||||
params->m_Adaptive_Lowpass___X1 = sample;
|
||||
params->m_Adaptive_Lowpass___X2 = sample;
|
||||
params->m_Adaptive_Lowpass___X3 = sample;
|
||||
params->m_Adaptive_Lowpass___Initialized = true;
|
||||
}
|
||||
params->m_Adaptive_Lowpass___X1 =
|
||||
((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A1),
|
||||
params->m_Adaptive_Lowpass___X1)) +
|
||||
(fix16_mul(params->m_Adaptive_Lowpass__A1, sample)));
|
||||
params->m_Adaptive_Lowpass___X2 =
|
||||
((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A2),
|
||||
params->m_Adaptive_Lowpass___X2)) +
|
||||
(fix16_mul(params->m_Adaptive_Lowpass__A2, sample)));
|
||||
abs_delta =
|
||||
(params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
|
||||
if ((abs_delta < F16(0.)))
|
||||
{
|
||||
abs_delta = (-abs_delta);
|
||||
}
|
||||
F1 = fix16_exp((fix16_mul(F16(VocAlgorithm_LP_ALPHA), abs_delta)));
|
||||
tau_a =
|
||||
((fix16_mul(F16((VocAlgorithm_LP_TAU_SLOW - VocAlgorithm_LP_TAU_FAST)),
|
||||
F1)) +
|
||||
F16(VocAlgorithm_LP_TAU_FAST));
|
||||
a3 = (fix16_div(F16(VocAlgorithm_SAMPLING_INTERVAL),
|
||||
(F16(VocAlgorithm_SAMPLING_INTERVAL) + tau_a)));
|
||||
params->m_Adaptive_Lowpass___X3 =
|
||||
((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass___X3)) +
|
||||
(fix16_mul(a3, sample)));
|
||||
return params->m_Adaptive_Lowpass___X3;
|
||||
}
|
||||
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