Bringup BME688 and BME680 sensors

This changeset brings the BOSCH BME68X Sensor API library in as a submodule and makes it buildable with CMake.

A thin wrapper- the BME68X driver- provides simple init, configure, read_forced and read_parallel functions.

Two BME688 examples are available for forced-mode and parallel-mode operation.
pull/143/head
Phil Howard 2021-05-11 17:31:38 +01:00
rodzic 73d92023e9
commit 22d39faf5f
14 zmienionych plików z 388 dodań i 1 usunięć

Wyświetl plik

@ -27,6 +27,8 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: true
# Check out the Pico SDK
- name: Checkout Pico SDK
@ -62,4 +64,4 @@ jobs:
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
cmake --build . --config $BUILD_TYPE -j 2
cmake --build . --config $BUILD_TYPE -j 2

3
.gitmodules vendored
Wyświetl plik

@ -1,3 +1,6 @@
[submodule "micropython/modules/ulab"]
path = micropython/modules/ulab
url = https://github.com/v923z/micropython-ulab.git
[submodule "drivers/bme68x-sensor-api/src"]
path = drivers/bme68x/src
url = https://github.com/BoschSensortec/BME68x-Sensor-API

Wyświetl plik

@ -14,5 +14,6 @@ add_subdirectory(fatfs)
add_subdirectory(sdcard)
add_subdirectory(as7262)
add_subdirectory(bh1745)
add_subdirectory(bme68x)
add_subdirectory(button)
add_subdirectory(rgbled)

Wyświetl plik

@ -0,0 +1 @@
include(bme68x.cmake)

Wyświetl plik

@ -0,0 +1,17 @@
set(DRIVER_NAME bme68x)
add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/src/bme68x.c
${CMAKE_CURRENT_LIST_DIR}/bme68x.cpp
)
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src)
# Pull in pico libraries that we need
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_i2c pimoroni_i2c)
# 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)

Wyświetl plik

@ -0,0 +1,119 @@
#include "bme68x.hpp"
#include "pico/stdlib.h"
namespace pimoroni {
bool BME68x::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);
}
device.intf_ptr = new i2c_intf_ptr{.i2c = i2c, .address = address};
device.intf = bme68x_intf::BME68X_I2C_INTF;
device.read = (bme68x_read_fptr_t)&read_bytes;
device.write = (bme68x_write_fptr_t)&write_bytes;
device.delay_us = (bme68x_delay_us_fptr_t)&delay_us;
device.amb_temp = 20;
result = bme68x_init(&device);
bme68x_check_rslt("bme68x_init", result);
if(result != BME68X_OK) return false;
result = bme68x_get_conf(&conf, &device);
bme68x_check_rslt("bme68x_get_conf", result);
if(result != BME68X_OK) return false;
configure(BME68X_FILTER_OFF, BME68X_ODR_NONE, BME68X_OS_16X, BME68X_OS_1X, BME68X_OS_2X);
return true;
}
bool BME68x::configure(uint8_t filter, uint8_t odr, uint8_t os_humidity, uint8_t os_pressure, uint8_t os_temp) {
int8_t result;
conf.filter = filter;
conf.odr = odr;
conf.os_hum = os_humidity;
conf.os_pres = os_pressure;
conf.os_temp = os_temp;
bme68x_set_conf(&conf, &device);
bme68x_check_rslt("bme68x_set_conf", result);
if(result != BME68X_OK) return false;
return true;
}
bool BME68x::read_forced(bme68x_data *data) {
int8_t result = 0;
uint8_t n_fields;
uint32_t delay_period;
heatr_conf.enable = BME68X_ENABLE;
heatr_conf.heatr_temp = 300;
heatr_conf.heatr_dur = 100;
result = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &device);
bme68x_check_rslt("bme68x_set_heatr_conf", result);
if(result != BME68X_OK) return false;
result = bme68x_set_op_mode(BME68X_FORCED_MODE, &device);
bme68x_check_rslt("bme68x_set_op_mode", result);
if(result != BME68X_OK) return false;
delay_period = bme68x_get_meas_dur(BME68X_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 = bme68x_get_data(BME68X_FORCED_MODE, data, &n_fields, &device);
bme68x_check_rslt("bme68x_get_data", result);
if(result != BME68X_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 BME68x::read_parallel(bme68x_data *results, uint16_t *profile_temps, uint16_t *profile_durations, size_t profile_length) {
int8_t result;
bme68x_data data[3]; // Parallel & Sequential mode read 3 simultaneous fields
uint8_t n_fields;
uint32_t delay_period;
heatr_conf.enable = BME68X_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 - (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &conf, &device) / 1000);
result = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &heatr_conf, &device);
bme68x_check_rslt("bme68x_set_heatr_conf", result);
if(result != BME68X_OK) return false;
result = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &device);
bme68x_check_rslt("bme68x_set_op_mode", result);
if(result != BME68X_OK) return false;
while (1) {
delay_period = bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &conf, &device) + (heatr_conf.shared_heatr_dur * 1000);
device.delay_us(delay_period, device.intf_ptr);
result = bme68x_get_data(BME68X_PARALLEL_MODE, data, &n_fields, &device);
if(result == BME68X_W_NO_NEW_DATA) continue;
bme68x_check_rslt("bme68x_get_data", result);
if(result != BME68X_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;
}
}

Wyświetl plik

@ -0,0 +1,103 @@
#pragma once
#include "hardware/i2c.h"
#include "hardware/gpio.h"
#include "bme68x.h"
#include "bme68x_defs.h"
#include "common/pimoroni_i2c.hpp"
#include "stdio.h"
namespace pimoroni {
class BME68x {
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;
};
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(bme68x_data *data);
bool read_parallel(bme68x_data *results, uint16_t *profile_temps, uint16_t *profile_durations, size_t profile_length);
BME68x() : BME68x(new I2C()) {}
BME68x(uint8_t address, uint interrupt = PIN_UNUSED) : BME68x(new I2C(), address, interrupt) {}
BME68x(I2C *i2c, uint8_t address = DEFAULT_I2C_ADDRESS, uint interrupt = PIN_UNUSED) : i2c(i2c), address(address), interrupt(interrupt) {}
// Bindings for bme68x_dev
static int write_bytes(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) {
BME68x::i2c_intf_ptr* i2c = (BME68x::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) {
BME68x::i2c_intf_ptr* i2c = (BME68x::i2c_intf_ptr *)intf_ptr;
int result = i2c->i2c->write_blocking(i2c->address, &reg_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 BME68X API examples/common/common.c */
void bme68x_check_rslt(const char api_name[], int8_t rslt)
{
if(!debug) return;
switch (rslt)
{
case BME68X_OK:
/* Do nothing */
break;
case BME68X_E_NULL_PTR:
printf("%s: Error [%d] : Null pointer\r\n", api_name, rslt);
break;
case BME68X_E_COM_FAIL:
printf("%s: Error [%d] : Communication failure\r\n", api_name, rslt);
break;
case BME68X_E_INVALID_LENGTH:
printf("%s: Error [%d] : Incorrect length parameter\r\n", api_name, rslt);
break;
case BME68X_E_DEV_NOT_FOUND:
printf("%s: Error [%d] : Device not found\r\n", api_name, rslt);
break;
case BME68X_E_SELF_TEST:
printf("%s: Error [%d] : Self test error\r\n", api_name, rslt);
break;
case BME68X_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:
bme68x_dev device;
bme68x_conf conf;
bme68x_heatr_conf heatr_conf;
I2C *i2c;
int8_t address = DEFAULT_I2C_ADDRESS;
uint interrupt = I2C_DEFAULT_INT;
};
}

@ -0,0 +1 @@
Subproject commit a31906a455fdde92e3ce5aeaa946ee4a20af5697

Wyświetl plik

@ -13,6 +13,7 @@ add_subdirectory(breakout_trackball)
add_subdirectory(breakout_sgp30)
add_subdirectory(breakout_colourlcd240x240)
add_subdirectory(breakout_msa301)
add_subdirectory(breakout_bme688)
add_subdirectory(pico_display)
add_subdirectory(pico_unicorn)

Wyświetl plik

@ -0,0 +1,2 @@
include("${CMAKE_CURRENT_LIST_DIR}/bme688_forced.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/bme688_parallel.cmake")

Wyświetl plik

@ -0,0 +1,12 @@
set(OUTPUT_NAME bme688_forced)
add_executable(
${OUTPUT_NAME}
${OUTPUT_NAME}.cpp
)
# Pull in pico libraries that we need
target_link_libraries(${OUTPUT_NAME} pico_stdlib bme68x)
# create map/bin/hex file etc.
pico_add_extra_outputs(${OUTPUT_NAME})

Wyświetl plik

@ -0,0 +1,47 @@
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "bme68x.hpp"
#include "common/pimoroni_i2c.hpp"
/*
Read a single reading from the BME688
*/
using namespace pimoroni;
I2C i2c(BOARD::BREAKOUT_GARDEN);
BME68x bme68x(&i2c);
int main() {
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
stdio_init_all();
bme68x.init();
while (1) {
sleep_ms(1000);
auto time_abs = get_absolute_time();
auto time_ms = to_ms_since_boot(time_abs);
bme68x_data data;
auto result = bme68x.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;
}

Wyświetl plik

@ -0,0 +1,12 @@
set(OUTPUT_NAME bme688_parallel)
add_executable(
${OUTPUT_NAME}
${OUTPUT_NAME}.cpp
)
# Pull in pico libraries that we need
target_link_libraries(${OUTPUT_NAME} pico_stdlib bme68x)
# create map/bin/hex file etc.
pico_add_extra_outputs(${OUTPUT_NAME})

Wyświetl plik

@ -0,0 +1,66 @@
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "bme68x.hpp"
#include "common/pimoroni_i2c.hpp"
/*
Read a sequence of readings from the BME688 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);
BME68x bme68x(&i2c);
constexpr uint16_t profile_length = 10;
// Space for <profile_length> results
bme68x_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() {
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
stdio_init_all();
bme68x.init();
while (1) {
sleep_ms(1000);
auto time_start = get_absolute_time();
printf("Fetching %u readings, please wait...\n", profile_length);
auto result = bme68x.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;
}