diff --git a/examples/bme680/README.md b/examples/bme680/README.md new file mode 100644 index 0000000..d61d872 --- /dev/null +++ b/examples/bme680/README.md @@ -0,0 +1,42 @@ +# BME680 Driver Examples + +These examples references two addtional drivers **i2c** and **bme680**, which are provided in the `../../extras` folder. + +If you plan to use one or both of this drivers in your own projects, please check the main development pages for updated versions or reported issues. + +## Hardware setup + +There are examples that are using either I2C or SPI with one or two sensors. + +For examples using BME680 sensor as I2C slave, just use GPIO5 (SCL) and GPIO4 (SDA) to connect to the BME680 sensor's I2C interface. + +``` + +-------------------------+ +--------+ + | ESP8266 Bus 0 | | BME680 | + | GPIO 5 (SCL) +---->+ SCL | + | GPIO 4 (SDA) +-----+ SDA | + | | +--------+ + +-------------------------+ +``` + +For examples that are using SPI, BME680 sensor has to be connected to SPI bus 1. Since GPIO15 used as default CS signal of SPI bus 1 does not work correctly together with BME680, you have to connect CS to another GPIO pin, e.g., GPIO2. + + +-------------------------+ +----------+ + | ESP8266 Bus 1 | | BME680 | + | GPIO 12 (MISO) <-----< SDO | + | GPIO 13 (MOSI) >-----> SDI | + | GPIO 14 (SCK) >-----> SCK | + | GPIO 2 (CS) >-----> CS | + +-------------------------+ +----------+ + +The example with two sensors use the combination of I2C and SPI. + +## Example description + +__*bme680_one_sensor*__ + +In this simple example, only **one sensor** connected either to **I2C** or to **SPI** is used. Constant **SPI_USED** defines which interface is used. + +__*bme680_two_sensors*__ + +Simple example with two sensors, one sensor connected to **I2C** bus 0 and one sensor connected to **SPI**. It defines two different user tasks that use the sensors as well as different approaches for the implementation of waiting for measurement results, one as busy waiting using **_bme680_is_measuring_** and one as passive waiting using *vTaskDelay*. diff --git a/examples/bme680/bme680_one_sensor/Makefile b/examples/bme680/bme680_one_sensor/Makefile new file mode 100644 index 0000000..dba740d --- /dev/null +++ b/examples/bme680/bme680_one_sensor/Makefile @@ -0,0 +1,3 @@ +PROGRAM=BME680_One_Sensor +EXTRA_COMPONENTS = extras/i2c extras/bme680 +include ../../../common.mk diff --git a/examples/bme680/bme680_one_sensor/bme680_one_sensor.c b/examples/bme680/bme680_one_sensor/bme680_one_sensor.c new file mode 100644 index 0000000..190e9e0 --- /dev/null +++ b/examples/bme680/bme680_one_sensor/bme680_one_sensor.c @@ -0,0 +1,126 @@ +/** + * Simple example with one sensor connected either to I2C bus 0 or + * SPI bus 1. + * + * Harware configuration: + * + * I2C +-------------------------+ +----------+ + * | ESP8266 Bus 0 | | BME680 | + * | GPIO 5 (SCL) ------> SCL | + * | GPIO 4 (SDA) ------- SDA | + * +-------------------------+ +----------+ + * + * SPI +-------------------------+ +----------+ + * | ESP8266 Bus 1 | | BME680 | + * | GPIO 12 (MISO) <-----< SDO | + * | GPIO 13 (MOSI) >-----> SDI | + * | GPIO 14 (SCK) >-----> SCK | + * | GPIO 2 (CS) >-----> CS | + * +-------------------------+ +----------+ + */ + +// Uncomment to use SPI +// #define SPI_USED + +#include "espressif/esp_common.h" +#include "esp/uart.h" + +// include communication interface driver +#include "esp/spi.h" +#include "i2c/i2c.h" + +// include BME680 driver +#include "bme680/bme680.h" + +#ifdef SPI_USED +// define SPI interface for BME680 sensors +#define SPI_BUS 1 +#define SPI_CS_GPIO 2 // GPIO 15, the default CS of SPI bus 1, can't be used +#else +// define I2C interface for BME680 sensors +#define I2C_BUS 0 +#define I2C_SCL_PIN GPIO_ID_PIN((5)) +#define I2C_SDA_PIN GPIO_ID_PIN((4)) +#endif + +static bme680_sensor_t* sensor; + +/* + * User task that triggers measurements of sensor every seconds. It uses + * function *vTaskDelay* to wait for measurement results. Busy wating + * alternative is shown in comments + */ +void user_task(void *pvParameters) +{ + bme680_values_float_t values; + int32_t duration; + + TickType_t last_wakeup = xTaskGetTickCount(); + + while (1) + { + // trigger the sensor to start one TPHG measurement cycle + duration = bme680_force_measurement (sensor); + + // passive waiting until measurement results are available + if (duration > 0) vTaskDelay (duration); + + // busy waiting until measurement results are available + // while (bme680_is_measuring (sensor) > 0) ; + + // get the results and do something with them + if (bme680_get_results_float (sensor, &values)) + printf("%.3f BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n", + (double)sdk_system_get_time()*1e-3, + values.temperature, values.humidity, + values.pressure, values.gas_resistance); + + // passive waiting until 1 second is over + vTaskDelayUntil(&last_wakeup, 1000 / portTICK_PERIOD_MS); + } +} + + +void user_init(void) +{ + // Set UART Parameter + uart_set_baud(0, 115200); + // Give the UART some time to settle + sdk_os_delay_us(500); + + /** -- MANDATORY PART -- */ + + + #ifdef SPI_USED + // Init the sensor connected either to SPI. + sensor = bme680_init_sensor (SPI_BUS, 0, SPI_CS_GPIO); + #else + // Init all I2C bus interfaces at which BME680 sensors are connected + i2c_init(I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ_100K); + + // Init the sensor connected either to I2C. + sensor = bme680_init_sensor (I2C_BUS, BME680_I2C_ADDRESS_2, 0); + #endif + + if (sensor) + { + // Create a task that uses the sensor + xTaskCreate(user_task, "user_task", 256, NULL, 2, NULL); + + /** -- OPTIONAL PART -- */ + + // Changes the oversampling rates (default os_1x) to 4x oversampling + // for temperature and 2x oversampling for humidity. Pressure + // measurement is skipped in this example. + bme680_set_oversampling_rates(sensor, osr_4x, osr_none, osr_2x); + + // Change the IIR filter size (default iir_size_3) for temperature and + // and pressure to 7. + bme680_set_filter_size(sensor, iir_size_7); + + // Change the heaeter profile (default 320 degree Celcius for 150 ms) + // to 200 degree Celcius for 100 ms. + bme680_set_heater_profile (sensor, 320, 150); + } +} + diff --git a/examples/bme680/bme680_two_sensors/Makefile b/examples/bme680/bme680_two_sensors/Makefile new file mode 100644 index 0000000..e17e876 --- /dev/null +++ b/examples/bme680/bme680_two_sensors/Makefile @@ -0,0 +1,3 @@ +PROGRAM=BME680_Tow_Sensors +EXTRA_COMPONENTS = extras/i2c extras/bme680 +include ../../../common.mk diff --git a/examples/bme680/bme680_two_sensors/bme680_two_sensors.c b/examples/bme680/bme680_two_sensors/bme680_two_sensors.c new file mode 100644 index 0000000..889c75b --- /dev/null +++ b/examples/bme680/bme680_two_sensors/bme680_two_sensors.c @@ -0,0 +1,146 @@ +/** + * Simple example with two sensors, one sensor connected to I2C bus 0 and + * one sensor connected to SPI. It also shows both approaches for the + * implementation of waiting for measurement results, one as busy waiting + * and one as passive waiting using *vTaskDelay*. + * + * Harware configuration: + * + * +-------------------------+ +----------+ + * | ESP8266 I2C Bus 0 | | BME680_1 | + * | GPIO 5 (SCL) ------> SCL | + * | GPIO 4 (SDA) ------- SDA | + * | | +----------+ + * | SPI Bus 1 | | BME680_2 | + * | GPIO 12 (MISO) <------ SDO | + * | GPIO 13 (MOSI) >-----> SDI | + * | GPIO 14 (SCK) >-----> SCK | + * | GPIO 2 (CS) >-----> CS | + * +-------------------------+ +----------+ + */ + +#include "espressif/esp_common.h" +#include "esp/uart.h" + +// include communication interface driver +#include "esp/spi.h" +#include "i2c/i2c.h" + +// include BME680 driver +#include "bme680/bme680.h" + +// define I2C interface for BME680 sensor 1 +#define SPI_BUS 1 +#define SPI_CS_GPIO 2 // GPIO 15, the default CS of SPI bus 1, can't be used +// define SPI interface for BME680 sensor 2 +#define I2C_BUS 0 +#define I2C_SCL_PIN GPIO_ID_PIN((5)) +#define I2C_SDA_PIN GPIO_ID_PIN((4)) + +static bme680_sensor_t* sensor1; +static bme680_sensor_t* sensor2; + +/* + * User task that triggers measurements of sensor1 every 5 seconds. It + * uses *vTaskDelay* to wait for measurement results. + */ +void user_task_sensor1(void *pvParameters) +{ + bme680_values_float_t values; + int32_t duration; + + TickType_t last_wakeup = xTaskGetTickCount(); + + while (1) + { + // trigger the sensor to start one TPHG measurement cycle + duration = bme680_force_measurement (sensor1); + + // passive waiting until measurement results are available + if (duration > 0) vTaskDelay (duration); + + // get the results and so something with them + if (bme680_get_results_float (sensor1, &values)) + printf("%.3f BME680 Sensor1: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n", + (double)sdk_system_get_time()*1e-3, + values.temperature, values.humidity, + values.pressure, values.gas_resistance); + + // passive waiting until 5 seconds are over + vTaskDelayUntil(&last_wakeup, 5000 / portTICK_PERIOD_MS); + } +} + +/* + * User task that triggers measurements of sensor1 every 2 seconds. It + * uses *vTaskDelay* to wait for measurement results. + */ +void user_task_sensor2(void *pvParameters) +{ + bme680_values_float_t values; + + TickType_t last_wakeup = xTaskGetTickCount(); + + while (1) + { + // trigger the sensor to start one TPHG measurement cycle + bme680_force_measurement (sensor2); + + // busy waiting until measurement results are available + while (bme680_is_measuring (sensor2) > 0) ; + + // get the results and so something with them + if (bme680_get_results_float (sensor2, &values)) + printf("%.3f BME680 Sensor2: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n", + (double)sdk_system_get_time()*1e-3, + values.temperature, values.humidity, + values.pressure, values.gas_resistance); + + // passive waiting until 2 seconds are over + vTaskDelayUntil(&last_wakeup, 2000 / portTICK_PERIOD_MS); + } +} + + +void user_init(void) +{ + // Set UART Parameter + uart_set_baud(0, 115200); + // Give the UART some time to settle + sdk_os_delay_us(500); + + /** -- MANDATORY PART -- */ + + // Init all I2C bus interfaces at which BME680 sensors are connected + i2c_init(I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ_100K); + + // Init the sensors connected to different I2C buses with same address + sensor1 = bme680_init_sensor (I2C_BUS, BME680_I2C_ADDRESS_2, 0); + sensor2 = bme680_init_sensor (SPI_BUS, 0, SPI_CS_GPIO); + + if (sensor1 && sensor2) + { + // Create the tasks that use the sensors + xTaskCreate(user_task_sensor1, "user_task_sensor1", 256, NULL, 2, 0); + xTaskCreate(user_task_sensor2, "user_task_sensor2", 256, NULL, 2, 0); + + // That's it. + + /** -- OPTIONAL PART -- */ + + // Changes the oversampling rates for both sensor to different values + bme680_set_oversampling_rates(sensor1, osr_1x, osr_1x, osr_1x); + bme680_set_oversampling_rates(sensor2, osr_16x, osr_16x, osr_16x); + + // Change the IIR filter size (default iir_size_3) for temperature and + // and pressure to 7. + bme680_set_filter_size(sensor1, iir_size_7); + bme680_set_filter_size(sensor2, iir_size_7); + + // Change the heaeter profile (default 20 degree Celcius for 150 ms) to + // 200 degree Celcius for 100 ms. + bme680_set_heater_profile (sensor1, 200, 100); + bme680_set_heater_profile (sensor2, 200, 100); + } +} + diff --git a/extras/bme680/README.md b/extras/bme680/README.md new file mode 100644 index 0000000..5c7ca9a --- /dev/null +++ b/extras/bme680/README.md @@ -0,0 +1,405 @@ +# Driver for **BME680** digital **environmental sensor** + +This driver is written for usage with the ESP8266 and FreeRTOS. It supports multiple sensors that are either connected to the SPI or to different I2C interfaces with different addresses. + +## About the sensor + +BME680 is an ulta-low-power environmental sensor that integrates temperature, pressure, humidity and gas sensors in only one unit. + +## Communication interfaces + +The BME680 sensor can be connected using I2C or SPI. The driver supports both communication interfaces. + +The I2C interface supports data rates up to 3.4 Mbps. It is possible to connect multiple BME680 sensors with different I2C slave addresses to the same I2C bus or to different I2C buses. Possible I2C slave addresses are 0x76 and 0x77. + +The SPI interface allows clock rates up to 10 MHz and the SPI modes '00' (CPOL=CPHA=0) and '11' (CPOL=CPHA=1). + +Interface selection is done automatically using the SPI CS signal. As long as the CS signal keeps high after power-on reset, the I2C interface is used. Once the CS signal has been pulled down, SPI interface is used until next power-on reset. + +## Measurement process + +Once the BME680 has been initialized, it can be used for measurements. The BME680 operates in two different low-level power modes, the **sleep mode** and the **forced mode**. + +The *sleep mode* is the default mode. The sensor starts automatically in this mode after power-up sequence. It does not perform any measurement in this mode and therefore just consumes 0.15 μA. + +#### Measurement cylce + +To perform measurements, the BME680 sensor has to be triggered to switch to the **forced mode**. In this mode, it performs exactly one measurement of temperature, pressure, humidity, and gas in that order, the so-called **TPHG measurement cycle**. After the execution of this TPHG measurement cylce, the **raw sensor data** become available and the sensor returns automatically back to sleep mode. + +Each of the individual measurements can be configured or skipped separately via the sensor settings, see section **Measurement settings**. Dependent on the configuration, the **duration of a TPHG measurement cycle** can vary from some milliseconds up to about 4.5 seconds, escpecially if gas measurement is enabled. + +Therefore, the measurement process is separated into the following steps: + +1. Trigger the sensor with function **_bme680_force_measurement_** to switch to *forced mode* in which it performs exactly one THPG measurement cycle. + +2. Wait the measurement duration using either passive waiting with function **_vTaskDelay_** or busy waiting with function **_bme680_is_measuring_** until the results are available. + +3. Fetch the results as fixed point values with function **_bme680_get_results_fixed_** or as floating point values with function **_bme680_get_results_float_**. + +For convenience reasons, it is also possible to use either function **_bme680_measure_float_** or function **_bme680_measure_fixed_**, which combine all 3 steps above within a single function. **Please note**, these functions must not be used when the are called from a software timer callback function since the calling task is delayed using function *vTaskDelay*. + +#### Measurement results + +Once the sensor has finished the measurement, either function **_bme680_get_results_fixed_** or function **_bme680_get_results_float_** can be used to fetch the results. These functions read the raw data from the sensor and converts them into fixed point or floating point sensor values. This conversion process bases in very complex calculations using a large number of calibration parameters. + +Dependent on sensor value representation, measurement results contain different dimensions: + +| Value | Fixed Point | Floating Point | Conversion +| ----- | ----------- | -------------- |----------- +| temperature | 1/100 °C | °C | float = fixed / 100 +| pressure | Pascal | hPascal | float = fixed / 100 +| humdidity | 1/1000 % | % | float = fixed / 1000 +| gas_resistance | Ohm | Ohm | float = fixed + +The gas resistance value in Ohm represents the resistance of sensor's gas sensitive layer. + +## Measurement settings + +The sensor allows to change different measurement parameters. + +#### Oversampling rates + +Using function **_bme680_set_oversampling_rates_**, it is possible to define individual oversampling rates for the measurements of temperature, pressure, and humidity. Possible oversampling rates are 1x (default by the driver) 2x, 4x, 8x and 16x. Using an oversampling rate *osr*, the resolution of raw sensor data can be increased from 16 bit to 16+ld(*osr*) bit. + +It is also possible to define an oversampling rate of 0. This **deactivates** the corresponding measurement and the output values become invalid. + +#### IIR Filter + +The sensor integrates an internal IIR filter (low pass filter) to reduce short-term changes in sensor output values caused by external disturbances. It effectively reduces the bandwidth of the sensor output values. + +The filter can optionally be used for pressure and temperature data that are subject to many short-term changes. Using the IIR filter, increases the resolution of pressure and temperature data to 20 bit. Humidity and gas inside the sensor does not fluctuate rapidly and does not require such a low pass filtering. + +Using function **_bme680_set_filter_size_**, the user task can change the **size of the filter**. The default size is 3. If the size of the filter becomes 0, the filter is **not used**. + +#### Heater profile + +For gas measurement the sensor integrates a heater. The paremeters for this heater are defined by a heater profile. Such a heater profile consists of a temperature setting point (the target temperature) and the heating duration. + +Even though the sensor supports up to 10 different such profiles at them same time, only one profile is used by the driver for simplicity reasons. The **temperature setting point** and the **heating duration** of this one profile can be defined by the user task using function **_bme680_set_heater_profile_**. Default values used by the driver are 320 degree Celsius as target temperature and 150 ms heating duration. + +According to the datasheet, target temperatures between 200 and 400 degrees Celsius are typical and about 20 to 30 ms are necessary for the heater to reach the desired target temperature. + +If heating duration is 0 ms, the gas measurement **is skipped** and output values become invalid. + +## Error Handling + +Most driver functions return a simple boolean value to indicate whether its execution was successful or an error happened. In the latter case, the member **_error_code_** of the sensor device data structure is set which indicates what error happened. + +There are two different error levels that are ORed into one single *error_code*, errors in the I2C or SPI communication and errors with the BME680 sensor itself. To test for a certain error, you can AND the *error_code* with one of the error masks, **_BME680_INT_ERROR_MASK_** for I2C or SPI errors and **_BME680_DRV_ERROR_MASK_** for other errors and then test for a certain error code. + + +## Usage + +First, the hardware configuration has to be establisch. This can differ dependent on the communication interface used and the number of sensors. + +### Hardware configurations + +The driver supports multiple BME680 sensors at the same time that are connected either to I2C or SPI. Following figures show some possible hardware configurations. + +First figure shows the configuration with only one sensor at I2C bus 0. + +``` + +-------------------------+ +--------+ + | ESP8266 Bus 0 | | BME680 | + | GPIO 5 (SCL) +---->+ SCL | + | GPIO 4 (SDA) +-----+ SDA | + | | +--------+ + +-------------------------+ +``` +Next figure shows a possible configuration with two I2C buses. In that case, the sensors can have same or different I2C slave adresses. + +``` + +-------------------------+ +----------+ + | ESP8266 Bus 0 | | BME680_1 | + | GPIO 5 (SCL) ------> SCL | + | GPIO 4 (SDA) ------- SDA | + | | +----------+ + | Bus 1 | | BME680_2 | + | GPIO 14 (SCL) ------> SCL | + | GPIO 12 (SDA) ------- SDA | + +-------------------------+ +----------+ +``` + +Last figure shows a possible configuration using I2C bus 0 and SPI bus 1 at the same time. +``` + +-------------------------+ +----------+ + | ESP8266 Bus 0 | | BME680_1 | + | GPIO 5 (SCL) ------> SCL | + | GPIO 4 (SDA) ------- SDA | + | | +----------+ + | Bus 1 | | BME680_2 | + | GPIO 12 (MISO) <------ SDO | + | GPIO 13 (MOSI) >-----> SDI | + | GPIO 14 (SCK) >-----> SCK | + | GPIO 2 (CS) >-----> CS | + +-------------------------+ +----------+ +``` +**Please note:** + +1. Since the system flash memory is connected to SPI bus 0, the sensor has to be connected to SPI bus 1. + +2. GPIO15 used as CS signal of SPI bus 1 by default, does not work correctly together with BME680. Therefore, the user has to specify another GPIO pin as CS signal when the sensor is initialized, e.g., GPIO2. + +Further configurations are possible, e.g., two sensors that are connected at the same I2C bus with different slave addresses. + +### Communication interface settings + +Dependent on the hardware configuration, the communication interface settings have to be defined. + +``` +// define SPI interface for BME680 sensors +#define SPI_BUS 1 +#define SPI_CS_GPIO 2 // GPIO 15, the default CS of SPI bus 1, can't be used +#else + +// define I2C interface for BME680 sensors +#define I2C_BUS 0 +#define I2C_SCL_PIN GPIO_ID_PIN((5)) +#define I2C_SDA_PIN GPIO_ID_PIN((4)) +#endif + +``` + +### Main programm + +If I2C interfaces are used, they have to be initialized first. + +``` +i2c_init(I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ_100K)) +``` + +Once I2C interfaces are initialized, function **_bme680_init_sensor_** has to be called for each BME680 sensor to initialize the sensor and to check its availability as well as its error state. This function returns a pointer to the sensor device data structure or NULL in case of error. + +The parameters *bus* specifies the ID of the I2C or SPI bus to which the sensor is connected. + +``` +static bme680_sensor_t* sensor; +``` + +For sensors connected to an I2C interface, a valid I2C slave address has to be defined as parameter *addr*. In that case parameter *cs* is ignored. + +``` +sensor = bme680_init_sensor (I2C_BUS, BME680_I2C_ADDRESS_2, 0); + +``` + +If parameter *addr* is 0, the sensor is connected to a SPI bus. In that case, parameter *cs* defines the GPIO used as CS signal. + +``` +sensor = bme680_init_sensor (SPI_BUS, 0, SPI_CS_GPIO); + +``` + +The remaining part of the program is independent on the communication interface. + +Optionally, you could wish to set some measurement parameters. For details see the section **Measurement settings** above, the header file of the driver **bme680.h**, and of course the datasheet of the sensor. + +``` +if (sensor) +{ + // Changes the oversampling rates (default os_1x) to 4x oversampling + // for temperature and 2x oversampling for humidity. Pressure + // measurement is skipped in this example. + bme680_set_oversampling_rates(sensor, osr_4x, osr_none, osr_2x); + + // Change the IIR filter size (default iir_size_3) for temperature and + // and pressure to 7. + bme680_set_filter_size(sensor, iir_size_7); + + // Change the heaeter profile (default 320 degree Celcius for 150 ms) + // to 200 degree Celcius for 100 ms. + bme680_set_heater_profile (sensor, 320, 150); + + ... +} +``` + +Last, the user task that uses the sensor has to be created. + + +### User task + +BME680 supports only the *forced mode* that performs exactly one measuerement. Therefore, the measurement has to be triggered in each cycle. The waiting for measurement results is also required in each cylce, before the results can be fetched. + +Thus the user task could look like the following: + + +``` +void user_task(void *pvParameters) +{ + bme680_values_float_t values; + int32_t duration; + + TickType_t last_wakeup = xTaskGetTickCount(); + + while (1) + { + // trigger the sensor to start one TPHG measurement cycle + duration = bme680_force_measurement (sensor); + + // passive waiting until measurement results are available + if (duration > 0) vTaskDelay (duration); + + // busy waiting until measurement results are available + // while (bme680_is_measuring (sensor) > 0) ; + + // get the results and do something with them + if (bme680_get_results_float (sensor, &values)) + printf("%.3f BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n", + (double)sdk_system_get_time()*1e-3, + values.temperature, values.humidity, + values.pressure, values.gas_resistance); + + // passive waiting until 1 second is over + vTaskDelayUntil(&last_wakeup, 1000 / portTICK_PERIOD_MS); + } +} +``` + +Function **_bme680_force_measurement_** is called inside the task loop to start exactly one measurement in each cycle. It returns the estimated duration of the measurement dependent on the measurement settings. The task is then delayed by this duration using function **_vTaskDelay_** to wait passively before the results are fetched with function **_bme680_get_results_float**. The busy waiting alternative is shown in comments. + +### Error Handling + +The code could be extended by an error handling. In the event of an error, most driver functions set member **_error_code_** of the sensor device data structure. This indicates which error has occurred. Error codes are a combination of I2C and SPI communication error codes as well as BME680 sensor error codes. To test a particular error, the *error code* can be AND with one of the error masks **_BME680_INT_ERROR_MASK_** or **_BME680_DRV_ERROR_MASK_**. + +For example, error handling for **_bme680_get_results_** could look like: +``` + +if (bme680_get_results (sensor, &values)) +{ + // no error happened + ... +} +else +{ + // error happened + + switch (sensor->error_code & BME680_INT_ERROR_MASK) + { + case BME680_I2C_BUSY: ... + case BME680_I2C_READ_FAILED: ... + ... + } + switch (sensor->error_code & BME680_DRV_ERROR_MASK) + { + case BME680_MEAS_NOT_RUNNING: ... + case BME680_NO_NEW_DATA: ... + ... + } +} +``` + +## Full Example + +``` +// Uncomment to use SPI +// #define SPI_USED + +#include "espressif/esp_common.h" +#include "esp/uart.h" + +// include communication interface driver +#include "esp/spi.h" +#include "i2c/i2c.h" + +// include BME680 driver +#include "bme680/bme680.h" + +#ifdef SPI_USED +// define SPI interface for BME680 sensors +#define SPI_BUS 1 +#define SPI_CS_GPIO 2 // GPIO 15, the default CS of SPI bus 1, can't be used +#else +// define I2C interface for BME680 sensors +#define I2C_BUS 0 +#define I2C_SCL_PIN GPIO_ID_PIN((5)) +#define I2C_SDA_PIN GPIO_ID_PIN((4)) +#endif + +static bme680_sensor_t* sensor; + +/* + * User task that triggers measurements of sensor every seconds. It uses + * function *vTaskDelay* to wait for measurement results. Busy wating + * alternative is shown in comments + */ +void user_task(void *pvParameters) +{ + bme680_values_float_t values; + int32_t duration; + + TickType_t last_wakeup = xTaskGetTickCount(); + + while (1) + { + // trigger the sensor to start one TPHG measurement cycle + duration = bme680_force_measurement (sensor); + + // passive waiting until measurement results are available + if (duration > 0) vTaskDelay (duration); + + // busy waiting until measurement results are available + // while (bme680_is_measuring (sensor) > 0) ; + + // get the results and do something with them + if (bme680_get_results_float (sensor, &values)) + printf("%.3f BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n", + (double)sdk_system_get_time()*1e-3, + values.temperature, values.humidity, + values.pressure, values.gas_resistance); + + // passive waiting until 1 second is over + vTaskDelayUntil(&last_wakeup, 1000 / portTICK_PERIOD_MS); + } +} + + +void user_init(void) +{ + // Set UART Parameter + uart_set_baud(0, 115200); + // Give the UART some time to settle + sdk_os_delay_us(500); + + /** -- MANDATORY PART -- */ + + + #ifdef SPI_USED + // Init the sensor connected either to SPI. + sensor = bme680_init_sensor (SPI_BUS, 0, SPI_CS_GPIO); + #else + // Init all I2C bus interfaces at which BME680 sensors are connected + i2c_init(I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ_100K); + + // Init the sensor connected either to I2C. + sensor = bme680_init_sensor (I2C_BUS, BME680_I2C_ADDRESS_2, 0); + #endif + + if (sensor) + { + // Create a task that uses the sensor + xTaskCreate(user_task, "user_task", 256, NULL, 2, NULL); + + /** -- OPTIONAL PART -- */ + + // Changes the oversampling rates (default os_1x) to 4x oversampling + // for temperature and 2x oversampling for humidity. Pressure + // measurement is skipped in this example. + bme680_set_oversampling_rates(sensor, osr_4x, osr_none, osr_2x); + + // Change the IIR filter size (default iir_size_3) for temperature and + // and pressure to 7. + bme680_set_filter_size(sensor, iir_size_7); + + // Change the heaeter profile (default 320 degree Celcius for 150 ms) + // to 200 degree Celcius for 100 ms. + bme680_set_heater_profile (sensor, 320, 150); + } +} +``` + +## Further Examples + +For further examples see [examples directory](../../examples/bme680/README.md) + + diff --git a/extras/bme680/bme680.c b/extras/bme680/bme680.c new file mode 100644 index 0000000..a85a256 --- /dev/null +++ b/extras/bme680/bme680.c @@ -0,0 +1,1380 @@ +/* + * Driver for Bosch Sensortec BME680 digital temperature, humity, pressure and + * gas sensor connected to I2C or SPI + * + * Part of esp-open-rtos [https://github.com/SuperHouse/esp-open-rtos] + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht] + * All rights reserved. + * + * 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. + */ + +#include + +#include "FreeRTOS.h" +#include "task.h" + +#include "espressif/esp_common.h" +#include "espressif/sdk_private.h" + +#include "esp/spi.h" +#include "i2c/i2c.h" + +#include "bme680.h" + +#if defined(BME680_DEBUG_LEVEL_2) +#define debug(s, f, ...) printf("%s %s: " s "\n", "BME680", f, ## __VA_ARGS__) +#define debug_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "BME680", f, d->bus, d->addr, ## __VA_ARGS__) +#else +#define debug(s, f, ...) +#define debug_dev(s, f, d, ...) +#endif + +#if defined(BME680_DEBUG_LEVEL_1) || defined(BME680_DEBUG_LEVEL_2) +#define error(s, f, ...) printf("%s %s: " s "\n", "BME680", f, ## __VA_ARGS__) +#define error_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "BME680", f, d->bus, d->addr, ## __VA_ARGS__) +#else +#define error(s, f, ...) +#define error_dev(s, f, d, ...) +#endif + +// register addresses +#define BME680_REG_RES_HEAT_VAL 0x00 +#define BME680_REG_RES_HEAT_RANGE 0x02 +#define BME680_REG_RANGE_SW_ERROR 0x06 +#define BME680_REG_MEAS_STATUS_0 0x1d +#define BME680_REG_PRESS_MSB 0x1F +#define BME680_REG_PRESS_LSB 0x20 +#define BME680_REG_PRESS_XLSB 0x21 +#define BME680_REG_TEMP_MSB 0x22 +#define BME680_REG_TEMP_LSB 0x23 +#define BME680_REG_TEMP_XLSB 0x24 +#define BME680_REG_HUM_MSB 0x25 +#define BME680_REG_HUM_LSB 0x26 +#define BME680_REG_GAS_R_MSB 0x2a +#define BME680_REG_GAS_R_LSB 0x2b +#define BME680_REG_IDAC_HEAT_BASE 0x50 // 10 regsrs idac_heat_0 ... idac_heat_9 +#define BME680_REG_RES_HEAT_BASE 0x5a // 10 registers res_heat_0 ... res_heat_9 +#define BME680_REG_GAS_WAIT_BASE 0x64 // 10 registers gas_wait_0 ... gas_wait_9 +#define BME680_REG_CTRL_GAS_0 0x70 +#define BME680_REG_CTRL_GAS_1 0x71 +#define BME680_REG_CTRL_HUM 0x72 +#define BME680_REG_STATUS 0x73 +#define BME680_REG_CTRL_MEAS 0x74 +#define BME680_REG_CONFIG 0x75 +#define BME680_REG_ID 0xd0 +#define BME680_REG_RESET 0xe0 + +// field data registers 0x1f ... 0x2b +#define BME680_REG_RAW_DATA BME680_REG_PRESS_MSB +#define BME680_REG_RAW_DATA_LEN (BME680_REG_GAS_R_LSB - BME680_REG_PRESS_MSB + 1) + +// calibration data registers +#define BME680_REG_CD1_ADDR 0x89 // 25 byte calibration data +#define BME680_REG_CD1_LEN 25 +#define BME680_REG_CD2_ADDR 0xe1 // 16 byte calibration data +#define BME680_REG_CD2_LEN 16 +#define BME680_REG_CD3_ADDR 0x00 // 8 byte device specific calibration data +#define BME680_REG_CD3_LEN 8 + +// register structure definitions +#define BME680_NEW_DATA_0_BITS 0x80 // BME680_REG_MEAS_STATUS<7> +#define BME680_NEW_DATA_0_SHIFT 7 // BME680_REG_MEAS_STATUS<7> +#define BME680_GAS_MEASURING_BITS 0x40 // BME680_REG_MEAS_STATUS<6> +#define BME680_GAS_MEASURING_SHIFT 6 // BME680_REG_MEAS_STATUS<6> +#define BME680_MEASURING_BITS 0x20 // BME680_REG_MEAS_STATUS<5> +#define BME680_MEASURING_SHIFT 5 // BME680_REG_MEAS_STATUS<5> +#define BME680_GAS_MEAS_INDEX_BITS 0x0f // BME680_REG_MEAS_STATUS<3:0> +#define BME680_GAS_MEAS_INDEX_SHIFT 0 // BME680_REG_MEAS_STATUS<3:0> + +#define BME680_GAS_R_LSB_BITS 0xc0 // BME680_REG_GAS_R_LSB<7:6> +#define BME680_GAS_R_LSB_SHIFT 6 // BME680_REG_GAS_R_LSB<7:6> +#define BME680_GAS_VALID_BITS 0x20 // BME680_REG_GAS_R_LSB<5> +#define BME680_GAS_VALID_SHIFT 5 // BME680_REG_GAS_R_LSB<5> +#define BME680_HEAT_STAB_R_BITS 0x10 // BME680_REG_GAS_R_LSB<4> +#define BME680_HEAT_STAB_R_SHIFT 4 // BME680_REG_GAS_R_LSB<4> +#define BME680_GAS_RANGE_R_BITS 0x0f // BME680_REG_GAS_R_LSB<3:0> +#define BME680_GAS_RANGE_R_SHIFT 0 // BME680_REG_GAS_R_LSB<3:0> + +#define BME680_HEAT_OFF_BITS 0x04 // BME680_REG_CTRL_GAS_0<3> +#define BME680_HEAT_OFF_SHIFT 3 // BME680_REG_CTRL_GAS_0<3> + +#define BME680_RUN_GAS_BITS 0x10 // BME680_REG_CTRL_GAS_1<4> +#define BME680_RUN_GAS_SHIFT 4 // BME680_REG_CTRL_GAS_1<4> +#define BME680_NB_CONV_BITS 0x0f // BME680_REG_CTRL_GAS_1<3:0> +#define BME680_NB_CONV_SHIFT 0 // BME680_REG_CTRL_GAS_1<3:0> + +#define BME680_SPI_3W_INT_EN_BITS 0x40 // BME680_REG_CTRL_HUM<6> +#define BME680_SPI_3W_INT_EN_SHIFT 6 // BME680_REG_CTRL_HUM<6> +#define BME680_OSR_H_BITS 0x07 // BME680_REG_CTRL_HUM<2:0> +#define BME680_OSR_H_SHIFT 0 // BME680_REG_CTRL_HUM<2:0> + +#define BME680_OSR_T_BITS 0xe0 // BME680_REG_CTRL_MEAS<7:5> +#define BME680_OSR_T_SHIFT 5 // BME680_REG_CTRL_MEAS<7:5> +#define BME680_OSR_P_BITS 0x1c // BME680_REG_CTRL_MEAS<4:2> +#define BME680_OSR_P_SHIFT 2 // BME680_REG_CTRL_MEAS<4:2> +#define BME680_MODE_BITS 0x03 // BME680_REG_CTRL_MEAS<1:0> +#define BME680_MODE_SHIFT 0 // BME680_REG_CTRL_MEAS<1:0> + +#define BME680_FILTER_BITS 0x1c // BME680_REG_CONFIG<4:2> +#define BME680_FILTER_SHIFT 2 // BME680_REG_CONFIG<4:2> +#define BME680_SPI_3W_EN_BITS 0x01 // BME680_REG_CONFIG<0> +#define BME680_SPI_3W_EN_SHIFT 0 // BME680_REG_CONFIG<0> + +#define BME680_SPI_MEM_PAGE_BITS 0x10 // BME680_REG_STATUS<4> +#define BME680_SPI_MEM_PAGE_SHIFT 4 // BME680_REG_STATUS<4> + +#define BME680_GAS_WAIT_BITS 0x3f // BME680_REG_GAS_WAIT+x<5:0> +#define BME680_GAS_WAIT_SHIFT 0 // BME680_REG_GAS_WAIT+x<5:0> +#define BME680_GAS_WAIT_MULT_BITS 0xc0 // BME680_REG_GAS_WAIT+x<7:6> +#define BME680_GAS_WAIT_MULT_SHIFT 6 // BME680_REG_GAS_WAIT+x<7:6> + +// commands +#define BME680_SLEEP_MODE 0x00 // BME680_REG_CTRL_MEAS<1:0> +#define BME680_FORCED_MODE 0x01 // BME680_REG_CTRL_MEAS<1:0> +#define BME680_RESET_CMD 0xb6 // BME680_REG_RESET<7:0> + +#define BME680_RESET_PERIOD 5 // reset time in ms + +#define BME680_RHR_BITS 0x30 // BME680_REG_RES_HEAT_RANGE<5:4> +#define BME680_RHR_SHIFT 4 // BME680_REG_RES_HEAT_RANGE<5:4> +#define BME680_RSWE_BITS 0xf0 // BME680_REG_RANGE_SW_ERROR<7:4> +#define BME680_RSWE_SHIFT 4 // BME680_REG_RANGE_SW_ERROR<7:4> + +// calibration data are stored in a calibration data map +#define BME680_CDM_SIZE (BME680_REG_CD1_LEN + BME680_REG_CD2_LEN + BME680_REG_CD3_LEN) +#define BME680_CDM_OFF1 0 +#define BME680_CDM_OFF2 BME680_REG_CD1_LEN +#define BME680_CDM_OFF3 BME680_CDM_OFF2 + BME680_REG_CD2_LEN + +// calibration parameter offsets in calibration data map +// calibration data from 0x89 +#define BME680_CDM_T2 1 +#define BME680_CDM_T3 3 +#define BME680_CDM_P1 5 +#define BME680_CDM_P2 7 +#define BME680_CDM_P3 9 +#define BME680_CDM_P4 11 +#define BME680_CDM_P5 13 +#define BME680_CDM_P7 15 +#define BME680_CDM_P6 16 +#define BME680_CDM_P8 19 +#define BME680_CDM_P9 21 +#define BME680_CDM_P10 23 +// calibration data from 0e1 +#define BME680_CDM_H2 25 +#define BME680_CDM_H1 26 +#define BME680_CDM_H3 28 +#define BME680_CDM_H4 29 +#define BME680_CDM_H5 30 +#define BME680_CDM_H6 31 +#define BME680_CDM_H7 32 +#define BME680_CDM_T1 33 +#define BME680_CDM_GH2 35 +#define BME680_CDM_GH1 37 +#define BME680_CDM_GH3 38 +// device specific calibration data from 0x00 +#define BME680_CDM_RHV 41 // 0x00 - res_heat_val +#define BME680_CDM_RHR 43 // 0x02 - res_heat_range +#define BME680_CDM_RSWE 45 // 0x04 - range_sw_error + + +/** + * @brief Raw data (integer values) read from sensor + */ +typedef struct { + + bool new_data; // indicate that read raw data were new in sensor + bool gas_valid; // indicate that gas measurement results are valid + bool heater_stable; // indicate that heater temperature was stable + + uint32_t temperature; // degree celsius x100 + uint32_t pressure; // pressure in Pascal + uint16_t humidity; // relative humidity x1000 in % + uint16_t gas_resistance; // gas resistance data + uint8_t gas_range; // gas resistance range + +} bme680_raw_data_t; + + +/** Forward declaration of functions for internal use */ + +static bool bme680_set_mode (bme680_sensor_t* dev, uint8_t mode); +static bool bme680_get_raw_data (bme680_sensor_t* dev, bme680_raw_data_t* raw); +static int16_t bme680_convert_temperature (bme680_sensor_t *dev, uint32_t raw_temperature); +static uint32_t bme680_convert_pressure (bme680_sensor_t *dev, uint32_t raw_pressure); +static uint32_t bme680_convert_humidity (bme680_sensor_t *dev, uint16_t raw_humidity); +static uint32_t bme680_convert_gas (bme680_sensor_t *dev, uint16_t raw_gas, uint8_t gas_range); + +static uint8_t bme680_heater_resistance (const bme680_sensor_t* dev, uint16_t temperature); +static uint8_t bme680_heater_duration (uint16_t duration); +static int32_t bme680_measurement_duration (const bme680_sensor_t* dev); + +static bool bme680_reset (bme680_sensor_t* dev); +static bool bme680_is_available (bme680_sensor_t* dev); +static void bme680_delay_ms(uint32_t delay); + +static bool bme680_read_reg (bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool bme680_write_reg (bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool bme680_i2c_read (bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool bme680_i2c_write (bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool bme680_spi_read (bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool bme680_spi_write (bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); + +/** */ + +#define lsb_msb_to_type(t,b,o) (t)(((t)b[o+1] << 8) | b[o]) +#define lsb_to_type(t,b,o) (t)(b[o]) + +bme680_sensor_t* bme680_init_sensor(uint8_t bus, uint8_t addr, uint8_t cs) +{ + bme680_sensor_t* dev; + + if ((dev = malloc (sizeof(bme680_sensor_t))) == NULL) + return NULL; + + // init sensor data structure + dev->active = false; + dev->bus = bus; + dev->addr = addr; + dev->spi_cs_pin = cs; + dev->meas_started = false; + dev->settings.osr_temperature = osr_none; + dev->settings.osr_pressure = osr_none; + dev->settings.osr_humidity = osr_none; + dev->settings.filter_size = iir_size_0; + dev->settings.heater_temperature = 0; + dev->settings.heater_duration = 0; + dev->settings.ambient_temperature = 0; + + if (!addr) + { + // SPI interface used + gpio_enable(dev->spi_cs_pin, GPIO_OUTPUT); + gpio_write (dev->spi_cs_pin, true); + } + + // reset the sensor + if (!bme680_reset(dev)) + { + error_dev ("Could not reset the sensor device.", __FUNCTION__, dev); + free (dev); + return NULL; + } + + // check availability of the sensor + if (!bme680_is_available (dev)) + { + error_dev ("Sensor is not available.", __FUNCTION__, dev); + free (dev); + return NULL; + } + + dev->active = true; + + uint8_t buf[BME680_CDM_SIZE]; + + // read all calibration parameters from sensor + if (bme680_read_reg(dev, BME680_REG_CD1_ADDR, buf+BME680_CDM_OFF1, BME680_REG_CD1_LEN) && + bme680_read_reg(dev, BME680_REG_CD2_ADDR, buf+BME680_CDM_OFF2, BME680_REG_CD2_LEN) && + bme680_read_reg(dev, BME680_REG_CD3_ADDR, buf+BME680_CDM_OFF3, BME680_REG_CD3_LEN)) + { + dev->calib_data.par_t1 = lsb_msb_to_type (uint16_t, buf, BME680_CDM_T1); + dev->calib_data.par_t2 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_T2); + dev->calib_data.par_t3 = lsb_to_type ( int8_t, buf, BME680_CDM_T3); + + // pressure compensation parameters + dev->calib_data.par_p1 = lsb_msb_to_type (uint16_t, buf, BME680_CDM_P1); + dev->calib_data.par_p2 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_P2); + dev->calib_data.par_p3 = lsb_to_type ( int8_t, buf, BME680_CDM_P3); + dev->calib_data.par_p4 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_P4); + dev->calib_data.par_p5 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_P5); + dev->calib_data.par_p6 = lsb_to_type ( int8_t, buf, BME680_CDM_P6); + dev->calib_data.par_p7 = lsb_to_type ( int8_t, buf, BME680_CDM_P7); + dev->calib_data.par_p8 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_P8); + dev->calib_data.par_p9 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_P9); + dev->calib_data.par_p10 = lsb_to_type ( uint8_t, buf, BME680_CDM_P10); + + // humidity compensation parameters + dev->calib_data.par_h1 = (uint16_t)(((uint16_t)buf[BME680_CDM_H1+1] << 4) | + (buf[BME680_CDM_H1] & 0x0F)); + dev->calib_data.par_h2 = (uint16_t)(((uint16_t)buf[BME680_CDM_H2] << 4) | + (buf[BME680_CDM_H2+1] >> 4)); + dev->calib_data.par_h3 = lsb_to_type ( int8_t, buf, BME680_CDM_H3); + dev->calib_data.par_h4 = lsb_to_type ( int8_t, buf, BME680_CDM_H4); + dev->calib_data.par_h5 = lsb_to_type ( int8_t, buf, BME680_CDM_H5); + dev->calib_data.par_h6 = lsb_to_type ( uint8_t, buf, BME680_CDM_H6); + dev->calib_data.par_h7 = lsb_to_type ( int8_t, buf, BME680_CDM_H7); + + // gas sensor compensation parameters + dev->calib_data.par_gh1 = lsb_to_type ( int8_t, buf, BME680_CDM_GH1); + dev->calib_data.par_gh2 = lsb_msb_to_type ( int16_t, buf, BME680_CDM_GH2); + dev->calib_data.par_gh3 = lsb_to_type ( int8_t, buf, BME680_CDM_GH3); + + dev->calib_data.res_heat_range = (lsb_to_type (uint8_t, buf ,BME680_CDM_RHR) & BME680_RHR_BITS) >> + BME680_RHR_SHIFT; + dev->calib_data.res_heat_val = (lsb_to_type ( int8_t, buf, BME680_CDM_RHV)); + dev->calib_data.range_sw_err = (lsb_to_type ( int8_t, buf, BME680_CDM_RSWE) & BME680_RSWE_BITS) >> + BME680_RSWE_SHIFT; + } + else + { + error_dev ("Could not read in calibration parameters.", __FUNCTION__, dev); + dev->error_code |= BME680_READ_CALIB_DATA_FAILED; + free (dev); + return NULL; + + } + + // Set the default temperature, pressure and humidity settings + if (!bme680_set_oversampling_rates (dev, osr_1x, osr_1x, osr_1x) || + !bme680_set_filter_size (dev, iir_size_3)) + { + error_dev ("Could not configure default sensor settings for TPH.", __FUNCTION__, dev); + free (dev); + return NULL; + } + + // Set ambient temperature of sensor to default value (25 degree C) + dev->settings.ambient_temperature = 25; + + // Set heater default profile 320 degree Celcius for 150 ms + if (!bme680_set_heater_profile (dev, 320, 150)) + { + error_dev ("Could not configure default sensor settings for TPH.", __FUNCTION__, dev); + free (dev); + return NULL; + } + return dev; +} + +int32_t bme680_force_measurement (bme680_sensor_t* dev) +{ + if (!dev || !dev->active) return BME680_NOK; + + dev->error_code = BME680_OK; + + // return remaining time when measurement is already running + if (dev->meas_started) + { + debug_dev ("Measurement is already running.", __FUNCTION__, dev); + dev->error_code |= BME680_MEAS_ALREADY_RUNNING; + return bme680_is_measuring (dev); + } + + // Set the power mode to forced mode to trigger one TPHG measurement cycle + if (!bme680_set_mode(dev, BME680_FORCED_MODE)) + { + error_dev ("Could not set forced mode to start TPHG measurement cycle.", __FUNCTION__, dev); + dev->error_code |= BME680_FORCE_MODE_FAILED; + return BME680_NOK; + } + + /* Get the total measurement duration to sleep or wait till the measurement is complete */ + int32_t meas_period = bme680_measurement_duration (dev); + + if (meas_period < 0) + return BME680_NOK; + + dev->meas_duration = meas_period; + dev->meas_started = true; + dev->meas_start_tick = xTaskGetTickCount (); // system time in RTOS ticks + + debug_dev ("Started measurement at %.3f.", __FUNCTION__, dev, + (double)sdk_system_get_time()*1e-3); + + return dev->meas_duration; +} + + +// returns 0 when finished, -1 on error, remaining measurement time otherwise +int32_t bme680_is_measuring (bme680_sensor_t* dev) +{ + if (!dev || !dev->active) return BME680_NOK; + + dev->error_code = BME680_OK; + + if (!dev->meas_started) + { + error_dev ("Measurement was not started.", __FUNCTION__, dev); + dev->error_code |= BME680_MEAS_NOT_RUNNING; + return BME680_NOK; + } + + int32_t elapsed_ticks; + + elapsed_ticks = (xTaskGetTickCount () - dev->meas_start_tick); // in ms + + if (elapsed_ticks >= dev->meas_duration) + return 0; + else + return dev->meas_duration - elapsed_ticks; +} + + +bool bme680_get_results_fixed (bme680_sensor_t* dev, bme680_values_fixed_t* results) +{ + if (!dev || !results) return false; + + dev->error_code = BME680_OK; + + // fill data structure with invalid values + results->temperature = INT16_MIN; + results->pressure = 0; + results->humidity = 0; + results->gas_resistance = 0; + + bme680_raw_data_t raw; + + if (!bme680_get_raw_data(dev, &raw) || !raw.new_data) + // return invalid values + return false; + + dev->meas_started = false; + + // use compensation algorithms to compute sensor values in fixed point format + + if (dev->settings.osr_temperature) + results->temperature = bme680_convert_temperature (dev, raw.temperature); + + if (dev->settings.osr_pressure) + results->pressure = bme680_convert_pressure (dev, raw.pressure); + + if (dev->settings.osr_humidity) + results->humidity = bme680_convert_humidity (dev, raw.humidity); + + if (dev->settings.heater_duration) + results->gas_resistance = bme680_convert_gas (dev, raw.gas_resistance, + raw.gas_range); + + debug_dev ("Fixed point sensor valus - %d ms: %d/100 C, %d/1000 Percent, %d Pascal, %d Ohm", + __FUNCTION__, dev, sdk_system_get_time (), + results->temperature, + results->humidity, + results->pressure, + results->gas_resistance); + + return true; +} + + +bool bme680_get_results_float (bme680_sensor_t* dev, bme680_values_float_t* results) +{ + if (!dev || !results) return false; + + // fill data structure with invalid values + results->temperature = INT16_MIN / 100.0f; + results->pressure = 0; + results->humidity = 0; + results->gas_resistance = 0; + + bme680_values_fixed_t fixed; + + if (!bme680_get_results_fixed (dev, &fixed)) + return false; + + results->temperature = dev->settings.osr_temperature ? fixed.temperature / 100.0f : -327.68; + results->pressure = dev->settings.osr_pressure ? fixed.pressure / 100.0f : 0.0; + results->humidity = dev->settings.osr_humidity ? fixed.humidity / 1000.0f : 0.0; + results->gas_resistance = dev->settings.heater_duration ? fixed.gas_resistance : 0.0; + + return true; +} + + +bool bme680_measure_fixed (bme680_sensor_t* dev, bme680_values_fixed_t* results) +{ + int32_t duration = bme680_force_measurement (dev); + + if (duration == BME680_NOK) + return false; // measurment couldn't be started + + else if (duration > 0) // wait for results + vTaskDelay (duration); + + return bme680_get_results_fixed (dev, results); +} + + +bool bme680_measure_float (bme680_sensor_t* dev, bme680_values_float_t* results) +{ + int32_t duration = bme680_force_measurement (dev); + + if (duration == BME680_NOK) + return false; // measurment couldn't be started + + else if (duration > 0) // wait for results + vTaskDelay (duration); + + return bme680_get_results_float (dev, results); +} + + + +#define bme_set_reg_bit(byte, bitname, bit) ( (byte & ~bitname##_BITS) | \ + ((bit << bitname##_SHIFT) & bitname##_BITS) ) +#define bme_get_reg_bit(byte, bitname) ( (byte & bitname##_BITS) >> bitname##_SHIFT ) + +bool bme680_set_oversampling_rates (bme680_sensor_t* dev, + bme680_oversampling_rate_t ost, + bme680_oversampling_rate_t osp, + bme680_oversampling_rate_t osh) +{ + if (!dev || !dev->active) return false; + + dev->error_code = BME680_OK; + + bool ost_changed = dev->settings.osr_temperature != ost; + bool osp_changed = dev->settings.osr_pressure != osp; + bool osh_changed = dev->settings.osr_humidity != osh; + + if (!ost_changed && !osp_changed && !osh_changed) + return true; + + // Set the temperature, pressure and humidity oversampling + dev->settings.osr_temperature = ost; + dev->settings.osr_pressure = osp; + dev->settings.osr_humidity = osh; + + uint8_t reg; + + if (ost_changed || osp_changed) + { + // read the current register value + if (!bme680_read_reg(dev, BME680_REG_CTRL_MEAS, ®, 1)) + return false; + + // set changed bit values + if (ost_changed) reg = bme_set_reg_bit (reg, BME680_OSR_T, ost); + if (osp_changed) reg = bme_set_reg_bit (reg, BME680_OSR_P, osp); + + // write back the new register value + if (!bme680_write_reg(dev, BME680_REG_CTRL_MEAS, ®, 1)) + return false; + } + + if (osh_changed) + { + // read the current register value + if (!bme680_read_reg(dev, BME680_REG_CTRL_HUM, ®, 1)) + return false; + + // set changed bit value + reg = bme_set_reg_bit (reg, BME680_OSR_H, osh); + + // write back the new register value + if (!bme680_write_reg(dev, BME680_REG_CTRL_HUM, ®, 1)) + return false; + } + + debug_dev ("Setting oversampling rates done: osrt=%d osp=%d osrh=%d", + __FUNCTION__, dev, + dev->settings.osr_temperature, + dev->settings.osr_pressure, + dev->settings.osr_humidity); + + return true; +} + + +bool bme680_set_filter_size(bme680_sensor_t* dev, bme680_filter_size_t size) +{ + if (!dev || !dev->active) return false; + + dev->error_code = BME680_OK; + + bool size_changed = dev->settings.filter_size != size; + + if (!size_changed) return true; + + /* Set the temperature, pressure and humidity settings */ + dev->settings.filter_size = size; + + uint8_t reg; + + // read the current register value + if (!bme680_read_reg(dev, BME680_REG_CONFIG, ®, 1)) + return false; + + // set changed bit value + reg = bme_set_reg_bit (reg, BME680_FILTER, size); + + // write back the new register value + if (!bme680_write_reg(dev, BME680_REG_CONFIG, ®, 1)) + return false; + + debug_dev ("Setting filter size done: size=%d", __FUNCTION__, dev, + dev->settings.filter_size); + + return true; +} + +bool bme680_set_heater_profile (bme680_sensor_t* dev, uint16_t temperature, uint16_t duration) +{ + if (!dev || !dev->active) return false; + + dev->error_code = BME680_OK; + + bool temperature_changed = dev->settings.heater_temperature != temperature; + bool duration_changed = dev->settings.heater_duration != duration; + + if (!temperature_changed && !duration_changed) + return true; + + // set external gas sensor configuration + dev->settings.heater_temperature = temperature; // degree Celsius + dev->settings.heater_duration = duration; // milliseconds + + // compute internal gas sensor configuration parameters + uint8_t profile = 0; // only profile 0 is supported for the moment + uint8_t heat_dur = bme680_heater_duration(duration); // internal duration value + uint8_t heat_res = bme680_heater_resistance(dev, temperature); // internal temperature value + + // set internal gas sensor configuration parameters if changed + if (temperature_changed && + !bme680_write_reg(dev, BME680_REG_RES_HEAT_BASE+profile, &heat_res, 1)) + return false; + + if (duration_changed && + !bme680_write_reg(dev, BME680_REG_GAS_WAIT_BASE+profile, &heat_dur, 1)) + return false; + + debug_dev ("Setting heater profile done: temperature=%d duration=%d " + "heater_resistance=%02x heater_duration=%02x", + __FUNCTION__, dev, + dev->settings.heater_temperature, dev->settings.heater_duration, + heat_dur, heat_res); + + uint8_t reg = 0; // set + // set active profile + reg = bme_set_reg_bit (reg, BME680_NB_CONV, profile); + + // enable or disable gas measurement + reg = bme_set_reg_bit (reg, BME680_RUN_GAS, (temperature && duration)); + + if (!bme680_write_reg(dev, BME680_REG_CTRL_GAS_1, ®, 1)) + return false; + + return true; +} + + +bool bme680_set_ambient_temperature (bme680_sensor_t* dev, int16_t ambient) +{ + if (!dev || !dev->active) return false; + + dev->error_code = BME680_OK; + + bool ambient_changed = dev->settings.ambient_temperature != ambient; + + if (!ambient_changed) + return true; + + // set ambient temperature configuration + dev->settings.ambient_temperature = ambient; // degree Celsius + + // compute internal gas sensor configuration parameters + uint8_t profile = 0; // only profile 0 is supported for the moment + uint8_t heat_res = bme680_heater_resistance(dev, dev->settings.heater_temperature); + + // set internal gas sensor configuration parameters if changed + if (ambient_changed && + !bme680_write_reg(dev, BME680_REG_RES_HEAT_BASE+profile, &heat_res, 1)) + return false; + + debug_dev ("Setting heater ambient temperature done: ambient=%d", + __FUNCTION__, dev, + dev->settings.ambient_temperature); + + uint8_t reg = 0; // reset register value + + // set active profile + reg = bme_set_reg_bit (reg, BME680_NB_CONV, profile); + + // enable or disable gas measurement + reg = bme_set_reg_bit (reg, BME680_RUN_GAS, dev->settings.heater_temperature && + dev->settings.heater_duration); + + if (!bme680_write_reg(dev, BME680_REG_CTRL_GAS_1, ®, 1)) + return false; + + return true; +} + +bool bme680_set_mode (bme680_sensor_t *dev, uint8_t mode) +{ + if (!dev || !dev->active) return false; + + dev->error_code = BME680_OK; + + uint8_t reg; + + if (!bme680_read_reg(dev, BME680_REG_CTRL_MEAS, ®, 1)) + return false; + + reg = bme_set_reg_bit (reg, BME680_MODE, mode); + + if (!bme680_write_reg(dev, BME680_REG_CTRL_MEAS, ®, 1)) + return false; + + return true; +} + + +/** Functions for internal use only */ + +/** + * @brief Check the chip ID to test whether sensor is available + */ +static bool bme680_is_available (bme680_sensor_t* dev) +{ + uint8_t chip_id; + + if (!dev) return false; + + dev->error_code = BME680_OK; + + if (!bme680_read_reg (dev, BME680_REG_ID, &chip_id, 1)) + return false; + + if (chip_id != 0x61) + { + error_dev ("Chip id %02x is wrong, should be 0x61.", __FUNCTION__, dev, chip_id); + dev->error_code = BME680_WRONG_CHIP_ID; + return false; + } + + return true; +} + + +static bool bme680_reset (bme680_sensor_t* dev) +{ + if (!dev) return false; + + dev->error_code = BME680_OK; + + uint8_t reg = BME680_RESET_CMD; + + // send reset command + if (!bme680_write_reg(dev, BME680_REG_RESET, ®, 1)) + return false; + + // wait the time the sensor needs for reset + bme680_delay_ms (BME680_RESET_PERIOD); + + // check whether the sensor is reachable again + if (!bme680_read_reg(dev, BME680_REG_STATUS, ®, 1)) + { + dev->error_code = BME680_RESET_CMD_FAILED; + return false; + } + + return true; +} + + +/** + * @brief Calculate temperature from raw temperature value + * @ref BME280 datasheet, page 50 + */ +static int16_t bme680_convert_temperature (bme680_sensor_t *dev, uint32_t raw_temperature) +{ + if (!dev) return 0; + + bme680_calib_data_t* cd = &dev->calib_data; + + int64_t var1; + int64_t var2; + int16_t temperature; + + var1 = ((((raw_temperature >> 3) - ((int32_t)cd->par_t1 << 1))) * + ((int32_t)cd->par_t2)) >> 11; + var2 = (((((raw_temperature >> 4) - ((int32_t)cd->par_t1)) * + ((raw_temperature >> 4) - ((int32_t)cd->par_t1))) >> 12) * + ((int32_t)cd->par_t3)) >> 14; + cd->t_fine = (int32_t)(var1 + var2); + temperature = (cd->t_fine * 5 + 128) >> 8; + + return temperature; +} + + +/** + * @brief Calculate pressure from raw pressure value + * @ref [https://github.com/BoschSensortec/BME680_driver] + * @ref BME280 datasheet, page 50 + */ +static uint32_t bme680_convert_pressure (bme680_sensor_t *dev, uint32_t raw_pressure) +{ + if (!dev) return 0; + + bme680_calib_data_t* cd = &dev->calib_data; + + int32_t var1; + int32_t var2; + int32_t var3; + int32_t pressure; + + var1 = (((int32_t) cd->t_fine) / 2) - 64000; + var2 = ((var1 / 4) * (var1 / 4)) / 2048; + var2 = ((var2) * (int32_t) cd->par_p6) / 4; + var2 = var2 + ((var1 * (int32_t) cd->par_p5) * 2); + var2 = (var2 / 4) + ((int32_t) cd->par_p4 * 65536); + var1 = ((var1 / 4) * (var1 / 4)) / 8192; + var1 = (((var1) * ((int32_t) cd->par_p3 * 32)) / 8) + (((int32_t) cd->par_p2 * var1) / 2); + var1 = var1 / 262144; + + var1 = ((32768 + var1) * (int32_t) cd->par_p1) / 32768; + pressure = (int32_t) (1048576 - raw_pressure); + pressure = (int32_t) ((pressure - (var2 / 4096)) * (3125)); + pressure = ((pressure / var1) * 2); + var1 = ((int32_t) cd->par_p9 * (int32_t) (((pressure / 8) * (pressure / 8)) / 8192)) / 4096; + var2 = ((int32_t) (pressure / 4) * (int32_t) cd->par_p8) / 8192; + var3 = ((int32_t) (pressure / 256) * (int32_t) (pressure / 256) * (int32_t) (pressure / 256) + * (int32_t) cd->par_p10) / 131072; + pressure = (int32_t) (pressure) + ((var1 + var2 + var3 + ((int32_t) cd->par_p7 * 128)) / 16); + + return (uint32_t) pressure; +} + +/** + * @brief Calculate humidty from raw humidity data + * @ref [https://github.com/BoschSensortec/BME680_driver] + */ +static uint32_t bme680_convert_humidity (bme680_sensor_t *dev, uint16_t raw_humidity) +{ + if (!dev) return 0; + + bme680_calib_data_t* cd = &dev->calib_data; + + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t var6; + int32_t temp_scaled; + int32_t humidity; + + temp_scaled = (((int32_t) cd->t_fine * 5) + 128) >> 8; + var1 = (int32_t) (raw_humidity - ((int32_t) ((int32_t) cd->par_h1 << 4))) + - (((temp_scaled * (int32_t) cd->par_h3) / ((int32_t) 100)) >> 1); + var2 = ((int32_t) cd->par_h2 + * (((temp_scaled * (int32_t) cd->par_h4) / ((int32_t) 100)) + + (((temp_scaled * ((temp_scaled * (int32_t) cd->par_h5) / ((int32_t) 100))) >> 6) + / ((int32_t) 100)) + (int32_t) (1 << 14))) >> 10; + var3 = var1 * var2; + var4 = (int32_t) cd->par_h6 << 7; + var4 = ((var4) + ((temp_scaled * (int32_t) cd->par_h7) / ((int32_t) 100))) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + humidity = (((var3 + var6) >> 10) * ((int32_t) 1000)) >> 12; + + if (humidity > 100000) /* Cap at 100%rH */ + humidity = 100000; + else if (humidity < 0) + humidity = 0; + + return (uint32_t) humidity; +} + + +/** + * @brief Lookup table for gas resitance computation + * @ref BME680 datasheet, page 19 + */ +static float lookup_table[16][2] = { // const1, const2 // gas_range + { 1.0 , 8000000.0 }, // 0 + { 1.0 , 4000000.0 }, // 1 + { 1.0 , 2000000.0 }, // 2 + { 1.0 , 1000000.0 }, // 3 + { 1.0 , 499500.4995 }, // 4 + { 0.99 , 248262.1648 }, // 5 + { 1.0 , 125000.0 }, // 6 + { 0.992, 63004.03226 }, // 7 + { 1.0 , 31281.28128 }, // 8 + { 1.0 , 15625.0 }, // 9 + { 0.998, 7812.5 }, // 10 + { 0.995, 3906.25 }, // 11 + { 1.0 , 1953.125 }, // 12 + { 0.99 , 976.5625 }, // 13 + { 1.0 , 488.28125 }, // 14 + { 1.0 , 244.140625 } // 15 + }; + +/** + * @brief Calculate gas resistance from raw gas resitance value and gas range + * @ref BME680 datasheet + */ +static uint32_t bme680_convert_gas (bme680_sensor_t *dev, uint16_t gas, uint8_t gas_range) +{ + if (!dev) return 0; + + bme680_calib_data_t* cd = &dev->calib_data; + + float var1 = (1340.0 + 5.0 * cd->range_sw_err) * lookup_table[gas_range][0]; + return var1 * lookup_table[gas_range][1] / (gas - 512.0 + var1); +} + +#define msb_lsb_xlsb_to_20bit(t,b,o) (t)((t) b[o] << 12 | (t) b[o+1] << 4 | b[o+2] >> 4) +#define msb_lsb_to_type(t,b,o) (t)(((t)b[o] << 8) | b[o+1]) + +#define BME680_RAW_P_OFF 0 +#define BME680_RAW_T_OFF (BME680_REG_TEMP_MSB - BME680_REG_PRESS_MSB) +#define BME680_RAW_H_OFF (BME680_REG_HUM_MSB - BME680_REG_PRESS_MSB) +#define BME680_RAW_G_OFF (BME680_REG_GAS_R_MSB - BME680_REG_PRESS_MSB) + +static bool bme680_get_raw_data(bme680_sensor_t *dev, bme680_raw_data_t* raw_data) +{ + if (!dev || !dev->active || !raw_data) return false; + + dev->error_code = BME680_OK; + + if (!dev->meas_started) + { + debug_dev ("Measurement was not started.", __FUNCTION__, dev); + dev->error_code = BME680_MEAS_NOT_RUNNING; + return false; + } + + uint8_t raw[BME680_REG_RAW_DATA_LEN] = { 0 }; + + raw_data->new_data = false; + + // read maesurment status from sensor + if (!bme680_read_reg(dev, BME680_REG_MEAS_STATUS_0, raw, 1)) + { + error_dev ("Could not read new data status from sensor.", __FUNCTION__, dev); + return false; + } + + // test whether there are new data + if (!(raw_data->new_data = raw[0] & BME680_NEW_DATA_0_BITS)) + { + debug_dev ("No new data.", __FUNCTION__, dev); + dev->error_code = BME680_NO_NEW_DATA; + return false; + } + + // XXXXX improve status check + // raw_data->gas_index = raw[0] & BME680_GAS_MEAS_INDEX_BITS >> BME680_GAS_MEAS_INDEX_SHIFT; + // raw_data->meas_index = raw[1]; + + if (!bme680_read_reg(dev, BME680_REG_RAW_DATA, raw, BME680_REG_RAW_DATA_LEN)) + { + error_dev ("Could not read raw data from sensor.", __FUNCTION__, dev); + return false; + } + + raw_data->gas_valid = bme_get_reg_bit(raw[BME680_RAW_G_OFF+1],BME680_GAS_VALID); + raw_data->heater_stable = bme_get_reg_bit(raw[BME680_RAW_G_OFF+1],BME680_HEAT_STAB_R); + + // if there are new data, read raw data from sensor + + raw_data->temperature = msb_lsb_xlsb_to_20bit (uint32_t, raw, BME680_RAW_T_OFF); + raw_data->pressure = msb_lsb_xlsb_to_20bit (uint32_t, raw, BME680_RAW_P_OFF); + raw_data->humidity = msb_lsb_to_type (uint16_t, raw, BME680_RAW_H_OFF); + raw_data->gas_resistance = ((uint16_t)raw[BME680_RAW_G_OFF] << 2) | raw[BME680_RAW_G_OFF+1] >> 6; + raw_data->gas_range = raw[BME680_RAW_G_OFF+1] & BME680_GAS_RANGE_R_BITS; + + debug ("Raw data: %d %d %d %d %d",__FUNCTION__, + raw_data->temperature, raw_data->pressure, + raw_data->humidity, raw_data->gas_resistance, raw_data->gas_range); + + return true; +} + + +/** + * @brief Calculate internal duration representation + * + * Durations are internally representes as one byte + * + * duration = value<5:0> * multiplier<7:6> + * + * where the multiplier is 1, 4, 16, or 64. Maximum duration is therefore + * 64*64 = 4032 ms. The function takes a real world duration value given + * in milliseconds and computes the internal representation. + * + * @ref Datasheet + */ +static uint8_t bme680_heater_duration (uint16_t duration) +{ + uint8_t multiplier = 0; + + while (duration > 63) + { + duration = duration / 4; + multiplier++; + } + return (uint8_t) (duration | (multiplier << 6)); +} + + +/** + * @brief Calculate internal heater resistance value from real temperature. + * + * @ref Datasheet of BME680 + */ +static uint8_t bme680_heater_resistance (const bme680_sensor_t *dev, uint16_t temp) +{ + if (!dev || !dev->active) return 0; + + if (temp < BME680_HEATER_TEMP_MIN) + temp = BME680_HEATER_TEMP_MIN; + else if (temp > BME680_HEATER_TEMP_MAX) + temp = BME680_HEATER_TEMP_MAX; + + const bme680_calib_data_t* cd = &dev->calib_data; + + // from datasheet + double var1; + double var2; + double var3; + double var4; + double var5; + uint8_t res_heat_x; + + var1 = ((double)cd->par_gh1 / 16.0) + 49.0; + var2 = (((double)cd->par_gh2 / 32768.0) * 0.0005) + 0.00235; + var3 = (double)cd->par_gh3 / 1024.0; + var4 = var1 * (1.0 + (var2 * (double) temp)); + var5 = var4 + (var3 * (double)dev->settings.ambient_temperature); + res_heat_x = (uint8_t)(3.4 * ((var5 * (4.0 / (4.0 + (double)cd->res_heat_range)) * + (1.0/(1.0 + ((double)cd->res_heat_val * 0.002)))) - 25)); + return res_heat_x; + +} + +/** + * @brief Estimate the measuerment duration in ms + * + * Timing formulas extracted from BME280 datasheet and some experiments. They + * represent maximum measurement duration. + * + * @return estimated measurument duration in RTOS ticks or -1 on error + */ +static int32_t bme680_measurement_duration (const bme680_sensor_t *dev) +{ + if (!dev || !dev->active) return BME680_NOK; + + int32_t duration = 0; /* Calculate in us */ + + // wake up duration from sleep into forced mode + duration += 1250; + + // THP cycle duration which consumes 1963 µs for each measurement at maximum + if (dev->settings.osr_temperature) duration += (1 << (dev->settings.osr_temperature-1)) * 2300; + if (dev->settings.osr_pressure ) duration += (1 << (dev->settings.osr_pressure-1)) * 2300 + 575; + if (dev->settings.osr_humidity ) duration += (1 << (dev->settings.osr_humidity-1)) * 2300 + 575; + + // if gas measurement is used + if (dev->settings.heater_duration && dev->settings.heater_temperature) + { + // gas heating time + duration += dev->settings.heater_duration*1000; + // gas measurement duration; + duration += 2300 + 575; + } + + // round up to next ms (1 us ... 1000 us => 1 ms) + duration += 999; + duration /= 1000; + + // some ms tolerance + duration += 3; + + // ceil to next integer value that is divisible by portTICK_PERIOD_MS and + // compute RTOS ticks (1 ... portTICK_PERIOD_MS = 1 tick) + duration = (duration + portTICK_PERIOD_MS-1) / portTICK_PERIOD_MS; + + // Since first RTOS tick can be shorter than the half of defined tick period, + // the delay caused by vTaskDelay(duration) might be 1 or 2 ms shorter than + // computed duration in rare cases. Since the duration is computed for maximum + // and not the typical durations and therefore tends to be too long, this should + // not be a problem. + return duration; +} + +static void bme680_delay_ms(uint32_t period) +{ + uint32_t start_time = sdk_system_get_time () / 1000; + + vTaskDelay((period + portTICK_PERIOD_MS-1) / portTICK_PERIOD_MS); + + while (sdk_system_get_time()/1000 - start_time < period) + vTaskDelay (1); +} + + +#define BME680_SPI_BUF_SIZE 64 // SPI register data buffer size of ESP866 + +static const spi_settings_t bus_settings = { + .mode = SPI_MODE3, + .freq_divider = SPI_FREQ_DIV_1M, + .msb = true, + .minimal_pins = false, + .endianness = SPI_LITTLE_ENDIAN +}; + + +static bool bme680_read_reg(bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + return (dev->addr) ? bme680_i2c_read (dev, reg, data, len) + : bme680_spi_read (dev, reg, data, len); +} + + +static bool bme680_write_reg(bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + return (dev->addr) ? bme680_i2c_write (dev, reg, data, len) + : bme680_spi_write (dev, reg, data, len); +} + +#define BME680_REG_SWITCH_MEM_PAGE BME680_REG_STATUS +#define BME680_BIT_SWITCH_MEM_PAGE_0 0x00 +#define BME680_BIT_SWITCH_MEM_PAGE_1 0x10 + +static bool bme680_spi_set_mem_page (bme680_sensor_t* dev, uint8_t reg) +{ + // mem pages (reg 0x00 .. 0x7f = 1, reg 0x80 ... 0xff = 0 + uint8_t mem_page = (reg < 0x80) ? BME680_BIT_SWITCH_MEM_PAGE_1 + : BME680_BIT_SWITCH_MEM_PAGE_0; + + debug_dev ("Set mem page for register %02x to %d.", __FUNCTION__, dev, reg, mem_page); + + if (!bme680_spi_write (dev, BME680_REG_SWITCH_MEM_PAGE, &mem_page, 1)) + { + dev->error_code |= BME680_SPI_SET_PAGE_FAILED; + return false; + } + // sdk_os_delay_us (100); + + return true; +} + + +static bool bme680_spi_read(bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + if (len >= BME680_SPI_BUF_SIZE) + { + dev->error_code |= BME680_SPI_BUFFER_OVERFLOW; + error_dev ("Error on read from SPI slave on bus 1. Tried to transfer " + "more than %d byte in one read operation.", + __FUNCTION__, dev, BME680_SPI_BUF_SIZE); + return false; + } + + // set mem page first + if (!bme680_spi_set_mem_page (dev, reg)) + { + error_dev ("Error on read from SPI slave on bus 1. Could not set mem page.", + __FUNCTION__, dev); + return false; + } + + reg &= 0x7f; + reg |= 0x80; + + spi_settings_t old_settings; + + static uint8_t mosi[BME680_SPI_BUF_SIZE]; + static uint8_t miso[BME680_SPI_BUF_SIZE]; + + memset (mosi, 0xff, BME680_SPI_BUF_SIZE); + memset (miso, 0xff, BME680_SPI_BUF_SIZE); + + mosi[0] = reg; + + spi_get_settings(dev->bus, &old_settings); + spi_set_settings(dev->bus, &bus_settings); + gpio_write(dev->spi_cs_pin, false); + + size_t transfered = spi_transfer (dev->bus, (const void*)mosi, (void*)miso, len+1, SPI_8BIT); + + gpio_write(dev->spi_cs_pin, true); + spi_set_settings(dev->bus, &old_settings); + + if (!transfered) + { + error_dev ("Could not read data from SPI", __FUNCTION__, dev); + dev->error_code |= BME680_SPI_READ_FAILED; + return false; + } + + // shift data one by left, first byte received while sending register address is invalid + for (int i=0; i < len; i++) + data[i] = miso[i+1]; + +# ifdef BME680_DEBUG_LEVEL_2 + printf("BME680 %s: read the following bytes: ", __FUNCTION__); + printf("%0x ", reg); + for (int i=0; i < len; i++) + printf("%0x ", data[i]); + printf("\n"); +# endif + + return true; +} + + +static bool bme680_spi_write(bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + static uint8_t mosi[BME680_SPI_BUF_SIZE]; + + if (len >= BME680_SPI_BUF_SIZE) + { + dev->error_code |= BME680_SPI_BUFFER_OVERFLOW; + error_dev ("Error on write to SPI slave on bus 1. Tried to transfer more" + "than %d byte in one write operation.", __FUNCTION__, dev, BME680_SPI_BUF_SIZE); + + return false; + } + + // set mem page first if not mem page register is used + if (reg != BME680_REG_STATUS && !bme680_spi_set_mem_page (dev, reg)) + { + error_dev ("Error on write from SPI slave on bus 1. Could not set mem page.", + __FUNCTION__, dev); + return false; + } + + reg &= 0x7f; + + // first byte in output is the register address + mosi[0] = reg; + + // shift data one byte right, first byte in output is the register address + for (int i = 0; i < len; i++) + mosi[i+1] = data[i]; + +# ifdef BME680_DEBUG_LEVEL_2 + printf("BME680 %s: Write the following bytes: ", __FUNCTION__); + for (int i = 0; i < len+1; i++) + printf("%0x ", mosi[i]); + printf("\n"); +# endif + + spi_settings_t old_settings; + + spi_get_settings(dev->bus, &old_settings); + spi_set_settings(dev->bus, &bus_settings); + gpio_write(dev->spi_cs_pin, false); + + size_t transfered = spi_transfer (dev->bus, (const void*)mosi, NULL, len+1, SPI_8BIT); + + gpio_write(dev->spi_cs_pin, true); + spi_set_settings(dev->bus, &old_settings); + + if (!transfered) + { + error_dev ("Could not write data to SPI.", __FUNCTION__, dev); + dev->error_code |= BME680_SPI_WRITE_FAILED; + return false; + } + + return true; +} + +static bool bme680_i2c_read(bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + debug_dev ("Read %d byte from i2c slave register %02x.", __FUNCTION__, dev, len, reg); + + int result; + int count = 10; + + while (count-- && (result = i2c_slave_read(dev->bus, dev->addr, ®, data, len)) == -EBUSY) + bme680_delay_ms (10); + + if (result) + { + dev->error_code |= (result == -EBUSY) ? BME680_I2C_BUSY : BME680_I2C_READ_FAILED; + error_dev ("Error %d on read %d byte from I2C slave register %02x.", + __FUNCTION__, dev, result, len, reg); + return false; + } + +# ifdef BME680_DEBUG_LEVEL_2 + printf("BME680 %s: Read following bytes: ", __FUNCTION__); + printf("%0x: ", reg); + for (int i=0; i < len; i++) + printf("%0x ", data[i]); + printf("\n"); +# endif + + return true; +} + + +static bool bme680_i2c_write(bme680_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + debug_dev ("Write %d byte to i2c slave register %02x.", __FUNCTION__, dev, len, reg); + + int result; + int count = 10; + + while (count-- && (result = i2c_slave_write(dev->bus, dev->addr, ®, data, len)) == -EBUSY) + bme680_delay_ms (10); + + if (result) + { + dev->error_code |= (result == -EBUSY) ? BME680_I2C_BUSY : BME680_I2C_WRITE_FAILED; + error_dev ("Error %d on write %d byte to i2c slave register %02x.", + __FUNCTION__, dev, result, len, reg); + return false; + } + +# ifdef BME680_DEBUG_LEVEL_2 + printf("BME680 %s: Wrote the following bytes: ", __FUNCTION__); + printf("%0x: ", reg); + for (int i=0; i < len; i++) + printf("%0x ", data[i]); + printf("\n"); +# endif + + return true; +} + + diff --git a/extras/bme680/bme680.h b/extras/bme680/bme680.h new file mode 100644 index 0000000..748aaff --- /dev/null +++ b/extras/bme680/bme680.h @@ -0,0 +1,351 @@ +/* + * Driver for Bosch Sensortec BME680 digital temperature, humidity, pressure and + * gas sensor connected to I2C or SPI + * + * Part of esp-open-rtos [https://github.com/SuperHouse/esp-open-rtos] + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht] + * All rights reserved. + * + * 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. + */ + +#ifndef __BME680_H__ +#define __BME680_H__ + +#include "bme680/bme680_types.h" + +// Uncomment one of the following defines to enable debug output +// #define BME680_DEBUG_LEVEL_1 // only error messages +// #define BME680_DEBUG_LEVEL_2 // debug and error messages + +// BME680 addresses +#define BME680_I2C_ADDRESS_1 0x76 // SDO pin is low +#define BME680_I2C_ADDRESS_2 0x77 // SDO pin is high + +// BME680 chip id +#define BME680_CHIP_ID 0x61 // BME680_REG_ID<7:0> + +// Definition of error codes +#define BME680_OK 0 +#define BME680_NOK -1 + +#define BME680_INT_ERROR_MASK 0x000f +#define BME680_DRV_ERROR_MASK 0xfff0 + +// Error codes for I2C and SPI interfaces ORed with BME680 driver error codes +#define BME680_I2C_READ_FAILED 1 +#define BME680_I2C_WRITE_FAILED 2 +#define BME680_I2C_BUSY 3 +#define BME680_SPI_WRITE_FAILED 4 +#define BME680_SPI_READ_FAILED 5 +#define BME680_SPI_BUFFER_OVERFLOW 6 +#define BME680_SPI_SET_PAGE_FAILED 7 + +// BME680 driver error codes ORed with error codes for I2C and SPI interfaces +#define BME680_RESET_CMD_FAILED (1 << 8) +#define BME680_WRONG_CHIP_ID (2 << 8) +#define BME680_READ_CALIB_DATA_FAILED (3 << 8) +#define BME680_MEAS_ALREADY_RUNNING (4 << 8) +#define BME680_MEAS_NOT_RUNNING (5 << 8) +#define BME680_FORCE_MODE_FAILED (6 << 8) +#define BME680_NO_NEW_DATA (7 << 8) + +// Driver range definitions +#define BME680_HEATER_TEMP_MIN 200 // min. 200 degree Celsius +#define BME680_HEATER_TEMP_MAX 400 // max. 200 degree Celsius + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** -------------------------------------------------------------------------- + * + * Functional Description of the BME680 sensor + * + * The BME680 sensor only support two modes, the sleep mode and the forced + * mode in which measurements are done. After power-up sequence, the sensor + * automatically starts in sleep mode. To start a measurement, the sensor has + * to switch in the forced mode. In this mode it performs exactly one + * measurement of temperature, pressure, humidity, and gas in that order, + * the so-called TPHG measurement cycle. After the execution of this TPHG + * measurement cycle, raw sensor data are available and the sensor returns + * automatically back to sleep mode. + * + * Using the BME680 consists of the following steps + * + * 1. Trigger the sensor to switch into forced mode to perform one THPG cycle + * 2. Wait until the THPG cycle has been finished (measurement duration) + * 3. Fetch raw sensor data, compensate and convert them to sensor values + * + * --------------------------------------------------------------------------- + */ + +/** + * @brief Initialize a BME680 sensor + * + * The function initializes the sensor device data structure, probes the + * sensor, soft resets the sensor, and configures the sensor with default + * settings: + * + * - Oversampling rate for temperature, pressure, humidity is osr_1x + * - Filter size for pressure and temperature is iir_size 3 + * - Heater profile for gas is 320 degree Celsius for 150 milliseconds. + * + * The sensor can be connected either to an I2C or a SPI bus. In both cases, + * the parameter *bus* specifies the ID of the corresponding bus. Please note + * that in case of SPI, bus 1 has to be used since bus 0 is used for system + * flash memory. + * + * If parameter *addr* is greater than 0, it defines a valid I2C slave address + * and the sensor is connected to an I2C bus. In that case parameter *cs* is + * ignored. + * + * If parameter *addr* is 0, the sensor is connected to a SPI bus. In that + * case, parameter *cs* defines the GPIO used as CS signal + * + * @param bus I2C or SPI bus at which BME680 sensor is connected + * @param addr I2C addr of the BME680 sensor, 0 for SPI + * @param cs SPI CS GPIO, ignored for I2C + * @return pointer to sensor data structure, or NULL on error + */ +bme680_sensor_t* bme680_init_sensor (uint8_t bus, uint8_t addr, uint8_t cs_pin); + + +/** + * @brief Force one single TPHG measurement + * + * The function triggers the sensor to start one THPG measurement cycle in + * forced mode, the only measurement mode supported by the BME680. Measurement + * parameters for the measurement like oversampling rates, IIR filter sizes + * and heater profile can be configured before. + * + * On success, the function returns an estimated measurement duration given + * in RTOS ticks. This is the time in ticks needed by the sensor before + * measurement results become available. The user task has to wait this + * duration before it can use function *bme680_get_results_fixed* or + * function *bme680_get_results_float* to fetch the measurement results. + * + * The measurement duration strongly depends on which measurements in the THPG + * measurement cycle are performed and which configuration parameters were set. + * It can vary from 1 RTOS (10 ms) tick up to 4500 RTOS ticks (4.5 seconds). + * + * @param dev pointer to the sensor device data structure + * @return measurement duration given in RTOS ticks or -1 on error + */ +int32_t bme680_force_measurement (bme680_sensor_t* dev); + + +/** + * @brief Get remaining duration of a running measurement + * + * The function can be used to test whether a measurement has been started + * and how long it still takes before measurement results become + * available. The return value is given in RTOS ticks and can be + * + * >0 in case the measurement is is still running, + * 0 in case the measurement has been already finished, or + * <0 in case of error. + * + * That is, a return value greater than 0 indicates that the measurement + * results are still not available and the user task has to wait that time. + * + * @param dev pointer to the sensor device data structure + * @return remaining measurement duration in RTOS ticks or -1 on error + */ +int32_t bme680_is_measuring (bme680_sensor_t* dev); + + +/** + * @brief Get results of a measurement in fixed point representation + * + * The function returns the results of a TPHG measurement that has been + * started before. If the measurement is still running, the function fails + * and returns invalid values (see type declaration). + * + * @param dev pointer to the sensor device data structure + * @param results pointer to a data structure that is filled with results + * @return true on success, false on error + */ +bool bme680_get_results_fixed (bme680_sensor_t* dev, + bme680_values_fixed_t* results); + +/** + * @brief Get results of a measurement in floating point representation + * + * The function returns the results of a TPHG measurement that has been + * started before. If the measurement is still running, the function fails + * and returns invalid values (see type declaration). + * + * @param dev pointer to the sensor device data structure + * @param results pointer to a data structure that is filled with results + * @return true on success, false on error + */ +bool bme680_get_results_float (bme680_sensor_t* dev, + bme680_values_float_t* results); + +/** + * @brief Start a measurement, wait and return the results (fixed point) + * + * This function is a combination of functions above. For convenience it + * starts a TPHG measurement using *bme680_force_measurement*, then waits it + * the measurement duration for the results using *vTaskDelay* and finally it + * returns the results using function *bme680_get_results_fixed*. + * + * Note: Since the calling task is delayed using function *vTaskDelay*, this + * function must not be used when it is called by a software timer callback + * function. + * + * @param dev pointer to the sensor device data structure + * @param results pointer to a data structure that is filled with results + * @return true on success, false on error + */ +bool bme680_measure_fixed (bme680_sensor_t* dev, + bme680_values_fixed_t* results); + + +/** + * @brief Start a measurement, wait and return the results (floating point) + * + * This function is a combination of functions above. For convenience it + * starts a TPHG measurement using *bme680_force_measurement*, then it waits + * the measurement duration for the results using *vTaskDelay* and finally it + * returns the results using function *bme680_get_results_float*. + * + * Note: Since the calling task is delayed using function *vTaskDelay*, this + * function must not be used when it is called by a software timer callback + * function. + * + * @param dev pointer to the sensor device data structure + * @param results pointer to a data structure that is filled with results + * @return true on success, false on error + */ +bool bme680_measure_float (bme680_sensor_t* dev, + bme680_values_float_t* results); + +/** + * @brief Set the oversampling rates for measurements + * + * The BME680 sensor allows to define individual oversampling rates for + * the measurements of temperature, pressure and humidity. Using an + * oversampling rate of *osr*, the resolution of raw sensor data can be + * increased by ld(*osr*) bits. + * + * Possible oversampling rates are 1x (default), 2x, 4x, 8x, 16x, see type + * *bme680_oversampling_rate_t*. The default oversampling rate is 1. + * + * Please note: Use *osr_none* to skip the corresponding measurement. + * + * @param dev pointer to the sensor device data structure + * @param ost oversampling rate for temperature measurements + * @param osp oversampling rate for pressure measurements + * @param osh oversampling rate for humidity measurements + * @return true on success, false on error + */ +bool bme680_set_oversampling_rates (bme680_sensor_t* dev, + bme680_oversampling_rate_t osr_t, + bme680_oversampling_rate_t osr_p, + bme680_oversampling_rate_t osr_h); + + +/** + * @brief Set the size of the IIR filter + * + * The sensor integrates an internal IIR filter (low pass filter) to reduce + * short-term changes in sensor output values caused by external disturbances. + * It effectively reduces the bandwidth of the sensor output values. + * + * The filter can optionally be used for pressure and temperature data that + * are subject to many short-term changes. Using the IIR filter, increases the + * resolution of pressure and temperature data to 20 bit. Humidity and gas + * inside the sensor does not fluctuate rapidly and does not require such a + * low pass filtering. + * + * The default filter size is 3 (*iir_size_3*). + * + * Please note: If the size of the filter is 0, the filter is not used. + * + * @param dev pointer to the sensor device data structure + * @param size IIR filter size + * @return true on success, false on error + */ +bool bme680_set_filter_size(bme680_sensor_t* dev, bme680_filter_size_t size); + + +/** + * @brief Set the heater profile for gas measurements + * + * For gas measurement the sensor integrates a heater. The paremeters for + * this heater are defined by a heater profile. Such a heater profile + * consists of a temperature setting point (the target temperature) and the + * heating duration. The target temperature is converted to the heater + * resistance value. + * + * Even though the sensor supports up to 10 different profiles, only one + * profile is used by this driver for simplicity. The temperature setting + * point and the heating duration of this profile can be defined by this + * function. Default values are 320 degree Celsius as target temperature and + * 150 ms heating duration. + * + * Please note: To disable the measurement of gas, set the heating duration + * to 0 ms. + * + * Please note: According to the data sheet, target temperatures of between + * 200 and 400 degrees Celsius are typical and about 20 to 30 ms are necessary + * for the heater to reach the desired target temperature. + * + * @param dev pointer to the sensor device data structure + * @param temperature heating temperature in degree Celsius + * @param duration heating duration in milliseconds + * @return true on success, false on error + */ +bool bme680_set_heater_profile (bme680_sensor_t* dev, + uint16_t temperature, + uint16_t duration); + +/** + * @brief Set ambient temperature + * + * The heater resistance conversion algorithm takes into account the ambient + * temperature of the sensor. This function can be used to set this ambient + * temperature. Either values determined from this or another temperature + * sensor can be used. The default ambient temperature is 25 degree Celsius. + */ +bool bme680_set_ambient_temperature (bme680_sensor_t* dev, + int16_t temperature); + + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ + +#endif /* __BME680_H__ */ diff --git a/extras/bme680/bme680_types.h b/extras/bme680/bme680_types.h new file mode 100644 index 0000000..f575e5e --- /dev/null +++ b/extras/bme680/bme680_types.h @@ -0,0 +1,193 @@ +/* + * Driver for Bosch Sensortec BME680 digital temperature, humity, pressure and + * gas sensor connected to I2C or SPI + * + * Part of esp-open-rtos [https://github.com/SuperHouse/esp-open-rtos] + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht] + * All rights reserved. + * + * 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. + */ + +#ifndef __BME680_TYPES_H__ +#define __BME680_TYPES_H__ + +#include "stdint.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Fixed point sensor values (fixed THPG values) + */ +typedef struct { // invalid value + int16_t temperature; // temperature in degree C * 100 (INT16_MIN) + uint32_t pressure; // barometric pressure in Pascal (0) + uint32_t humidity; // relative humidity in % * 1000 (0) + uint32_t gas_resistance; // gas resistance in Ohm (0) +} bme680_values_fixed_t; + +/** + * @brief Floating point sensor values (real THPG values) + */ +typedef struct { // invalid value + float temperature; // temperature in degree C (-327.68) + float pressure; // barometric pressure in hPascal (0.0) + float humidity; // relative humidity in % (0.0) + float gas_resistance; // gas resistance in Ohm (0.0) +} bme680_values_float_t; + + +/** + * @brief Oversampling rates + */ +typedef enum { + osr_none = 0, // measurement is skipped, output values are invalid + osr_1x = 1, // default oversampling rates + osr_2x = 2, + osr_4x = 3, + osr_8x = 4, + osr_16x = 5 +} bme680_oversampling_rate_t; + + +/** + * @brief Filter sizes + */ +typedef enum { + iir_size_0 = 0, // filter is not used + iir_size_1 = 1, + iir_size_3 = 2, + iir_size_7 = 3, + iir_size_15 = 4, + iir_size_31 = 5, + iir_size_63 = 6, + iir_size_127 = 7 +} bme680_filter_size_t; + + +/** + * @brief Sensor parameters that configure the TPHG measurement cycle + * + * T - temperature measurement + * P - pressure measurement + * H - humidity measurement + * G - gas measurement + */ +typedef struct { + + uint8_t osr_temperature; // T oversampling rate (default osr_1x) + uint8_t osr_pressure; // P oversampling rate (default osr_1x) + uint8_t osr_humidity; // H oversampling rate (default osr_1x) + uint8_t filter_size; // IIR filter size (default iir_size_3) + + uint16_t heater_temperature; // Heater temperature for G (default 320) + uint16_t heater_duration; // Heater duration for G (default 150) + + int8_t ambient_temperature; // Ambient temperature for G (default 25); + +} bme680_settings_t; + +/** + * @brief Data structure for calibration parameters + * + * These calibration parameters are used in compensation algorithms to convert + * raw sensor data to measurement results. + */ +typedef struct { + + uint16_t par_t1; // calibration data for temperature compensation + int16_t par_t2; + int8_t par_t3; + + uint16_t par_p1; // calibration data for pressure compensation + int16_t par_p2; + int8_t par_p3; + int16_t par_p4; + int16_t par_p5; + int8_t par_p7; + int8_t par_p6; + int16_t par_p8; + int16_t par_p9; + uint8_t par_p10; + + uint16_t par_h1; // calibration data for humidity compensation + uint16_t par_h2; + int8_t par_h3; + int8_t par_h4; + int8_t par_h5; + uint8_t par_h6; + int8_t par_h7; + + int8_t par_gh1; // calibration data for gas compensation + int16_t par_gh2; + int8_t par_gh3; + + int32_t t_fine; // temperatur correction factor for P and G + uint8_t res_heat_range; + int8_t res_heat_val; + int8_t range_sw_err; + +} bme680_calib_data_t; + + +/** + * @brief BME680 sensor device data structure type + */ +typedef struct { + + bool active; // indicates whether sensor is active + int error_code; // contains the error code of last operation + + uint8_t bus; // I2C = x, SPI = 1 + uint8_t addr; // I2C = slave address, SPI = 0 + uint8_t spi_cs_pin; // GPIO used as SPI CS + + bool meas_started; // indicates whether measurement started + uint32_t meas_start_tick; // measurement start time in RTOS ticks + uint32_t meas_duration; // measurement duration in RTOS ticks + + bme680_settings_t settings; // sensor settings + bme680_calib_data_t calib_data; // calibration data of the sensor + +} bme680_sensor_t; + + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ + +#endif /* __BME680_TYPES_H__ */ + diff --git a/extras/bme680/component.mk b/extras/bme680/component.mk new file mode 100644 index 0000000..f890ad0 --- /dev/null +++ b/extras/bme680/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/bme60 + +# expected anyone using bme680 driver includes it as 'bme680/bme680.h' +INC_DIRS += $(bme680_ROOT).. + +# args for passing into compile rule generation +bme680_SRC_DIR = $(bme680_ROOT) + +$(eval $(call component_compile_rules,bme680))