From b8d0f36caf1a179bd8e931a3188ec8f7c203f67a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 13 Mar 2025 10:21:39 +0000 Subject: [PATCH] BME69X: Add C driver and MicroPython bindings. This is just a straight copy and paste of BME68X, dropping in the BME69X Sensor API and wishing upon a star that it works. --- .gitmodules | 3 + drivers/CMakeLists.txt | 3 +- drivers/bme69x/CMakeLists.txt | 1 + drivers/bme69x/bme69x.cmake | 15 +++ drivers/bme69x/bme69x.cpp | 121 ++++++++++++++++++ drivers/bme69x/bme69x.hpp | 108 ++++++++++++++++ drivers/bme69x/src | 1 + examples/CMakeLists.txt | 1 + examples/breakout_bme690/CMakeLists.txt | 2 + examples/breakout_bme690/bme690_forced.cmake | 12 ++ examples/breakout_bme690/bme690_forced.cpp | 49 +++++++ .../breakout_bme690/bme690_parallel.cmake | 12 ++ examples/breakout_bme690/bme690_parallel.cpp | 68 ++++++++++ .../modules/breakout_bme69x/breakout_bme69x.c | 99 ++++++++++++++ .../breakout_bme69x/breakout_bme69x.cpp | 105 +++++++++++++++ .../modules/breakout_bme69x/breakout_bme69x.h | 13 ++ .../modules/breakout_bme69x/micropython.cmake | 21 +++ .../modules/breakout_bme69x/micropython.mk | 13 ++ .../micropython-common-breakouts.cmake | 1 + 19 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 drivers/bme69x/CMakeLists.txt create mode 100644 drivers/bme69x/bme69x.cmake create mode 100644 drivers/bme69x/bme69x.cpp create mode 100644 drivers/bme69x/bme69x.hpp create mode 160000 drivers/bme69x/src create mode 100644 examples/breakout_bme690/CMakeLists.txt create mode 100644 examples/breakout_bme690/bme690_forced.cmake create mode 100644 examples/breakout_bme690/bme690_forced.cpp create mode 100644 examples/breakout_bme690/bme690_parallel.cmake create mode 100644 examples/breakout_bme690/bme690_parallel.cpp create mode 100644 micropython/modules/breakout_bme69x/breakout_bme69x.c create mode 100644 micropython/modules/breakout_bme69x/breakout_bme69x.cpp create mode 100644 micropython/modules/breakout_bme69x/breakout_bme69x.h create mode 100644 micropython/modules/breakout_bme69x/micropython.cmake create mode 100644 micropython/modules/breakout_bme69x/micropython.mk diff --git a/.gitmodules b/.gitmodules index b1f258d2..0228faca 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "drivers/mlx90640/src"] path = drivers/mlx90640/src url = https://github.com/melexis/mlx90640-library +[submodule "drivers/bme69x/src"] + path = drivers/bme69x/src + url = https://github.com/boschsensortec/BME690_SensorAPI diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 45ec8d5a..8ee7e0f6 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -44,4 +44,5 @@ add_subdirectory(st7567) add_subdirectory(psram_display) add_subdirectory(shiftregister) add_subdirectory(inky73) -add_subdirectory(mlx90640) \ No newline at end of file +add_subdirectory(mlx90640) +add_subdirectory(bme69x) \ No newline at end of file diff --git a/drivers/bme69x/CMakeLists.txt b/drivers/bme69x/CMakeLists.txt new file mode 100644 index 00000000..5515d576 --- /dev/null +++ b/drivers/bme69x/CMakeLists.txt @@ -0,0 +1 @@ +include(bme69x.cmake) \ No newline at end of file diff --git a/drivers/bme69x/bme69x.cmake b/drivers/bme69x/bme69x.cmake new file mode 100644 index 00000000..0668cded --- /dev/null +++ b/drivers/bme69x/bme69x.cmake @@ -0,0 +1,15 @@ +set(DRIVER_NAME bme69x) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/bme69x.c + ${CMAKE_CURRENT_LIST_DIR}/bme69x.cpp +) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src) + + +# We can't control the uninitialized result variables in the BME68X API +# so demote unitialized to a warning for this target. +target_compile_options(${DRIVER_NAME} INTERFACE -Wno-error=uninitialized) \ No newline at end of file diff --git a/drivers/bme69x/bme69x.cpp b/drivers/bme69x/bme69x.cpp new file mode 100644 index 00000000..4de1c167 --- /dev/null +++ b/drivers/bme69x/bme69x.cpp @@ -0,0 +1,121 @@ +#include "bme69x.hpp" +#include "pico/stdlib.h" + +namespace pimoroni { + bool BME69X::init() { + int8_t result = 0; + + if(interrupt != PIN_UNUSED) { + gpio_set_function(interrupt, GPIO_FUNC_SIO); + gpio_set_dir(interrupt, GPIO_IN); + gpio_pull_up(interrupt); + } + + i2c_interface.i2c = i2c; + i2c_interface.address = address; + + device.intf_ptr = &i2c_interface; + device.intf = bme69x_intf::BME69X_I2C_INTF; + device.read = (bme69x_read_fptr_t)&read_bytes; + device.write = (bme69x_write_fptr_t)&write_bytes; + device.delay_us = (bme69x_delay_us_fptr_t)&delay_us; + device.amb_temp = 20; + + result = bme69x_init(&device); + bme69x_check_rslt("bme69x_init", result); + if(result != BME69X_OK) return false; + + result = bme69x_get_conf(&conf, &device); + bme69x_check_rslt("bme69x_get_conf", result); + if(result != BME69X_OK) return false; + + configure(BME69X_FILTER_OFF, BME69X_ODR_NONE, BME69X_OS_16X, BME69X_OS_1X, BME69X_OS_2X); + + return true; + } + + bool BME69X::configure(uint8_t filter, uint8_t odr, uint8_t os_humidity, uint8_t os_pressure, uint8_t os_temp) { + int8_t result = 0; + + conf.filter = filter; + conf.odr = odr; + conf.os_hum = os_humidity; + conf.os_pres = os_pressure; + conf.os_temp = os_temp; + + bme69x_set_conf(&conf, &device); + bme69x_check_rslt("bme69x_set_conf", result); + if(result != BME69X_OK) return false; + + return true; + } + + bool BME69X::read_forced(bme69x_data *data, uint16_t heater_temp, uint16_t heater_duration) { + int8_t result = 0; + uint8_t n_fields; + uint32_t delay_period; + + heatr_conf.enable = BME69X_ENABLE; + heatr_conf.heatr_temp = heater_temp; + heatr_conf.heatr_dur = heater_duration; + result = bme69x_set_heatr_conf(BME69X_FORCED_MODE, &heatr_conf, &device); + bme69x_check_rslt("bme69x_set_heatr_conf", result); + if(result != BME69X_OK) return false; + + result = bme69x_set_op_mode(BME69X_FORCED_MODE, &device); + bme69x_check_rslt("bme69x_set_op_mode", result); + if(result != BME69X_OK) return false; + + delay_period = bme69x_get_meas_dur(BME69X_FORCED_MODE, &conf, &device) + (heatr_conf.heatr_dur * 1000); + // Could probably just call sleep_us here directly, I guess the API uses this internally + device.delay_us(delay_period, device.intf_ptr); + + result = bme69x_get_data(BME69X_FORCED_MODE, data, &n_fields, &device); + bme69x_check_rslt("bme69x_get_data", result); + if(result != BME69X_OK) return false; + + return true; + } + + /* + Will read profile_length results with the given temperatures and duration multipliers into the results array. + Blocks until it has a valid result for each temp/duration, and returns the entire set in the given order. + */ + bool BME69X::read_parallel(bme69x_data *results, uint16_t *profile_temps, uint16_t *profile_durations, size_t profile_length) { + int8_t result; + bme69x_data data[3]; // Parallel & Sequential mode read 3 simultaneous fields + uint8_t n_fields; + uint32_t delay_period; + + heatr_conf.enable = BME69X_ENABLE; + heatr_conf.heatr_temp_prof = profile_temps; + heatr_conf.heatr_dur_prof = profile_durations; + heatr_conf.profile_len = profile_length; + heatr_conf.shared_heatr_dur = 140 - (bme69x_get_meas_dur(BME69X_PARALLEL_MODE, &conf, &device) / 1000); + result = bme69x_set_heatr_conf(BME69X_PARALLEL_MODE, &heatr_conf, &device); + bme69x_check_rslt("bme69x_set_heatr_conf", result); + if(result != BME69X_OK) return false; + + result = bme69x_set_op_mode(BME69X_PARALLEL_MODE, &device); + bme69x_check_rslt("bme69x_set_op_mode", result); + if(result != BME69X_OK) return false; + + while (1) { + delay_period = bme69x_get_meas_dur(BME69X_PARALLEL_MODE, &conf, &device) + (heatr_conf.shared_heatr_dur * 1000); + device.delay_us(delay_period, device.intf_ptr); + + result = bme69x_get_data(BME69X_PARALLEL_MODE, data, &n_fields, &device); + if(result == BME69X_W_NO_NEW_DATA) continue; + bme69x_check_rslt("bme69x_get_data", result); + if(result != BME69X_OK) return false; + + for(auto i = 0u; i < n_fields; i++) { + results[data[i].gas_index] = data[i]; + + if(data[i].gas_index == profile_length - 1) return true; + } + } + + return true; + } +} \ No newline at end of file diff --git a/drivers/bme69x/bme69x.hpp b/drivers/bme69x/bme69x.hpp new file mode 100644 index 00000000..49a6eecc --- /dev/null +++ b/drivers/bme69x/bme69x.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "hardware/i2c.h" +#include "hardware/gpio.h" +#include "bme69x.h" +#include "bme69x_defs.h" +#include "common/pimoroni_i2c.hpp" +#include "stdio.h" + +namespace pimoroni { + class BME69X { + public: + static const uint8_t DEFAULT_I2C_ADDRESS = 0x76; + static const uint8_t ALTERNATE_I2C_ADDRESS = 0x77; + + struct i2c_intf_ptr { + I2C *i2c; + int8_t address; + }; + + i2c_intf_ptr i2c_interface; + + bool debug = true; + + bool init(); + bool configure(uint8_t filter, uint8_t odr, uint8_t os_humidity, uint8_t os_pressure, uint8_t os_temp); + bool read_forced(bme69x_data *data, uint16_t heater_temp=300, uint16_t heater_duration=100); + bool read_parallel(bme69x_data *results, uint16_t *profile_temps, uint16_t *profile_durations, size_t profile_length); + + BME69X() : BME69X(new I2C()) {} + BME69X(uint8_t address, uint interrupt = PIN_UNUSED) : BME69X(new I2C(), address, interrupt) {} + BME69X(I2C *i2c, uint8_t address = DEFAULT_I2C_ADDRESS, uint interrupt = PIN_UNUSED) : i2c(i2c), address(address), interrupt(interrupt) {} + + I2C *get_i2c() {return i2c;} + uint get_int() {return PIN_UNUSED;} + + // Bindings for bme69x_dev + static int write_bytes(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) { + BME69X::i2c_intf_ptr* i2c = (BME69X::i2c_intf_ptr *)intf_ptr; + + uint8_t buffer[length + 1]; + buffer[0] = reg_addr; + for(auto x = 0u; x < length; x++) { + buffer[x + 1] = reg_data[x]; + } + + int result = i2c->i2c->write_blocking(i2c->address, buffer, length + 1, false); + + return result == PICO_ERROR_GENERIC ? 1 : 0; + }; + + static int read_bytes(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) { + BME69X::i2c_intf_ptr* i2c = (BME69X::i2c_intf_ptr *)intf_ptr; + + int result = i2c->i2c->write_blocking(i2c->address, ®_addr, 1, true); + result = i2c->i2c->read_blocking(i2c->address, reg_data, length, false); + + return result == PICO_ERROR_GENERIC ? 1 : 0; + }; + + static void delay_us(uint32_t period, void *intf_ptr) { + sleep_us(period); + } + + /* From BME69X API examples/common/common.c */ + void bme69x_check_rslt(const char api_name[], int8_t rslt) + { + if(!debug) return; + switch (rslt) + { + case BME69X_OK: + /* Do nothing */ + break; + case BME69X_E_NULL_PTR: + printf("%s: Error [%d] : Null pointer\r\n", api_name, rslt); + break; + case BME69X_E_COM_FAIL: + printf("%s: Error [%d] : Communication failure\r\n", api_name, rslt); + break; + case BME69X_E_INVALID_LENGTH: + printf("%s: Error [%d] : Incorrect length parameter\r\n", api_name, rslt); + break; + case BME69X_E_DEV_NOT_FOUND: + printf("%s: Error [%d] : Device not found\r\n", api_name, rslt); + break; + case BME69X_E_SELF_TEST: + printf("%s: Error [%d] : Self test error\r\n", api_name, rslt); + break; + case BME69X_W_NO_NEW_DATA: + printf("%s: Warning [%d] : No new data found\r\n", api_name, rslt); + break; + default: + printf("%s: Error [%d] : Unknown error code\r\n", api_name, rslt); + break; + } + } + + private: + bme69x_dev device; + bme69x_conf conf; + bme69x_heatr_conf heatr_conf; + + I2C *i2c; + + int8_t address = DEFAULT_I2C_ADDRESS; + uint interrupt = I2C_DEFAULT_INT; + }; +} \ No newline at end of file diff --git a/drivers/bme69x/src b/drivers/bme69x/src new file mode 160000 index 00000000..4381e37a --- /dev/null +++ b/drivers/bme69x/src @@ -0,0 +1 @@ +Subproject commit 4381e37a6dceacf0dcb79df2143793062f8eab56 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6a84630e..d4efa237 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(breakout_trackball) add_subdirectory(breakout_sgp30) add_subdirectory(breakout_colourlcd240x240) add_subdirectory(breakout_msa301) +add_subdirectory(breakout_bme690) add_subdirectory(breakout_bme688) add_subdirectory(breakout_bmp280) add_subdirectory(breakout_bme280) diff --git a/examples/breakout_bme690/CMakeLists.txt b/examples/breakout_bme690/CMakeLists.txt new file mode 100644 index 00000000..4cd081e1 --- /dev/null +++ b/examples/breakout_bme690/CMakeLists.txt @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/bme690_forced.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/bme690_parallel.cmake") \ No newline at end of file diff --git a/examples/breakout_bme690/bme690_forced.cmake b/examples/breakout_bme690/bme690_forced.cmake new file mode 100644 index 00000000..0ef7df7d --- /dev/null +++ b/examples/breakout_bme690/bme690_forced.cmake @@ -0,0 +1,12 @@ +set(OUTPUT_NAME bme690_forced) + +add_executable( + ${OUTPUT_NAME} + ${OUTPUT_NAME}.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_i2c pimoroni_i2c bme69x) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_bme690/bme690_forced.cpp b/examples/breakout_bme690/bme690_forced.cpp new file mode 100644 index 00000000..f4a17cd0 --- /dev/null +++ b/examples/breakout_bme690/bme690_forced.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "pico/stdlib.h" + +#include "bme69x.hpp" +#include "common/pimoroni_i2c.hpp" + +/* +Read a single reading from the BME690 +*/ + +using namespace pimoroni; + +I2C i2c(BOARD::BREAKOUT_GARDEN); +BME69X bme69x(&i2c); + +int main() { +#ifdef PICO_DEFAULT_LED_PIN + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); +#endif + + stdio_init_all(); + + bme69x.init(); + + while (1) { + sleep_ms(1000); + auto time_abs = get_absolute_time(); + auto time_ms = to_ms_since_boot(time_abs); + + bme69x_data data; + + auto result = bme69x.read_forced(&data); + (void)result; + + printf("%lu, %.2f, %.2f, %.2f, %.2f, 0x%x, %d, %d\n", + (long unsigned int)time_ms, + data.temperature, + data.pressure, + data.humidity, + data.gas_resistance, + data.status, + data.gas_index, + data.meas_index); + } + + return 0; +} diff --git a/examples/breakout_bme690/bme690_parallel.cmake b/examples/breakout_bme690/bme690_parallel.cmake new file mode 100644 index 00000000..91a48a20 --- /dev/null +++ b/examples/breakout_bme690/bme690_parallel.cmake @@ -0,0 +1,12 @@ +set(OUTPUT_NAME bme690_parallel) + +add_executable( + ${OUTPUT_NAME} + ${OUTPUT_NAME}.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_i2c pimoroni_i2c bme69x) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_bme690/bme690_parallel.cpp b/examples/breakout_bme690/bme690_parallel.cpp new file mode 100644 index 00000000..8cd64d5b --- /dev/null +++ b/examples/breakout_bme690/bme690_parallel.cpp @@ -0,0 +1,68 @@ +#include +#include +#include "pico/stdlib.h" + +#include "bme69x.hpp" +#include "common/pimoroni_i2c.hpp" + +/* +Read a sequence of readings from the BME690 with given heat/duration profiles +Reading the full batch of readings will take some time. This seems to take ~10sec. +*/ + +using namespace pimoroni; + +I2C i2c(BOARD::BREAKOUT_GARDEN); +BME69X bme69x(&i2c); + +constexpr uint16_t profile_length = 10; + +// Space for results +bme69x_data data[profile_length]; + +/* Heater temperature in degree Celsius */ +uint16_t temps[profile_length] = { 320, 100, 100, 100, 200, 200, 200, 320, 320, 320 }; + +/* Multiplier to the shared heater duration */ +uint16_t durations[profile_length] = { 5, 2, 10, 30, 5, 5, 5, 5, 5, 5 }; + + +int main() { +#ifdef PICO_DEFAULT_LED_PIN + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); +#endif + + stdio_init_all(); + + bme69x.init(); + + while (1) { + sleep_ms(1000); + auto time_start = get_absolute_time(); + + printf("Fetching %u readings, please wait...\n", profile_length); + + auto result = bme69x.read_parallel(data, temps, durations, profile_length); + (void)result; + + auto time_end = get_absolute_time(); + auto duration = absolute_time_diff_us(time_start, time_end); + auto time_ms = to_ms_since_boot(time_start); + + printf("Done at %lu in %lluus\n", (long unsigned int)time_ms, (long long unsigned int)duration); + + for(auto i = 0u; i < 10u; i++){ + printf("%d, %d: %.2f, %.2f, %.2f, %.2f, 0x%x\n", + data[i].gas_index, + data[i].meas_index, + data[i].temperature, + data[i].pressure, + data[i].humidity, + data[i].gas_resistance, + data[i].status); + } + } + + return 0; +} diff --git a/micropython/modules/breakout_bme69x/breakout_bme69x.c b/micropython/modules/breakout_bme69x/breakout_bme69x.c new file mode 100644 index 00000000..df9c87fd --- /dev/null +++ b/micropython/modules/breakout_bme69x/breakout_bme69x.c @@ -0,0 +1,99 @@ +#include "breakout_bme69x.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// BreakoutBME69X Class +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Methods *****/ +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutBME69X_read_obj, 1, BreakoutBME69X_read); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutBME69X_configure_obj, 1, BreakoutBME69X_configure); + +/***** Binding of Methods *****/ +static const mp_rom_map_elem_t BreakoutBME69X_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&BreakoutBME69X_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_configure), MP_ROM_PTR(&BreakoutBME69X_configure_obj) }, +}; +static MP_DEFINE_CONST_DICT(BreakoutBME69X_locals_dict, BreakoutBME69X_locals_dict_table); + +/***** Class Definition *****/ +#ifdef MP_DEFINE_CONST_OBJ_TYPE +MP_DEFINE_CONST_OBJ_TYPE( + breakout_bme69x_BreakoutBME69X_type, + MP_QSTR_BreakoutBME69X, + MP_TYPE_FLAG_NONE, + make_new, BreakoutBME69X_make_new, + locals_dict, (mp_obj_dict_t*)&BreakoutBME69X_locals_dict +); +#else +const mp_obj_type_t breakout_bme69x_BreakoutBME69X_type = { + { &mp_type_type }, + .name = MP_QSTR_BreakoutBME69X, + .make_new = BreakoutBME69X_make_new, + .locals_dict = (mp_obj_dict_t*)&BreakoutBME69X_locals_dict, +}; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// breakout_bme69x Module +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Globals Table *****/ +static const mp_map_elem_t breakout_bme69x_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_breakout_bme69x) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BreakoutBME69X), (mp_obj_t)&breakout_bme69x_BreakoutBME69X_type }, + + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_OFF), MP_ROM_INT(BME69X_FILTER_OFF) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_1), MP_ROM_INT(BME69X_FILTER_SIZE_1) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_3), MP_ROM_INT(BME69X_FILTER_SIZE_3) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_7), MP_ROM_INT(BME69X_FILTER_SIZE_7) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_15), MP_ROM_INT(BME69X_FILTER_SIZE_15) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_31), MP_ROM_INT(BME69X_FILTER_SIZE_31) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_63), MP_ROM_INT(BME69X_FILTER_SIZE_63) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_COEFF_127), MP_ROM_INT(BME69X_FILTER_SIZE_127) }, + + { MP_ROM_QSTR(MP_QSTR_NO_OVERSAMPLING), MP_ROM_INT(BME69X_OS_NONE) }, + { MP_ROM_QSTR(MP_QSTR_OVERSAMPLING_1X), MP_ROM_INT(BME69X_OS_1X) }, + { MP_ROM_QSTR(MP_QSTR_OVERSAMPLING_2X), MP_ROM_INT(BME69X_OS_2X) }, + { MP_ROM_QSTR(MP_QSTR_OVERSAMPLING_4X), MP_ROM_INT(BME69X_OS_4X) }, + { MP_ROM_QSTR(MP_QSTR_OVERSAMPLING_8X), MP_ROM_INT(BME69X_OS_8X) }, + { MP_ROM_QSTR(MP_QSTR_OVERSAMPLING_16X), MP_ROM_INT(BME69X_OS_16X) }, + +/* TODO add MicroPython support for alternate reading modes? + { MP_ROM_QSTR(MP_QSTR_SLEEP_MODE), MP_ROM_INT(BME69X_SLEEP_MODE) }, + { MP_ROM_QSTR(MP_QSTR_FORCED_MODE), MP_ROM_INT(BME69X_FORCED_MODE) }, + { MP_ROM_QSTR(MP_QSTR_PARALLEL_MODE), MP_ROM_INT(BME69X_PARALLEL_MODE) }, + { MP_ROM_QSTR(MP_QSTR_SEQUENTIAL_MODE), MP_ROM_INT(BME69X_SEQUENTIAL_MODE) }, +*/ + + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_OFF), MP_ROM_INT(BME69X_ODR_NONE) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_0_59_MS), MP_ROM_INT(BME69X_ODR_0_59_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_62_5_MS), MP_ROM_INT(BME69X_ODR_62_5_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_125_MS), MP_ROM_INT(BME69X_ODR_125_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_250_MS), MP_ROM_INT(BME69X_ODR_250_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_500_MS), MP_ROM_INT(BME69X_ODR_500_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_1000_MS), MP_ROM_INT(BME69X_ODR_1000_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_10_MS), MP_ROM_INT(BME69X_ODR_10_MS) }, + { MP_ROM_QSTR(MP_QSTR_STANDBY_TIME_20_MS), MP_ROM_INT(BME69X_ODR_20_MS) }, + + { MP_ROM_QSTR(MP_QSTR_STATUS_GAS_VALID), MP_ROM_INT(BME69X_GASM_VALID_MSK) }, + { MP_ROM_QSTR(MP_QSTR_STATUS_HEATER_STABLE), MP_ROM_INT(BME69X_HEAT_STAB_MSK) }, + + { MP_ROM_QSTR(MP_QSTR_I2C_ADDRESS_DEFAULT), MP_ROM_INT(BME69X_I2C_ADDR_LOW) }, + { MP_ROM_QSTR(MP_QSTR_I2C_ADDRESS_ALT), MP_ROM_INT(BME69X_I2C_ADDR_HIGH) }, +}; +static MP_DEFINE_CONST_DICT(mp_module_breakout_bme69x_globals, breakout_bme69x_globals_table); + +/***** Module Definition *****/ +const mp_obj_module_t breakout_bme69x_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_breakout_bme69x_globals, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#if MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_breakout_bme69x, breakout_bme69x_user_cmodule, MODULE_BREAKOUT_BME69X_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_breakout_bme69x, breakout_bme69x_user_cmodule); +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/micropython/modules/breakout_bme69x/breakout_bme69x.cpp b/micropython/modules/breakout_bme69x/breakout_bme69x.cpp new file mode 100644 index 00000000..a20dbe9a --- /dev/null +++ b/micropython/modules/breakout_bme69x/breakout_bme69x.cpp @@ -0,0 +1,105 @@ +#include "drivers/bme69x/bme69x.hpp" +#include "micropython/modules/util.hpp" + + +using namespace pimoroni; + +extern "C" { +#include "breakout_bme69x.h" +#include "pimoroni_i2c.h" + + +/***** Variables Struct *****/ +typedef struct _breakout_bme69x_BreakoutBME69X_obj_t { + mp_obj_base_t base; + BME69X *breakout; + _PimoroniI2C_obj_t *i2c; +} breakout_bme69x_BreakoutBME69X_obj_t; + +/***** Constructor *****/ +mp_obj_t BreakoutBME69X_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + breakout_bme69x_BreakoutBME69X_obj_t *self = nullptr; + + enum { ARG_i2c, ARG_address, ARG_int }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_i2c, MP_ARG_OBJ, {.u_obj = nullptr} }, + { MP_QSTR_address, MP_ARG_INT, {.u_int = BME69X::DEFAULT_I2C_ADDRESS} }, + { MP_QSTR_interrupt, MP_ARG_INT, {.u_int = PIN_UNUSED} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self = m_new_obj(breakout_bme69x_BreakoutBME69X_obj_t); + self->base.type = &breakout_bme69x_BreakoutBME69X_type; + + self->i2c = PimoroniI2C_from_machine_i2c_or_native(args[ARG_i2c].u_obj); + + self->breakout = m_new_class(BME69X, (pimoroni::I2C *)(self->i2c->i2c), args[ARG_address].u_int, args[ARG_int].u_int); + + if(!self->breakout->init()) { + mp_raise_msg(&mp_type_RuntimeError, "BreakoutBME69X: breakout not found when initialising"); + } + + return MP_OBJ_FROM_PTR(self); +} + +mp_obj_t BreakoutBME69X_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_temp, ARG_duration }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_heater_temp, MP_ARG_INT, { .u_int=300 } }, + { MP_QSTR_heater_duration, MP_ARG_INT, { .u_int=100 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_bme69x_BreakoutBME69X_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_bme69x_BreakoutBME69X_obj_t); + + bme69x_data result; + if(self->breakout->read_forced(&result, args[ARG_temp].u_int, args[ARG_duration].u_int)){ + mp_obj_t tuple[7]; + tuple[0] = mp_obj_new_float(result.temperature); + tuple[1] = mp_obj_new_float(result.pressure); + tuple[2] = mp_obj_new_float(result.humidity); + tuple[3] = mp_obj_new_float(result.gas_resistance); + tuple[4] = mp_obj_new_int(result.status); + tuple[5] = mp_obj_new_int(result.gas_index); + tuple[6] = mp_obj_new_int(result.meas_index); + return mp_obj_new_tuple(7, tuple); + } + else { + mp_raise_msg(&mp_type_RuntimeError, "BreakoutBME69X: failed read_forced"); + return mp_const_none; + } +} + +mp_obj_t BreakoutBME69X_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_filter, ARG_standby_time, ARG_os_pressure, ARG_os_temp, ARG_os_humidity }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_filter, MP_ARG_INT, { .u_int=BME69X_FILTER_SIZE_3 } }, + { MP_QSTR_standby_time, MP_ARG_INT, { .u_int=BME69X_ODR_0_59_MS } }, + { MP_QSTR_os_pressure, MP_ARG_INT, { .u_int=BME69X_OS_16X } }, + { MP_QSTR_os_temp, MP_ARG_INT, { .u_int=BME69X_OS_2X } }, + { MP_QSTR_os_humidity, MP_ARG_INT, { .u_int=BME69X_OS_1X } } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_bme69x_BreakoutBME69X_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_bme69x_BreakoutBME69X_obj_t); + self->breakout->configure( + args[ARG_filter].u_int, + args[ARG_standby_time].u_int, + args[ARG_os_humidity].u_int, + args[ARG_os_pressure].u_int, + args[ARG_os_temp].u_int + ); + + return mp_const_none; +} + +} \ No newline at end of file diff --git a/micropython/modules/breakout_bme69x/breakout_bme69x.h b/micropython/modules/breakout_bme69x/breakout_bme69x.h new file mode 100644 index 00000000..823f87a3 --- /dev/null +++ b/micropython/modules/breakout_bme69x/breakout_bme69x.h @@ -0,0 +1,13 @@ +// Include MicroPython API. +#include "py/runtime.h" +#include "drivers/bme69x/src/bme69x_defs.h" + +/***** Constants *****/ + +/***** Extern of Class Definition *****/ +extern const mp_obj_type_t breakout_bme69x_BreakoutBME69X_type; + +/***** Extern of Class Methods *****/ +extern mp_obj_t BreakoutBME69X_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t BreakoutBME69X_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutBME69X_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); \ No newline at end of file diff --git a/micropython/modules/breakout_bme69x/micropython.cmake b/micropython/modules/breakout_bme69x/micropython.cmake new file mode 100644 index 00000000..714adeb0 --- /dev/null +++ b/micropython/modules/breakout_bme69x/micropython.cmake @@ -0,0 +1,21 @@ +set(MOD_NAME breakout_bme69x) +string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER) +add_library(usermod_${MOD_NAME} INTERFACE) + +target_sources(usermod_${MOD_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp +) + +include(drivers/bme69x/bme69x) +target_link_libraries(usermod_${MOD_NAME} INTERFACE bme69x) + +target_include_directories(usermod_${MOD_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_${MOD_NAME} INTERFACE + MODULE_${MOD_NAME_UPPER}_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_${MOD_NAME}) \ No newline at end of file diff --git a/micropython/modules/breakout_bme69x/micropython.mk b/micropython/modules/breakout_bme69x/micropython.mk new file mode 100644 index 00000000..54679ef2 --- /dev/null +++ b/micropython/modules/breakout_bme69x/micropython.mk @@ -0,0 +1,13 @@ +set(MOD_NAME breakout_bme69x) +BREAKOUT_MOD_DIR := $(USERMOD_DIR) + +# Add our source files to the respective variables. +SRC_USERMOD += $(BREAKOUT_MOD_DIR)/${MOD_NAME}.c +SRC_USERMOD_CXX += $(BREAKOUT_MOD_DIR)/${MOD_NAME}.cpp + +# Add our module directory to the include path. +CFLAGS_USERMOD += -I$(BREAKOUT_MOD_DIR) +CXXFLAGS_USERMOD += -I$(BREAKOUT_MOD_DIR) + +# We use C++ features so have to link against the standard library. +LDFLAGS_USERMOD += -lstdc++ \ No newline at end of file diff --git a/micropython/modules/micropython-common-breakouts.cmake b/micropython/modules/micropython-common-breakouts.cmake index 60ee990e..7766aeb2 100644 --- a/micropython/modules/micropython-common-breakouts.cmake +++ b/micropython/modules/micropython-common-breakouts.cmake @@ -15,6 +15,7 @@ include(breakout_rtc/micropython) include(breakout_trackball/micropython) include(breakout_sgp30/micropython) include(breakout_bh1745/micropython) +include(breakout_bme69x/micropython) include(breakout_bme68x/micropython) include(breakout_bme280/micropython) include(breakout_bmp280/micropython)