You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

347 lines
11 KiB
C++

#include "FreeRTOS.h"
#include "gpio.h"
#include "main.h"
#include "semphr.h"
#include "spi.h"
#include "stm32l1xx_hal_flash_ex.h"
#include "task.h"
#include <cstring>
#include "BME68x-Sensor-API/bme68x.h"
#include "BSEC/bsec_interface.h"
#include "SSDSpiInterface.hpp"
#include "oled-driver/Renderer.hpp"
extern QueueHandle_t spiMutex;
extern void waitForSpiFinished();
extern Renderer renderer;
extern void initDisplay();
namespace
{
uint8_t txBuffer[512 + 1];
constexpr auto HeaterProfileLength = 1;
constexpr auto TemperatureOffset = 7.0f;
constexpr auto MaximumChars = 22 * 4;
char buffer[MaximumChars];
struct bme68x_dev bmeSensor;
struct bme68x_conf bmeConf;
struct bme68x_heatr_conf bmeHeaterConf;
struct bme68x_data bmeData[3];
uint8_t numberOfData;
// Heater temperature in degree Celsius
uint16_t temperatureProfile[HeaterProfileLength] = {320};
// Heating duration in milliseconds
uint16_t durationProfile[HeaterProfileLength] = {150};
constexpr uint8_t numberRequestedVirtualSensors = 4;
bsec_sensor_configuration_t requestedVirtualSensors[numberRequestedVirtualSensors];
float iaq, temperature, humidity, co2Equivalent;
uint8_t iaqAccuracy, co2Accuracy;
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE];
uint8_t workBuffer[BSEC_MAX_WORKBUFFER_SIZE];
constexpr uintptr_t EepromAddress = FLASH_EEPROM_BASE;
//--------------------------------------------------------------------------------------------------
void setChipSelect(bool state)
{
HAL_GPIO_WritePin(VocSensorCS_GPIO_Port, VocSensorCS_Pin,
state ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
//--------------------------------------------------------------------------------------------------
// SPI read function map
BME68X_INTF_RET_TYPE bme68x_spi_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, void *)
{
xSemaphoreTake(spiMutex, portMAX_DELAY);
setChipSelect(true);
HAL_SPI_Transmit_DMA(UsedSpiPeripherie, &reg_addr, 1);
waitForSpiFinished();
HAL_SPI_Receive_DMA(UsedSpiPeripherie, reg_data, len);
waitForSpiFinished();
setChipSelect(false);
xSemaphoreGive(spiMutex);
return 0;
}
//--------------------------------------------------------------------------------------------------
// SPI write function map
BME68X_INTF_RET_TYPE bme68x_spi_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len,
void *)
{
if (len > 512)
return 1;
txBuffer[0] = reg_addr;
std::memcpy(&txBuffer[1], reg_data, len);
xSemaphoreTake(spiMutex, portMAX_DELAY);
setChipSelect(true);
HAL_SPI_Transmit_DMA(UsedSpiPeripherie, const_cast<uint8_t *>(txBuffer), len + 1);
waitForSpiFinished();
setChipSelect(false);
xSemaphoreGive(spiMutex);
return 0;
}
//--------------------------------------------------------------------------------------------------
// Delay function maps
void bme68x_delay_us(uint32_t period, void *)
{
vTaskDelay(period / 1000);
}
//--------------------------------------------------------------------------------------------------
void bme68x_spi_init(struct bme68x_dev *bme)
{
if (bme == NULL)
return;
bme->read = bme68x_spi_read;
bme->write = bme68x_spi_write;
bme->intf = BME68X_SPI_INTF;
bme->delay_us = bme68x_delay_us;
bme->amb_temp = 25; /* The ambient temperature in deg C is used for defining the heater
temperature */
}
//--------------------------------------------------------------------------------------------------
void bmeSensorInit()
{
bme68x_spi_init(&bmeSensor);
bme68x_init(&bmeSensor);
bme68x_get_conf(&bmeConf, &bmeSensor);
bmeConf.os_hum = BME68X_OS_16X;
bmeConf.os_temp = BME68X_OS_2X;
bmeConf.os_pres = BME68X_OS_1X;
bmeConf.filter = BME68X_FILTER_OFF;
bmeConf.odr = BME68X_ODR_NONE;
bme68x_set_conf(&bmeConf, &bmeSensor);
bmeHeaterConf.enable = BME68X_ENABLE;
bmeHeaterConf.heatr_temp_prof = temperatureProfile;
bmeHeaterConf.heatr_dur_prof = durationProfile;
bmeHeaterConf.profile_len = HeaterProfileLength;
bme68x_set_heatr_conf(BME68X_SEQUENTIAL_MODE, &bmeHeaterConf, &bmeSensor);
bme68x_set_op_mode(BME68X_SEQUENTIAL_MODE, &bmeSensor);
bsec_init();
// create 3 virtual sensor
requestedVirtualSensors[0].sensor_id = BSEC_OUTPUT_IAQ;
requestedVirtualSensors[0].sample_rate = BSEC_SAMPLE_RATE_LP;
requestedVirtualSensors[1].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
requestedVirtualSensors[1].sample_rate = BSEC_SAMPLE_RATE_LP;
requestedVirtualSensors[2].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
requestedVirtualSensors[2].sample_rate = BSEC_SAMPLE_RATE_LP;
requestedVirtualSensors[3].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
requestedVirtualSensors[3].sample_rate = BSEC_SAMPLE_RATE_LP;
// Allocate a struct for the returned physical sensor settings
bsec_sensor_configuration_t requiredSensorSettings[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t numberRequiredSensorSettings = BSEC_MAX_PHYSICAL_SENSOR;
// Call bsec_update_subscription() to enable/disable the requested virtual sensors
bsec_update_subscription(requestedVirtualSensors, numberRequestedVirtualSensors,
requiredSensorSettings, &numberRequiredSensorSettings);
}
//--------------------------------------------------------------------------------------------------
void bmeRun()
{
uint32_t delayInUs = bme68x_get_meas_dur(BME68X_SEQUENTIAL_MODE, &bmeConf, &bmeSensor) +
(bmeHeaterConf.heatr_dur_prof[0] * 1000);
vTaskDelay(delayInUs / 1000);
auto status = bme68x_get_data(BME68X_SEQUENTIAL_MODE, bmeData, &numberOfData, &bmeSensor);
}
//--------------------------------------------------------------------------------------------------
void bsecRun()
{
if (!(bmeData[numberOfData - 1].status & BME68X_NEW_DATA_MSK))
return;
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t nInputs = 0;
int64_t currentTimeInNs = xTaskGetTickCount() * int64_t(1'000'000);
inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE;
inputs[nInputs].signal = bmeData[numberOfData - 1].temperature / 100.0f;
inputs[nInputs].time_stamp = currentTimeInNs;
nInputs++;
inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY;
inputs[nInputs].signal = bmeData[numberOfData - 1].humidity / 1000.0f;
inputs[nInputs].time_stamp = currentTimeInNs;
nInputs++;
inputs[nInputs].sensor_id = BSEC_INPUT_PRESSURE;
inputs[nInputs].signal = bmeData[numberOfData - 1].pressure;
inputs[nInputs].time_stamp = currentTimeInNs;
nInputs++;
inputs[nInputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[nInputs].signal = bmeData[numberOfData - 1].gas_resistance;
inputs[nInputs].time_stamp = currentTimeInNs;
nInputs++;
inputs[nInputs].sensor_id = BSEC_INPUT_HEATSOURCE;
inputs[nInputs].signal = TemperatureOffset;
inputs[nInputs].time_stamp = currentTimeInNs;
uint8_t nOutputs = 0;
nOutputs = BSEC_NUMBER_OUTPUTS;
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
auto status = bsec_do_steps(inputs, nInputs, outputs, &nOutputs);
if (status != BSEC_OK)
return;
if (nOutputs > 0)
{
for (uint8_t i = 0; i < nOutputs; i++)
{
switch (outputs[i].sensor_id)
{
case BSEC_OUTPUT_IAQ:
iaq = outputs[i].signal;
iaqAccuracy = outputs[i].accuracy;
break;
case BSEC_OUTPUT_CO2_EQUIVALENT:
co2Equivalent = outputs[i].signal;
co2Accuracy = outputs[i].accuracy;
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
temperature = outputs[i].signal;
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
humidity = outputs[i].signal;
break;
default:
break;
}
}
}
}
//--------------------------------------------------------------------------------------------------
void printBmeSensorData()
{
renderer.clearAll();
const auto MaxTextWidth = renderer.getLineWidth("1000hPa");
snprintf(buffer, MaximumChars, "%d°C\n%luhPa\n%d%%\nAcc: %d",
static_cast<int>(temperature), //
bmeData[numberOfData - 1].pressure / 100, //
static_cast<int>(humidity), //
iaqAccuracy);
renderer.print({128, 0}, buffer, Renderer::Alignment::Right);
renderer.drawVerticalLine(OledWidth - MaxTextWidth - 2, 0, OledPages - 1);
if (iaqAccuracy == 0)
snprintf(buffer, MaximumChars, "IAQ:---\n----ppm\n");
else
snprintf(buffer, MaximumChars, "IAQ:%d\n%dppm\n",
static_cast<int>(iaq), //
static_cast<int>(co2Equivalent));
renderer.print({0, 0}, buffer, Renderer::Alignment::Left, 2);
renderer.render();
}
//--------------------------------------------------------------------------------------------------
void readStateFromEeprom()
{
uint8_t sizeOfData = *reinterpret_cast<uint8_t *>(EepromAddress);
if (sizeOfData != BSEC_MAX_STATE_BLOB_SIZE)
return;
// Existing state in EEPROM
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++)
{
bsecState[i] = *reinterpret_cast<uint8_t *>(EepromAddress + i + 1);
}
bsec_set_state(bsecState, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, sizeof(workBuffer));
}
//--------------------------------------------------------------------------------------------------
void writeStateToEeprom()
{
// only write calibrated state to EEPROM
if (iaqAccuracy != 3)
return;
uint32_t numberSerializedState = BSEC_MAX_STATE_BLOB_SIZE;
auto status = bsec_get_state(0, bsecState, BSEC_MAX_STATE_BLOB_SIZE, workBuffer,
BSEC_MAX_STATE_BLOB_SIZE, &numberSerializedState);
if (status != BSEC_OK)
return;
HAL_FLASHEx_DATAEEPROM_Unlock();
// write state array size
HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, EepromAddress,
BSEC_MAX_STATE_BLOB_SIZE);
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++)
{
HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, EepromAddress + 1 + i,
bsecState[i]);
}
HAL_FLASHEx_DATAEEPROM_Lock();
}
} // namespace
//--------------------------------------------------------------------------------------------------
extern "C" void sensorTask(void *)
{
initDisplay();
bmeSensorInit();
readStateFromEeprom();
constexpr auto TaskDelay = 10;
constexpr auto SaveContentDelay = 12 * 60 * 60 * 1000;
constexpr auto SaveContentDelayCounts = SaveContentDelay / TaskDelay;
uint32_t counter = 0;
while (1)
{
bmeRun();
bsecRun();
printBmeSensorData();
if (counter++ >= SaveContentDelayCounts)
{
counter = 0;
initDisplay();
writeStateToEeprom();
}
vTaskDelay(pdMS_TO_TICKS(TaskDelay));
}
}