Close

eCV and BSEC

A project log for Electronic CV

Yet another overly engineered business card, that probably I would never give a way, because it is too expensive.

dimitarDimitar 07/12/2022 at 19:170 Comments

Hello all, it has been a while! 

If you are following this project, you know that I would like to make the eCV a little bit more useful. To accomplish this, I have added a gas sensor called BME680. Cool little gadget. It reports on temperature, humidity, pressure and also indoor air quality index (from now on called IAQ).

This sensor is from BOSCH. They were very kind to provide drivers for it. You can find them at their git hub repo  [here] .

Before we start you can find my example code and the modified BOSCH libraries at my repo [here].

Part 1 - Get the BME68x running on I2C

First get the provided library from the repo [BME68x-Sensor-API]. There is just one source file and two headers to integrate. It is straight forward process. I have chosen to use I2C interface and 0x77 address for my project (the address is configured in hardware). What is needed are three functions: reading/writing to the sensor and a delay function in microseconds. 

    airSensorAddr      = BME68X_I2C_ADDR_HIGH;
    airSensor.read     = user_i2c_read;
    airSensor.write    = user_i2c_write;
    airSensor.delay_us = user_delay_us;
    airSensor.intf     = BME68X_I2C_INTF;
    airSensor.intf_ptr = &airSensorAddr;
    airSensor.amb_temp = 25;
    
    airSensorResult = bme68x_init(&airSensor);
    if (airSensorResult != BME68X_OK)
    {
        while(1);
    }

    airSensorConf.filter  = BME68X_FILTER_OFF;
    airSensorConf.odr     = BME68X_ODR_NONE;
    airSensorConf.os_hum  = BME68X_OS_16X;
    airSensorConf.os_pres = BME68X_OS_1X;
    airSensorConf.os_temp = BME68X_OS_2X;

    airSensorResult = bme68x_set_conf(&airSensorConf, &airSensor);
    if (airSensorResult != BME68X_OK)
    {
        while(1);
    }

    /* Check if rslt == BME68X_OK, report or handle if otherwise */
    heaterConf.enable     = BME68X_ENABLE;
    heaterConf.heatr_temp = 300;
    heaterConf.heatr_dur  = 100;

    airSensorResult = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heaterConf, &airSensor);

You should test your interface functions by reading the sensor ID. When you are sure that everything is running correctly you call a few functions to configure the sensor. The above values are taken from the examples making a basic setup. 

Part 2 - Getting the BSEC library to run

This part is harder. You only need this if you want the VOC, CO2, IAQ index. If you are happy with temperature, humidity and air pressure, you do not need bsec. First thing first, get acquainted with [Bosch Software Environmental Cluster - Integration Guide].  Next get the library's binary from [github]. Be careful though, you have to choose the correct one for your MCU. I know right? Not an open source this one :( 

Note: I am going to use the bsec2 

The interface functions provided for the bsec2 are written in C++, so I made C version you can get from my repo. The choice is yours. They are not pretty, but it works okay.

A visual aid is in order. This gem is directly taken from the integration guide. 

You can think of the BSEC as a black box. It has input from real world sensors and virtual sensors as outputs. For an example: you feed it raw data from the BME860 such as temp, humidity, pressure and air resistance and you get IAQ, CO2 and VOC index.

In essence it does some voodoo, and it is very very strict the way it does it. You do not get to configure many things. In fact you get provided two configurations for the BSEC library... like a BOSCH. 

One is called FieldAir_HandSanitizer and the other is Default_H2S_NonH2S. Very catchy. 

Things you may mess up: Using bsec configurations with bsec2 library - this is a no go. The bsec configs are quite smaller than bsec2. The first ones start with [0,8,4,1,...] and the later ones start with [1,6,0,2...]. Check bsec_get_version to be sure.

    statusBSEC = bsec_init();
    
    if (BSEC_OK != statusBSEC)
    {
        return 1;
    }

    statusBSEC = bsec_get_version(&version);
    
    if (BSEC_OK != statusBSEC)
    {
        return 1;
    }

    statusBSEC = bsec_set_configuration(FieldAir_HandSanitizer_config, BSEC_MAX_PROPERTY_BLOB_SIZE, workBuffer, BSEC_MAX_WORKBUFFER_SIZE);

    if (BSEC_OK != statusBSEC)
    {
        return 1;
    }

The other thing you are allowed to choose is how often you get the virtual sensors updated. Ultra low power (ULP) - every 300s,  Low Power (LP) - every 3s , then it comes HIGH PERFORMANCE what ever this is and finally ULP ON DEMAND - which I guess is whenever you want as long as it is 300s apart.

    bsecSensor virtualSensorList[] = {
        BSEC_OUTPUT_RAW_TEMPERATURE,
        BSEC_OUTPUT_RAW_PRESSURE,
        BSEC_OUTPUT_RAW_HUMIDITY,
        BSEC_OUTPUT_RAW_GAS,
        BSEC_OUTPUT_IAQ,                           
        BSEC_OUTPUT_STATIC_IAQ,                             /*!< Unscaled indoor-air-quality estimate */ 
        BSEC_OUTPUT_CO2_EQUIVALENT,                         /*!< co2 equivalent estimate [ppm] */   
        BSEC_OUTPUT_BREATH_VOC_EQUIVALENT
    };

    BSEC2_updateSubscription(virtualSensorList, ARRAY_LEN(virtualSensorList), BSEC_SAMPLE_RATE_LP);

Things you may mess up: you need to choose the right combo of virtual sensor and sample rates. If you get a error from updateSubscription choose another set of sensors or play around with different sample rates. 

As we said BSEC is very particular in the way it likes things to run. So what it does is, it tells you how to configure the BME680. You can think of it like updating the structures airSensorConf and heaterConf from step 1. An then it tells you after how long you should call it with data you got from the sensor, so it does it's thing. 

    uint8_t nFieldsLeft = 0;
    bme68xData data;
    uint64_t currTimeNs = (uint64_t)BSEC2_getTimeMs() * INT64_C(1000000);
    airSensorOpMode = bmeConf.op_mode;
    int8_t bmeStat = 0;

    if (currTimeNs >= bmeConf.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 */
        statusBSEC = bsec_sensor_control(currTimeNs, &bmeConf);

        if (statusBSEC != BSEC_OK)
        {
            return 1;
        }

        switch (bmeConf.op_mode)
        {
            case BME68X_FORCED_MODE:
                BSEC2_setBme68xConfigForced();
                break;

            case BME68X_PARALLEL_MODE:
                if (airSensorOpMode != bmeConf.op_mode)
                {
                    BSEC2_setBme68xConfigParallel();
                }
                break;

            case BME68X_SLEEP_MODE:
                if (airSensorOpMode != bmeConf.op_mode)
                {
                    bmeStat = bme68x_set_op_mode(BME68X_SLEEP_MODE, airSensorPtr);
                    airSensorOpMode = BME68X_SLEEP_MODE;
                }
                break;
        }

        if (BME68X_OK != bmeStat)
        {
            return 1;
        }

        if (bmeConf.trigger_measurement && bmeConf.op_mode != BME68X_SLEEP_MODE)
        {
            nFields = 0;
            bmeStat = bme68x_get_data(bmeConf.op_mode, sensorDataArr, &nFields, airSensorPtr);
            iFields = 0;

            if (BME68X_OK == bmeStat)
            {
                do
                {
                    nFieldsLeft = getData(&data);
                    /* check for valid gas data */
                    if (data.status & BME68X_GASM_VALID_MSK)
                    {
                        if (!BSEC2_processData(currTimeNs, &data))
                        {
                            return 1;
                        }
                    }
                } while (nFieldsLeft);
            }
        }
    }

Things you may mess up: Getting your timings wrong. Don't be late! Not calling the bsec library on time will make it faulty. Not using the sensor settings provided from bsec will get you false results. 

After all this is done you can find the data you so much desired in outputs (in my case). You get things like sensor_id, signal or accuracy. 

Things you may mess up: the accuracy in the beginning will be 0. The sensor might need 5 or 10 minutes to get calibrated. Yeah bummer.  

Every time you power up your device it need to run for 10 minutes, that is not cool especially if you are running on batteries. The good people from BOSCH have provided a solution for this, but you will need a non volatile memory to store the state of the bsec2 library. The methods in question are bsec_get_state and bsec_set_state.

This is from me everybody, thank you for your time. As always I am looking forward to your questions and comments. 

Cheers,

Dimitar

Discussions