From 8c0a9376355ffd54e74ee0b3000962578f7b4025 Mon Sep 17 00:00:00 2001 From: Rudy Nurhadi Date: Wed, 3 Jan 2024 09:49:25 +0700 Subject: [PATCH] test-lorawan initial commit based on https://github.com/ARMmbed/mbed-os-example-lorawan --- src/mbedos/test-lorawan/.gitignore | 46 + src/mbedos/test-lorawan/custom_targets.json | 141 ++ src/mbedos/test-lorawan/lora_radio_helper.h | 86 + src/mbedos/test-lorawan/mbed-os.lib | 1 + src/mbedos/test-lorawan/mbed_app.json | 34 + src/mbedos/test-lorawan/mbedtls_lora_config.h | 43 + .../patch/deep_sleep_override.cpp | 132 ++ src/mbedos/test-lorawan/readme.md | 22 + .../test-lorawan/source/bme688/BME688.cpp | 716 +++++++ .../test-lorawan/source/bme688/BME688.h | 179 ++ .../test-lorawan/source/bme688/bsec2/bme68x.c | 1848 +++++++++++++++++ .../test-lorawan/source/bme688/bsec2/bme68x.h | 323 +++ .../source/bme688/bsec2/bme68x_defs.h | 972 +++++++++ .../source/bme688/bsec2/bsec_datatypes.h | 509 +++++ .../source/bme688/bsec2/bsec_interface.h | 563 +++++ .../bme688/bsec2/bsec_interface_multi.h | 334 +++ .../source/bme688/bsec2/libalgobsec.ar | Bin 0 -> 254616 bytes .../test-lorawan/source/bme688/utils/Defer.h | 24 + src/mbedos/test-lorawan/source/main.cpp | 258 +++ .../packet/environmental_sensor_type_c.h | 153 ++ .../test-lorawan/stm32customtargets.lib | 1 + src/mbedos/test-lorawan/trace_helper.cpp | 72 + src/mbedos/test-lorawan/trace_helper.h | 28 + 23 files changed, 6485 insertions(+) create mode 100644 src/mbedos/test-lorawan/.gitignore create mode 100644 src/mbedos/test-lorawan/custom_targets.json create mode 100644 src/mbedos/test-lorawan/lora_radio_helper.h create mode 100644 src/mbedos/test-lorawan/mbed-os.lib create mode 100644 src/mbedos/test-lorawan/mbed_app.json create mode 100644 src/mbedos/test-lorawan/mbedtls_lora_config.h create mode 100644 src/mbedos/test-lorawan/patch/deep_sleep_override.cpp create mode 100644 src/mbedos/test-lorawan/readme.md create mode 100644 src/mbedos/test-lorawan/source/bme688/BME688.cpp create mode 100644 src/mbedos/test-lorawan/source/bme688/BME688.h create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.c create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.h create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/bme68x_defs.h create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/bsec_datatypes.h create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface.h create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface_multi.h create mode 100644 src/mbedos/test-lorawan/source/bme688/bsec2/libalgobsec.ar create mode 100644 src/mbedos/test-lorawan/source/bme688/utils/Defer.h create mode 100644 src/mbedos/test-lorawan/source/main.cpp create mode 100644 src/mbedos/test-lorawan/source/packet/environmental_sensor_type_c.h create mode 100644 src/mbedos/test-lorawan/stm32customtargets.lib create mode 100644 src/mbedos/test-lorawan/trace_helper.cpp create mode 100644 src/mbedos/test-lorawan/trace_helper.h diff --git a/src/mbedos/test-lorawan/.gitignore b/src/mbedos/test-lorawan/.gitignore new file mode 100644 index 0000000..6d245b3 --- /dev/null +++ b/src/mbedos/test-lorawan/.gitignore @@ -0,0 +1,46 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a + +# Executables +*.exe +*.out +*.app + +# clion +.idea/ +cmake-build-*/ + +# exporters +GettingStarted.html + +# mbed build system +mbed_settings.py +mbed_config.h +*.pyc +BUILD/ +cmake_build/ + +BUILD +.mbed +mbed-os/ +stm32customtargets/ diff --git a/src/mbedos/test-lorawan/custom_targets.json b/src/mbedos/test-lorawan/custom_targets.json new file mode 100644 index 0000000..4180a0b --- /dev/null +++ b/src/mbedos/test-lorawan/custom_targets.json @@ -0,0 +1,141 @@ +{ + "BLUEPILL_F103C8": { + "inherits": [ + "MCU_STM32F103x8" + ], + "overrides": { + "clock_source": "USE_PLL_HSE_XTAL" + }, + "device_has_add": [ + "USBDEVICE" + ], + "device_name": "STM32F103C8" + }, + "MTB_MURATA_ABZ": { + "inherits": [ + "MCU_STM32L082xZ" + ], + "detect_code": ["0456"], + "device_name": "STM32L082CZYx" + }, + "GRASSHOPPER": { + "inherits": [ + "MTB_MURATA_ABZ" + ] + }, + "GNAT": { + "inherits": [ + "MTB_MURATA_ABZ" + ] + }, + "STWIN": { + "inherits": [ + "MCU_STM32L4R9xI" + ], + "device_name": "STM32L4R9ZITx", + "supported_form_factors": [ + "STMOD" + ], + "components_add": [ + "BlueNRG_MS" + ], + "extra_labels_add": [ + "CORDIO" + ], + "features": [ + "BLE" + ] + }, + "LORA_E5": { + "inherits": [ + "MCU_STM32WLE5xC" + ], + "device_name": "STM32WLE5JCIx" + }, + "LORA_E5_BREAKOUT": { + "inherits": [ + "LORA_E5" + ], + "macros_add": [ + "LED1=PB_5", + "LED2=PB_10", + "BUTTON1=PB_13" + ] + }, + "LORA_E5_MINI": { + "inherits": [ + "LORA_E5" + ], + "macros_add": [ + "LED1=PB_5", + "BUTTON1=PB_13" + ] + }, + "LORA_E5_DEV_BOARD": { + "inherits": [ + "LORA_E5" + ], + "macros_add": [ + "LED1=PB_5", + "EN_3V3=PA_9", + "EN_5V=PB_10", + "BUTTON1=PB_13", + "BUTTON2=PA_0", + "RS485_REDE=PB_4" + ] + }, + "LORA_E5_TINY": { + "inherits": [ + "LORA_E5" + ], + "macros_add": [ + "LED1=PA_9", + "LED2=PB_13", + "BUTTON1=PB_4" + ] + }, + "RAK3172": { + "inherits": [ + "MCU_STM32WLE5xC" + ], + "device_name": "STM32WLE5CCUx" + }, + "RAK3172_BREAKOUT": { + "inherits": [ + "RAK3172" + ], + "macros_add": [ + "LED1=PA_9", + "LED2=PA_10" + ] + }, + "RAK3172SIP": { + "inherits": [ + "MCU_STM32WLE5xC" + ], + "device_name": "STM32WLE5CCUx" + }, + "RAK3272_BREAKOUT": { + "inherits": [ + "RAK3172SIP" + ] + }, + "CORE2": + { + "inherits": ["MCU_STM32F4"], + "extra_labels_add": ["STM32F407xG"], + "macros_add": [ + "STM32F407xx", + "UPPER_RESISTOR=5.6e4", + "LOWER_RESISTOR=1.0e4", + "VIN_MEAS_CORRECTION=0.986" + ], + "overrides": { + "lse_available": 0, + "clock_source": "USE_PLL_HSE_XTAL" + }, + "device_has_add": ["TRNG", "CAN"], + "device_has_remove": ["LPTICKER"], + "device_name": "STM32F407ZG" + } +} diff --git a/src/mbedos/test-lorawan/lora_radio_helper.h b/src/mbedos/test-lorawan/lora_radio_helper.h new file mode 100644 index 0000000..f81d401 --- /dev/null +++ b/src/mbedos/test-lorawan/lora_radio_helper.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APP_LORA_RADIO_HELPER_H_ +#define APP_LORA_RADIO_HELPER_H_ + +#include "lorawan/LoRaRadio.h" + +#if COMPONENT_SX1272 +#include "SX1272_LoRaRadio.h" +SX1272_LoRaRadio radio(MBED_CONF_SX1272_LORA_DRIVER_SPI_MOSI, + MBED_CONF_SX1272_LORA_DRIVER_SPI_MISO, + MBED_CONF_SX1272_LORA_DRIVER_SPI_SCLK, + MBED_CONF_SX1272_LORA_DRIVER_SPI_CS, + MBED_CONF_SX1272_LORA_DRIVER_RESET, + MBED_CONF_SX1272_LORA_DRIVER_DIO0, + MBED_CONF_SX1272_LORA_DRIVER_DIO1, + MBED_CONF_SX1272_LORA_DRIVER_DIO2, + MBED_CONF_SX1272_LORA_DRIVER_DIO3, + MBED_CONF_SX1272_LORA_DRIVER_DIO4, + MBED_CONF_SX1272_LORA_DRIVER_DIO5, + MBED_CONF_SX1272_LORA_DRIVER_RF_SWITCH_CTL1, + MBED_CONF_SX1272_LORA_DRIVER_RF_SWITCH_CTL2, + MBED_CONF_SX1272_LORA_DRIVER_TXCTL, + MBED_CONF_SX1272_LORA_DRIVER_RXCTL, + MBED_CONF_SX1272_LORA_DRIVER_ANT_SWITCH, + MBED_CONF_SX1272_LORA_DRIVER_PWR_AMP_CTL, + MBED_CONF_SX1272_LORA_DRIVER_TCXO); + +#elif COMPONENT_SX1276 +#include "SX1276_LoRaRadio.h" +SX1276_LoRaRadio radio(MBED_CONF_SX1276_LORA_DRIVER_SPI_MOSI, + MBED_CONF_SX1276_LORA_DRIVER_SPI_MISO, + MBED_CONF_SX1276_LORA_DRIVER_SPI_SCLK, + MBED_CONF_SX1276_LORA_DRIVER_SPI_CS, + MBED_CONF_SX1276_LORA_DRIVER_RESET, + MBED_CONF_SX1276_LORA_DRIVER_DIO0, + MBED_CONF_SX1276_LORA_DRIVER_DIO1, + MBED_CONF_SX1276_LORA_DRIVER_DIO2, + MBED_CONF_SX1276_LORA_DRIVER_DIO3, + MBED_CONF_SX1276_LORA_DRIVER_DIO4, + MBED_CONF_SX1276_LORA_DRIVER_DIO5, + MBED_CONF_SX1276_LORA_DRIVER_RF_SWITCH_CTL1, + MBED_CONF_SX1276_LORA_DRIVER_RF_SWITCH_CTL2, + MBED_CONF_SX1276_LORA_DRIVER_TXCTL, + MBED_CONF_SX1276_LORA_DRIVER_RXCTL, + MBED_CONF_SX1276_LORA_DRIVER_ANT_SWITCH, + MBED_CONF_SX1276_LORA_DRIVER_PWR_AMP_CTL, + MBED_CONF_SX1276_LORA_DRIVER_TCXO); + +#elif COMPONENT_SX126X +#include "SX126X_LoRaRadio.h" +SX126X_LoRaRadio radio(MBED_CONF_SX126X_LORA_DRIVER_SPI_MOSI, + MBED_CONF_SX126X_LORA_DRIVER_SPI_MISO, + MBED_CONF_SX126X_LORA_DRIVER_SPI_SCLK, + MBED_CONF_SX126X_LORA_DRIVER_SPI_CS, + MBED_CONF_SX126X_LORA_DRIVER_RESET, + MBED_CONF_SX126X_LORA_DRIVER_DIO1, + MBED_CONF_SX126X_LORA_DRIVER_BUSY, + MBED_CONF_SX126X_LORA_DRIVER_FREQ_SELECT, + MBED_CONF_SX126X_LORA_DRIVER_DEVICE_SELECT, + MBED_CONF_SX126X_LORA_DRIVER_CRYSTAL_SELECT, + MBED_CONF_SX126X_LORA_DRIVER_ANT_SWITCH); + +#elif (TARGET_STM32WL) +#include "STM32WL_LoRaRadio.h" +STM32WL_LoRaRadio radio; +#else +#error "Unknown LoRa radio specified (SX126X, SX1272, SX1276, STM32WL are valid)" +#endif + +#endif /* APP_LORA_RADIO_HELPER_H_ */ diff --git a/src/mbedos/test-lorawan/mbed-os.lib b/src/mbedos/test-lorawan/mbed-os.lib new file mode 100644 index 0000000..aa5ae85 --- /dev/null +++ b/src/mbedos/test-lorawan/mbed-os.lib @@ -0,0 +1 @@ +https://github.com/teapotlaboratories/mbed-os/#daf31245c4c87207bd3fd6a55302a7734305ca87 diff --git a/src/mbedos/test-lorawan/mbed_app.json b/src/mbedos/test-lorawan/mbed_app.json new file mode 100644 index 0000000..f81ec13 --- /dev/null +++ b/src/mbedos/test-lorawan/mbed_app.json @@ -0,0 +1,34 @@ +{ + "config": { + "main_stack_size": { "value": 4096 } + }, + "target_overrides": { + "*": { + "platform.cpu-stats-enabled": 0, + "target.macros_add": ["MBED_TICKLESS", "MBED_SLEEP_TRACING_ENABLED", "DEVICE_I2C_FREE_DESTRUCTOR"], + + "target.c_lib": "std", + "target.printf_lib": "std", + "platform.stdio-buffered-serial": 1, + "platform.stdio-convert-newlines": true, + "platform.stdio-baud-rate": 115200, + "platform.default-serial-baud-rate": 115200, + + "mbed-trace.enable": false, + "mbed-trace.max-level": "TRACE_LEVEL_DEBUG", + + "lora.over-the-air-activation": true, + "lora.duty-cycle-on": false, + "lora.phy": "US915", + "lora.device-eui": "{ 0xAC, 0x1F, 0x09, 0xFF, 0xFE, 0x0E, 0x39, 0xE8} ", + "lora.application-eui": "{ 0x0E, 0x0D, 0x0D, 0x01, 0x0E, 0x01, 0x02, 0x0E }", + "lora.application-key": "{ 0xb5, 0x12, 0xfb, 0x72, 0xfc, 0x87, 0x20, 0x2a, 0xb3, 0x90, 0xc1, 0xbe, 0x8e, 0x46, 0x99, 0xf6 }" + }, + "RAK3172": { + "stm32wl-lora-driver.rf_switch_config": 2, + "stm32wl-lora-driver.crystal_select" : 0 + } + }, + "macros": ["MBEDTLS_USER_CONFIG_FILE=\"mbedtls_lora_config.h\""] +} + diff --git a/src/mbedos/test-lorawan/mbedtls_lora_config.h b/src/mbedos/test-lorawan/mbedtls_lora_config.h new file mode 100644 index 0000000..c57f52f --- /dev/null +++ b/src/mbedos/test-lorawan/mbedtls_lora_config.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBEDTLS_LORA_CONFIG_H +#define MBEDTLS_LORA_CONFIG_H + +/* + * Configure mbedtls for LoRa stack. + * + * These settings are just a customization of the main mbedtls config + * mbed-os/features/mbedtls/inc/mbedtls/config.h + */ + +// Ensure LoRa required ciphers are enabled +#define MBEDTLS_CIPHER_C +#define MBEDTLS_AES_C +#define MBEDTLS_CMAC_C + +// Reduce ROM usage by optimizing some mbedtls features. +// These are only reference configurations for this LoRa example application. +// Other LoRa applications might need different configurations. +#define MBEDTLS_AES_FEWER_TABLES + +#undef MBEDTLS_GCM_C +#undef MBEDTLS_CHACHA20_C +#undef MBEDTLS_CHACHAPOLY_C +#undef MBEDTLS_POLY1305_C + +#endif /* MBEDTLS_LORA_CONFIG_H */ diff --git a/src/mbedos/test-lorawan/patch/deep_sleep_override.cpp b/src/mbedos/test-lorawan/patch/deep_sleep_override.cpp new file mode 100644 index 0000000..a1c02d1 --- /dev/null +++ b/src/mbedos/test-lorawan/patch/deep_sleep_override.cpp @@ -0,0 +1,132 @@ +/** + * Public domain code from https://github.com/ARMmbed/mbed-os/issues/15331 + * by hallard (hallard.me) + */ + +#include "hal/static_pinmap.h" +#include "platform/mbed_critical.h" + +#define DEEPSLEEP_MODE_STOP1 1 +#define DEEPSLEEP_MODE_STOP2 2 +volatile uint8_t deep_sleep_mode = DEEPSLEEP_MODE_STOP2; + +extern serial_t stdio_uart; + +// May be check if console is enabled in mbed_conf ? +void restart_console() +{ + static const serial_pinmap_t console_pinmap = get_uart_pinmap(CONSOLE_TX, CONSOLE_RX); + serial_init_direct(&stdio_uart, &console_pinmap); +#if MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE + int baud = MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE; +# else + int baud = 9600; +#endif + serial_baud(&stdio_uart, baud); +} + +extern "C" +{ + extern int mbed_sdk_inited; + extern int serial_is_tx_ongoing(void); + extern void save_timer_ctx(void); + extern void restore_timer_ctx(void); + extern void PWR_EnterStopMode(void); + extern void PWR_ExitStopMode(void); + extern void SetSysClock(void); + extern void ForceOscOutofDeepSleep(void); + extern void ForcePeriphOutofDeepSleep(void); + extern void wait_loop(uint32_t timeout); +} + +/* Most of STM32 targets can have the same generic deep sleep + * function, but a few targets might need very specific sleep + * mode management, so this function is defined as WEAK. + * This one works on STM32WL only */ +void hal_deepsleep(void) +{ + /* WORKAROUND: + * MBED serial driver does not handle deepsleep lock + * to prevent entering deepsleep until HW serial FIFO is empty. + * This is tracked in mbed issue 4408 */ + if (serial_is_tx_ongoing()) + { /* FIXME: return or postpone deepsleep? */ + return; + } + + // Disable IRQs + core_util_critical_section_enter(); + + save_timer_ctx(); + + // Request to enter STOP mode with regulator in low power mode + int pwrClockEnabled = __HAL_RCC_PWR_IS_CLK_ENABLED(); + int lowPowerModeEnabled = PWR->CR1 & PWR_CR1_LPR; + + if (!pwrClockEnabled) + { + __HAL_RCC_PWR_CLK_ENABLE(); + } + if (lowPowerModeEnabled) + { + HAL_PWREx_DisableLowPowerRunMode(); + } + + // If deep sleep stop 1 set it, else use default stop 2 + if (deep_sleep_mode == DEEPSLEEP_MODE_STOP1) + { + HAL_PWREx_EnterSTOP1Mode(PWR_STOPENTRY_WFI); + } + else + { + HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); + } + + if (lowPowerModeEnabled) + { + HAL_PWREx_EnableLowPowerRunMode(); + } + if (!pwrClockEnabled) + { + __HAL_RCC_PWR_CLK_DISABLE(); + } + + /* Prevent HAL_GetTick() from using ticker_read_us() to read the + * us_ticker timestamp until the us_ticker context is restored. */ + mbed_sdk_inited = 0; + + /* We've seen unstable PLL CLK configuration when DEEP SLEEP exits just few + * µs after being entered So we need to force clock init out of Deep Sleep. + * This init has been split into 2 separate functions so that the involved + * structures are not allocated on the stack in parallel. This will reduce + * the maximum stack usage in case on non-optimized / debug compilers + * settings + */ + ForceOscOutofDeepSleep(); + ForcePeriphOutofDeepSleep(); + SetSysClock(); + + /* Wait for clock to be stabilized. + * TO DO: a better way of doing this, would be to rely on + * HW Flag. At least this ensures proper operation out of + * deep sleep */ + /* Charles : Tried those 3 with no luck, so kept wait_loop(500) + while (!(RCC->CR & RCC_CR_HSERDY)); + while (!(RCC->CR & RCC_CR_PLLRDY)); + while (!(RCC->CR & RCC_CR_HSIRDY)); + */ + wait_loop(500); + + restore_timer_ctx(); + + /* us_ticker context restored, allow HAL_GetTick() to read the us_ticker + * timestamp via ticker_read_us() again. */ + mbed_sdk_inited = 1; + + // Enable IRQs + core_util_critical_section_exit(); + + // Enable back serial console + restart_console(); + +} \ No newline at end of file diff --git a/src/mbedos/test-lorawan/readme.md b/src/mbedos/test-lorawan/readme.md new file mode 100644 index 0000000..0f4a1e5 --- /dev/null +++ b/src/mbedos/test-lorawan/readme.md @@ -0,0 +1,22 @@ + +# BSEC BME688 Test Example + +Tested on: + + - Compiler ARMC6 + - With/without sleep trace enabled + - [BSEC2 Library](https://www.bosch-sensortec.com/software-tools/software/bme688-software/) + +## Acknowledgement +Source code is based on https://github.com/Mircerson/AERQ + +## TODO + +- [x] Update BSEC library to use the latest version +- [x] Provide BSEC static library + - `libalgobsec.a` for GCC_ARM + - `libalgobsec.ar` for ARMC6 ( currently rename the .lib file to .ar from the BSEC library) + - STM32WL55CC is Cortex-M4 +- [x] Implement simple AQI reader without proper sleep +- [ ] Rewrite BME688 high-level library +- [ ] Create a different repo for proper AQI reader with sleep diff --git a/src/mbedos/test-lorawan/source/bme688/BME688.cpp b/src/mbedos/test-lorawan/source/bme688/BME688.cpp new file mode 100644 index 0000000..614b73b --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/BME688.cpp @@ -0,0 +1,716 @@ +#include "BME688.h" +#include "Defer.h" + +#define BSEC_CHECK_INPUT(x, shift) (x & (1 << (shift-1))) + +using namespace std::chrono; +using namespace teapotlabs::utils; + +namespace teapotlabs { +namespace sensors { +namespace bme688 { + +// ********************************************************************* +// SENSOR STATIC CALLBACK DEFINITION +// ********************************************************************* + +/** + * @brief Internal callback to read register used by bme688 library + */ +static int8_t SensorInternalReadRegisterCb( uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr ) +{ + // check user intf_ptr object + if( intf_ptr == nullptr ) + { + return 0xFF; // return error + } + + BME688* bme688 = (BME688*) intf_ptr; + if( bme688->i2c_local == nullptr ) + { + // return error + return 0xFF; + } + + // local variable definition + uint32_t aux = 0; + aux = bme688->i2c_local->write ( bme688->bme688_addr_8bit, (char*)®_addr, 1, true ); + if ( aux != 0 ) { + return 0xFF; // return error + } + + aux = bme688->i2c_local->read ( bme688->bme688_addr_8bit, (char*)®_data[0], length ); + if ( aux != 0 ) { + return 0xFF; // return error + } + + return 0; // Return 0 for Success, non-zero for failure +} + +/** + * @brief Internal callback to write register used by bme688 library + */ +static int8_t SensorInternalWriteRegisterCb( uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, void *intf_ptr ) +{ + // check user intf_ptr object + if( intf_ptr == nullptr ) + { + return 0xFF; // return error + } + + // check callback obeject pointer + BME688* bme688 = (BME688*) intf_ptr; + if( bme688->i2c_local == nullptr ) + { + return 0xFF; // return error + } + + // local variable definition + uint32_t aux = 0; + char cmd[16] = {0}; + uint32_t i = 0; + + // Prepare the data to be sent + cmd[0] = reg_addr; + for ( i = 1; i <= length; i++ ) { + cmd[i] = reg_data[i - 1]; + } + + // Write data + aux = bme688->i2c_local->write( bme688->bme688_addr_8bit, &cmd[0], length + 1, false ); + if ( aux != 0 ) { + return 0xFF; // return error + } + + return 0; // Return 0 for Success, non-zero for failure +} + +static void SensorInternalDelayUsCb(uint32_t time_us, void *intf_ptr) +{ + /* use wait_us to wait without sleep + if system goes to sleep, I2C comm need to be re-init */ + wait_us ( time_us ); +} + +// ********************************************************************* +// PUBLIC +// ********************************************************************* + +/** + * @brief BME688 constructor to store necessary variable for library to use + */ +BME688::BME688( PinName i2c_sda, PinName i2c_scl, uint32_t bme688_addr ):bsec( {0} ), + sensor( {0} ), + callback( nullptr ) +{ + this->i2c_sda = i2c_sda; + this->i2c_scl = i2c_scl; + this->bme688_addr = bme688_addr; + this->bme688_addr_8bit = ( bme688_addr << 1 ); +} + +/** + * @brief Initialise bme688 library and bsec library, and initialise bme688 sensor + */ +ReturnCode BME688::Initialise( bsec_virtual_sensor_t* sensor_list, uint8_t n_sensors, float sample_rate ) +{ + /* enable I2C */ + /* I2C peripheral will be free after function return and deep sleep will be unlock */ + /* DEVICE_I2C_FREE_DESTRUCTOR need to be enabled in mbed_app.json*/ + I2C i2c_temp( i2c_sda, i2c_scl ); // i2c will be freed after function return + this->i2c_local = &i2c_temp; // set i2c object for bsec to use, see SensorInternalWriteRegisterCb and SensorInternalReadRegisterCb + Defer i2c_defer( this->i2c_local, nullptr ); // defer changing i2c_local to nullptr at function return + + ReturnCode result; + + /* copy virtual sensor list and sample rate */ + this->bsec.sample_rate = sample_rate; + this->bsec.n_sensors = n_sensors; + memcpy(this->bsec.sensor_list, sensor_list, n_sensors); + + /* verify bme688 chip id */ + uint8_t chip_id; + result = this->GetChipId( chip_id ); + if( result != ReturnCode::kOk ) + { + return result; + } + if( chip_id != kBme688ChipId ) + { + return ReturnCode::kSensorChipIdUnknown; + } + + /* initialise bme688 sensor */ + result = this->InitialiseSensor(); + if( result != ReturnCode::kOk ) + { + return result; + } + + /* initialise bsec library */ + result = this->InitialiseBsec(); + if( result != ReturnCode::kOk ) + { + return result; + } + + /* tell bsec library which virtual sensor to process */ + result = this->UpdateSubscription( this->bsec.sensor_list, + this->bsec.n_sensors, + this->bsec.sample_rate ); + if( result != ReturnCode::kOk ) + { + return result; + } + + return ReturnCode::kOk; +} + +/** + * @brief Set callback to notify data ready to user + */ +ReturnCode BME688::SetCallback( Callback cb ) +{ + if( cb == nullptr ) + { + return ReturnCode::kNullPointer; + } + + this->callback = cb; + return ReturnCode::kOk; +} + +/** + * @brief Set temperature offset for post processing sensor reading + */ +ReturnCode BME688::SetTemperatureOffset( const float temp_offset ) +{ + this->bsec.temperature_offset = temp_offset; + return ReturnCode::kOk; +} + +/** + * @brief Get next time Run() need to be called in ns + */ +int64_t BME688::GetNextRunTimeNs(){ + return this->bsec.sensor_conf.next_call; +} + +/** + * @brief Call from the user to read data from the BME68X using parallel mode/forced mode, process and store outputs + */ +ReturnCode BME688::Run(void) +{ + /* enable I2C */ + /* I2C peripheral will be free after function return and deep sleep will be unlock */ + /* DEVICE_I2C_FREE_DESTRUCTOR need to be enabled in mbed_app.json*/ + I2C i2c_temp( i2c_sda, i2c_scl ); // i2c will be freed after function return + this->i2c_local = &i2c_temp; // set i2c object for bsec to use, see SensorInternalWriteRegisterCb and SensorInternalReadRegisterCb + Defer i2c_defer( this->i2c_local, nullptr ); // defer changing i2c_local to nullptr at function return + + ReturnCode ret = ReturnCode::kError; + + int64_t curr_time_ns = std::chrono::time_point_cast(Kernel::Clock::now()).time_since_epoch().count(); + this->sensor.last_op_mode = this->bsec.sensor_conf.op_mode; + + if (curr_time_ns >= this->bsec.sensor_conf.next_call) + { + /* Provides the information about the current sensor configuration that is + necessary to fulfill the input requirements, eg: operation mode, timestamp + at which the sensor data shall be fetched etc */ + this->bsec.status = bsec_sensor_control( curr_time_ns, + &(this->bsec.sensor_conf) ); + if (this->bsec.status != BSEC_OK) + return ReturnCode::kBsecRunFail; + + switch ( this->bsec.sensor_conf.op_mode ) + { + case BME68X_FORCED_MODE: + ret = SetSensorToForcedMode( this->bsec.sensor_conf ); + break; + case BME68X_PARALLEL_MODE: + if ( this->sensor.last_op_mode != this->bsec.sensor_conf.op_mode ) + { + ret = SetSensorToParallelMode( this->bsec.sensor_conf ); + } + break; + + case BME68X_SLEEP_MODE: + if ( this->sensor.last_op_mode != this->bsec.sensor_conf.op_mode ) + { + ret = SetSensorOperationMode( BME68X_SLEEP_MODE ); + } + break; + } + + if( ret != ReturnCode::kOk ) + return ret; + + if( this->bsec.sensor_conf.trigger_measurement && + this->bsec.sensor_conf.op_mode != BME68X_SLEEP_MODE ) + { + Bme688FetchedData raw_data; + ret = FetchSensorData( raw_data ); + if( ret == ReturnCode::kOk ) + { + uint8_t n_fields_left = 0; + bme68x_data data; + do + { + n_fields_left = GetSensorData( raw_data, data ); + /* check for valid gas data */ + if( data.status & BME68X_GASM_VALID_MSK ) + { + ret = ProcessData(curr_time_ns, data); + if( ret != ReturnCode::kOk ) + { + return ret; + } + } + } while (n_fields_left); + } + + } + + } + return ReturnCode::kOk; +} + +/** + * @brief Get last bme68x library call status + */ +int8_t BME688::GetLastSensorCallStatus() +{ + return this->sensor.status; +} + +/** + * @brief Get last bsec library call status + */ +bsec_library_return_t BME688::GetLastBsecCallStatus() +{ + return this->bsec.status; +} + +// ********************************************************************* +// PRIVATE +// ********************************************************************* + +/** + * @brief Initialise bme688 library and sensor + */ +ReturnCode BME688::InitialiseSensor() +{ + sensor.dev.intf_ptr = this; + sensor.dev.intf = BME68X_I2C_INTF; + sensor.dev.read = SensorInternalReadRegisterCb; + sensor.dev.write = SensorInternalWriteRegisterCb; + sensor.dev.delay_us = SensorInternalDelayUsCb; + sensor.dev.amb_temp = 25; + + this->sensor.status = bme68x_init(&sensor.dev); + if( this->sensor.status != BME68X_OK ) + { + return ReturnCode::kSensorInitFail; + } + return ReturnCode::kOk; +} + +/** + * @brief Initialise bsec library + */ +ReturnCode BME688::InitialiseBsec() +{ + this->bsec.status = bsec_init(); + if( this->bsec.status != BSEC_OK ) + { + return ReturnCode::kBsecInitFail; + } + return ReturnCode::kOk; +} + +/** + * @brief Set list of sensors to enable + */ +ReturnCode BME688::UpdateSubscription(bsec_virtual_sensor_t* sensor_list, uint8_t n_sensors, float sample_rate) +{ + bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS]; + bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t n_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; + + for (uint8_t i = 0; i < n_sensors; i++) + { + virtual_sensors[i].sensor_id = sensor_list[i]; + virtual_sensors[i].sample_rate = sample_rate; + } + + /* Subscribe to library virtual sensors outputs */ + this->bsec.status = bsec_update_subscription( virtual_sensors, + n_sensors, + sensor_settings, + &n_sensor_settings); // TODO: need to check if all sensor allocated in n_sensor_settings + if (this->bsec.status != BSEC_OK) + { + return ReturnCode::kSensorBsecSubscriptionFail; + } + + return ReturnCode::kOk; +} + +/** + * @brief Set the BME68X sensor configuration to forced mode + */ +ReturnCode BME688::SetSensorToForcedMode( const bsec_bme_settings_t &conf ) +{ + ReturnCode status = ReturnCode::kError; + + // Set the filter, odr, temperature, pressure and humidity settings */ + status = SetSensorTphOverSampling( conf.temperature_oversampling, + conf.pressure_oversampling, + conf.humidity_oversampling ); + if( status != ReturnCode::kOk ) + { + return status; + } + + // set heater profile + status = SetSensorHeaterProfile( conf.heater_temperature, + conf.heater_duration ); + if( status != ReturnCode::kOk ) + { + return status; + } + + // set operation mode + status = SetSensorOperationMode( BME68X_FORCED_MODE ); + if( status != ReturnCode::kOk ) + { + return status; + } + + this->sensor.last_op_mode = BME68X_FORCED_MODE; + return status; +} + + +/** + * @brief Set the BME68X sensor configuration to parallel mode + */ +ReturnCode BME688::SetSensorToParallelMode( const bsec_bme_settings_t& conf ) +{ + uint16_t shared_heater_dur = 0; + + ReturnCode status = ReturnCode::kError; + + // Set the filter, odr, temperature, pressure and humidity settings */ + status = SetSensorTphOverSampling( conf.temperature_oversampling, + conf.pressure_oversampling, + conf.humidity_oversampling ); + if( status != ReturnCode::kOk ) + { + return status; + } + + // calculate heater duration + shared_heater_dur = kBsecTotalHeatDur - (GetSensorMeasurementDuration(BME68X_PARALLEL_MODE) / INT64_C(1000)); + + // set heater profile + status = SetSensorHeaterProfile( (uint16_t*) conf.heater_temperature_profile, + (uint16_t*) conf.heater_duration_profile, + shared_heater_dur, + conf.heater_profile_len ); + if( status != ReturnCode::kOk ) + { + return status; + } + + // set bme688 operation mode + status = SetSensorOperationMode( BME68X_PARALLEL_MODE ); + if( status != ReturnCode::kOk ) + { + return status; + } + + this->sensor.last_op_mode = BME68X_PARALLEL_MODE; + return status; +} + +/** + * @brief Function to set the Temperature, Pressure and Humidity over-sampling + */ +ReturnCode BME688::SetSensorTphOverSampling( const uint8_t os_temp, + const uint8_t os_pres, + const uint8_t os_hum ) +{ + this->sensor.status = bme68x_get_conf(&sensor.conf, &sensor.dev); + if( this->sensor.status == BME68X_OK ) + { + sensor.conf.os_hum = os_hum; + sensor.conf.os_pres = os_pres; + sensor.conf.os_temp = os_temp; + + this->sensor.status = bme68x_set_conf(&sensor.conf, &sensor.dev); + if( this->sensor.status == BME68X_OK ) + { + return ReturnCode::kOk; + } + } + + return ReturnCode::kSensorConfigFail; +} + +/** + * @brief Function to set the heater profile for Forced mode + */ +ReturnCode BME688::SetSensorHeaterProfile( const uint16_t temp, + const uint16_t dur ) +{ + bme68x_heatr_conf heater_conf + { + .enable = BME68X_ENABLE, + .heatr_temp = temp, + .heatr_dur = dur + }; + + this->sensor.status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heater_conf, &sensor.dev); + if( this->sensor.status == BME68X_OK ) + { + return ReturnCode::kOk; + } + + return ReturnCode::kSensorHeaterFail; +} + +/** + * @brief Function to set the heater profile for Parallel mode + */ +ReturnCode BME688::SetSensorHeaterProfile( uint16_t* const temp, + uint16_t* const mul, + const uint16_t shared_heater_dur, + const uint8_t profile_len ) +{ + bme68x_heatr_conf heater_conf + { + .enable = BME68X_ENABLE, + .heatr_temp_prof = temp, + .heatr_dur_prof = mul, + .profile_len = profile_len, + .shared_heatr_dur = shared_heater_dur + }; + + this->sensor.status = bme68x_set_heatr_conf( BME68X_PARALLEL_MODE, &heater_conf, &sensor.dev); + if( this->sensor.status == BME68X_OK ) + { + return ReturnCode::kOk; + } + + return ReturnCode::kSensorHeaterFail; +} + + +/** + * @brief Function to set the operation mode + */ +ReturnCode BME688::SetSensorOperationMode( const uint8_t op_mode ) +{ + this->sensor.status = bme68x_set_op_mode(op_mode, &sensor.dev); + if( this->sensor.status == BME68X_OK && + op_mode != BME68X_SLEEP_MODE ) + { + this->sensor.last_op_mode = op_mode; + } + + if( this->sensor.status == BME68X_OK ) + { + return ReturnCode::kOk; + } + return ReturnCode::kSensorSetOperationFail; +} + +/** + * @brief Function to get the measurement duration in microseconds + */ +uint32_t BME688::GetSensorMeasurementDuration( const uint8_t op_mode ) +{ + uint8_t target_op_mode = op_mode; + if (target_op_mode == BME68X_SLEEP_MODE) + { + target_op_mode = this->sensor.last_op_mode; + } + + return bme68x_get_meas_dur(target_op_mode, &sensor.conf, &sensor.dev); +} + +/** + * @brief Function to fetch data from the sensor into the local buffer + */ +ReturnCode BME688::FetchSensorData( Bme688FetchedData &data_out ) +{ + data_out.n_fields = 0; + data_out.i_fields = 0; + this->sensor.status = bme68x_get_data( this->sensor.last_op_mode, + data_out.data, + &(data_out.n_fields), + &(this->sensor.dev) ); + + if ( this->sensor.status != BME68X_OK ) + { + return ReturnCode::kSensorGetDataFail; + } + + return ReturnCode::kOk; +} + +/** + * @brief Function to get a single data field + */ +uint8_t BME688::GetSensorData( Bme688FetchedData &data_in, + bme68x_data &data_out ) +{ + if (this->sensor.last_op_mode == BME68X_FORCED_MODE) + { + data_out = data_in.data[0]; + } + else + { + if( data_in.n_fields ) + { + /* iFields spans from 0-2 while nFields spans from + * 0-3, where 0 means that there is no new data + */ + data_out = data_in.data[data_in.i_fields]; + data_in.i_fields++; + + /* Limit reading continuously to the last fields read */ + if ( data_in.i_fields >= data_in.n_fields ) + { + data_in.i_fields = data_in.n_fields - 1; + return 0; + } + + /* Indicate if there is something left to read */ + return (data_in.n_fields) - (data_in.i_fields); + } + } + + return 0; +} + +/** + * @brief Reads data from the BME68X sensor and process it + */ +ReturnCode BME688::ProcessData( const int64_t curr_time_ns, const bme68x_data &data ) +{ + bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; /* Temp, Pres, Hum & Gas */ + uint8_t n_inputs = 0; + /* Checks all the required sensor inputs, required for the BSEC library for the requested outputs */ + if (BSEC_CHECK_INPUT(this->bsec.sensor_conf.process_data, BSEC_INPUT_HEATSOURCE)) + { + inputs[n_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; + inputs[n_inputs].signal = this->bsec.temperature_offset; + inputs[n_inputs].time_stamp = curr_time_ns; + n_inputs++; + } + if (BSEC_CHECK_INPUT(this->bsec.sensor_conf.process_data, BSEC_INPUT_TEMPERATURE)) + { +#ifdef BME68X_USE_FPU + inputs[n_inputs].signal = data.temperature; +#else + inputs[n_inputs].signal = data.temperature / 100.0f; +#endif + inputs[n_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; + inputs[n_inputs].time_stamp = curr_time_ns; + n_inputs++; + } + if (BSEC_CHECK_INPUT(this->bsec.sensor_conf.process_data, BSEC_INPUT_HUMIDITY)) + { +#ifdef BME68X_USE_FPU + inputs[n_inputs].signal = data.humidity; +#else + inputs[n_inputs].signal = data.humidity / 1000.0f; +#endif + inputs[n_inputs].sensor_id = BSEC_INPUT_HUMIDITY; + inputs[n_inputs].time_stamp = curr_time_ns; + n_inputs++; + } + if (BSEC_CHECK_INPUT(this->bsec.sensor_conf.process_data, BSEC_INPUT_PRESSURE)) + { + inputs[n_inputs].sensor_id = BSEC_INPUT_PRESSURE; + inputs[n_inputs].signal = data.pressure; + inputs[n_inputs].time_stamp = curr_time_ns; + n_inputs++; + } + if (BSEC_CHECK_INPUT(this->bsec.sensor_conf.process_data, BSEC_INPUT_GASRESISTOR) && + (data.status & BME68X_GASM_VALID_MSK)) + { + inputs[n_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; + inputs[n_inputs].signal = data.gas_resistance; + inputs[n_inputs].time_stamp = curr_time_ns; + n_inputs++; + } + if (BSEC_CHECK_INPUT(this->bsec.sensor_conf.process_data, BSEC_INPUT_PROFILE_PART) && + (data.status & BME68X_GASM_VALID_MSK)) + { + inputs[n_inputs].sensor_id = BSEC_INPUT_PROFILE_PART; + inputs[n_inputs].signal = (this->sensor.last_op_mode == BME68X_FORCED_MODE) ? 0 : data.gas_index; + inputs[n_inputs].time_stamp = curr_time_ns; + n_inputs++; + } + + bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; + uint8_t n_outputs; + + if (n_inputs > 0) + { + + n_outputs = BSEC_NUMBER_OUTPUTS; + memset(outputs, 0, sizeof(outputs)); + + /* Processing of the input signals and returning of output samples is performed by bsec_do_steps() */ + this->sensor.status = bsec_do_steps( inputs, n_inputs, outputs, &n_outputs ); + + if (this->sensor.status != BSEC_OK) + { + return ReturnCode::kSensorBsecProcessFail; + } + + // let user know that data output is ready + if( callback != nullptr ) + { + this->callback(data, outputs, n_outputs); + } + } + return ReturnCode::kOk; +} + +/** + * @brief Get bme688 chip id + */ +ReturnCode BME688::GetChipId( uint8_t& chip_id_out ) +{ + if( ReadRegister( kBme688ChipIdAddr, &chip_id_out, 1 ) != ReturnCode::kOk ) + { + return ReturnCode::kSensorGetChipIdFail; + } + return ReturnCode::kOk; +} + +/** + * @brief Read bme688 register + */ +ReturnCode BME688::ReadRegister( const uint8_t reg_addr, uint8_t* const reg_data, uint32_t length ) +{ + // re-use static function from bme68x_init() + if( SensorInternalReadRegisterCb( reg_addr, reg_data, length, this ) != 0 ) + { + return ReturnCode::kSensorReadRegisterFail; + } + + return ReturnCode::kOk; +} + +} // namespace bme688 +} // namespace sensors +} // namespace teapotlabs + + diff --git a/src/mbedos/test-lorawan/source/bme688/BME688.h b/src/mbedos/test-lorawan/source/bme688/BME688.h new file mode 100644 index 0000000..7046a1e --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/BME688.h @@ -0,0 +1,179 @@ +#ifndef BME688_H +#define BME688_H + +#include "mbed.h" +#include "bme68x_defs.h" +#include "bme68x.h" +#include "bsec_datatypes.h" +#include "bsec_interface.h" + +namespace teapotlabs { +namespace sensors { +namespace bme688 { + +/* NOTE: defining constexpr in header might not be the best way to replace macro */ +constexpr uint8_t kBme688ChipId = 0x61; +constexpr uint8_t kBme688ChipIdAddr = 0xD0; +constexpr uint16_t kBsecTotalHeatDur = UINT16_C(140); + +enum class ReturnCode +{ + // success code + kOk = 0, + // error code + kError, + kSensorStructureFail, + kSensorConfigFail, + kSensorHeaterFail, + kSensorSetOperationFail, + kSensorOperationSeqFail, + kSensorBsecFail, + kSensorBsecSubscriptionFail, + kSensorBsecProcessFail, + kSensorGetDataFail, + kBsecInitFail, + kBsecRunFail, + kSensorInitFail, + kNullPointer, + kSensorReadRegisterFail, + kSensorChipIdUnknown, + kSensorGetChipIdFail +}; + +using Callback = void (*)( const bme68x_data data, bsec_output_t* const outputs, const uint8_t n_outputs ); + +class BME688{ + + public: + + uint32_t bme688_addr; + uint32_t bme688_addr_8bit; + PinName i2c_sda; + PinName i2c_scl; + I2C* i2c_local; + + BME688( PinName i2c_sda, PinName i2c_scl, uint32_t bme688_addr ); + + /** + * @brief Function to initialize the library + * @param sensor_list : The list of output sensors + * @param n_sensors : Number of outputs requested + * @param sample_rate : The sample rate of requested sensors + * @return ReturnCode::kOk if everything initialized correctly + */ + ReturnCode Initialise( bsec_virtual_sensor_t* sensor_list, uint8_t n_sensors, float sample_rate ); + + /** + * @brief Set callback to notify data ready to user + * @param cb : User callback + * @return ReturnCode::kOk if cb is valid + */ + ReturnCode SetCallback( Callback cb ); + + /** + * @brief Set temperature offset for post processing sensor reading + * @param temp_offset : temperature to offset sensor readings + * @return ReturnCode::kOk if cb is valid + */ + ReturnCode SetTemperatureOffset( const float temp_offset ); + + /** + * @brief Get next time Run() need to be called in ns + * @return target time in nanoseconds for next Run() call + */ + int64_t GetNextRunTimeNs(); + + /** + * @brief Call from the user to read data from the BME68X using parallel mode/forced mode, process and store outputs + * @return ReturnCode::kOk if processing is succesful + */ + ReturnCode Run(); + + /** + * @brief Get last bme688 library call status + * @return last status of a bme68x api call + */ + int8_t GetLastSensorCallStatus(); + + + /** + * @brief Get last bme68x library call status + * @return last status of a bme68x api call + */ + + /** + * @brief Get last bsec library call status + * @return last status of a bsec api call + */ + bsec_library_return_t GetLastBsecCallStatus(); + + private: + /* local struct definition */ + struct Bme688FetchedData + { + bme68x_data data[3]; + uint8_t n_fields; + uint8_t i_fields; + }; + + /* bme688 sensor internal variables + store configuration for other bsec method to use */ + struct + { + bme68x_conf conf; // sensor configuration + bme68x_dev dev; // low-level sensor device structure definition + int8_t status; // last low-level api call return value + uint8_t last_op_mode; // last operation mode of the sensor + } sensor; + + /* bsec internal variables */ + struct + { + // bsec libray configuration + bsec_bme_settings_t sensor_conf; // sensor settings used by bsec + float temperature_offset; + bsec_library_return_t status; // last bsec api call return value + + // requested virtual sensor list and sample rate + float sample_rate; + uint8_t n_sensors; + bsec_virtual_sensor_t sensor_list[32]; // allocate 32 for now + } bsec; + Callback callback; + + /* BME688 sensor specific method */ + ReturnCode InitialiseSensor(); + ReturnCode SetSensorTphOverSampling( const uint8_t os_temp, + const uint8_t os_pres, + const uint8_t os_hum ); + ReturnCode SetSensorOperationMode( const uint8_t op_mode ); + ReturnCode SetSensorHeaterProfile( const uint16_t temp, + const uint16_t dur ); + ReturnCode SetSensorHeaterProfile( uint16_t* const temp, + uint16_t* const mul, + const uint16_t shared_heatr_dur, + const uint8_t profile_len ); + uint32_t GetSensorMeasurementDuration( const uint8_t op_mode ); + ReturnCode SetSensorToForcedMode( const bsec_bme_settings_t& bsec_bme_conf ); + ReturnCode SetSensorToParallelMode( const bsec_bme_settings_t& bsec_bme_conf ); + ReturnCode FetchSensorData( Bme688FetchedData &data_out ); + uint8_t GetSensorData( Bme688FetchedData &data_in, + bme68x_data &data_out ); + + /* BSEC sensor specific method */ + ReturnCode InitialiseBsec(); + ReturnCode UpdateSubscription( bsec_virtual_sensor_t* sensor_list, + uint8_t n_sensors, + float sample_rate ); + ReturnCode ProcessData( const int64_t curr_time_ns, const bme68x_data &data ); + + + ReturnCode ReadRegister( const uint8_t reg_addr, uint8_t* const reg_data, uint32_t length ); + ReturnCode GetChipId( uint8_t& chip_id_out ); +}; + +} // bme688 +} // namespace sensors +} // namespace teapotlabs + +#endif \ No newline at end of file diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.c b/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.c new file mode 100644 index 0000000..7c3c935 --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.c @@ -0,0 +1,1848 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* 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. +* +* @file bme68x.c +* @date 2021-11-09 +* @version v4.4.7 +* +*/ + +#include "bme68x.h" +#include + +/* This internal API is used to read the calibration coefficients */ +static int8_t get_calib_data(struct bme68x_dev *dev); + +/* This internal API is used to read variant ID information register status */ +static int8_t read_variant_id(struct bme68x_dev *dev); + +/* This internal API is used to calculate the gas wait */ +static uint8_t calc_gas_wait(uint16_t dur); + +#ifndef BME68X_USE_FPU + +/* This internal API is used to calculate the temperature in integer */ +static int16_t calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev); + +/* This internal API is used to calculate the pressure in integer */ +static uint32_t calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the humidity in integer */ +static uint32_t calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the gas resistance high */ +static uint32_t calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range); + +/* This internal API is used to calculate the gas resistance low */ +static uint32_t calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the heater resistance using integer */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev); + +#else + +/* This internal API is used to calculate the temperature value in float */ +static float calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev); + +/* This internal API is used to calculate the pressure value in float */ +static float calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the humidity value in float */ +static float calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the gas resistance high value in float */ +static float calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range); + +/* This internal API is used to calculate the gas resistance low value in float */ +static float calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the heater resistance value using float */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev); + +#endif + +/* This internal API is used to read a single data of the sensor */ +static int8_t read_field_data(uint8_t index, struct bme68x_data *data, struct bme68x_dev *dev); + +/* This internal API is used to read all data fields of the sensor */ +static int8_t read_all_field_data(struct bme68x_data * const data[], struct bme68x_dev *dev); + +/* This internal API is used to switch between SPI memory pages */ +static int8_t set_mem_page(uint8_t reg_addr, struct bme68x_dev *dev); + +/* This internal API is used to get the current SPI memory page */ +static int8_t get_mem_page(struct bme68x_dev *dev); + +/* This internal API is used to check the bme68x_dev for null pointers */ +static int8_t null_ptr_check(const struct bme68x_dev *dev); + +/* This internal API is used to set heater configurations */ +static int8_t set_conf(const struct bme68x_heatr_conf *conf, uint8_t op_mode, uint8_t *nb_conv, struct bme68x_dev *dev); + +/* This internal API is used to limit the max value of a parameter */ +static int8_t boundary_check(uint8_t *value, uint8_t max, struct bme68x_dev *dev); + +/* This internal API is used to calculate the register value for + * shared heater duration */ +static uint8_t calc_heatr_dur_shared(uint16_t dur); + +/* This internal API is used to swap two fields */ +static void swap_fields(uint8_t index1, uint8_t index2, struct bme68x_data *field[]); + +/* This internal API is used sort the sensor data */ +static void sort_sensor_data(uint8_t low_index, uint8_t high_index, struct bme68x_data *field[]); + +/* + * @brief Function to analyze the sensor data + * + * @param[in] data Array of measurement data + * @param[in] n_meas Number of measurements + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +static int8_t analyze_sensor_data(const struct bme68x_data *data, uint8_t n_meas); + +/******************************************************************************************/ +/* Global API definitions */ +/******************************************************************************************/ + +/* @brief This API reads the chip-id of the sensor which is the first step to +* verify the sensor and also calibrates the sensor +* As this API is the entry point, call this API before using other APIs. +*/ +int8_t bme68x_init(struct bme68x_dev *dev) +{ + int8_t rslt; + + rslt = bme68x_soft_reset(dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_CHIP_ID, &dev->chip_id, 1, dev); + if (rslt == BME68X_OK) + { + if (dev->chip_id == BME68X_CHIP_ID) + { + /* Read Variant ID */ + rslt = read_variant_id(dev); + + if (rslt == BME68X_OK) + { + /* Get the Calibration data */ + rslt = get_calib_data(dev); + } + } + else + { + rslt = BME68X_E_DEV_NOT_FOUND; + } + } + } + + return rslt; +} + +/* + * @brief This API writes the given data to the register address of the sensor + */ +int8_t bme68x_set_regs(const uint8_t *reg_addr, const uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) +{ + int8_t rslt; + + /* Length of the temporary buffer is 2*(length of register)*/ + uint8_t tmp_buff[BME68X_LEN_INTERLEAVE_BUFF] = { 0 }; + uint16_t index; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if ((rslt == BME68X_OK) && reg_addr && reg_data) + { + if ((len > 0) && (len <= (BME68X_LEN_INTERLEAVE_BUFF / 2))) + { + /* Interleave the 2 arrays */ + for (index = 0; index < len; index++) + { + if (dev->intf == BME68X_SPI_INTF) + { + /* Set the memory page */ + rslt = set_mem_page(reg_addr[index], dev); + tmp_buff[(2 * index)] = reg_addr[index] & BME68X_SPI_WR_MSK; + } + else + { + tmp_buff[(2 * index)] = reg_addr[index]; + } + + tmp_buff[(2 * index) + 1] = reg_data[index]; + } + + /* Write the interleaved array */ + if (rslt == BME68X_OK) + { + dev->intf_rslt = dev->write(tmp_buff[0], &tmp_buff[1], (2 * len) - 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + } + } + else + { + rslt = BME68X_E_INVALID_LENGTH; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API reads the data from the given register address of sensor. + */ +int8_t bme68x_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) +{ + int8_t rslt; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if ((rslt == BME68X_OK) && reg_data) + { + if (dev->intf == BME68X_SPI_INTF) + { + /* Set the memory page */ + rslt = set_mem_page(reg_addr, dev); + if (rslt == BME68X_OK) + { + reg_addr = reg_addr | BME68X_SPI_RD_MSK; + } + } + + dev->intf_rslt = dev->read(reg_addr, reg_data, len, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API soft-resets the sensor. + */ +int8_t bme68x_soft_reset(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg_addr = BME68X_REG_SOFT_RESET; + + /* 0xb6 is the soft reset command */ + uint8_t soft_rst_cmd = BME68X_SOFT_RESET_CMD; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if (rslt == BME68X_OK) + { + if (dev->intf == BME68X_SPI_INTF) + { + rslt = get_mem_page(dev); + } + + /* Reset the device */ + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(®_addr, &soft_rst_cmd, 1, dev); + + /* Wait for 5ms */ + dev->delay_us(BME68X_PERIOD_RESET, dev->intf_ptr); + if (rslt == BME68X_OK) + { + /* After reset get the memory page */ + if (dev->intf == BME68X_SPI_INTF) + { + rslt = get_mem_page(dev); + } + } + } + } + + return rslt; +} + +/* + * @brief This API is used to set the oversampling, filter and odr configuration + */ +int8_t bme68x_set_conf(struct bme68x_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t odr20 = 0, odr3 = 1; + uint8_t current_op_mode; + + /* Register data starting from BME68X_REG_CTRL_GAS_1(0x71) up to BME68X_REG_CONFIG(0x75) */ + uint8_t reg_array[BME68X_LEN_CONFIG] = { 0x71, 0x72, 0x73, 0x74, 0x75 }; + uint8_t data_array[BME68X_LEN_CONFIG] = { 0 }; + + rslt = bme68x_get_op_mode(¤t_op_mode, dev); + if (rslt == BME68X_OK) + { + /* Configure only in the sleep mode */ + rslt = bme68x_set_op_mode(BME68X_SLEEP_MODE, dev); + } + + if (conf == NULL) + { + rslt = BME68X_E_NULL_PTR; + } + else if (rslt == BME68X_OK) + { + /* Read the whole configuration and write it back once later */ + rslt = bme68x_get_regs(reg_array[0], data_array, BME68X_LEN_CONFIG, dev); + dev->info_msg = BME68X_OK; + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->filter, BME68X_FILTER_SIZE_127, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->os_temp, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->os_pres, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->os_hum, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->odr, BME68X_ODR_NONE, dev); + } + + if (rslt == BME68X_OK) + { + data_array[4] = BME68X_SET_BITS(data_array[4], BME68X_FILTER, conf->filter); + data_array[3] = BME68X_SET_BITS(data_array[3], BME68X_OST, conf->os_temp); + data_array[3] = BME68X_SET_BITS(data_array[3], BME68X_OSP, conf->os_pres); + data_array[1] = BME68X_SET_BITS_POS_0(data_array[1], BME68X_OSH, conf->os_hum); + if (conf->odr != BME68X_ODR_NONE) + { + odr20 = conf->odr; + odr3 = 0; + } + + data_array[4] = BME68X_SET_BITS(data_array[4], BME68X_ODR20, odr20); + data_array[0] = BME68X_SET_BITS(data_array[0], BME68X_ODR3, odr3); + } + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(reg_array, data_array, BME68X_LEN_CONFIG, dev); + } + + if ((current_op_mode != BME68X_SLEEP_MODE) && (rslt == BME68X_OK)) + { + rslt = bme68x_set_op_mode(current_op_mode, dev); + } + + return rslt; +} + +/* + * @brief This API is used to get the oversampling, filter and odr + */ +int8_t bme68x_get_conf(struct bme68x_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + + /* starting address of the register array for burst read*/ + uint8_t reg_addr = BME68X_REG_CTRL_GAS_1; + uint8_t data_array[BME68X_LEN_CONFIG]; + + rslt = bme68x_get_regs(reg_addr, data_array, 5, dev); + if (!conf) + { + rslt = BME68X_E_NULL_PTR; + } + else if (rslt == BME68X_OK) + { + conf->os_hum = BME68X_GET_BITS_POS_0(data_array[1], BME68X_OSH); + conf->filter = BME68X_GET_BITS(data_array[4], BME68X_FILTER); + conf->os_temp = BME68X_GET_BITS(data_array[3], BME68X_OST); + conf->os_pres = BME68X_GET_BITS(data_array[3], BME68X_OSP); + if (BME68X_GET_BITS(data_array[0], BME68X_ODR3)) + { + conf->odr = BME68X_ODR_NONE; + } + else + { + conf->odr = BME68X_GET_BITS(data_array[4], BME68X_ODR20); + } + } + + return rslt; +} + +/* + * @brief This API is used to set the operation mode of the sensor + */ +int8_t bme68x_set_op_mode(const uint8_t op_mode, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t tmp_pow_mode; + uint8_t pow_mode = 0; + uint8_t reg_addr = BME68X_REG_CTRL_MEAS; + + /* Call until in sleep */ + do + { + rslt = bme68x_get_regs(BME68X_REG_CTRL_MEAS, &tmp_pow_mode, 1, dev); + if (rslt == BME68X_OK) + { + /* Put to sleep before changing mode */ + pow_mode = (tmp_pow_mode & BME68X_MODE_MSK); + if (pow_mode != BME68X_SLEEP_MODE) + { + tmp_pow_mode &= ~BME68X_MODE_MSK; /* Set to sleep */ + rslt = bme68x_set_regs(®_addr, &tmp_pow_mode, 1, dev); + dev->delay_us(BME68X_PERIOD_POLL, dev->intf_ptr); + } + } + } while ((pow_mode != BME68X_SLEEP_MODE) && (rslt == BME68X_OK)); + + /* Already in sleep */ + if ((op_mode != BME68X_SLEEP_MODE) && (rslt == BME68X_OK)) + { + tmp_pow_mode = (tmp_pow_mode & ~BME68X_MODE_MSK) | (op_mode & BME68X_MODE_MSK); + rslt = bme68x_set_regs(®_addr, &tmp_pow_mode, 1, dev); + } + + return rslt; +} + +/* + * @brief This API is used to get the operation mode of the sensor. + */ +int8_t bme68x_get_op_mode(uint8_t *op_mode, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t mode; + + if (op_mode) + { + rslt = bme68x_get_regs(BME68X_REG_CTRL_MEAS, &mode, 1, dev); + + /* Masking the other register bit info*/ + *op_mode = mode & BME68X_MODE_MSK; + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API is used to get the remaining duration that can be used for heating. + */ +uint32_t bme68x_get_meas_dur(const uint8_t op_mode, struct bme68x_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint32_t meas_dur = 0; /* Calculate in us */ + uint32_t meas_cycles; + uint8_t os_to_meas_cycles[6] = { 0, 1, 2, 4, 8, 16 }; + + if (conf != NULL) + { + /* Boundary check for temperature oversampling */ + rslt = boundary_check(&conf->os_temp, BME68X_OS_16X, dev); + + if (rslt == BME68X_OK) + { + /* Boundary check for pressure oversampling */ + rslt = boundary_check(&conf->os_pres, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + /* Boundary check for humidity oversampling */ + rslt = boundary_check(&conf->os_hum, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + meas_cycles = os_to_meas_cycles[conf->os_temp]; + meas_cycles += os_to_meas_cycles[conf->os_pres]; + meas_cycles += os_to_meas_cycles[conf->os_hum]; + + /* TPH measurement duration */ + meas_dur = meas_cycles * UINT32_C(1963); + meas_dur += UINT32_C(477 * 4); /* TPH switching duration */ + meas_dur += UINT32_C(477 * 5); /* Gas measurement duration */ + + if (op_mode != BME68X_PARALLEL_MODE) + { + meas_dur += UINT32_C(1000); /* Wake up duration of 1ms */ + } + } + } + + return meas_dur; +} + +/* + * @brief This API reads the pressure, temperature and humidity and gas data + * from the sensor, compensates the data and store it in the bme68x_data + * structure instance passed by the user. + */ +int8_t bme68x_get_data(uint8_t op_mode, struct bme68x_data *data, uint8_t *n_data, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t i = 0, j = 0, new_fields = 0; + struct bme68x_data *field_ptr[3] = { 0 }; + struct bme68x_data field_data[3] = { { 0 } }; + + field_ptr[0] = &field_data[0]; + field_ptr[1] = &field_data[1]; + field_ptr[2] = &field_data[2]; + + rslt = null_ptr_check(dev); + if ((rslt == BME68X_OK) && (data != NULL)) + { + /* Reading the sensor data in forced mode only */ + if (op_mode == BME68X_FORCED_MODE) + { + rslt = read_field_data(0, data, dev); + if (rslt == BME68X_OK) + { + if (data->status & BME68X_NEW_DATA_MSK) + { + new_fields = 1; + } + else + { + new_fields = 0; + rslt = BME68X_W_NO_NEW_DATA; + } + } + } + else if ((op_mode == BME68X_PARALLEL_MODE) || (op_mode == BME68X_SEQUENTIAL_MODE)) + { + /* Read the 3 fields and count the number of new data fields */ + rslt = read_all_field_data(field_ptr, dev); + + new_fields = 0; + for (i = 0; (i < 3) && (rslt == BME68X_OK); i++) + { + if (field_ptr[i]->status & BME68X_NEW_DATA_MSK) + { + new_fields++; + } + } + + /* Sort the sensor data in parallel & sequential modes*/ + for (i = 0; (i < 2) && (rslt == BME68X_OK); i++) + { + for (j = i + 1; j < 3; j++) + { + sort_sensor_data(i, j, field_ptr); + } + } + + /* Copy the sorted data */ + for (i = 0; ((i < 3) && (rslt == BME68X_OK)); i++) + { + data[i] = *field_ptr[i]; + } + + if (new_fields == 0) + { + rslt = BME68X_W_NO_NEW_DATA; + } + } + else + { + rslt = BME68X_W_DEFINE_OP_MODE; + } + + if (n_data == NULL) + { + rslt = BME68X_E_NULL_PTR; + } + else + { + *n_data = new_fields; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API is used to set the gas configuration of the sensor. + */ +int8_t bme68x_set_heatr_conf(uint8_t op_mode, const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t nb_conv = 0; + uint8_t hctrl, run_gas = 0; + uint8_t ctrl_gas_data[2]; + uint8_t ctrl_gas_addr[2] = { BME68X_REG_CTRL_GAS_0, BME68X_REG_CTRL_GAS_1 }; + + if (conf != NULL) + { + rslt = bme68x_set_op_mode(BME68X_SLEEP_MODE, dev); + if (rslt == BME68X_OK) + { + rslt = set_conf(conf, op_mode, &nb_conv, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_CTRL_GAS_0, ctrl_gas_data, 2, dev); + if (rslt == BME68X_OK) + { + if (conf->enable == BME68X_ENABLE) + { + hctrl = BME68X_ENABLE_HEATER; + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + run_gas = BME68X_ENABLE_GAS_MEAS_H; + } + else + { + run_gas = BME68X_ENABLE_GAS_MEAS_L; + } + } + else + { + hctrl = BME68X_DISABLE_HEATER; + run_gas = BME68X_DISABLE_GAS_MEAS; + } + + ctrl_gas_data[0] = BME68X_SET_BITS(ctrl_gas_data[0], BME68X_HCTRL, hctrl); + ctrl_gas_data[1] = BME68X_SET_BITS_POS_0(ctrl_gas_data[1], BME68X_NBCONV, nb_conv); + ctrl_gas_data[1] = BME68X_SET_BITS(ctrl_gas_data[1], BME68X_RUN_GAS, run_gas); + rslt = bme68x_set_regs(ctrl_gas_addr, ctrl_gas_data, 2, dev); + } + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API is used to get the gas configuration of the sensor. + */ +int8_t bme68x_get_heatr_conf(const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t data_array[10] = { 0 }; + uint8_t i; + + /* FIXME: Add conversion to deg C and ms and add the other parameters */ + rslt = bme68x_get_regs(BME68X_REG_RES_HEAT0, data_array, 10, dev); + if (rslt == BME68X_OK) + { + if (conf && conf->heatr_dur_prof && conf->heatr_temp_prof) + { + for (i = 0; i < 10; i++) + { + conf->heatr_temp_prof[i] = data_array[i]; + } + + rslt = bme68x_get_regs(BME68X_REG_GAS_WAIT0, data_array, 10, dev); + if (rslt == BME68X_OK) + { + for (i = 0; i < 10; i++) + { + conf->heatr_dur_prof[i] = data_array[i]; + } + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + } + + return rslt; +} + +/* + * @brief This API performs Self-test of low and high gas variants of BME68X + */ +int8_t bme68x_selftest_check(const struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t n_fields; + uint8_t i = 0; + struct bme68x_data data[BME68X_N_MEAS] = { { 0 } }; + struct bme68x_dev t_dev; + struct bme68x_conf conf; + struct bme68x_heatr_conf heatr_conf; + + /* Copy required parameters from reference bme68x_dev struct */ + t_dev.amb_temp = 25; + t_dev.read = dev->read; + t_dev.write = dev->write; + t_dev.intf = dev->intf; + t_dev.delay_us = dev->delay_us; + t_dev.intf_ptr = dev->intf_ptr; + rslt = bme68x_init(&t_dev); + if (rslt == BME68X_OK) + { + /* Set the temperature, pressure and humidity & filter settings */ + conf.os_hum = BME68X_OS_1X; + conf.os_pres = BME68X_OS_16X; + conf.os_temp = BME68X_OS_2X; + + /* Set the remaining gas sensor settings and link the heating profile */ + heatr_conf.enable = BME68X_ENABLE; + heatr_conf.heatr_dur = BME68X_HEATR_DUR1; + heatr_conf.heatr_temp = BME68X_HIGH_TEMP; + rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_conf(&conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &t_dev); /* Trigger a measurement */ + if (rslt == BME68X_OK) + { + /* Wait for the measurement to complete */ + t_dev.delay_us(BME68X_HEATR_DUR1_DELAY, t_dev.intf_ptr); + rslt = bme68x_get_data(BME68X_FORCED_MODE, &data[0], &n_fields, &t_dev); + if (rslt == BME68X_OK) + { + if ((data[0].idac != 0x00) && (data[0].idac != 0xFF) && + (data[0].status & BME68X_GASM_VALID_MSK)) + { + rslt = BME68X_OK; + } + else + { + rslt = BME68X_E_SELF_TEST; + } + } + } + } + } + + heatr_conf.heatr_dur = BME68X_HEATR_DUR2; + while ((rslt == BME68X_OK) && (i < BME68X_N_MEAS)) + { + if (i % 2 == 0) + { + heatr_conf.heatr_temp = BME68X_HIGH_TEMP; /* Higher temperature */ + } + else + { + heatr_conf.heatr_temp = BME68X_LOW_TEMP; /* Lower temperature */ + } + + rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_conf(&conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &t_dev); /* Trigger a measurement */ + if (rslt == BME68X_OK) + { + /* Wait for the measurement to complete */ + t_dev.delay_us(BME68X_HEATR_DUR2_DELAY, t_dev.intf_ptr); + rslt = bme68x_get_data(BME68X_FORCED_MODE, &data[i], &n_fields, &t_dev); + } + } + } + + i++; + } + + if (rslt == BME68X_OK) + { + rslt = analyze_sensor_data(data, BME68X_N_MEAS); + } + } + + return rslt; +} + +/*****************************INTERNAL APIs***********************************************/ +#ifndef BME68X_USE_FPU + +/* @brief This internal API is used to calculate the temperature value. */ +static int16_t calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev) +{ + int64_t var1; + int64_t var2; + int64_t var3; + int16_t calc_temp; + + /*lint -save -e701 -e702 -e704 */ + var1 = ((int32_t)temp_adc >> 3) - ((int32_t)dev->calib.par_t1 << 1); + var2 = (var1 * (int32_t)dev->calib.par_t2) >> 11; + var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; + var3 = ((var3) * ((int32_t)dev->calib.par_t3 << 4)) >> 14; + dev->calib.t_fine = (int32_t)(var2 + var3); + calc_temp = (int16_t)(((dev->calib.t_fine * 5) + 128) >> 8); + + /*lint -restore */ + return calc_temp; +} + +/* @brief This internal API is used to calculate the pressure value. */ +static uint32_t calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev) +{ + int32_t var1; + int32_t var2; + int32_t var3; + int32_t pressure_comp; + + /* This value is used to check precedence to multiplication or division + * in the pressure compensation equation to achieve least loss of precision and + * avoiding overflows. + * i.e Comparing value, pres_ovf_check = (1 << 31) >> 1 + */ + const int32_t pres_ovf_check = INT32_C(0x40000000); + + /*lint -save -e701 -e702 -e713 */ + var1 = (((int32_t)dev->calib.t_fine) >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * (int32_t)dev->calib.par_p6) >> 2; + var2 = var2 + ((var1 * (int32_t)dev->calib.par_p5) << 1); + var2 = (var2 >> 2) + ((int32_t)dev->calib.par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * ((int32_t)dev->calib.par_p3 << 5)) >> 3) + + (((int32_t)dev->calib.par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * (int32_t)dev->calib.par_p1) >> 15; + pressure_comp = 1048576 - pres_adc; + pressure_comp = (int32_t)((pressure_comp - (var2 >> 12)) * ((uint32_t)3125)); + if (pressure_comp >= pres_ovf_check) + { + pressure_comp = ((pressure_comp / var1) << 1); + } + else + { + pressure_comp = ((pressure_comp << 1) / var1); + } + + var1 = ((int32_t)dev->calib.par_p9 * (int32_t)(((pressure_comp >> 3) * (pressure_comp >> 3)) >> 13)) >> 12; + var2 = ((int32_t)(pressure_comp >> 2) * (int32_t)dev->calib.par_p8) >> 13; + var3 = + ((int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * + (int32_t)dev->calib.par_p10) >> 17; + pressure_comp = (int32_t)(pressure_comp) + ((var1 + var2 + var3 + ((int32_t)dev->calib.par_p7 << 7)) >> 4); + + /*lint -restore */ + return (uint32_t)pressure_comp; +} + +/* This internal API is used to calculate the humidity in integer */ +static uint32_t calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev) +{ + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t var6; + int32_t temp_scaled; + int32_t calc_hum; + + /*lint -save -e702 -e704 */ + temp_scaled = (((int32_t)dev->calib.t_fine * 5) + 128) >> 8; + var1 = (int32_t)(hum_adc - ((int32_t)((int32_t)dev->calib.par_h1 * 16))) - + (((temp_scaled * (int32_t)dev->calib.par_h3) / ((int32_t)100)) >> 1); + var2 = + ((int32_t)dev->calib.par_h2 * + (((temp_scaled * (int32_t)dev->calib.par_h4) / ((int32_t)100)) + + (((temp_scaled * ((temp_scaled * (int32_t)dev->calib.par_h5) / ((int32_t)100))) >> 6) / ((int32_t)100)) + + (int32_t)(1 << 14))) >> 10; + var3 = var1 * var2; + var4 = (int32_t)dev->calib.par_h6 << 7; + var4 = ((var4) + ((temp_scaled * (int32_t)dev->calib.par_h7) / ((int32_t)100))) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + calc_hum = (((var3 + var6) >> 10) * ((int32_t)1000)) >> 12; + if (calc_hum > 100000) /* Cap at 100%rH */ + { + calc_hum = 100000; + } + else if (calc_hum < 0) + { + calc_hum = 0; + } + + /*lint -restore */ + return (uint32_t)calc_hum; +} + +/* This internal API is used to calculate the gas resistance low */ +static uint32_t calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev) +{ + int64_t var1; + uint64_t var2; + int64_t var3; + uint32_t calc_gas_res; + uint32_t lookup_table1[16] = { + UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2130303777), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2143188679), UINT32_C(2136746228), UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), + UINT32_C(2147483647) + }; + uint32_t lookup_table2[16] = { + UINT32_C(4096000000), UINT32_C(2048000000), UINT32_C(1024000000), UINT32_C(512000000), UINT32_C(255744255), + UINT32_C(127110228), UINT32_C(64000000), UINT32_C(32258064), UINT32_C(16016016), UINT32_C(8000000), UINT32_C( + 4000000), UINT32_C(2000000), UINT32_C(1000000), UINT32_C(500000), UINT32_C(250000), UINT32_C(125000) + }; + + /*lint -save -e704 */ + var1 = (int64_t)((1340 + (5 * (int64_t)dev->calib.range_sw_err)) * ((int64_t)lookup_table1[gas_range])) >> 16; + var2 = (((int64_t)((int64_t)gas_res_adc << 15) - (int64_t)(16777216)) + var1); + var3 = (((int64_t)lookup_table2[gas_range] * (int64_t)var1) >> 9); + calc_gas_res = (uint32_t)((var3 + ((int64_t)var2 >> 1)) / (int64_t)var2); + + /*lint -restore */ + return calc_gas_res; +} + +/* This internal API is used to calculate the gas resistance */ +static uint32_t calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range) +{ + uint32_t calc_gas_res; + uint32_t var1 = UINT32_C(262144) >> gas_range; + int32_t var2 = (int32_t)gas_res_adc - INT32_C(512); + + var2 *= INT32_C(3); + var2 = INT32_C(4096) + var2; + + /* multiplying 10000 then dividing then multiplying by 100 instead of multiplying by 1000000 to prevent overflow */ + calc_gas_res = (UINT32_C(10000) * var1) / (uint32_t)var2; + calc_gas_res = calc_gas_res * 100; + + return calc_gas_res; +} + +/* This internal API is used to calculate the heater resistance value using float */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev) +{ + uint8_t heatr_res; + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t heatr_res_x100; + + if (temp > 400) /* Cap temperature */ + { + temp = 400; + } + + var1 = (((int32_t)dev->amb_temp * dev->calib.par_gh3) / 1000) * 256; + var2 = (dev->calib.par_gh1 + 784) * (((((dev->calib.par_gh2 + 154009) * temp * 5) / 100) + 3276800) / 10); + var3 = var1 + (var2 / 2); + var4 = (var3 / (dev->calib.res_heat_range + 4)); + var5 = (131 * dev->calib.res_heat_val) + 65536; + heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34); + heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100); + + return heatr_res; +} + +#else + +/* @brief This internal API is used to calculate the temperature value. */ +static float calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev) +{ + float var1; + float var2; + float calc_temp; + + /* calculate var1 data */ + var1 = ((((float)temp_adc / 16384.0f) - ((float)dev->calib.par_t1 / 1024.0f)) * ((float)dev->calib.par_t2)); + + /* calculate var2 data */ + var2 = + (((((float)temp_adc / 131072.0f) - ((float)dev->calib.par_t1 / 8192.0f)) * + (((float)temp_adc / 131072.0f) - ((float)dev->calib.par_t1 / 8192.0f))) * ((float)dev->calib.par_t3 * 16.0f)); + + /* t_fine value*/ + dev->calib.t_fine = (var1 + var2); + + /* compensated temperature data*/ + calc_temp = ((dev->calib.t_fine) / 5120.0f); + + return calc_temp; +} + +/* @brief This internal API is used to calculate the pressure value. */ +static float calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev) +{ + float var1; + float var2; + float var3; + float calc_pres; + + var1 = (((float)dev->calib.t_fine / 2.0f) - 64000.0f); + var2 = var1 * var1 * (((float)dev->calib.par_p6) / (131072.0f)); + var2 = var2 + (var1 * ((float)dev->calib.par_p5) * 2.0f); + var2 = (var2 / 4.0f) + (((float)dev->calib.par_p4) * 65536.0f); + var1 = (((((float)dev->calib.par_p3 * var1 * var1) / 16384.0f) + ((float)dev->calib.par_p2 * var1)) / 524288.0f); + var1 = ((1.0f + (var1 / 32768.0f)) * ((float)dev->calib.par_p1)); + calc_pres = (1048576.0f - ((float)pres_adc)); + + /* Avoid exception caused by division by zero */ + if ((int)var1 != 0) + { + calc_pres = (((calc_pres - (var2 / 4096.0f)) * 6250.0f) / var1); + var1 = (((float)dev->calib.par_p9) * calc_pres * calc_pres) / 2147483648.0f; + var2 = calc_pres * (((float)dev->calib.par_p8) / 32768.0f); + var3 = ((calc_pres / 256.0f) * (calc_pres / 256.0f) * (calc_pres / 256.0f) * (dev->calib.par_p10 / 131072.0f)); + calc_pres = (calc_pres + (var1 + var2 + var3 + ((float)dev->calib.par_p7 * 128.0f)) / 16.0f); + } + else + { + calc_pres = 0; + } + + return calc_pres; +} + +/* This internal API is used to calculate the humidity in integer */ +static float calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev) +{ + float calc_hum; + float var1; + float var2; + float var3; + float var4; + float temp_comp; + + /* compensated temperature data*/ + temp_comp = ((dev->calib.t_fine) / 5120.0f); + var1 = (float)((float)hum_adc) - + (((float)dev->calib.par_h1 * 16.0f) + (((float)dev->calib.par_h3 / 2.0f) * temp_comp)); + var2 = var1 * + ((float)(((float)dev->calib.par_h2 / 262144.0f) * + (1.0f + (((float)dev->calib.par_h4 / 16384.0f) * temp_comp) + + (((float)dev->calib.par_h5 / 1048576.0f) * temp_comp * temp_comp)))); + var3 = (float)dev->calib.par_h6 / 16384.0f; + var4 = (float)dev->calib.par_h7 / 2097152.0f; + calc_hum = var2 + ((var3 + (var4 * temp_comp)) * var2 * var2); + if (calc_hum > 100.0f) + { + calc_hum = 100.0f; + } + else if (calc_hum < 0.0f) + { + calc_hum = 0.0f; + } + + return calc_hum; +} + +/* This internal API is used to calculate the gas resistance low value in float */ +static float calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev) +{ + float calc_gas_res; + float var1; + float var2; + float var3; + float gas_res_f = gas_res_adc; + float gas_range_f = (1U << gas_range); /*lint !e790 / Suspicious truncation, integral to float */ + const float lookup_k1_range[16] = { + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, -0.8f, 0.0f, 0.0f, -0.2f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f + }; + const float lookup_k2_range[16] = { + 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.7f, 0.0f, -0.8f, -0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + }; + + var1 = (1340.0f + (5.0f * dev->calib.range_sw_err)); + var2 = (var1) * (1.0f + lookup_k1_range[gas_range] / 100.0f); + var3 = 1.0f + (lookup_k2_range[gas_range] / 100.0f); + calc_gas_res = 1.0f / (float)(var3 * (0.000000125f) * gas_range_f * (((gas_res_f - 512.0f) / var2) + 1.0f)); + + return calc_gas_res; +} + +/* This internal API is used to calculate the gas resistance value in float */ +static float calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range) +{ + float calc_gas_res; + uint32_t var1 = UINT32_C(262144) >> gas_range; + int32_t var2 = (int32_t)gas_res_adc - INT32_C(512); + + var2 *= INT32_C(3); + var2 = INT32_C(4096) + var2; + + calc_gas_res = 1000000.0f * (float)var1 / (float)var2; + + return calc_gas_res; +} + +/* This internal API is used to calculate the heater resistance value */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev) +{ + float var1; + float var2; + float var3; + float var4; + float var5; + uint8_t res_heat; + + if (temp > 400) /* Cap temperature */ + { + temp = 400; + } + + var1 = (((float)dev->calib.par_gh1 / (16.0f)) + 49.0f); + var2 = ((((float)dev->calib.par_gh2 / (32768.0f)) * (0.0005f)) + 0.00235f); + var3 = ((float)dev->calib.par_gh3 / (1024.0f)); + var4 = (var1 * (1.0f + (var2 * (float)temp))); + var5 = (var4 + (var3 * (float)dev->amb_temp)); + res_heat = + (uint8_t)(3.4f * + ((var5 * (4 / (4 + (float)dev->calib.res_heat_range)) * + (1 / (1 + ((float)dev->calib.res_heat_val * 0.002f)))) - + 25)); + + return res_heat; +} + +#endif + +/* This internal API is used to calculate the gas wait */ +static uint8_t calc_gas_wait(uint16_t dur) +{ + uint8_t factor = 0; + uint8_t durval; + + if (dur >= 0xfc0) + { + durval = 0xff; /* Max duration*/ + } + else + { + while (dur > 0x3F) + { + dur = dur / 4; + factor += 1; + } + + durval = (uint8_t)(dur + (factor * 64)); + } + + return durval; +} + +/* This internal API is used to read a single data of the sensor */ +static int8_t read_field_data(uint8_t index, struct bme68x_data *data, struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + uint8_t buff[BME68X_LEN_FIELD] = { 0 }; + uint8_t gas_range_l, gas_range_h; + uint32_t adc_temp; + uint32_t adc_pres; + uint16_t adc_hum; + uint16_t adc_gas_res_low, adc_gas_res_high; + uint8_t tries = 5; + + while ((tries) && (rslt == BME68X_OK)) + { + rslt = bme68x_get_regs(((uint8_t)(BME68X_REG_FIELD0 + (index * BME68X_LEN_FIELD_OFFSET))), + buff, + (uint16_t)BME68X_LEN_FIELD, + dev); + if (!data) + { + rslt = BME68X_E_NULL_PTR; + break; + } + + data->status = buff[0] & BME68X_NEW_DATA_MSK; + data->gas_index = buff[0] & BME68X_GAS_INDEX_MSK; + data->meas_index = buff[1]; + + /* read the raw data from the sensor */ + adc_pres = (uint32_t)(((uint32_t)buff[2] * 4096) | ((uint32_t)buff[3] * 16) | ((uint32_t)buff[4] / 16)); + adc_temp = (uint32_t)(((uint32_t)buff[5] * 4096) | ((uint32_t)buff[6] * 16) | ((uint32_t)buff[7] / 16)); + adc_hum = (uint16_t)(((uint32_t)buff[8] * 256) | (uint32_t)buff[9]); + adc_gas_res_low = (uint16_t)((uint32_t)buff[13] * 4 | (((uint32_t)buff[14]) / 64)); + adc_gas_res_high = (uint16_t)((uint32_t)buff[15] * 4 | (((uint32_t)buff[16]) / 64)); + gas_range_l = buff[14] & BME68X_GAS_RANGE_MSK; + gas_range_h = buff[16] & BME68X_GAS_RANGE_MSK; + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data->status |= buff[16] & BME68X_GASM_VALID_MSK; + data->status |= buff[16] & BME68X_HEAT_STAB_MSK; + } + else + { + data->status |= buff[14] & BME68X_GASM_VALID_MSK; + data->status |= buff[14] & BME68X_HEAT_STAB_MSK; + } + + if ((data->status & BME68X_NEW_DATA_MSK) && (rslt == BME68X_OK)) + { + rslt = bme68x_get_regs(BME68X_REG_RES_HEAT0 + data->gas_index, &data->res_heat, 1, dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_IDAC_HEAT0 + data->gas_index, &data->idac, 1, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_GAS_WAIT0 + data->gas_index, &data->gas_wait, 1, dev); + } + + if (rslt == BME68X_OK) + { + data->temperature = calc_temperature(adc_temp, dev); + data->pressure = calc_pressure(adc_pres, dev); + data->humidity = calc_humidity(adc_hum, dev); + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data->gas_resistance = calc_gas_resistance_high(adc_gas_res_high, gas_range_h); + } + else + { + data->gas_resistance = calc_gas_resistance_low(adc_gas_res_low, gas_range_l, dev); + } + + break; + } + } + + if (rslt == BME68X_OK) + { + dev->delay_us(BME68X_PERIOD_POLL, dev->intf_ptr); + } + + tries--; + } + + return rslt; +} + +/* This internal API is used to read all data fields of the sensor */ +static int8_t read_all_field_data(struct bme68x_data * const data[], struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + uint8_t buff[BME68X_LEN_FIELD * 3] = { 0 }; + uint8_t gas_range_l, gas_range_h; + uint32_t adc_temp; + uint32_t adc_pres; + uint16_t adc_hum; + uint16_t adc_gas_res_low, adc_gas_res_high; + uint8_t off; + uint8_t set_val[30] = { 0 }; /* idac, res_heat, gas_wait */ + uint8_t i; + + if (!data[0] && !data[1] && !data[2]) + { + rslt = BME68X_E_NULL_PTR; + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_FIELD0, buff, (uint32_t) BME68X_LEN_FIELD * 3, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_IDAC_HEAT0, set_val, 30, dev); + } + + for (i = 0; ((i < 3) && (rslt == BME68X_OK)); i++) + { + off = (uint8_t)(i * BME68X_LEN_FIELD); + data[i]->status = buff[off] & BME68X_NEW_DATA_MSK; + data[i]->gas_index = buff[off] & BME68X_GAS_INDEX_MSK; + data[i]->meas_index = buff[off + 1]; + + /* read the raw data from the sensor */ + adc_pres = + (uint32_t) (((uint32_t) buff[off + 2] * 4096) | ((uint32_t) buff[off + 3] * 16) | + ((uint32_t) buff[off + 4] / 16)); + adc_temp = + (uint32_t) (((uint32_t) buff[off + 5] * 4096) | ((uint32_t) buff[off + 6] * 16) | + ((uint32_t) buff[off + 7] / 16)); + adc_hum = (uint16_t) (((uint32_t) buff[off + 8] * 256) | (uint32_t) buff[off + 9]); + adc_gas_res_low = (uint16_t) ((uint32_t) buff[off + 13] * 4 | (((uint32_t) buff[off + 14]) / 64)); + adc_gas_res_high = (uint16_t) ((uint32_t) buff[off + 15] * 4 | (((uint32_t) buff[off + 16]) / 64)); + gas_range_l = buff[off + 14] & BME68X_GAS_RANGE_MSK; + gas_range_h = buff[off + 16] & BME68X_GAS_RANGE_MSK; + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data[i]->status |= buff[off + 16] & BME68X_GASM_VALID_MSK; + data[i]->status |= buff[off + 16] & BME68X_HEAT_STAB_MSK; + } + else + { + data[i]->status |= buff[off + 14] & BME68X_GASM_VALID_MSK; + data[i]->status |= buff[off + 14] & BME68X_HEAT_STAB_MSK; + } + + data[i]->idac = set_val[data[i]->gas_index]; + data[i]->res_heat = set_val[10 + data[i]->gas_index]; + data[i]->gas_wait = set_val[20 + data[i]->gas_index]; + data[i]->temperature = calc_temperature(adc_temp, dev); + data[i]->pressure = calc_pressure(adc_pres, dev); + data[i]->humidity = calc_humidity(adc_hum, dev); + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data[i]->gas_resistance = calc_gas_resistance_high(adc_gas_res_high, gas_range_h); + } + else + { + data[i]->gas_resistance = calc_gas_resistance_low(adc_gas_res_low, gas_range_l, dev); + } + } + + return rslt; +} + +/* This internal API is used to switch between SPI memory pages */ +static int8_t set_mem_page(uint8_t reg_addr, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg; + uint8_t mem_page; + + /* Check for null pointers in the device structure*/ + rslt = null_ptr_check(dev); + if (rslt == BME68X_OK) + { + if (reg_addr > 0x7f) + { + mem_page = BME68X_MEM_PAGE1; + } + else + { + mem_page = BME68X_MEM_PAGE0; + } + + if (mem_page != dev->mem_page) + { + dev->mem_page = mem_page; + dev->intf_rslt = dev->read(BME68X_REG_MEM_PAGE | BME68X_SPI_RD_MSK, ®, 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + + if (rslt == BME68X_OK) + { + reg = reg & (~BME68X_MEM_PAGE_MSK); + reg = reg | (dev->mem_page & BME68X_MEM_PAGE_MSK); + dev->intf_rslt = dev->write(BME68X_REG_MEM_PAGE & BME68X_SPI_WR_MSK, ®, 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + } + } + } + + return rslt; +} + +/* This internal API is used to get the current SPI memory page */ +static int8_t get_mem_page(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if (rslt == BME68X_OK) + { + dev->intf_rslt = dev->read(BME68X_REG_MEM_PAGE | BME68X_SPI_RD_MSK, ®, 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + else + { + dev->mem_page = reg & BME68X_MEM_PAGE_MSK; + } + } + + return rslt; +} + +/* This internal API is used to limit the max value of a parameter */ +static int8_t boundary_check(uint8_t *value, uint8_t max, struct bme68x_dev *dev) +{ + int8_t rslt; + + rslt = null_ptr_check(dev); + if ((value != NULL) && (rslt == BME68X_OK)) + { + /* Check if value is above maximum value */ + if (*value > max) + { + /* Auto correct the invalid value to maximum value */ + *value = max; + dev->info_msg |= BME68X_I_PARAM_CORR; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* This internal API is used to check the bme68x_dev for null pointers */ +static int8_t null_ptr_check(const struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + + if ((dev == NULL) || (dev->read == NULL) || (dev->write == NULL) || (dev->delay_us == NULL)) + { + /* Device structure pointer is not valid */ + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* This internal API is used to set heater configurations */ +static int8_t set_conf(const struct bme68x_heatr_conf *conf, uint8_t op_mode, uint8_t *nb_conv, struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + uint8_t i; + uint8_t shared_dur; + uint8_t write_len = 0; + uint8_t heater_dur_shared_addr = BME68X_REG_SHD_HEATR_DUR; + uint8_t rh_reg_addr[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t rh_reg_data[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t gw_reg_addr[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t gw_reg_data[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + switch (op_mode) + { + case BME68X_FORCED_MODE: + rh_reg_addr[0] = BME68X_REG_RES_HEAT0; + rh_reg_data[0] = calc_res_heat(conf->heatr_temp, dev); + gw_reg_addr[0] = BME68X_REG_GAS_WAIT0; + gw_reg_data[0] = calc_gas_wait(conf->heatr_dur); + (*nb_conv) = 0; + write_len = 1; + break; + case BME68X_SEQUENTIAL_MODE: + if ((!conf->heatr_dur_prof) || (!conf->heatr_temp_prof)) + { + rslt = BME68X_E_NULL_PTR; + break; + } + + for (i = 0; i < conf->profile_len; i++) + { + rh_reg_addr[i] = BME68X_REG_RES_HEAT0 + i; + rh_reg_data[i] = calc_res_heat(conf->heatr_temp_prof[i], dev); + gw_reg_addr[i] = BME68X_REG_GAS_WAIT0 + i; + gw_reg_data[i] = calc_gas_wait(conf->heatr_dur_prof[i]); + } + + (*nb_conv) = conf->profile_len; + write_len = conf->profile_len; + break; + case BME68X_PARALLEL_MODE: + if ((!conf->heatr_dur_prof) || (!conf->heatr_temp_prof)) + { + rslt = BME68X_E_NULL_PTR; + break; + } + + if (conf->shared_heatr_dur == 0) + { + rslt = BME68X_W_DEFINE_SHD_HEATR_DUR; + } + + for (i = 0; i < conf->profile_len; i++) + { + rh_reg_addr[i] = BME68X_REG_RES_HEAT0 + i; + rh_reg_data[i] = calc_res_heat(conf->heatr_temp_prof[i], dev); + gw_reg_addr[i] = BME68X_REG_GAS_WAIT0 + i; + gw_reg_data[i] = (uint8_t) conf->heatr_dur_prof[i]; + } + + (*nb_conv) = conf->profile_len; + write_len = conf->profile_len; + shared_dur = calc_heatr_dur_shared(conf->shared_heatr_dur); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(&heater_dur_shared_addr, &shared_dur, 1, dev); + } + + break; + default: + rslt = BME68X_W_DEFINE_OP_MODE; + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(rh_reg_addr, rh_reg_data, write_len, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(gw_reg_addr, gw_reg_data, write_len, dev); + } + + return rslt; +} + +/* This internal API is used to calculate the register value for + * shared heater duration */ +static uint8_t calc_heatr_dur_shared(uint16_t dur) +{ + uint8_t factor = 0; + uint8_t heatdurval; + + if (dur >= 0x783) + { + heatdurval = 0xff; /* Max duration */ + } + else + { + /* Step size of 0.477ms */ + dur = (uint16_t)(((uint32_t)dur * 1000) / 477); + while (dur > 0x3F) + { + dur = dur >> 2; + factor += 1; + } + + heatdurval = (uint8_t)(dur + (factor * 64)); + } + + return heatdurval; +} + +/* This internal API is used sort the sensor data */ +static void sort_sensor_data(uint8_t low_index, uint8_t high_index, struct bme68x_data *field[]) +{ + int16_t meas_index1; + int16_t meas_index2; + + meas_index1 = (int16_t)field[low_index]->meas_index; + meas_index2 = (int16_t)field[high_index]->meas_index; + if ((field[low_index]->status & BME68X_NEW_DATA_MSK) && (field[high_index]->status & BME68X_NEW_DATA_MSK)) + { + int16_t diff = meas_index2 - meas_index1; + if (((diff > -3) && (diff < 0)) || (diff > 2)) + { + swap_fields(low_index, high_index, field); + } + } + else if (field[high_index]->status & BME68X_NEW_DATA_MSK) + { + swap_fields(low_index, high_index, field); + } + + /* Sorting field data + * + * The 3 fields are filled in a fixed order with data in an incrementing + * 8-bit sub-measurement index which looks like + * Field index | Sub-meas index + * 0 | 0 + * 1 | 1 + * 2 | 2 + * 0 | 3 + * 1 | 4 + * 2 | 5 + * ... + * 0 | 252 + * 1 | 253 + * 2 | 254 + * 0 | 255 + * 1 | 0 + * 2 | 1 + * + * The fields are sorted in a way so as to always deal with only a snapshot + * of comparing 2 fields at a time. The order being + * field0 & field1 + * field0 & field2 + * field1 & field2 + * Here the oldest data should be in field0 while the newest is in field2. + * In the following documentation, field0's position would referred to as + * the lowest and field2 as the highest. + * + * In order to sort we have to consider the following cases, + * + * Case A: No fields have new data + * Then do not sort, as this data has already been read. + * + * Case B: Higher field has new data + * Then the new field get's the lowest position. + * + * Case C: Both fields have new data + * We have to put the oldest sample in the lowest position. Since the + * sub-meas index contains in essence the age of the sample, we calculate + * the difference between the higher field and the lower field. + * Here we have 3 sub-cases, + * Case 1: Regular read without overwrite + * Field index | Sub-meas index + * 0 | 3 + * 1 | 4 + * + * Field index | Sub-meas index + * 0 | 3 + * 2 | 5 + * + * The difference is always <= 2. There is no need to swap as the + * oldest sample is already in the lowest position. + * + * Case 2: Regular read with an overflow and without an overwrite + * Field index | Sub-meas index + * 0 | 255 + * 1 | 0 + * + * Field index | Sub-meas index + * 0 | 254 + * 2 | 0 + * + * The difference is always <= -3. There is no need to swap as the + * oldest sample is already in the lowest position. + * + * Case 3: Regular read with overwrite + * Field index | Sub-meas index + * 0 | 6 + * 1 | 4 + * + * Field index | Sub-meas index + * 0 | 6 + * 2 | 5 + * + * The difference is always > -3. There is a need to swap as the + * oldest sample is not in the lowest position. + * + * Case 4: Regular read with overwrite and overflow + * Field index | Sub-meas index + * 0 | 0 + * 1 | 254 + * + * Field index | Sub-meas index + * 0 | 0 + * 2 | 255 + * + * The difference is always > 2. There is a need to swap as the + * oldest sample is not in the lowest position. + * + * To summarize, we have to swap when + * - The higher field has new data and the lower field does not. + * - If both fields have new data, then the difference of sub-meas index + * between the higher field and the lower field creates the + * following condition for swapping. + * - (diff > -3) && (diff < 0), combination of cases 1, 2, and 3. + * - diff > 2, case 4. + * + * Here the limits of -3 and 2 derive from the fact that there are 3 fields. + * These values decrease or increase respectively if the number of fields increases. + */ +} + +/* This internal API is used sort the sensor data */ +static void swap_fields(uint8_t index1, uint8_t index2, struct bme68x_data *field[]) +{ + struct bme68x_data *temp; + + temp = field[index1]; + field[index1] = field[index2]; + field[index2] = temp; +} + +/* This Function is to analyze the sensor data */ +static int8_t analyze_sensor_data(const struct bme68x_data *data, uint8_t n_meas) +{ + int8_t rslt = BME68X_OK; + uint8_t self_test_failed = 0, i; + uint32_t cent_res = 0; + + if ((data[0].temperature < BME68X_MIN_TEMPERATURE) || (data[0].temperature > BME68X_MAX_TEMPERATURE)) + { + self_test_failed++; + } + + if ((data[0].pressure < BME68X_MIN_PRESSURE) || (data[0].pressure > BME68X_MAX_PRESSURE)) + { + self_test_failed++; + } + + if ((data[0].humidity < BME68X_MIN_HUMIDITY) || (data[0].humidity > BME68X_MAX_HUMIDITY)) + { + self_test_failed++; + } + + for (i = 0; i < n_meas; i++) /* Every gas measurement should be valid */ + { + if (!(data[i].status & BME68X_GASM_VALID_MSK)) + { + self_test_failed++; + } + } + + if (n_meas >= 6) + { + cent_res = (uint32_t)((5 * (data[3].gas_resistance + data[5].gas_resistance)) / (2 * data[4].gas_resistance)); + } + + if (cent_res < 6) + { + self_test_failed++; + } + + if (self_test_failed) + { + rslt = BME68X_E_SELF_TEST; + } + + return rslt; +} + +/* This internal API is used to read the calibration coefficients */ +static int8_t get_calib_data(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t coeff_array[BME68X_LEN_COEFF_ALL]; + + rslt = bme68x_get_regs(BME68X_REG_COEFF1, coeff_array, BME68X_LEN_COEFF1, dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_COEFF2, &coeff_array[BME68X_LEN_COEFF1], BME68X_LEN_COEFF2, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_COEFF3, + &coeff_array[BME68X_LEN_COEFF1 + BME68X_LEN_COEFF2], + BME68X_LEN_COEFF3, + dev); + } + + if (rslt == BME68X_OK) + { + /* Temperature related coefficients */ + dev->calib.par_t1 = + (uint16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_T1_MSB], coeff_array[BME68X_IDX_T1_LSB])); + dev->calib.par_t2 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_T2_MSB], coeff_array[BME68X_IDX_T2_LSB])); + dev->calib.par_t3 = (int8_t)(coeff_array[BME68X_IDX_T3]); + + /* Pressure related coefficients */ + dev->calib.par_p1 = + (uint16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P1_MSB], coeff_array[BME68X_IDX_P1_LSB])); + dev->calib.par_p2 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P2_MSB], coeff_array[BME68X_IDX_P2_LSB])); + dev->calib.par_p3 = (int8_t)coeff_array[BME68X_IDX_P3]; + dev->calib.par_p4 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P4_MSB], coeff_array[BME68X_IDX_P4_LSB])); + dev->calib.par_p5 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P5_MSB], coeff_array[BME68X_IDX_P5_LSB])); + dev->calib.par_p6 = (int8_t)(coeff_array[BME68X_IDX_P6]); + dev->calib.par_p7 = (int8_t)(coeff_array[BME68X_IDX_P7]); + dev->calib.par_p8 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P8_MSB], coeff_array[BME68X_IDX_P8_LSB])); + dev->calib.par_p9 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P9_MSB], coeff_array[BME68X_IDX_P9_LSB])); + dev->calib.par_p10 = (uint8_t)(coeff_array[BME68X_IDX_P10]); + + /* Humidity related coefficients */ + dev->calib.par_h1 = + (uint16_t)(((uint16_t)coeff_array[BME68X_IDX_H1_MSB] << 4) | + (coeff_array[BME68X_IDX_H1_LSB] & BME68X_BIT_H1_DATA_MSK)); + dev->calib.par_h2 = + (uint16_t)(((uint16_t)coeff_array[BME68X_IDX_H2_MSB] << 4) | ((coeff_array[BME68X_IDX_H2_LSB]) >> 4)); + dev->calib.par_h3 = (int8_t)coeff_array[BME68X_IDX_H3]; + dev->calib.par_h4 = (int8_t)coeff_array[BME68X_IDX_H4]; + dev->calib.par_h5 = (int8_t)coeff_array[BME68X_IDX_H5]; + dev->calib.par_h6 = (uint8_t)coeff_array[BME68X_IDX_H6]; + dev->calib.par_h7 = (int8_t)coeff_array[BME68X_IDX_H7]; + + /* Gas heater related coefficients */ + dev->calib.par_gh1 = (int8_t)coeff_array[BME68X_IDX_GH1]; + dev->calib.par_gh2 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_GH2_MSB], coeff_array[BME68X_IDX_GH2_LSB])); + dev->calib.par_gh3 = (int8_t)coeff_array[BME68X_IDX_GH3]; + + /* Other coefficients */ + dev->calib.res_heat_range = ((coeff_array[BME68X_IDX_RES_HEAT_RANGE] & BME68X_RHRANGE_MSK) / 16); + dev->calib.res_heat_val = (int8_t)coeff_array[BME68X_IDX_RES_HEAT_VAL]; + dev->calib.range_sw_err = ((int8_t)(coeff_array[BME68X_IDX_RANGE_SW_ERR] & BME68X_RSERROR_MSK)) / 16; + } + + return rslt; +} + +/* This internal API is used to read variant ID information from the register */ +static int8_t read_variant_id(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg_data = 0; + + /* Read variant ID information register */ + rslt = bme68x_get_regs(BME68X_REG_VARIANT_ID, ®_data, 1, dev); + + if (rslt == BME68X_OK) + { + dev->variant_id = reg_data; + } + + return rslt; +} diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.h b/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.h new file mode 100644 index 0000000..e6d4828 --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x.h @@ -0,0 +1,323 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* 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. +* +* @file bme68x.h +* @date 2021-11-09 +* @version v4.4.7 +* +*/ + +/*! + * @defgroup bme68x BME68X + * @brief Product Overview + * and Sensor API Source Code + */ + +#ifndef BME68X_H_ +#define BME68X_H_ + +#include "bme68x_defs.h" + +/* CPP guard */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \ingroup bme68x + * \defgroup bme68xApiInit Initialization + * @brief Initialize the sensor and device structure + */ + +/*! + * \ingroup bme68xApiInit + * \page bme68x_api_bme68x_init bme68x_init + * \code + * int8_t bme68x_init(struct bme68x_dev *dev); + * \endcode + * @details This API reads the chip-id of the sensor which is the first step to + * verify the sensor and also calibrates the sensor + * As this API is the entry point, call this API before using other APIs. + * + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_init(struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiRegister Registers + * @brief Generic API for accessing sensor registers + */ + +/*! + * \ingroup bme68xApiRegister + * \page bme68x_api_bme68x_set_regs bme68x_set_regs + * \code + * int8_t bme68x_set_regs(const uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) + * \endcode + * @details This API writes the given data to the register address of the sensor + * + * @param[in] reg_addr : Register addresses to where the data is to be written + * @param[in] reg_data : Pointer to data buffer which is to be written + * in the reg_addr of sensor. + * @param[in] len : No of bytes of data to write + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_regs(const uint8_t *reg_addr, const uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiRegister + * \page bme68x_api_bme68x_get_regs bme68x_get_regs + * \code + * int8_t bme68x_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) + * \endcode + * @details This API reads the data from the given register address of sensor. + * + * @param[in] reg_addr : Register address from where the data to be read + * @param[out] reg_data : Pointer to data buffer to store the read data. + * @param[in] len : No of bytes of data to be read. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiSystem System + * @brief API that performs system-level operations + */ + +/*! + * \ingroup bme68xApiSystem + * \page bme68x_api_bme68x_soft_reset bme68x_soft_reset + * \code + * int8_t bme68x_soft_reset(struct bme68x_dev *dev); + * \endcode + * @details This API soft-resets the sensor. + * + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_soft_reset(struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiOm Operation mode + * @brief API to configure operation mode + */ + +/*! + * \ingroup bme68xApiOm + * \page bme68x_api_bme68x_set_op_mode bme68x_set_op_mode + * \code + * int8_t bme68x_set_op_mode(const uint8_t op_mode, struct bme68x_dev *dev); + * \endcode + * @details This API is used to set the operation mode of the sensor + * @param[in] op_mode : Desired operation mode. + * @param[in] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_op_mode(const uint8_t op_mode, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiOm + * \page bme68x_api_bme68x_get_op_mode bme68x_get_op_mode + * \code + * int8_t bme68x_get_op_mode(uint8_t *op_mode, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the operation mode of the sensor. + * + * @param[out] op_mode : Desired operation mode. + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_op_mode(uint8_t *op_mode, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_get_meas_dur bme68x_get_meas_dur + * \code + * uint32_t bme68x_get_meas_dur(const uint8_t op_mode, struct bme68x_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the remaining duration that can be used for heating. + * + * @param[in] op_mode : Desired operation mode. + * @param[in] conf : Desired sensor configuration. + * @param[in] dev : Structure instance of bme68x_dev + * + * @return Measurement duration calculated in microseconds + */ +uint32_t bme68x_get_meas_dur(const uint8_t op_mode, struct bme68x_conf *conf, struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiData Data Read out + * @brief Read our data from the sensor + */ + +/*! + * \ingroup bme68xApiData + * \page bme68x_api_bme68x_get_data bme68x_get_data + * \code + * int8_t bme68x_get_data(uint8_t op_mode, struct bme68x_data *data, uint8_t *n_data, struct bme68x_dev *dev); + * \endcode + * @details This API reads the pressure, temperature and humidity and gas data + * from the sensor, compensates the data and store it in the bme68x_data + * structure instance passed by the user. + * + * @param[in] op_mode : Expected operation mode. + * @param[out] data : Structure instance to hold the data. + * @param[out] n_data : Number of data instances available. + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_data(uint8_t op_mode, struct bme68x_data *data, uint8_t *n_data, struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiConfig Configuration + * @brief Configuration API of sensor + */ + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_set_conf bme68x_set_conf + * \code + * int8_t bme68x_set_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to set the oversampling, filter and odr configuration + * + * @param[in] conf : Desired sensor configuration. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_get_conf bme68x_get_conf + * \code + * int8_t bme68x_get_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the oversampling, filter and odr + * configuration + * + * @param[out] conf : Present sensor configuration. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_set_heatr_conf bme68x_set_heatr_conf + * \code + * int8_t bme68x_set_heatr_conf(uint8_t op_mode, const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to set the gas configuration of the sensor. + * + * @param[in] op_mode : Expected operation mode of the sensor. + * @param[in] conf : Desired heating configuration. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_heatr_conf(uint8_t op_mode, const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_get_heatr_conf bme68x_get_heatr_conf + * \code + * int8_t bme68x_get_heatr_conf(const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the gas configuration of the sensor. + * + * @param[out] conf : Current configurations of the gas sensor. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_heatr_conf(const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiSystem + * \page bme68x_api_bme68x_selftest_check bme68x_selftest_check + * \code + * int8_t bme68x_selftest_check(const struct bme68x_dev *dev); + * \endcode + * @details This API performs Self-test of low gas variant of BME68X + * + * @param[in, out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_selftest_check(const struct bme68x_dev *dev); + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ +#endif /* BME68X_H_ */ diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x_defs.h b/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x_defs.h new file mode 100644 index 0000000..861b2f7 --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/bsec2/bme68x_defs.h @@ -0,0 +1,972 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* 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. +* +* @file bme68x_defs.h +* @date 2021-11-09 +* @version v4.4.7 +* +*/ + +/*! @cond DOXYGEN_SUPRESS */ + +#ifndef BME68X_DEFS_H_ +#define BME68X_DEFS_H_ + +/********************************************************* */ +/*! Header includes */ +/********************************************************* */ +#ifdef __KERNEL__ +#include +#include +#else +#include +#include +#endif + +/********************************************************* */ +/*! Common Macros */ +/********************************************************* */ +#ifdef __KERNEL__ +#if !defined(UINT8_C) && !defined(INT8_C) +#define INT8_C(x) S8_C(x) +#define UINT8_C(x) U8_C(x) +#endif + +#if !defined(UINT16_C) && !defined(INT16_C) +#define INT16_C(x) S16_C(x) +#define UINT16_C(x) U16_C(x) +#endif + +#if !defined(INT32_C) && !defined(UINT32_C) +#define INT32_C(x) S32_C(x) +#define UINT32_C(x) U32_C(x) +#endif + +#if !defined(INT64_C) && !defined(UINT64_C) +#define INT64_C(x) S64_C(x) +#define UINT64_C(x) U64_C(x) +#endif +#endif + +/*! C standard macros */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *) 0) +#endif +#endif + +#ifndef BME68X_DO_NOT_USE_FPU + +/* Comment or un-comment the macro to provide floating point data output */ +#define BME68X_USE_FPU +#endif + +/* Period between two polls (value can be given by user) */ +#ifndef BME68X_PERIOD_POLL +#define BME68X_PERIOD_POLL UINT32_C(10000) +#endif + +/* BME68X unique chip identifier */ +#define BME68X_CHIP_ID UINT8_C(0x61) + +/* Period for a soft reset */ +#define BME68X_PERIOD_RESET UINT32_C(10000) + +/* BME68X lower I2C address */ +#define BME68X_I2C_ADDR_LOW UINT8_C(0x76) + +/* BME68X higher I2C address */ +#define BME68X_I2C_ADDR_HIGH UINT8_C(0x77) + +/* Soft reset command */ +#define BME68X_SOFT_RESET_CMD UINT8_C(0xb6) + +/* Return code definitions */ +/* Success */ +#define BME68X_OK INT8_C(0) + +/* Errors */ +/* Null pointer passed */ +#define BME68X_E_NULL_PTR INT8_C(-1) + +/* Communication failure */ +#define BME68X_E_COM_FAIL INT8_C(-2) + +/* Sensor not found */ +#define BME68X_E_DEV_NOT_FOUND INT8_C(-3) + +/* Incorrect length parameter */ +#define BME68X_E_INVALID_LENGTH INT8_C(-4) + +/* Self test fail error */ +#define BME68X_E_SELF_TEST INT8_C(-5) + +/* Warnings */ +/* Define a valid operation mode */ +#define BME68X_W_DEFINE_OP_MODE INT8_C(1) + +/* No new data was found */ +#define BME68X_W_NO_NEW_DATA INT8_C(2) + +/* Define the shared heating duration */ +#define BME68X_W_DEFINE_SHD_HEATR_DUR INT8_C(3) + +/* Information - only available via bme68x_dev.info_msg */ +#define BME68X_I_PARAM_CORR UINT8_C(1) + +/* Register map addresses in I2C */ +/* Register for 3rd group of coefficients */ +#define BME68X_REG_COEFF3 UINT8_C(0x00) + +/* 0th Field address*/ +#define BME68X_REG_FIELD0 UINT8_C(0x1d) + +/* 0th Current DAC address*/ +#define BME68X_REG_IDAC_HEAT0 UINT8_C(0x50) + +/* 0th Res heat address */ +#define BME68X_REG_RES_HEAT0 UINT8_C(0x5a) + +/* 0th Gas wait address */ +#define BME68X_REG_GAS_WAIT0 UINT8_C(0x64) + +/* Shared heating duration address */ +#define BME68X_REG_SHD_HEATR_DUR UINT8_C(0x6E) + +/* CTRL_GAS_0 address */ +#define BME68X_REG_CTRL_GAS_0 UINT8_C(0x70) + +/* CTRL_GAS_1 address */ +#define BME68X_REG_CTRL_GAS_1 UINT8_C(0x71) + +/* CTRL_HUM address */ +#define BME68X_REG_CTRL_HUM UINT8_C(0x72) + +/* CTRL_MEAS address */ +#define BME68X_REG_CTRL_MEAS UINT8_C(0x74) + +/* CONFIG address */ +#define BME68X_REG_CONFIG UINT8_C(0x75) + +/* MEM_PAGE address */ +#define BME68X_REG_MEM_PAGE UINT8_C(0xf3) + +/* Unique ID address */ +#define BME68X_REG_UNIQUE_ID UINT8_C(0x83) + +/* Register for 1st group of coefficients */ +#define BME68X_REG_COEFF1 UINT8_C(0x8a) + +/* Chip ID address */ +#define BME68X_REG_CHIP_ID UINT8_C(0xd0) + +/* Soft reset address */ +#define BME68X_REG_SOFT_RESET UINT8_C(0xe0) + +/* Register for 2nd group of coefficients */ +#define BME68X_REG_COEFF2 UINT8_C(0xe1) + +/* Variant ID Register */ +#define BME68X_REG_VARIANT_ID UINT8_C(0xF0) + +/* Enable/Disable macros */ + +/* Enable */ +#define BME68X_ENABLE UINT8_C(0x01) + +/* Disable */ +#define BME68X_DISABLE UINT8_C(0x00) + +/* Variant ID macros */ + +/* Low Gas variant */ +#define BME68X_VARIANT_GAS_LOW UINT8_C(0x00) + +/* High Gas variant */ +#define BME68X_VARIANT_GAS_HIGH UINT8_C(0x01) + +/* Oversampling setting macros */ + +/* Switch off measurement */ +#define BME68X_OS_NONE UINT8_C(0) + +/* Perform 1 measurement */ +#define BME68X_OS_1X UINT8_C(1) + +/* Perform 2 measurements */ +#define BME68X_OS_2X UINT8_C(2) + +/* Perform 4 measurements */ +#define BME68X_OS_4X UINT8_C(3) + +/* Perform 8 measurements */ +#define BME68X_OS_8X UINT8_C(4) + +/* Perform 16 measurements */ +#define BME68X_OS_16X UINT8_C(5) + +/* IIR Filter settings */ + +/* Switch off the filter */ +#define BME68X_FILTER_OFF UINT8_C(0) + +/* Filter coefficient of 2 */ +#define BME68X_FILTER_SIZE_1 UINT8_C(1) + +/* Filter coefficient of 4 */ +#define BME68X_FILTER_SIZE_3 UINT8_C(2) + +/* Filter coefficient of 8 */ +#define BME68X_FILTER_SIZE_7 UINT8_C(3) + +/* Filter coefficient of 16 */ +#define BME68X_FILTER_SIZE_15 UINT8_C(4) + +/* Filter coefficient of 32 */ +#define BME68X_FILTER_SIZE_31 UINT8_C(5) + +/* Filter coefficient of 64 */ +#define BME68X_FILTER_SIZE_63 UINT8_C(6) + +/* Filter coefficient of 128 */ +#define BME68X_FILTER_SIZE_127 UINT8_C(7) + +/* ODR/Standby time macros */ + +/* Standby time of 0.59ms */ +#define BME68X_ODR_0_59_MS UINT8_C(0) + +/* Standby time of 62.5ms */ +#define BME68X_ODR_62_5_MS UINT8_C(1) + +/* Standby time of 125ms */ +#define BME68X_ODR_125_MS UINT8_C(2) + +/* Standby time of 250ms */ +#define BME68X_ODR_250_MS UINT8_C(3) + +/* Standby time of 500ms */ +#define BME68X_ODR_500_MS UINT8_C(4) + +/* Standby time of 1s */ +#define BME68X_ODR_1000_MS UINT8_C(5) + +/* Standby time of 10ms */ +#define BME68X_ODR_10_MS UINT8_C(6) + +/* Standby time of 20ms */ +#define BME68X_ODR_20_MS UINT8_C(7) + +/* No standby time */ +#define BME68X_ODR_NONE UINT8_C(8) + +/* Operating mode macros */ + +/* Sleep operation mode */ +#define BME68X_SLEEP_MODE UINT8_C(0) + +/* Forced operation mode */ +#define BME68X_FORCED_MODE UINT8_C(1) + +/* Parallel operation mode */ +#define BME68X_PARALLEL_MODE UINT8_C(2) + +/* Sequential operation mode */ +#define BME68X_SEQUENTIAL_MODE UINT8_C(3) + +/* SPI page macros */ + +/* SPI memory page 0 */ +#define BME68X_MEM_PAGE0 UINT8_C(0x10) + +/* SPI memory page 1 */ +#define BME68X_MEM_PAGE1 UINT8_C(0x00) + +/* Coefficient index macros */ + +/* Length for all coefficients */ +#define BME68X_LEN_COEFF_ALL UINT8_C(42) + +/* Length for 1st group of coefficients */ +#define BME68X_LEN_COEFF1 UINT8_C(23) + +/* Length for 2nd group of coefficients */ +#define BME68X_LEN_COEFF2 UINT8_C(14) + +/* Length for 3rd group of coefficients */ +#define BME68X_LEN_COEFF3 UINT8_C(5) + +/* Length of the field */ +#define BME68X_LEN_FIELD UINT8_C(17) + +/* Length between two fields */ +#define BME68X_LEN_FIELD_OFFSET UINT8_C(17) + +/* Length of the configuration register */ +#define BME68X_LEN_CONFIG UINT8_C(5) + +/* Length of the interleaved buffer */ +#define BME68X_LEN_INTERLEAVE_BUFF UINT8_C(20) + +/* Coefficient index macros */ + +/* Coefficient T2 LSB position */ +#define BME68X_IDX_T2_LSB (0) + +/* Coefficient T2 MSB position */ +#define BME68X_IDX_T2_MSB (1) + +/* Coefficient T3 position */ +#define BME68X_IDX_T3 (2) + +/* Coefficient P1 LSB position */ +#define BME68X_IDX_P1_LSB (4) + +/* Coefficient P1 MSB position */ +#define BME68X_IDX_P1_MSB (5) + +/* Coefficient P2 LSB position */ +#define BME68X_IDX_P2_LSB (6) + +/* Coefficient P2 MSB position */ +#define BME68X_IDX_P2_MSB (7) + +/* Coefficient P3 position */ +#define BME68X_IDX_P3 (8) + +/* Coefficient P4 LSB position */ +#define BME68X_IDX_P4_LSB (10) + +/* Coefficient P4 MSB position */ +#define BME68X_IDX_P4_MSB (11) + +/* Coefficient P5 LSB position */ +#define BME68X_IDX_P5_LSB (12) + +/* Coefficient P5 MSB position */ +#define BME68X_IDX_P5_MSB (13) + +/* Coefficient P7 position */ +#define BME68X_IDX_P7 (14) + +/* Coefficient P6 position */ +#define BME68X_IDX_P6 (15) + +/* Coefficient P8 LSB position */ +#define BME68X_IDX_P8_LSB (18) + +/* Coefficient P8 MSB position */ +#define BME68X_IDX_P8_MSB (19) + +/* Coefficient P9 LSB position */ +#define BME68X_IDX_P9_LSB (20) + +/* Coefficient P9 MSB position */ +#define BME68X_IDX_P9_MSB (21) + +/* Coefficient P10 position */ +#define BME68X_IDX_P10 (22) + +/* Coefficient H2 MSB position */ +#define BME68X_IDX_H2_MSB (23) + +/* Coefficient H2 LSB position */ +#define BME68X_IDX_H2_LSB (24) + +/* Coefficient H1 LSB position */ +#define BME68X_IDX_H1_LSB (24) + +/* Coefficient H1 MSB position */ +#define BME68X_IDX_H1_MSB (25) + +/* Coefficient H3 position */ +#define BME68X_IDX_H3 (26) + +/* Coefficient H4 position */ +#define BME68X_IDX_H4 (27) + +/* Coefficient H5 position */ +#define BME68X_IDX_H5 (28) + +/* Coefficient H6 position */ +#define BME68X_IDX_H6 (29) + +/* Coefficient H7 position */ +#define BME68X_IDX_H7 (30) + +/* Coefficient T1 LSB position */ +#define BME68X_IDX_T1_LSB (31) + +/* Coefficient T1 MSB position */ +#define BME68X_IDX_T1_MSB (32) + +/* Coefficient GH2 LSB position */ +#define BME68X_IDX_GH2_LSB (33) + +/* Coefficient GH2 MSB position */ +#define BME68X_IDX_GH2_MSB (34) + +/* Coefficient GH1 position */ +#define BME68X_IDX_GH1 (35) + +/* Coefficient GH3 position */ +#define BME68X_IDX_GH3 (36) + +/* Coefficient res heat value position */ +#define BME68X_IDX_RES_HEAT_VAL (37) + +/* Coefficient res heat range position */ +#define BME68X_IDX_RES_HEAT_RANGE (39) + +/* Coefficient range switching error position */ +#define BME68X_IDX_RANGE_SW_ERR (41) + +/* Gas measurement macros */ + +/* Disable gas measurement */ +#define BME68X_DISABLE_GAS_MEAS UINT8_C(0x00) + +/* Enable gas measurement low */ +#define BME68X_ENABLE_GAS_MEAS_L UINT8_C(0x01) + +/* Enable gas measurement high */ +#define BME68X_ENABLE_GAS_MEAS_H UINT8_C(0x02) + +/* Heater control macros */ + +/* Enable heater */ +#define BME68X_ENABLE_HEATER UINT8_C(0x00) + +/* Disable heater */ +#define BME68X_DISABLE_HEATER UINT8_C(0x01) + +#ifdef BME68X_USE_FPU + +/* 0 degree Celsius */ +#define BME68X_MIN_TEMPERATURE INT16_C(0) + +/* 60 degree Celsius */ +#define BME68X_MAX_TEMPERATURE INT16_C(60) + +/* 900 hecto Pascals */ +#define BME68X_MIN_PRESSURE UINT32_C(90000) + +/* 1100 hecto Pascals */ +#define BME68X_MAX_PRESSURE UINT32_C(110000) + +/* 20% relative humidity */ +#define BME68X_MIN_HUMIDITY UINT32_C(20) + +/* 80% relative humidity*/ +#define BME68X_MAX_HUMIDITY UINT32_C(80) +#else + +/* 0 degree Celsius */ +#define BME68X_MIN_TEMPERATURE INT16_C(0) + +/* 60 degree Celsius */ +#define BME68X_MAX_TEMPERATURE INT16_C(6000) + +/* 900 hecto Pascals */ +#define BME68X_MIN_PRESSURE UINT32_C(90000) + +/* 1100 hecto Pascals */ +#define BME68X_MAX_PRESSURE UINT32_C(110000) + +/* 20% relative humidity */ +#define BME68X_MIN_HUMIDITY UINT32_C(20000) + +/* 80% relative humidity*/ +#define BME68X_MAX_HUMIDITY UINT32_C(80000) + +#endif + +#define BME68X_HEATR_DUR1 UINT16_C(1000) +#define BME68X_HEATR_DUR2 UINT16_C(2000) +#define BME68X_HEATR_DUR1_DELAY UINT32_C(1000000) +#define BME68X_HEATR_DUR2_DELAY UINT32_C(2000000) +#define BME68X_N_MEAS UINT8_C(6) +#define BME68X_LOW_TEMP UINT8_C(150) +#define BME68X_HIGH_TEMP UINT16_C(350) + +/* Mask macros */ +/* Mask for number of conversions */ +#define BME68X_NBCONV_MSK UINT8_C(0X0f) + +/* Mask for IIR filter */ +#define BME68X_FILTER_MSK UINT8_C(0X1c) + +/* Mask for ODR[3] */ +#define BME68X_ODR3_MSK UINT8_C(0x80) + +/* Mask for ODR[2:0] */ +#define BME68X_ODR20_MSK UINT8_C(0xe0) + +/* Mask for temperature oversampling */ +#define BME68X_OST_MSK UINT8_C(0Xe0) + +/* Mask for pressure oversampling */ +#define BME68X_OSP_MSK UINT8_C(0X1c) + +/* Mask for humidity oversampling */ +#define BME68X_OSH_MSK UINT8_C(0X07) + +/* Mask for heater control */ +#define BME68X_HCTRL_MSK UINT8_C(0x08) + +/* Mask for run gas */ +#define BME68X_RUN_GAS_MSK UINT8_C(0x30) + +/* Mask for operation mode */ +#define BME68X_MODE_MSK UINT8_C(0x03) + +/* Mask for res heat range */ +#define BME68X_RHRANGE_MSK UINT8_C(0x30) + +/* Mask for range switching error */ +#define BME68X_RSERROR_MSK UINT8_C(0xf0) + +/* Mask for new data */ +#define BME68X_NEW_DATA_MSK UINT8_C(0x80) + +/* Mask for gas index */ +#define BME68X_GAS_INDEX_MSK UINT8_C(0x0f) + +/* Mask for gas range */ +#define BME68X_GAS_RANGE_MSK UINT8_C(0x0f) + +/* Mask for gas measurement valid */ +#define BME68X_GASM_VALID_MSK UINT8_C(0x20) + +/* Mask for heater stability */ +#define BME68X_HEAT_STAB_MSK UINT8_C(0x10) + +/* Mask for SPI memory page */ +#define BME68X_MEM_PAGE_MSK UINT8_C(0x10) + +/* Mask for reading a register in SPI */ +#define BME68X_SPI_RD_MSK UINT8_C(0x80) + +/* Mask for writing a register in SPI */ +#define BME68X_SPI_WR_MSK UINT8_C(0x7f) + +/* Mask for the H1 calibration coefficient */ +#define BME68X_BIT_H1_DATA_MSK UINT8_C(0x0f) + +/* Position macros */ + +/* Filter bit position */ +#define BME68X_FILTER_POS UINT8_C(2) + +/* Temperature oversampling bit position */ +#define BME68X_OST_POS UINT8_C(5) + +/* Pressure oversampling bit position */ +#define BME68X_OSP_POS UINT8_C(2) + +/* ODR[3] bit position */ +#define BME68X_ODR3_POS UINT8_C(7) + +/* ODR[2:0] bit position */ +#define BME68X_ODR20_POS UINT8_C(5) + +/* Run gas bit position */ +#define BME68X_RUN_GAS_POS UINT8_C(4) + +/* Heater control bit position */ +#define BME68X_HCTRL_POS UINT8_C(3) + +/* Macro to combine two 8 bit data's to form a 16 bit data */ +#define BME68X_CONCAT_BYTES(msb, lsb) (((uint16_t)msb << 8) | (uint16_t)lsb) + +/* Macro to set bits */ +#define BME68X_SET_BITS(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + ((data << bitname##_POS) & bitname##_MSK)) + +/* Macro to get bits */ +#define BME68X_GET_BITS(reg_data, bitname) ((reg_data & (bitname##_MSK)) >> \ + (bitname##_POS)) + +/* Macro to set bits starting from position 0 */ +#define BME68X_SET_BITS_POS_0(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + (data & bitname##_MSK)) + +/* Macro to get bits starting from position 0 */ +#define BME68X_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) + +/** + * BME68X_INTF_RET_TYPE is the read/write interface return type which can be overwritten by the build system. + * The default is set to int8_t. + */ +#ifndef BME68X_INTF_RET_TYPE +#define BME68X_INTF_RET_TYPE int8_t +#endif + +/** + * BME68X_INTF_RET_SUCCESS is the success return value read/write interface return type which can be + * overwritten by the build system. The default is set to 0. It is used to check for a successful + * execution of the read/write functions + */ +#ifndef BME68X_INTF_RET_SUCCESS +#define BME68X_INTF_RET_SUCCESS INT8_C(0) +#endif + +/********************************************************* */ +/*! Function Pointers */ +/********************************************************* */ + +/*! + * @brief Bus communication function pointer which should be mapped to + * the platform specific read functions of the user + * + * @param[in] reg_addr : 8bit register address of the sensor + * @param[out] reg_data : Data from the specified address + * @param[in] length : Length of the reg_data array + * @param[in,out] intf_ptr : Void pointer that can enable the linking of descriptors + * for interface related callbacks + * @retval 0 for Success + * @retval Non-zero for Failure + */ +typedef BME68X_INTF_RET_TYPE (*bme68x_read_fptr_t)(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, + void *intf_ptr); + +/*! + * @brief Bus communication function pointer which should be mapped to + * the platform specific write functions of the user + * + * @param[in] reg_addr : 8bit register address of the sensor + * @param[out] reg_data : Data to the specified address + * @param[in] length : Length of the reg_data array + * @param[in,out] intf_ptr : Void pointer that can enable the linking of descriptors + * for interface related callbacks + * @retval 0 for Success + * @retval Non-zero for Failure + * + */ +typedef BME68X_INTF_RET_TYPE (*bme68x_write_fptr_t)(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, + void *intf_ptr); + +/*! + * @brief Delay function pointer which should be mapped to + * delay function of the user + * + * @param period - The time period in microseconds + * @param[in,out] intf_ptr : Void pointer that can enable the linking of descriptors + * for interface related callbacks + */ +typedef void (*bme68x_delay_us_fptr_t)(uint32_t period, void *intf_ptr); + +/* + * @brief Generic communication function pointer + * @param[in] dev_id: Place holder to store the id of the device structure + * Can be used to store the index of the Chip select or + * I2C address of the device. + * @param[in] reg_addr: Used to select the register the where data needs to + * be read from or written to. + * @param[in,out] reg_data: Data array to read/write + * @param[in] len: Length of the data array + */ + +/* + * @brief Interface selection Enumerations + */ +enum bme68x_intf { + /*! SPI interface */ + BME68X_SPI_INTF, + /*! I2C interface */ + BME68X_I2C_INTF +}; + +/* Structure definitions */ + +/* + * @brief Sensor field data structure + */ +struct bme68x_data +{ + /*! Contains new_data, gasm_valid & heat_stab */ + uint8_t status; + + /*! The index of the heater profile used */ + uint8_t gas_index; + + /*! Measurement index to track order */ + uint8_t meas_index; + + /*! Heater resistance */ + uint8_t res_heat; + + /*! Current DAC */ + uint8_t idac; + + /*! Gas wait period */ + uint8_t gas_wait; +#ifndef BME68X_USE_FPU + + /*! Temperature in degree celsius x100 */ + int16_t temperature; + + /*! Pressure in Pascal */ + uint32_t pressure; + + /*! Humidity in % relative humidity x1000 */ + uint32_t humidity; + + /*! Gas resistance in Ohms */ + uint32_t gas_resistance; +#else + + /*! Temperature in degree celsius */ + float temperature; + + /*! Pressure in Pascal */ + float pressure; + + /*! Humidity in % relative humidity x1000 */ + float humidity; + + /*! Gas resistance in Ohms */ + float gas_resistance; + +#endif + +}; + +/* + * @brief Structure to hold the calibration coefficients + */ +struct bme68x_calib_data +{ + /*! Calibration coefficient for the humidity sensor */ + uint16_t par_h1; + + /*! Calibration coefficient for the humidity sensor */ + uint16_t par_h2; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h3; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h4; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h5; + + /*! Calibration coefficient for the humidity sensor */ + uint8_t par_h6; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h7; + + /*! Calibration coefficient for the gas sensor */ + int8_t par_gh1; + + /*! Calibration coefficient for the gas sensor */ + int16_t par_gh2; + + /*! Calibration coefficient for the gas sensor */ + int8_t par_gh3; + + /*! Calibration coefficient for the temperature sensor */ + uint16_t par_t1; + + /*! Calibration coefficient for the temperature sensor */ + int16_t par_t2; + + /*! Calibration coefficient for the temperature sensor */ + int8_t par_t3; + + /*! Calibration coefficient for the pressure sensor */ + uint16_t par_p1; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p2; + + /*! Calibration coefficient for the pressure sensor */ + int8_t par_p3; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p4; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p5; + + /*! Calibration coefficient for the pressure sensor */ + int8_t par_p6; + + /*! Calibration coefficient for the pressure sensor */ + int8_t par_p7; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p8; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p9; + + /*! Calibration coefficient for the pressure sensor */ + uint8_t par_p10; +#ifndef BME68X_USE_FPU + + /*! Variable to store the intermediate temperature coefficient */ + int32_t t_fine; +#else + + /*! Variable to store the intermediate temperature coefficient */ + float t_fine; +#endif + + /*! Heater resistance range coefficient */ + uint8_t res_heat_range; + + /*! Heater resistance value coefficient */ + int8_t res_heat_val; + + /*! Gas resistance range switching error coefficient */ + int8_t range_sw_err; +}; + +/* + * @brief BME68X sensor settings structure which comprises of ODR, + * over-sampling and filter settings. + */ +struct bme68x_conf +{ + /*! Humidity oversampling. Refer @ref osx*/ + uint8_t os_hum; + + /*! Temperature oversampling. Refer @ref osx */ + uint8_t os_temp; + + /*! Pressure oversampling. Refer @ref osx */ + uint8_t os_pres; + + /*! Filter coefficient. Refer @ref filter*/ + uint8_t filter; + + /*! + * Standby time between sequential mode measurement profiles. + * Refer @ref odr + */ + uint8_t odr; +}; + +/* + * @brief BME68X gas heater configuration + */ +struct bme68x_heatr_conf +{ + /*! Enable gas measurement. Refer @ref en_dis */ + uint8_t enable; + + /*! Store the heater temperature for forced mode degree Celsius */ + uint16_t heatr_temp; + + /*! Store the heating duration for forced mode in milliseconds */ + uint16_t heatr_dur; + + /*! Store the heater temperature profile in degree Celsius */ + uint16_t *heatr_temp_prof; + + /*! Store the heating duration profile in milliseconds */ + uint16_t *heatr_dur_prof; + + /*! Variable to store the length of the heating profile */ + uint8_t profile_len; + + /*! + * Variable to store heating duration for parallel mode + * in milliseconds + */ + uint16_t shared_heatr_dur; +}; + +/* + * @brief BME68X device structure + */ +struct bme68x_dev +{ + /*! Chip Id */ + uint8_t chip_id; + + /*! + * The interface pointer is used to enable the user + * to link their interface descriptors for reference during the + * implementation of the read and write interfaces to the + * hardware. + */ + void *intf_ptr; + + /*! + * Variant id + * ---------------------------------------- + * Value | Variant + * ---------------------------------------- + * 0 | BME68X_VARIANT_GAS_LOW + * 1 | BME68X_VARIANT_GAS_HIGH + * ---------------------------------------- + */ + uint32_t variant_id; + + /*! SPI/I2C interface */ + enum bme68x_intf intf; + + /*! Memory page used */ + uint8_t mem_page; + + /*! Ambient temperature in Degree C*/ + int8_t amb_temp; + + /*! Sensor calibration data */ + struct bme68x_calib_data calib; + + /*! Read function pointer */ + bme68x_read_fptr_t read; + + /*! Write function pointer */ + bme68x_write_fptr_t write; + + /*! Delay function pointer */ + bme68x_delay_us_fptr_t delay_us; + + /*! To store interface pointer error */ + BME68X_INTF_RET_TYPE intf_rslt; + + /*! Store the info messages */ + uint8_t info_msg; +}; + +#endif /* BME68X_DEFS_H_ */ +/*! @endcond */ diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_datatypes.h b/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_datatypes.h new file mode 100644 index 0000000..e85fe90 --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_datatypes.h @@ -0,0 +1,509 @@ +/** + * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no + * responsibility for the consequences of use of such Information nor for any infringement of patents or + * other rights of third parties which may result from its use. No license is granted by implication or + * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + * @file bsec_datatypes.h + * + * @brief + * Contains the data types used by BSEC + * + */ + + +#ifndef __BSEC_DATATYPES_H__ +#define __BSEC_DATATYPES_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*! + * @addtogroup bsec_datatypes BSEC Enumerations and Definitions + * @brief + * @{*/ + +#ifdef __KERNEL__ +#include +#endif +#include +#include + +#define BSEC_MAX_WORKBUFFER_SIZE (4096) /*!< Maximum size (in bytes) of the work buffer */ +#define BSEC_MAX_PHYSICAL_SENSOR (8) /*!< Number of physical sensors that need allocated space before calling bsec_update_subscription() */ +#define BSEC_MAX_PROPERTY_BLOB_SIZE (1974) /*!< Maximum size (in bytes) of the data blobs returned by bsec_get_configuration() */ +#define BSEC_MAX_STATE_BLOB_SIZE (221) /*!< Maximum size (in bytes) of the data blobs returned by bsec_get_state()*/ +#define BSEC_SAMPLE_RATE_DISABLED (65535.0f) /*!< Sample rate of a disabled sensor */ +#define BSEC_SAMPLE_RATE_ULP (0.0033333f) /*!< Sample rate in case of Ultra Low Power Mode */ +#define BSEC_SAMPLE_RATE_CONT (1.0f) /*!< Sample rate in case of Continuous Mode */ +#define BSEC_SAMPLE_RATE_LP (0.33333f) /*!< Sample rate in case of Low Power Mode */ +#define BSEC_SAMPLE_RATE_ULP_MEASUREMENT_ON_DEMAND (0.0f) /*!< Input value used to trigger an extra measurment (ULP plus) */ +#define BSEC_SAMPLE_RATE_SCAN (0.055556f) /*!< Sample rate in case of scan mode */ + +#define BSEC_PROCESS_PRESSURE (1 << (BSEC_INPUT_PRESSURE-1)) /*!< process_data bitfield constant for pressure @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_TEMPERATURE (1 << (BSEC_INPUT_TEMPERATURE-1))/*!< process_data bitfield constant for temperature @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_HUMIDITY (1 << (BSEC_INPUT_HUMIDITY-1)) /*!< process_data bitfield constant for humidity @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_GAS (1 << (BSEC_INPUT_GASRESISTOR-1)) /*!< process_data bitfield constant for gas sensor @sa bsec_bme_settings_t */ +#define BSEC_NUMBER_OUTPUTS (19) /*!< Number of outputs, depending on solution */ +#define BSEC_OUTPUT_INCLUDED (66222575) /*!< bitfield that indicates which outputs are included in the solution */ + +/*! + * @brief Enumeration for input (physical) sensors. + * + * Used to populate bsec_input_t::sensor_id. It is also used in bsec_sensor_configuration_t::sensor_id structs + * returned in the parameter required_sensor_settings of bsec_update_subscription(). + * + * @sa bsec_sensor_configuration_t @sa bsec_input_t + */ +typedef enum +{ + /** + * @brief Pressure sensor output of BME68x [Pa] + */ + BSEC_INPUT_PRESSURE = 1, + + /** + * @brief Humidity sensor output of BME68x [%] + * + * @note Relative humidity strongly depends on the temperature (it is measured at). It may require a conversion to + * the temperature outside of the device. + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_HUMIDITY = 2, + + /** + * @brief Temperature sensor output of BME68x [degrees Celsius] + * + * @note The BME68x is factory trimmed, thus the temperature sensor of the BME68x is very accurate. + * The temperature value is a very local measurement value and can be influenced by external heat sources. + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_TEMPERATURE = 3, + + /** + * @brief Gas sensor resistance output of BME68x [Ohm] + * + * The resistance value changes due to varying VOC concentrations (the higher the concentration of reducing VOCs, + * the lower the resistance and vice versa). + */ + BSEC_INPUT_GASRESISTOR = 4, /*!< */ + + /** + * @brief Additional input for device heat compensation + * + * IAQ solution: The value is subtracted from ::BSEC_INPUT_TEMPERATURE to compute + * ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE. + * + * ALL solution: Generic heat source 1 + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_HEATSOURCE = 14, + + + + /** + * @brief Additional input that disables baseline tracker + * + * 0 - Normal, + * 1 - Event 1, + * 2 - Event 2 + */ + BSEC_INPUT_DISABLE_BASELINE_TRACKER = 23, + + /** + * @brief Additional input that provides information about the state of the profile (1-9) + * + */ + BSEC_INPUT_PROFILE_PART = 24 + +} bsec_physical_sensor_t; + +/*! + * @brief Enumeration for output (virtual) sensors + * + * Used to populate bsec_output_t::sensor_id. It is also used in bsec_sensor_configuration_t::sensor_id structs + * passed in the parameter requested_virtual_sensors of bsec_update_subscription(). + * + * @sa bsec_sensor_configuration_t @sa bsec_output_t + */ +typedef enum +{ + /** + * @brief Indoor-air-quality estimate [0-500] + * + * Indoor-air-quality (IAQ) gives an indication of the relative change in ambient TVOCs detected by BME68x. + * + * @note The IAQ scale ranges from 0 (clean air) to 500 (heavily polluted air). During operation, algorithms + * automatically calibrate and adapt themselves to the typical environments where the sensor is operated + * (e.g., home, workplace, inside a car, etc.).This automatic background calibration ensures that users experience + * consistent IAQ performance. The calibration process considers the recent measurement history (typ. up to four + * days) to ensure that IAQ=50 corresponds to typical good air and IAQ=200 indicates typical polluted air. + */ + BSEC_OUTPUT_IAQ = 1, + BSEC_OUTPUT_STATIC_IAQ = 2, /*!< Unscaled indoor-air-quality estimate */ + BSEC_OUTPUT_CO2_EQUIVALENT = 3, /*!< CO2 equivalent estimate [ppm] */ + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT = 4, /*!< Breath VOC concentration estimate [ppm] */ + + /** + * @brief Temperature sensor signal [degrees Celsius] + * + * Temperature directly measured by BME68x in degree Celsius. + * + * @note This value is cross-influenced by the sensor heating and device specific heating. + */ + BSEC_OUTPUT_RAW_TEMPERATURE = 6, + + /** + * @brief Pressure sensor signal [Pa] + * + * Pressure directly measured by the BME68x in Pa. + */ + BSEC_OUTPUT_RAW_PRESSURE = 7, + + /** + * @brief Relative humidity sensor signal [%] + * + * Relative humidity directly measured by the BME68x in %. + * + * @note This value is cross-influenced by the sensor heating and device specific heating. + */ + BSEC_OUTPUT_RAW_HUMIDITY = 8, + + /** + * @brief Gas sensor signal [Ohm] + * + * Gas resistance measured directly by the BME68x in Ohm.The resistance value changes due to varying VOC + * concentrations (the higher the concentration of reducing VOCs, the lower the resistance and vice versa). + */ + BSEC_OUTPUT_RAW_GAS = 9, + + + /** + * @brief Gas sensor stabilization status [boolean] + * + * Indicates initial stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization + * is finished (1). + */ + BSEC_OUTPUT_STABILIZATION_STATUS = 12, + + /** + * @brief Gas sensor run-in status [boolean] + * + * Indicates power-on stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization + * is finished (1). + */ + BSEC_OUTPUT_RUN_IN_STATUS = 13, + + /** + * @brief Sensor heat compensated temperature [degrees Celsius] + * + * Temperature measured by BME68x which is compensated for the influence of sensor (heater) in degree Celsius. + * The self heating introduced by the heater is depending on the sensor operation mode and the sensor supply voltage. + * + * + * @note IAQ solution: In addition, the temperature output can be compensated by an user defined value + * (::BSEC_INPUT_HEATSOURCE in degrees Celsius), which represents the device specific self-heating. + * + * Thus, the value is calculated as follows: + * * IAQ solution: ```BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = ::BSEC_INPUT_TEMPERATURE - function(sensor operation mode, sensor supply voltage) - ::BSEC_INPUT_HEATSOURCE``` + * * other solutions: ```::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = ::BSEC_INPUT_TEMPERATURE - function(sensor operation mode, sensor supply voltage)``` + * + * The self-heating in operation mode BSEC_SAMPLE_RATE_ULP is negligible. + * The self-heating in operation mode BSEC_SAMPLE_RATE_LP is supported for 1.8V by default (no config file required). If the BME68x sensor supply voltage is 3.3V, the corresponding config file shall be used. + */ + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = 14, + + /** + * @brief Sensor heat compensated humidity [%] + * + * Relative measured by BME68x which is compensated for the influence of sensor (heater) in %. + * + * It converts the ::BSEC_INPUT_HUMIDITY from temperature ::BSEC_INPUT_TEMPERATURE to temperature + * ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE. + * + * @note IAQ solution: If ::BSEC_INPUT_HEATSOURCE is used for device specific temperature compensation, it will be + * effective for ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY too. + */ + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY = 15, + + BSEC_OUTPUT_GAS_PERCENTAGE = 21, /*!< Percentage of min and max filtered gas value [%] */ + + /** + * @brief Gas estimate output channel 1 [0-1] + * + * The gas scan result is given in probability for each of the used gases. + * In standard scan mode, the probability of H2S and non-H2S is provided by the variables BSEC_OUTPUT_GAS_ESTIMATE_1 & BSEC_OUTPUT_GAS_ESTIMATE_2 respectively. A maximum of 4 classes can be used by configuring using BME AI-studio. + */ + BSEC_OUTPUT_GAS_ESTIMATE_1 = 22, + BSEC_OUTPUT_GAS_ESTIMATE_2 = 23, /*!< Gas estimate output channel 2 [0-1] */ + BSEC_OUTPUT_GAS_ESTIMATE_3 = 24, /*!< Gas estimate output channel 3 [0-1] */ + BSEC_OUTPUT_GAS_ESTIMATE_4 = 25, /*!< Gas estimate output channel 4 [0-1] */ + + BSEC_OUTPUT_RAW_GAS_INDEX = 26 /*!< Gas index cyclically running from 0 to heater_profile_length-1, range of heater profile length is from 1 to 10, default being 10 */ +} bsec_virtual_sensor_t; + +/*! + * @brief Enumeration for function return codes + */ +typedef enum +{ + BSEC_OK = 0, /*!< Function execution successful */ + BSEC_E_DOSTEPS_INVALIDINPUT = -1, /*!< Input (physical) sensor id passed to bsec_do_steps() is not in the valid range or not valid for requested virtual sensor */ + BSEC_E_DOSTEPS_VALUELIMITS = -2, /*!< Value of input (physical) sensor signal passed to bsec_do_steps() is not in the valid range */ + BSEC_W_DOSTEPS_TSINTRADIFFOUTOFRANGE = 4, /*!< Past timestamps passed to bsec_do_steps() */ + BSEC_E_DOSTEPS_DUPLICATEINPUT = -6, /*!< Duplicate input (physical) sensor ids passed as input to bsec_do_steps() */ + BSEC_I_DOSTEPS_NOOUTPUTSRETURNABLE = 2, /*!< No memory allocated to hold return values from bsec_do_steps(), i.e., n_outputs == 0 */ + BSEC_W_DOSTEPS_EXCESSOUTPUTS = 3, /*!< Not enough memory allocated to hold return values from bsec_do_steps(), i.e., n_outputs < maximum number of requested output (virtual) sensors */ + BSEC_W_DOSTEPS_GASINDEXMISS = 5, /*!< Gas index not provided to bsec_do_steps() */ + BSEC_E_SU_WRONGDATARATE = -10, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() is zero */ + BSEC_E_SU_SAMPLERATELIMITS = -12, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() does not match with the sampling rate allowed for that sensor */ + BSEC_E_SU_DUPLICATEGATE = -13, /*!< Duplicate output (virtual) sensor ids requested through bsec_update_subscription() */ + BSEC_E_SU_INVALIDSAMPLERATE = -14, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() does not fall within the global minimum and maximum sampling rates */ + BSEC_E_SU_GATECOUNTEXCEEDSARRAY = -15, /*!< Not enough memory allocated to hold returned input (physical) sensor data from bsec_update_subscription(), i.e., n_required_sensor_settings ::BSEC_MAX_PHYSICAL_SENSOR */ + BSEC_E_SU_SAMPLINTVLINTEGERMULT = -16, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() is not correct */ + BSEC_E_SU_MULTGASSAMPLINTVL = -17, /*!< The sample_rate of the requested output (virtual), which requires the gas sensor, is not equal to the sample_rate that the gas sensor is being operated */ + BSEC_E_SU_HIGHHEATERONDURATION = -18, /*!< The duration of one measurement is longer than the requested sampling interval */ + BSEC_W_SU_UNKNOWNOUTPUTGATE = 10, /*!< Output (virtual) sensor id passed to bsec_update_subscription() is not in the valid range; e.g., n_requested_virtual_sensors > actual number of output (virtual) sensors requested */ + BSEC_W_SU_MODINNOULP = 11, /*!< ULP plus can not be requested in non-ulp mode */ /*MOD_ONLY*/ + BSEC_I_SU_SUBSCRIBEDOUTPUTGATES = 12, /*!< No output (virtual) sensor data were requested via bsec_update_subscription() */ + BSEC_I_SU_GASESTIMATEPRECEDENCE = 13, /*!< GAS_ESTIMATE is suscribed and take precedence over other requested outputs */ + BSEC_E_PARSE_SECTIONEXCEEDSWORKBUFFER = -32, /*!< n_work_buffer_size passed to bsec_set_[configuration/state]() not sufficient */ + BSEC_E_CONFIG_FAIL = -33, /*!< Configuration failed */ + BSEC_E_CONFIG_VERSIONMISMATCH = -34, /*!< Version encoded in serialized_[settings/state] passed to bsec_set_[configuration/state]() does not match with current version */ + BSEC_E_CONFIG_FEATUREMISMATCH = -35, /*!< Enabled features encoded in serialized_[settings/state] passed to bsec_set_[configuration/state]() does not match with current library implementation */ + BSEC_E_CONFIG_CRCMISMATCH = -36, /*!< serialized_[settings/state] passed to bsec_set_[configuration/state]() is corrupted */ + BSEC_E_CONFIG_EMPTY = -37, /*!< n_serialized_[settings/state] passed to bsec_set_[configuration/state]() is to short to be valid */ + BSEC_E_CONFIG_INSUFFICIENTWORKBUFFER = -38, /*!< Provided work_buffer is not large enough to hold the desired string */ + BSEC_E_CONFIG_INVALIDSTRINGSIZE = -40, /*!< String size encoded in configuration/state strings passed to bsec_set_[configuration/state]() does not match with the actual string size n_serialized_[settings/state] passed to these functions */ + BSEC_E_CONFIG_INSUFFICIENTBUFFER = -41, /*!< String buffer insufficient to hold serialized data from BSEC library */ + BSEC_E_SET_INVALIDCHANNELIDENTIFIER = -100, /*!< Internal error code, size of work buffer in setConfig must be set to #BSEC_MAX_WORKBUFFER_SIZE */ + BSEC_E_SET_INVALIDLENGTH = -104, /*!< Internal error code */ + BSEC_W_SC_CALL_TIMING_VIOLATION = 100, /*!< Difference between actual and defined sampling intervals of bsec_sensor_control() greater than allowed */ + BSEC_W_SC_MODEXCEEDULPTIMELIMIT = 101, /*!< ULP plus is not allowed because an ULP measurement just took or will take place */ /*MOD_ONLY*/ + BSEC_W_SC_MODINSUFFICIENTWAITTIME = 102 /*!< ULP plus is not allowed because not sufficient time passed since last ULP plus */ /*MOD_ONLY*/ +} bsec_library_return_t; + +/*! + * @brief Structure containing the version information + * + * Please note that configuration and state strings are coded to a specific version and will not be accepted by other + * versions of BSEC. + * + */ +typedef struct +{ + uint8_t major; /**< @brief Major version */ + uint8_t minor; /**< @brief Minor version */ + uint8_t major_bugfix; /**< @brief Major bug fix version */ + uint8_t minor_bugfix; /**< @brief Minor bug fix version */ +} bsec_version_t; + +/*! + * @brief Structure describing an input sample to the library + * + * Each input sample is provided to BSEC as an element in a struct array of this type. Timestamps must be provided + * in nanosecond resolution. Moreover, duplicate timestamps for subsequent samples are not allowed and will results in + * an error code being returned from bsec_do_steps(). + * + * The meaning unit of the signal field are determined by the bsec_input_t::sensor_id field content. Possible + * bsec_input_t::sensor_id values and and their meaning are described in ::bsec_physical_sensor_t. + * + * @sa bsec_physical_sensor_t + * + */ +typedef struct +{ + /** + * @brief Time stamp in nanosecond resolution [ns] + * + * Timestamps must be provided as non-repeating and increasing values. They can have their 0-points at system start or + * at a defined wall-clock time (e.g., 01-Jan-1970 00:00:00) + */ + int64_t time_stamp; + float signal; /*!< @brief Signal sample in the unit defined for the respective sensor_id @sa bsec_physical_sensor_t */ + uint8_t signal_dimensions; /*!< @brief Signal dimensions (reserved for future use, shall be set to 1) */ + uint8_t sensor_id; /*!< @brief Identifier of physical sensor @sa bsec_physical_sensor_t */ +} bsec_input_t; + +/*! + * @brief Structure describing an output sample of the library + * + * Each output sample is returned from BSEC by populating the element of a struct array of this type. The contents of + * the signal field is defined by the supplied bsec_output_t::sensor_id. Possible output + * bsec_output_t::sensor_id values are defined in ::bsec_virtual_sensor_t. + * + * @sa bsec_virtual_sensor_t + */ +typedef struct +{ + int64_t time_stamp; /*!< @brief Time stamp in nanosecond resolution as provided as input [ns] */ + float signal; /*!< @brief Signal sample in the unit defined for the respective bsec_output_t::sensor_id @sa bsec_virtual_sensor_t */ + uint8_t signal_dimensions; /*!< @brief Signal dimensions (reserved for future use, shall be set to 1) */ + uint8_t sensor_id; /*!< @brief Identifier of virtual sensor @sa bsec_virtual_sensor_t */ + + /** + * @brief Accuracy status 0-3 + * + * Some virtual sensors provide a value in the accuracy field. If this is the case, the meaning of the field is as + * follows: + * + * | Name | Value | Accuracy description | + * |----------------------------|-------|-------------------------------------------------------------------------------------------------------------| + * | UNRELIABLE | 0 | Sensor data is unreliable, the sensor must be calibrated | + * | LOW_ACCURACY | 1 | Low accuracy, sensor should be calibrated | + * | MEDIUM_ACCURACY | 2 | Medium accuracy, sensor calibration may improve performance | + * | HIGH_ACCURACY | 3 | High accuracy | + * + * For example: + * + * - IAQ accuracy indicator will notify the user when she/he should initiate a calibration process. Calibration is + * performed automatically in the background if the sensor is exposed to clean and polluted air for approximately + * 30 minutes each. + * + * | Virtual sensor | Value | Accuracy description | + * |----------------------------|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| + * | IAQ | 0 | Stabilization / run-in ongoing | + * | | 1 | Low accuracy,to reach high accuracy(3),please expose sensor once to good air (e.g. outdoor air) and bad air (e.g. box with exhaled breath) for auto-trimming | + * | | 2 | Medium accuracy: auto-trimming ongoing | + * | | 3 | High accuracy + * + * - Gas estimator accuracy indicator will notify the user when she/he gets valid gas prediction output. + * + * | Virtual sensor | Value | Accuracy description | + * |----------------------------|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| + * | GAS_ESTIMATE_x* | 0 | No valid gas estimate found - BSEC collecting gas features | + * | | 3 | Valid gas estimate found - BSEC gas estimation prediction available | + * + * * GAS_ESTIMATE_1, GAS_ESTIMATE_2, GAS_ESTIMATE_3, GAS_ESTIMATE_4 + */ + uint8_t accuracy; +} bsec_output_t; + +/*! + * @brief Structure describing sample rate of physical/virtual sensors + * + * This structure is used together with bsec_update_subscription() to enable BSEC outputs and to retrieve information + * about the sample rates used for BSEC inputs. + */ +typedef struct +{ + /** + * @brief Sample rate of the virtual or physical sensor in Hertz [Hz] + * + * Only supported sample rates are allowed. + */ + float sample_rate; + + /** + * @brief Identifier of the virtual or physical sensor + * + * The meaning of this field changes depending on whether the structs are as the requested_virtual_sensors argument + * to bsec_update_subscription() or as the required_sensor_settings argument. + * + * | bsec_update_subscription() argument | sensor_id field interpretation | + * |-------------------------------------|--------------------------------| + * | requested_virtual_sensors | ::bsec_virtual_sensor_t | + * | required_sensor_settings | ::bsec_physical_sensor_t | + * + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + */ + uint8_t sensor_id; +} bsec_sensor_configuration_t; + +/*! + * @brief Structure returned by bsec_sensor_control() to configure BME68x sensor + * + * This structure contains settings that must be used to configure the BME68x to perform a forced-mode measurement. + * A measurement should only be executed if bsec_bme_settings_t::trigger_measurement is 1. If so, the oversampling + * settings for temperature, humidity, and pressure should be set to the provided settings provided in + * bsec_bme_settings_t::temperature_oversampling, bsec_bme_settings_t::humidity_oversampling, and + * bsec_bme_settings_t::pressure_oversampling, respectively. + * + * In case of bsec_bme_settings_t::run_gas = 1, the gas sensor must be enabled with the provided + * bsec_bme_settings_t::heater_temperature and bsec_bme_settings_t::heating_duration settings. + */ +typedef struct +{ + int64_t next_call; /*!< @brief Time stamp of the next call of the sensor_control*/ + uint32_t process_data; /*!< @brief Bit field describing which data is to be passed to bsec_do_steps() @sa BSEC_PROCESS_GAS, BSEC_PROCESS_TEMPERATURE, BSEC_PROCESS_HUMIDITY, BSEC_PROCESS_PRESSURE */ + uint16_t heater_temperature; /*!< @brief Heater temperature [degrees Celsius] */ + uint16_t heater_duration; /*!< @brief Heater duration [ms] */ + uint16_t heater_temperature_profile[10];/*!< @brief Heater temperature profile [degrees Celsius] */ + uint16_t heater_duration_profile[10]; /*!< @brief Heater duration profile [ms] */ + uint8_t heater_profile_len; /*!< @brief Heater profile length [0-10] */ + uint8_t run_gas; /*!< @brief Enable gas measurements [0/1] */ + uint8_t pressure_oversampling; /*!< @brief Pressure oversampling settings [0-5] */ + uint8_t temperature_oversampling; /*!< @brief Temperature oversampling settings [0-5] */ + uint8_t humidity_oversampling; /*!< @brief Humidity oversampling settings [0-5] */ + uint8_t trigger_measurement; /*!< @brief Trigger a forced measurement with these settings now [0/1] */ + uint8_t op_mode; /*!< @brief Sensor operation mode [0/1] */ +} bsec_bme_settings_t; + +/* internal defines and backward compatibility */ +#define BSEC_STRUCT_NAME Bsec /*!< Internal struct name */ + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface.h b/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface.h new file mode 100644 index 0000000..18ea07e --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface.h @@ -0,0 +1,563 @@ +/** + * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no + * responsibility for the consequences of use of such Information nor for any infringement of patents or + * other rights of third parties which may result from its use. No license is granted by implication or + * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + * @file bsec_interface.h + * + * @brief + * Contains the API for BSEC + * + */ + +#ifndef __BSEC_INTERFACE_H__ +#define __BSEC_INTERFACE_H__ + +#include "bsec_datatypes.h" + +#ifdef __cplusplus + extern "C" { +#endif + + + /*! @addtogroup bsec_interface BSEC Standard Interfaces + * @brief Standard interfaces of BSEC signal processing library. + * These interfaces supports in interfacing single BME68x sensor with the BSEC library. + * + * ### Interface usage + * + * The following provides a short overview on the typical operation sequence for BSEC. + * + * - Initialization of the library + * + * | Steps | Function | + * |---------------------------------------------------------------------|--------------------------| + * | Initialization of library | bsec_init() | + * | Update configuration settings (optional) | bsec_set_configuration() | + * | Restore the state of the library (optional) | bsec_set_state() | + * + * + * - The following function is called to enable output signals and define their sampling rate / operation mode. + * + * | Steps | Function | + * |---------------------------------------------|----------------------------| + * | Enable library outputs with specified mode | bsec_update_subscription() | + * + * + * - This table describes the main processing loop. + * + * | Steps | Function | + * |-------------------------------------------|----------------------------------| + * | Retrieve sensor settings to be used | bsec_sensor_control() | + * | Configure sensor and trigger measurement | [See BME68x API and example codes](https://github.com/BoschSensortec/BME68x-Sensor-API) | + * | Read results from sensor | [See BME68x API and example codes](https://github.com/BoschSensortec/BME68x-Sensor-API) | + * | Perform signal processing | bsec_do_steps() | + * + * + * - Before shutting down the system, the current state of BSEC can be retrieved and can then be used during + * re-initialization to continue processing. + * + * | Steps | Function | + * |---------------------------------------------|-------------------| + * | Retrieve the current library state | bsec_get_state() | + * | Retrieve the current library configuration | bsec_get_configuration() | + * + * + * ### Configuration and state + * + * Values of variables belonging to a BSEC instance are divided into two groups: + * - Values **not updated by processing** of signals belong to the **configuration group**. If available, BSEC can be + * configured before use with a customer specific configuration string. + * - Values **updated during processing** are member of the **state group**. Saving and restoring of the state of BSEC + * is necessary to maintain previously estimated sensor models and baseline information which is important for best + * performance of the gas sensor outputs. + * + * @note BSEC library consists of adaptive algorithms which models the gas sensor which improves its performance over + * the time. These will be lost if library is initialized due to system reset. In order to avoid this situation + * library state shall be stored in non volatile memory so that it can be loaded after system reset. + * + * + * @{ + */ + + +/*! + * @brief Return the version information of BSEC library + * + * @param [out] bsec_version_p pointer to struct which is to be populated with the version information + * + * @return Zero if successful, otherwise an error code + * + * See also: bsec_version_t + * + \code{.c} + // Example // + bsec_version_t version; + bsec_get_version(&version); + printf("BSEC version: %d.%d.%d.%d",version.major, version.minor, version.major_bugfix, version.minor_bugfix); + + \endcode +*/ + +bsec_library_return_t bsec_get_version(bsec_version_t * bsec_version_p); + + +/*! + * @brief Initialize the library + * + * Initialization and reset of BSEC is performed by calling bsec_init(). Calling this function sets up the relation + * among all internal modules, initializes run-time dependent library states and resets the configuration and state + * of all BSEC signal processing modules to defaults. + * + * Before any further use, the library must be initialized. This ensure that all memory and states are in defined + * conditions prior to processing any data. + * + * @return Zero if successful, otherwise an error code + * + \code{.c} + + // Initialize BSEC library before further use + bsec_init(); + + \endcode +*/ + +bsec_library_return_t bsec_init(void); + +/*! + * @brief Subscribe to library virtual sensors outputs + * + * Use bsec_update_subscription() to instruct BSEC which of the processed output signals are requested at which sample rates. + * See ::bsec_virtual_sensor_t for available library outputs. + * + * Based on the requested virtual sensors outputs, BSEC will provide information about the required physical sensor input signals + * (see ::bsec_physical_sensor_t) with corresponding sample rates. This information is purely informational as bsec_sensor_control() + * will ensure the sensor is operated in the required manner. To disable a virtual sensor, set the sample rate to ::BSEC_SAMPLE_RATE_DISABLED. + * + * The subscription update using bsec_update_subscription() is apart from the signal processing one of the the most + * important functions. It allows to enable the desired library outputs. The function determines which physical input + * sensor signals are required at which sample rate to produce the virtual output sensor signals requested by the user. + * When this function returns with success, the requested outputs are called subscribed. A very important feature is the + * retaining of already subscribed outputs. Further outputs can be requested or disabled both individually and + * group-wise in addition to already subscribed outputs without changing them unless a change of already subscribed + * outputs is requested. + * + * @note The state of the library concerning the subscribed outputs cannot be retained among reboots. + * + * The interface of bsec_update_subscription() requires the usage of arrays of sensor configuration structures. + * Such a structure has the fields sensor identifier and sample rate. These fields have the properties: + * - Output signals of virtual sensors must be requested using unique identifiers (Member of ::bsec_virtual_sensor_t) + * - Different sets of identifiers are available for inputs of physical sensors and outputs of virtual sensors + * - Identifiers are unique values defined by the library, not from external + * - Sample rates must be provided as value of + * - An allowed sample rate for continuously sampled signals + * - 65535.0f (BSEC_SAMPLE_RATE_DISABLED) to turn off outputs and identify disabled inputs + * + * @note The same sensor identifiers are also used within the functions bsec_do_steps(). + * + * The usage principles of bsec_update_subscription() are: + * - Differential updates (i.e., only asking for outputs that the user would like to change) is supported. + * - Invalid requests of outputs are ignored. Also if one of the requested outputs is unavailable, all the requests + * are ignored. At the same time, a warning is returned. + * - To disable BSEC, all outputs shall be turned off. Only enabled (subscribed) outputs have to be disabled while + * already disabled outputs do not have to be disabled explicitly. + * + * @param[in] requested_virtual_sensors Pointer to array of requested virtual sensor (output) configurations for the library + * @param[in] n_requested_virtual_sensors Number of virtual sensor structs pointed by requested_virtual_sensors + * @param[out] required_sensor_settings Pointer to array of required physical sensor configurations for the library + * @param[in,out] n_required_sensor_settings [in] Size of allocated required_sensor_settings array, [out] number of sensor configurations returned + * + * @return Zero when successful, otherwise an error code + * + * @sa bsec_sensor_configuration_t + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + * + \code{.c} + // Example // + + // Change 3 virtual sensors (switch IAQ and raw temperature -> on / pressure -> off) + bsec_sensor_configuration_t requested_virtual_sensors[3]; + uint8_t n_requested_virtual_sensors = 3; + + requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ; + requested_virtual_sensors[0].sample_rate = BSEC_SAMPLE_RATE_ULP; + requested_virtual_sensors[1].sensor_id = BSEC_OUTPUT_RAW_TEMPERATURE; + requested_virtual_sensors[1].sample_rate = BSEC_SAMPLE_RATE_ULP; + requested_virtual_sensors[2].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; + requested_virtual_sensors[2].sample_rate = BSEC_SAMPLE_RATE_DISABLED; + + // Allocate a struct for the returned physical sensor settings + bsec_sensor_configuration_t required_sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; + + // Call bsec_update_subscription() to enable/disable the requested virtual sensors + bsec_update_subscription(requested_virtual_sensors, n_requested_virtual_sensors, required_sensor_settings, &n_required_sensor_settings); + \endcode + * + */ +bsec_library_return_t bsec_update_subscription(const bsec_sensor_configuration_t * const requested_virtual_sensors, + const uint8_t n_requested_virtual_sensors, bsec_sensor_configuration_t * required_sensor_settings, + uint8_t * n_required_sensor_settings); + + +/*! + * @brief Main signal processing function of BSEC + * + * + * Processing of the input signals and returning of output samples is performed by bsec_do_steps(). + * - The samples of all library inputs must be passed with unique identifiers representing the input signals from + * physical sensors where the order of these inputs can be chosen arbitrary. However, all input have to be provided + * within the same time period as they are read. A sequential provision to the library might result in undefined + * behavior. + * - The samples of all library outputs are returned with unique identifiers corresponding to the output signals of + * virtual sensors where the order of the returned outputs may be arbitrary. + * - The samples of all input as well as output signals of physical as well as virtual sensors use the same + * representation in memory with the following fields: + * - Sensor identifier: + * - For inputs: required to identify the input signal from a physical sensor + * - For output: overwritten by bsec_do_steps() to identify the returned signal from a virtual sensor + * - Time stamp of the sample + * + * Calling bsec_do_steps() requires the samples of the input signals to be provided along with their time stamp when + * they are recorded and only when they are acquired. Repetition of samples with the same time stamp are ignored and + * result in a warning. Repetition of values of samples which are not acquired anew by a sensor result in deviations + * of the computed output signals. Concerning the returned output samples, an important feature is, that a value is + * returned for an output only when a new occurrence has been computed. A sample of an output signal is returned only + * once. + * + * + * @param[in] inputs Array of input data samples. Each array element represents a sample of a different physical sensor. + * @param[in] n_inputs Number of passed input data structs. + * @param[out] outputs Array of output data samples. Each array element represents a sample of a different virtual sensor. + * @param[in,out] n_outputs [in] Number of allocated output structs, [out] number of outputs returned + * + * @return Zero when successful, otherwise an error code + * + + \code{.c} + // Example // + + // Allocate input and output memory + bsec_input_t input[3]; + uint8_t n_input = 3; + bsec_output_t output[2]; + uint8_t n_output=2; + + bsec_library_return_t status; + + // Populate the input structs, assuming the we have timestamp (ts), + // gas sensor resistance (R), temperature (T), and humidity (rH) available + // as input variables + input[0].sensor_id = BSEC_INPUT_GASRESISTOR; + input[0].signal = R; + input[0].time_stamp= ts; + input[1].sensor_id = BSEC_INPUT_TEMPERATURE; + input[1].signal = T; + input[1].time_stamp= ts; + input[2].sensor_id = BSEC_INPUT_HUMIDITY; + input[2].signal = rH; + input[2].time_stamp= ts; + + + // Invoke main processing BSEC function + status = bsec_do_steps( input, n_input, output, &n_output ); + + // Iterate through the BSEC output data, if the call succeeded + if(status == BSEC_OK) + { + for(int i = 0; i < n_output; i++) + { + switch(output[i].sensor_id) + { + case BSEC_OUTPUT_IAQ: + // Retrieve the IAQ results from output[i].signal + // and do something with the data + break; + case BSEC_OUTPUT_STATIC_IAQ: + // Retrieve the static IAQ results from output[i].signal + // and do something with the data + break; + + } + } + } + + \endcode + */ + +bsec_library_return_t bsec_do_steps(const bsec_input_t * const inputs, const uint8_t n_inputs, bsec_output_t * outputs, uint8_t * n_outputs); + + +/*! + * @brief Reset a particular virtual sensor output + * + * This function allows specific virtual sensor outputs to be reset. The meaning of "reset" depends on the specific + * output. In case of the IAQ output, reset means zeroing the output to the current ambient conditions. + * + * @param[in] sensor_id Virtual sensor to be reset + * + * @return Zero when successful, otherwise an error code + * + * + \code{.c} + // Example // + bsec_reset_output(BSEC_OUTPUT_IAQ); + + \endcode + */ + +bsec_library_return_t bsec_reset_output(uint8_t sensor_id); + + +/*! + * @brief Update algorithm configuration parameters + * + * BSEC uses a default configuration for the modules and common settings. The initial configuration can be customized + * by bsec_set_configuration(). This is an optional step. + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose + * the serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the required size. + * + * @param[in] serialized_settings Settings serialized to a binary blob + * @param[in] n_serialized_settings Size of the settings blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_settings[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_settings_max = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer = BSEC_MAX_WORKBUFFER_SIZE; + + // Here we will load a provided config string into serialized_settings + + // Apply the configuration + bsec_set_configuration(serialized_settings, n_serialized_settings_max, work_buffer, n_work_buffer); + + \endcode + */ + +bsec_library_return_t bsec_set_configuration(const uint8_t * const serialized_settings, + const uint32_t n_serialized_settings, uint8_t * work_buffer, + const uint32_t n_work_buffer_size); + + +/*! + * @brief Restore the internal state of the library + * + * BSEC uses a default state for all signal processing modules and the BSEC module. To ensure optimal performance, + * especially of the gas sensor functionality, it is recommended to retrieve the state using bsec_get_state() + * before unloading the library, storing it in some form of non-volatile memory, and setting it using bsec_set_state() + * before resuming further operation of the library. + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the required size. + * + * @param[in] serialized_state States serialized to a binary blob + * @param[in] n_serialized_state Size of the state blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_state[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_state = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer_state[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer_size = BSEC_MAX_WORKBUFFER_SIZE; + + // Here we will load a state string from a previous use of BSEC + + // Apply the previous state to the current BSEC session + bsec_set_state(serialized_state, n_serialized_state, work_buffer_state, n_work_buffer_size); + + \endcode +*/ + +bsec_library_return_t bsec_set_state(const uint8_t * const serialized_state, const uint32_t n_serialized_state, + uint8_t * work_buffer, const uint32_t n_work_buffer_size); + + +/*! + * @brief Retrieve the current library configuration + * + * BSEC allows to retrieve the current configuration using bsec_get_configuration(). Returns a binary blob encoding + * the current configuration parameters of the library in a format compatible with bsec_set_configuration(). + * + * @note The function bsec_get_configuration() is required to be used for debugging purposes only. + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the required size. + * + * @param[in] config_id Identifier for a specific set of configuration settings to be returned; + * shall be zero to retrieve all configuration settings. + * @param[out] serialized_settings Buffer to hold the serialized config blob + * @param[in] n_serialized_settings_max Maximum available size for the serialized settings + * @param[in,out] work_buffer Work buffer used to parse the binary blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_settings Actual size of the returned serialized configuration blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_settings[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_settings_max = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer = BSEC_MAX_WORKBUFFER_SIZE; + uint32_t n_serialized_settings = 0; + + // Configuration of BSEC algorithm is stored in 'serialized_settings' + bsec_get_configuration(0, serialized_settings, n_serialized_settings_max, work_buffer, n_work_buffer, &n_serialized_settings); + + \endcode + */ + +bsec_library_return_t bsec_get_configuration(const uint8_t config_id, uint8_t * serialized_settings, const uint32_t n_serialized_settings_max, + uint8_t * work_buffer, const uint32_t n_work_buffer, uint32_t * n_serialized_settings); + + +/*! + *@brief Retrieve the current internal library state + * + * BSEC allows to retrieve the current states of all signal processing modules and the BSEC module using + * bsec_get_state(). This allows a restart of the processing after a reboot of the system by calling bsec_set_state(). + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the required size. + * + * @param[in] state_set_id Identifier for a specific set of states to be returned; shall be + * zero to retrieve all states. + * @param[out] serialized_state Buffer to hold the serialized config blob + * @param[in] n_serialized_state_max Maximum available size for the serialized states + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_state Actual size of the returned serialized blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_state[BSEC_MAX_STATE_BLOB_SIZE]; + uint32_t n_serialized_state_max = BSEC_MAX_STATE_BLOB_SIZE; + uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; + uint8_t work_buffer_state[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer_size = BSEC_MAX_WORKBUFFER_SIZE; + + // Algorithm state is stored in 'serialized_state' + bsec_get_state(0, serialized_state, n_serialized_state_max, work_buffer_state, n_work_buffer_size, &n_serialized_state); + + \endcode + */ + +bsec_library_return_t bsec_get_state(const uint8_t state_set_id, uint8_t * serialized_state, + const uint32_t n_serialized_state_max, uint8_t * work_buffer, const uint32_t n_work_buffer, + uint32_t * n_serialized_state); + +/*! + * @brief Retrieve BMExxx sensor instructions + * + * The bsec_sensor_control() interface is a key feature of BSEC, as it allows an easy way for the signal processing + * library to control the operation of the BME sensor. This is important since gas sensor behaviour is mainly + * determined by how the integrated heater is configured. To ensure an easy integration of BSEC into any system, + * bsec_sensor_control() will provide the caller with information about the current sensor configuration that is + * necessary to fulfill the input requirements derived from the current outputs requested via + * bsec_update_subscription(). + * + * In practice the use of this function shall be as follows: + * - Call bsec_sensor_control() which returns a bsec_bme_settings_t struct. + * - Based on the information contained in this struct, the sensor is configured and a forced-mode measurement is + * triggered if requested by bsec_sensor_control(). + * - Once this forced-mode measurement is complete, the signals specified in this struct shall be passed to + * bsec_do_steps() to perform the signal processing. + * - After processing, the process should sleep until the bsec_bme_settings_t::next_call timestamp is reached. + * + * + * @param [in] time_stamp Current timestamp in [ns] + * @param[out] sensor_settings Settings to be passed to API to operate sensor at this time instance + * + * @return Zero when successful, otherwise an error code + */ + +bsec_library_return_t bsec_sensor_control(const int64_t time_stamp, bsec_bme_settings_t *sensor_settings); + +/*@}*/ //BSEC Interface + +#ifdef __cplusplus + } +#endif + +#endif /* __BSEC_INTERFACE_H__ */ diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface_multi.h b/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface_multi.h new file mode 100644 index 0000000..fb35619 --- /dev/null +++ b/src/mbedos/test-lorawan/source/bme688/bsec2/bsec_interface_multi.h @@ -0,0 +1,334 @@ +/** + * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no + * responsibility for the consequences of use of such Information nor for any infringement of patents or + * other rights of third parties which may result from its use. No license is granted by implication or + * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + * @file bsec_interface_multi.h + * + * @brief + * Contains the multi-instance API for BSEC + * + */ + +#ifndef __BSEC_INTERFACE_MULTI_H__ +#define __BSEC_INTERFACE_MULTI_H__ + +#include "bsec_datatypes.h" + +#ifdef __cplusplus + extern "C" { +#endif + + /*! @addtogroup bsec_lib_interface BSEC Multi-instance Interfaces + * @brief The multi-instance interface of BSEC signal processing library is used for interfacing multiple sensors with BSEC library. + * + * # Multi-instance interface usage + * + * The following provides a short overview on the typical operation sequence for BSEC. + * + * - Initialization of the library + * + * | Steps | Function | + * |---------------------------------------------------------------------|--------------------------| + * | Initialization of library | bsec_init_m() | + * | Update configuration settings (optional) | bsec_set_configuration_m() | + * | Restore the state of the library (optional) | bsec_set_state_m() | + * + * + * - The following function is called to enable output signals and define their sampling rate / operation mode. + * + * | Steps | Function | + * |---------------------------------------------|----------------------------| + * | Enable library outputs with specified mode | bsec_update_subscription_m() | + * + * + * - This table describes the main processing loop. + * + * | Steps | Function | + * |-------------------------------------------|----------------------------------| + * | Retrieve sensor settings to be used | bsec_sensor_control_m() | + * | Configure sensor and trigger measurement | See BME688 API and example codes | + * | Read results from sensor | See BME688 API and example codes | + * | Perform signal processing | bsec_do_steps_m() | + * + * + * - Before shutting down the system, the current state of BSEC can be retrieved and can then be used during + * re-initialization to continue processing. + * + * | Steps | Function | + * |---------------------------------------------|-------------------| + * | Retrieve the current library state | bsec_get_state_m() | + * | Retrieve the current library configuration | bsec_get_configuration_m() | + * + * + * ### Configuration and state + * + * Values of variables belonging to a BSEC instance are divided into two groups: + * - Values **not updated by processing** of signals belong to the **configuration group**. If available, BSEC can be + * configured before use with a customer specific configuration string. + * - Values **updated during processing** are member of the **state group**. Saving and restoring of the state of BSEC + * is necessary to maintain previously estimated sensor models and baseline information which is important for best + * performance of the gas sensor outputs. + * + * @note BSEC library consists of adaptive algorithms which models the gas sensor which improves its performance over + * the time. These will be lost if library is initialized due to system reset. In order to avoid this situation + * library state shall be stored in non volatile memory so that it can be loaded after system reset. + * + * + * @{ + */ + +/********************************************************/ +/* function prototype declarations */ + +/*! + * @brief Function that provides the size of the internal instance in bytes. + * To be used for allocating memory for struct BSEC_STRUCT_NAME + * @return Size of the internal instance in bytes + */ +size_t bsec_get_instance_size_m(void); + +/*! + * @brief Return the version information of BSEC library instance + * @param[in,out] inst Reference to the pointer containing the instance + * @param [out] bsec_version_p pointer to struct which is to be populated with the version information + * + * @return Zero if successful, otherwise an error code + * + * See also: bsec_version_t + * +*/ +bsec_library_return_t bsec_get_version_m(void *inst, bsec_version_t *bsec_version_p); + +/*! + * @brief Initialize the library instance + * + * Initialization and reset of BSEC library instance is performed by calling bsec_init_m() as done with bsec_init() for standard interface. + * + * @param[in,out] inst Reference to the pointer containing the instance + * + * @return Zero if successful, otherwise an error code + * +*/ +bsec_library_return_t bsec_init_m(void *inst); + +/*! + * @brief Subscribe to library virtual sensors outputs + * + * Like bsec_update_subscription(), bsec_update_subscription_m() is used to instruct BSEC which of the processed output signals + * of the library instance are requested at which sample rates. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] requested_virtual_sensors Pointer to array of requested virtual sensor (output) configurations for the library + * @param[in] n_requested_virtual_sensors Number of virtual sensor structs pointed by requested_virtual_sensors + * @param[out] required_sensor_settings Pointer to array of required physical sensor configurations for the library + * @param[in,out] n_required_sensor_settings [in] Size of allocated required_sensor_settings array, [out] number of sensor configurations returned + * + * @return Zero when successful, otherwise an error code + * + * @sa bsec_sensor_configuration_t + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + * + */ +bsec_library_return_t bsec_update_subscription_m(void *inst, const bsec_sensor_configuration_t *const requested_virtual_sensors, + const uint8_t n_requested_virtual_sensors, bsec_sensor_configuration_t *required_sensor_settings, + uint8_t *n_required_sensor_settings); + +/*! + * @brief Main signal processing function of BSEC library instance + * + * + * Processing of the input signals and returning of output samples for each instances of BSEC library is performed by bsec_do_steps_m(). + * bsec_do_steps_m() processes multiple instaces of BSEC library simillar to how bsec_do_steps() handles single instance. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] inputs Array of input data samples. Each array element represents a sample of a different physical sensor. + * @param[in] n_inputs Number of passed input data structs. + * @param[out] outputs Array of output data samples. Each array element represents a sample of a different virtual sensor. + * @param[in,out] n_outputs [in] Number of allocated output structs, [out] number of outputs returned + * + * @return Zero when successful, otherwise an error code + * + */ +bsec_library_return_t bsec_do_steps_m(void *inst, const bsec_input_t *const inputs, const uint8_t n_inputs, bsec_output_t *outputs, uint8_t *n_outputs); + +/*! + * @brief Reset a particular virtual sensor output of the library instance + * + * This function allows specific virtual sensor outputs of each library instance to be reset. + * It processes in same way as bsec_reset_output(). + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] sensor_id Virtual sensor to be reset + * + * @return Zero when successful, otherwise an error code + * + */ +bsec_library_return_t bsec_reset_output_m(void *inst, uint8_t sensor_id); + +/*! + * @brief Update algorithm configuration parameters of the library instance + * + * As done with bsec_set_configuration(), the initial configuration of BSEC libray instance can be customized + * by bsec_set_configuration_m(). This is an optional step. + * + * Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the required size. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] serialized_settings Settings serialized to a binary blob + * @param[in] n_serialized_settings Size of the settings blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + */ +bsec_library_return_t bsec_set_configuration_m(void *inst, const uint8_t *const serialized_settings, + const uint32_t n_serialized_settings, uint8_t *work_buffer, + const uint32_t n_work_buffer_size); +/*! + * @brief Restore the internal state of the library instance + * + * BSEC uses a default state for all signal processing modules and the BSEC module for each instance. To ensure optimal performance, + * especially of the gas sensor functionality, it is recommended to retrieve the state using bsec_get_state_m() + * before unloading the library, storing it in some form of non-volatile memory, and setting it using bsec_set_state_m() + * before resuming further operation of the library. + * + * Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the required size. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] serialized_state States serialized to a binary blob + * @param[in] n_serialized_state Size of the state blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * +*/ +bsec_library_return_t bsec_set_state_m(void *inst, const uint8_t *const serialized_state, const uint32_t n_serialized_state, + uint8_t *work_buffer, const uint32_t n_work_buffer_size); + +/*! + * @brief Retrieve the current library instance configuration + * + * BSEC allows to retrieve the current configuration of the library instance using bsec_get_configuration_m(). + * In the same way as bsec_get_configuration(), this API returns a binary blob encoding + * the current configuration parameters of the library in a format compatible with bsec_set_configuration_m(). + * + * Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the required size. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] config_id Identifier for a specific set of configuration settings to be returned; + * shall be zero to retrieve all configuration settings. + * @param[out] serialized_settings Buffer to hold the serialized config blob + * @param[in] n_serialized_settings_max Maximum available size for the serialized settings + * @param[in,out] work_buffer Work buffer used to parse the binary blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_settings Actual size of the returned serialized configuration blob + * + * @return Zero when successful, otherwise an error code + * + */ +bsec_library_return_t bsec_get_configuration_m(void *inst, const uint8_t config_id, uint8_t *serialized_settings, const uint32_t n_serialized_settings_max, + uint8_t *work_buffer, const uint32_t n_work_buffer, uint32_t *n_serialized_settings); + +/*! + *@brief Retrieve the current internal library instance state + * + * BSEC allows to retrieve the current states of all signal processing modules and the BSEC module of the library instance using + * bsec_get_state_m(). As done by bsec_get_state(), this allows a restart of the processing after a reboot of the system by calling bsec_set_state_m(). + * + * Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the required size. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param[in] state_set_id Identifier for a specific set of states to be returned; shall be + * zero to retrieve all states. + * @param[out] serialized_state Buffer to hold the serialized config blob + * @param[in] n_serialized_state_max Maximum available size for the serialized states + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_state Actual size of the returned serialized blob + * + * @return Zero when successful, otherwise an error code + * + */ +bsec_library_return_t bsec_get_state_m(void *inst, const uint8_t state_set_id, uint8_t *serialized_state, + const uint32_t n_serialized_state_max, uint8_t *work_buffer, const uint32_t n_work_buffer, + uint32_t *n_serialized_state); + +/*! + * @brief Retrieve BMExxx sensor instructions for the library instance + * + * The bsec_sensor_control_m() allows an easy way for the signal processing library to control the operation of the + * BME sensor which uses the correspodning BSEC library instance. Operation of bsec_sensor_control_m() is simillar to bsec_sensor_control() + * except that former API supports multiples library instances. + * + * @param[in,out] inst Reference to the pointer containing the instance + * @param [in] time_stamp Current timestamp in [ns] + * @param[out] sensor_settings Settings to be passed to API to operate sensor at this time instance + * + * @return Zero when successful, otherwise an error code + */ +bsec_library_return_t bsec_sensor_control_m(void *inst, const int64_t time_stamp, bsec_bme_settings_t *sensor_settings); + +/*@}*/ //BSEC Interface + +#ifdef __cplusplus +} +#endif + +#endif /* __BSEC_INTERFACE_MULTI_H__ */ diff --git a/src/mbedos/test-lorawan/source/bme688/bsec2/libalgobsec.ar b/src/mbedos/test-lorawan/source/bme688/bsec2/libalgobsec.ar new file mode 100644 index 0000000000000000000000000000000000000000..6578a006e1d7feeabdcadb4a4a512522bbef56de GIT binary patch literal 254616 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 +class Defer +{ + T& deferred_object; + T target_object; + + public: + Defer( T& deferred, T target ):deferred_object(deferred), target_object(target) {} + ~Defer() + { + deferred_object = target_object; + }; +}; + +} // namespace utils +} // namespace teapotlabs + +#endif \ No newline at end of file diff --git a/src/mbedos/test-lorawan/source/main.cpp b/src/mbedos/test-lorawan/source/main.cpp new file mode 100644 index 0000000..e52feef --- /dev/null +++ b/src/mbedos/test-lorawan/source/main.cpp @@ -0,0 +1,258 @@ +/** + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include "lorawan/LoRaWANInterface.h" +#include "lorawan/system/lorawan_data_structures.h" +#include "events/EventQueue.h" + +#include "packet/environmental_sensor_type_c.h" + +// Application helpers +#include "trace_helper.h" +#include "lora_radio_helper.h" + +using namespace events; + +// Max payload size can be LORAMAC_PHY_MAXPAYLOAD. +// This example only communicates with much shorter messages (<30 bytes). +// If longer messages are used, these buffers must be changed accordingly. +uint8_t tx_buffer[30]; +uint8_t rx_buffer[30]; + +/* + * Sets up an application dependent transmission timer in ms. Used only when Duty Cycling is off for testing + */ +#define TX_TIMER 300000 + +/** + * Maximum number of events for the event queue. + * 10 is the safe number for the stack events, however, if application + * also uses the queue for whatever purposes, this number should be increased. + */ +#define MAX_NUMBER_OF_EVENTS 10 + +/** + * Maximum number of retries for CONFIRMED messages before giving up + */ +#define CONFIRMED_MSG_RETRY_COUNTER 3 + +/** + * Dummy pin for dummy sensor + */ +#define PC_9 0 + + +/** +* This event queue is the global event queue for both the +* application and stack. To conserve memory, the stack is designed to run +* in the same thread as the application and the application is responsible for +* providing an event queue to the stack that will be used for ISR deferment as +* well as application information event queuing. +*/ +static EventQueue ev_queue(MAX_NUMBER_OF_EVENTS *EVENTS_EVENT_SIZE); + +/** + * Event handler. + * + * This will be passed to the LoRaWAN stack to queue events for the + * application which in turn drive the application. + */ +static void lora_event_handler(lorawan_event_t event); + +/** + * Constructing Mbed LoRaWANInterface and passing it the radio object from lora_radio_helper. + */ +static LoRaWANInterface lorawan(radio); + +/** + * Application specific callbacks + */ +static lorawan_app_callbacks_t callbacks; + +/** + * Entry point for application + */ +int main(void) +{ + // setup tracing + // setup_trace(); + SerialBase::enable_input(false); + SerialBase::enable_output(false); + // stores the status of a call to LoRaWAN protocol + lorawan_status_t retcode; + + // Initialize LoRaWAN stack + if (lorawan.initialize(&ev_queue) != LORAWAN_STATUS_OK) { + printf("\r\n LoRa initialization failed! \r\n"); + return -1; + } + + printf("\r\n Mbed LoRaWANStack initialized \r\n"); + + // prepare application callbacks + callbacks.events = mbed::callback(lora_event_handler); + lorawan.add_app_callbacks(&callbacks); + + // Set number of retries in case of CONFIRMED messages + if (lorawan.set_confirmed_msg_retries(CONFIRMED_MSG_RETRY_COUNTER) + != LORAWAN_STATUS_OK) { + printf("\r\n set_confirmed_msg_retries failed! \r\n\r\n"); + return -1; + } + + printf("\r\n CONFIRMED message retries : %d \r\n", + CONFIRMED_MSG_RETRY_COUNTER); + + // Enable adaptive data rate + if (lorawan.enable_adaptive_datarate() != LORAWAN_STATUS_OK) { + printf("\r\n enable_adaptive_datarate failed! \r\n"); + return -1; + } + + printf("\r\n Adaptive data rate (ADR) - Enabled \r\n"); + + retcode = lorawan.connect(); + + if (retcode == LORAWAN_STATUS_OK || + retcode == LORAWAN_STATUS_CONNECT_IN_PROGRESS) { + } else { + printf("\r\n Connection error, code = %d \r\n", retcode); + return -1; + } + + printf("\r\n Connection - In Progress ...\r\n"); + + // make your event queue dispatching events forever + ev_queue.dispatch_forever(); + + return 0; +} + +/** + * Sends a message to the Network Server + */ +static void send_message() +{ + int16_t retcode; + + EnvironmentalSensorTypeC mySensor; + + mySensor.setIaqAccuracy(1); + mySensor.setStaticIaqAccuracy(2); + mySensor.setCo2Accuracy(3); + mySensor.setIaq(3.4567); + mySensor.setStaticIaq(2.3456); + mySensor.setCo2(1.1234); + mySensor.setTemperature(27.12); + mySensor.setHumidity(51.52); + mySensor.setPressure(1013); + mySensor.setVoltage(3.1234); + + retcode = lorawan.send(MBED_CONF_LORA_APP_PORT, mySensor.getPacket(), mySensor.PACKET_LENGTH, + MSG_UNCONFIRMED_FLAG); + + if (retcode < 0) { + retcode == LORAWAN_STATUS_WOULD_BLOCK ? printf("send - WOULD BLOCK\r\n") + : printf("\r\n send() - Error code %d \r\n", retcode); + + if (retcode == LORAWAN_STATUS_WOULD_BLOCK) { + //retry in 3 seconds + if (MBED_CONF_LORA_DUTY_CYCLE_ON) { + ev_queue.call_in(std::chrono::milliseconds(3000), send_message); + } + } + return; + } + + printf("\r\n %d bytes scheduled for transmission \r\n", retcode); + memset(tx_buffer, 0, sizeof(tx_buffer)); +} + +/** + * Receive a message from the Network Server + */ +static void receive_message() +{ + uint8_t port; + int flags; + int16_t retcode = lorawan.receive(rx_buffer, sizeof(rx_buffer), port, flags); + + if (retcode < 0) { + printf("\r\n receive() - Error code %d \r\n", retcode); + return; + } + + printf(" RX Data on port %u (%d bytes): ", port, retcode); + for (uint8_t i = 0; i < retcode; i++) { + printf("%02x ", rx_buffer[i]); + } + printf("\r\n"); + + memset(rx_buffer, 0, sizeof(rx_buffer)); +} + +/** + * Event handler + */ +static void lora_event_handler(lorawan_event_t event) +{ + switch (event) { + case CONNECTED: + printf("\r\n Connection - Successful \r\n"); + ev_queue.call_every(std::chrono::milliseconds(TX_TIMER), send_message); + break; + case DISCONNECTED: + ev_queue.break_dispatch(); + printf("\r\n Disconnected Successfully \r\n"); + break; + case TX_DONE: + printf("\r\n Message Sent to Network Server \r\n"); + if (MBED_CONF_LORA_DUTY_CYCLE_ON) { + send_message(); + } + break; + case TX_TIMEOUT: + case TX_ERROR: + case TX_CRYPTO_ERROR: + case TX_SCHEDULING_ERROR: + printf("\r\n Transmission Error - EventCode = %d \r\n", event); + // try again + if (MBED_CONF_LORA_DUTY_CYCLE_ON) { + send_message(); + } + break; + case RX_DONE: + printf("\r\n Received message from Network Server \r\n"); + receive_message(); + break; + case RX_TIMEOUT: + case RX_ERROR: + printf("\r\n Error in reception - Code = %d \r\n", event); + break; + case JOIN_FAILURE: + printf("\r\n OTAA Failed - Check Keys \r\n"); + break; + case UPLINK_REQUIRED: + printf("\r\n Uplink required by NS \r\n"); + break; + default: + MBED_ASSERT("Unknown Event"); + } +} + +// EOF \ No newline at end of file diff --git a/src/mbedos/test-lorawan/source/packet/environmental_sensor_type_c.h b/src/mbedos/test-lorawan/source/packet/environmental_sensor_type_c.h new file mode 100644 index 0000000..8e0e78d --- /dev/null +++ b/src/mbedos/test-lorawan/source/packet/environmental_sensor_type_c.h @@ -0,0 +1,153 @@ +#ifndef PACKET_ENVIRONMENTAL_SENSOR_TYPE_C_H_ +#define PACKET_ENVIRONMENTAL_SENSOR_TYPE_C_H_ + +#pragma pack(push, 1) +class EnvironmentalSensorTypeC { + public: + const uint8_t PACKET_LENGTH = 23; + + uint8_t getIaqAccuracy() { + return m_sensor.sensorAccuracy & 0b00000011; + } + + bool setIaqAccuracy(uint8_t iaqAccuracy) { + if (iaqAccuracy > 3) { + return false; + } + m_sensor.sensorAccuracy &= 0b11111100; + m_sensor.sensorAccuracy |= iaqAccuracy; + return true; + } + + uint8_t getStaticIaqAccuracy() { + return (m_sensor.sensorAccuracy >> 2) & 0b00000011; + } + + bool setStaticIaqAccuracy(uint8_t staticIaqAccuracy) { + if (staticIaqAccuracy > 3) { + return false; + } + m_sensor.sensorAccuracy &= 0b11110011; + m_sensor.sensorAccuracy |= staticIaqAccuracy << 2; + return true; + } + + uint8_t getCo2Accuracy() { + return (m_sensor.sensorAccuracy >> 4) & 0b00000011; + } + + bool setCo2Accuracy(uint8_t co2Accuracy) { + if (co2Accuracy > 3) { + return false; + } + m_sensor.sensorAccuracy &= 0b11001111; + m_sensor.sensorAccuracy |= co2Accuracy << 4; + return true; + } + + float getIaq() { + return m_sensor.iaq; + } + + bool setIaq(float iaq) { + if (iaq < 0) { + return false; + } + m_sensor.iaq = iaq; + return true; + } + + float getStaticIaq() { + return m_sensor.staticIaq; + } + + bool setStaticIaq(float staticIaq) { + if (staticIaq < 0) { + return false; + } + m_sensor.staticIaq = staticIaq; + return true; + } + + float getCo2() { + return m_sensor.co2; + } + + bool setCo2(float co2) { + if (co2 < 0) { + return false; + } + m_sensor.co2 = co2; + return true; + } + + float getTemperature() { + return (float)m_sensor.temperature / 100; + } + + bool setTemperature(float temperature) { + if (temperature < -327.68 || temperature > 327.67) { + return false; + } + m_sensor.temperature = temperature * 100; + return true; + } + + float getHumidity() { + return (float)m_sensor.humidity / 100; + } + + bool setHumidity(float humidity) { + if (humidity < 0 || humidity > 100) { + return false; + } + m_sensor.humidity = humidity * 100; + return true; + } + + float getPressure() { + return (float)m_sensor.pressure / 100; + } + + bool setPressure(float pressure) { + if (pressure < 300 || pressure > 1100) { + return false; + } + m_sensor.pressure = pressure; + return true; + } + + float getVoltage() { + return (float)m_sensor.voltage / 100; + } + + bool setVoltage(float voltage) { + if (voltage < 0 || voltage > 5) { + return false; + } + m_sensor.voltage = voltage * 10000; + return true; + } + + uint8_t * getPacket() { + return (uint8_t*)&m_sensor; + } + + private: + struct EnvironmentalSensorTypeCStruct { + uint8_t version = 0x01; + uint8_t type = 0x10; + uint8_t sensorAccuracy = 0; + float iaq = 0; + float staticIaq = 0; + float co2 = 0; + int16_t temperature = 0; + uint16_t humidity = 0; + uint16_t pressure = 0; + uint16_t voltage = 0; + }; + EnvironmentalSensorTypeCStruct m_sensor; +}; +#pragma pack(pop) + +#endif // PACKET_ENVIRONMENTAL_SENSOR_TYPE_C_H \ No newline at end of file diff --git a/src/mbedos/test-lorawan/stm32customtargets.lib b/src/mbedos/test-lorawan/stm32customtargets.lib new file mode 100644 index 0000000..7715d61 --- /dev/null +++ b/src/mbedos/test-lorawan/stm32customtargets.lib @@ -0,0 +1 @@ +https://github.com/teapotlaboratories/stm32customtargets/#648ae48049eab435aa5aac1009e85a969af599f7 diff --git a/src/mbedos/test-lorawan/trace_helper.cpp b/src/mbedos/test-lorawan/trace_helper.cpp new file mode 100644 index 0000000..a55b4f6 --- /dev/null +++ b/src/mbedos/test-lorawan/trace_helper.cpp @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * If we have tracing library available, we can see traces from within the + * stack. The library could be made unavailable by removing FEATURE_COMMON_PAL + * from the mbed_app.json to save RAM. + */ + +#include "mbed_trace.h" + +#ifdef FEA_TRACE_SUPPORT +#include "platform/PlatformMutex.h" + +/** + * Local mutex object for synchronization + */ +static PlatformMutex mutex; + +static void serial_lock(); +static void serial_unlock(); + +/** + * Sets up trace for the application + * Wouldn't do anything if the FEATURE_COMMON_PAL is not added + * or if the trace is disabled using mbed_app.json + */ +void setup_trace() +{ + // setting up Mbed trace. + mbed_trace_mutex_wait_function_set(serial_lock); + mbed_trace_mutex_release_function_set(serial_unlock); + mbed_trace_init(); +} + +/** + * Lock provided for serial printing used by trace library + */ +static void serial_lock() +{ + mutex.lock(); +} + +/** + * Releasing lock provided for serial printing used by trace library + */ +static void serial_unlock() +{ + mutex.unlock(); +} +#else +void setup_trace() +{ + +} +#endif + + diff --git a/src/mbedos/test-lorawan/trace_helper.h b/src/mbedos/test-lorawan/trace_helper.h new file mode 100644 index 0000000..ddad385 --- /dev/null +++ b/src/mbedos/test-lorawan/trace_helper.h @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APP_TRACE_HELPER_H_ +#define APP_TRACE_HELPER_H_ + +/** + * Helper function for the application to setup Mbed trace. + * It Wouldn't do anything if the FEATURE_COMMON_PAL is not added + * or if the trace is disabled using mbed_app.json + */ +void setup_trace(); + +#endif /* APP_TRACE_HELPER_H_ */