From 2f0f6a01f2ca4d30fd9335a45c6439b62a9c8ed6 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Sat, 8 Oct 2022 20:50:08 +0200 Subject: [PATCH] Initial BMP280 integration This version uses only blocking i2c calls, and long transfers will probably cause frame loss. Also, if no BMP280 is present, the firmware will not start up and hang in an endless loop instead. --- src/bmp280.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++ src/bmp280.h | 18 ++++++ src/bmp280_comp.c | 77 ++++++++++++++++++++++ src/bmp280_comp.h | 23 +++++++ src/clock.c | 4 ++ src/main.c | 69 ++++++++++++++++++++ src/pinout.h | 7 ++ 7 files changed, 357 insertions(+) create mode 100644 src/bmp280.c create mode 100644 src/bmp280.h create mode 100644 src/bmp280_comp.c create mode 100644 src/bmp280_comp.h diff --git a/src/bmp280.c b/src/bmp280.c new file mode 100644 index 0000000..e26e70c --- /dev/null +++ b/src/bmp280.c @@ -0,0 +1,159 @@ +#include +#include + +#include "pinout.h" + +#include "bmp280_comp.h" +#include "bmp280.h" + +#define BMP280_7BIT_ADDR 0x76 + +// calibration value cache, shared with bmp280_comp.c + +extern uint16_t dig_T1; +extern int16_t dig_T2; +extern int16_t dig_T3; + +extern uint16_t dig_P1; +extern int16_t dig_P2; +extern int16_t dig_P3; +extern int16_t dig_P4; +extern int16_t dig_P5; +extern int16_t dig_P6; +extern int16_t dig_P7; +extern int16_t dig_P8; +extern int16_t dig_P9; + +static fxp_t m_last_temperature = 0; +static fxp_t m_last_pressure = 0; + +bool bmp280_init(void) +{ + // configure pins for I2C1 + gpio_set_af(BMP280_I2C_PORT, GPIO_AF4, BMP280_I2C_SCL | BMP280_I2C_SDA); + gpio_mode_setup(BMP280_I2C_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, BMP280_I2C_SCL | BMP280_I2C_SDA); + + // Set up I²C + i2c_reset(I2C1); + + i2c_set_speed(I2C1, i2c_speed_sm_100k, 48); + + i2c_peripheral_enable(I2C1); + + // Set up the Chip + uint8_t wdata[2]; + uint8_t rdata[32]; + uint8_t wsize, rsize; + + // read the chip ID + wsize = 0; + wdata[wsize++] = 0xD0; + rsize = 1; + + i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize); + + if(rdata[0] != 0x58) { + // unexpected chip id + goto err_out; + } + + // read calibration data + wsize = 0; + wdata[wsize++] = 0x88; // first calibration data register of first block + rsize = 0xA1 + 1 - 0x88; + + i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize); + + dig_T1 = (((uint16_t)rdata[ 1]) << 8) | rdata[ 0]; // 0x88, 0x89 + dig_T2 = (((int16_t)(int8_t)rdata[ 3]) << 8) | rdata[ 2]; // 0x8A, 0x8B + dig_T3 = (((int16_t)(int8_t)rdata[ 5]) << 8) | rdata[ 4]; // 0x8C, 0x8D + + dig_P1 = (((uint16_t)rdata[ 7]) << 8) | rdata[ 6]; // 0x8E, 0x8F + dig_P2 = (((int16_t)(int8_t)rdata[ 9]) << 8) | rdata[ 8]; // 0x90, 0x91 + dig_P3 = (((int16_t)(int8_t)rdata[11]) << 8) | rdata[10]; // 0x92, 0x93 + dig_P4 = (((int16_t)(int8_t)rdata[13]) << 8) | rdata[12]; // 0x94, 0x95 + dig_P5 = (((int16_t)(int8_t)rdata[15]) << 8) | rdata[14]; // 0x96, 0x97 + dig_P6 = (((int16_t)(int8_t)rdata[17]) << 8) | rdata[16]; // 0x98, 0x99 + dig_P7 = (((int16_t)(int8_t)rdata[19]) << 8) | rdata[18]; // 0x9A, 0x9B + dig_P8 = (((int16_t)(int8_t)rdata[21]) << 8) | rdata[20]; // 0x9C, 0x9D + dig_P9 = (((int16_t)(int8_t)rdata[23]) << 8) | rdata[22]; // 0x9E, 0x9F + + return true; + +err_out: + return false; +} + + +bool bmp280_start_measurement(void) +{ + uint8_t wdata[2]; + uint8_t wsize = 0; + + wdata[wsize++] = 0xF4; // measurement control register + wdata[wsize++] = (0x01 << 5) | (0x01 << 2) | 0x01; // pressure x1, temp x1, forced mode + + i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, NULL, 0); + + return true; +} + + +bool bmp280_is_measurement_complete(void) +{ + uint8_t wdata[1]; + uint8_t rdata[1]; + uint8_t wsize = 0, rsize; + + wdata[wsize++] = 0xF3; // status register + rsize = 1; + + i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize); + + if((rdata[0] & 0x09) != 0) { + // either "measuring" or "im_update" is active, so measurement is not complete yet. + return false; + } else { + return true; + } +} + + +bool bmp280_readout_and_compensate(void) +{ + uint8_t wdata[1]; + uint8_t rdata[8]; + uint8_t wsize = 0, rsize; + + wdata[wsize++] = 0xF7; + rsize = 8; + + i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize); + + int32_t press_raw = + ((int32_t)rdata[0] << 12) // press_msb (0xF7) + | ((int32_t)rdata[1] << 4) // press_lsb (0xF8) + | ((int32_t)rdata[2] >> 4); // press_xlsb (0xF9) + + int32_t temp_raw = + ((int32_t)rdata[3] << 12) // temp_msb (0xFA) + | ((int32_t)rdata[4] << 4) // temp_lsb (0xFB) + | ((int32_t)rdata[5] >> 4); // temp_xlsb (0xFC) + + m_last_temperature = bmp280_comp_temperature(temp_raw); + m_last_pressure = bmp280_comp_pressure(press_raw); + + return true; +} + + +fxp_t bmp280_get_temperature(void) +{ + return m_last_temperature; +} + + +fxp_t bmp280_get_pressure(void) +{ + return m_last_pressure; +} diff --git a/src/bmp280.h b/src/bmp280.h new file mode 100644 index 0000000..9837eae --- /dev/null +++ b/src/bmp280.h @@ -0,0 +1,18 @@ +#ifndef BMP280_H +#define BMP280_H + +#include +#include + +#include + +bool bmp280_init(void); + +bool bmp280_start_measurement(void); +bool bmp280_is_measurement_complete(void); +bool bmp280_readout_and_compensate(void); + +fxp_t bmp280_get_temperature(void); +fxp_t bmp280_get_pressure(void); + +#endif // BMP280_H diff --git a/src/bmp280_comp.c b/src/bmp280_comp.c new file mode 100644 index 0000000..a13f2e3 --- /dev/null +++ b/src/bmp280_comp.c @@ -0,0 +1,77 @@ +/* BMP280 value compensation code, from the datasheet. */ + +#include "bmp280_comp.h" + +// calibration value cache. Values are set externally in bmp280.c + +uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; + +uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; + + +typedef int32_t BMP280_S32_t; +typedef uint32_t BMP280_U32_t; +typedef int64_t BMP280_S64_t; + + +// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. +// t_fine carries fine temperature as global value +BMP280_S32_t t_fine; + +static BMP280_S32_t bmp280_compensate_T_int32(BMP280_S32_t adc_T) +{ + BMP280_S32_t var1, var2, T; + var1 = ((((adc_T>>3) - ((BMP280_S32_t)dig_T1<<1))) * ((BMP280_S32_t)dig_T2)) >> 11; + var2 = (((((adc_T>>4) - ((BMP280_S32_t)dig_T1)) * ((adc_T>>4) - ((BMP280_S32_t)dig_T1))) >> 12) * ((BMP280_S32_t)dig_T3)) >> 14; + t_fine = var1 + var2; + T = (t_fine * 5 + 128) >> 8; + return T; +} + +// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits). +// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa +static BMP280_U32_t bmp280_compensate_P_int64(BMP280_S32_t adc_P) +{ + BMP280_S64_t var1, var2, p; + var1 = ((BMP280_S64_t)t_fine) - 128000; + var2 = var1 * var1 * (BMP280_S64_t)dig_P6; + var2 = var2 + ((var1*(BMP280_S64_t)dig_P5)<<17); + var2 = var2 + (((BMP280_S64_t)dig_P4)<<35); + var1 = ((var1 * var1 * (BMP280_S64_t)dig_P3)>>8) + ((var1 * (BMP280_S64_t)dig_P2)<<12); + var1 = (((((BMP280_S64_t)1)<<47)+var1))*((BMP280_S64_t)dig_P1)>>33; + if (var1 == 0) { + return 0; // avoid exception caused by division by zero + } + p = 1048576-adc_P; + p = (((p<<31)-var2)*3125)/var1; + var1 = (((BMP280_S64_t)dig_P9) * (p>>13) * (p>>13)) >> 25; + var2 = (((BMP280_S64_t)dig_P8) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((BMP280_S64_t)dig_P7)<<4); + return (BMP280_U32_t)p; +} + + + +fxp_t bmp280_comp_temperature(int32_t adc) +{ + int32_t compensated = bmp280_compensate_T_int32(adc); + + return ((fxp_t)compensated << POINTPOS) / 100L; +} + +fxp_t bmp280_comp_pressure(int32_t adc) +{ + uint32_t compensated = bmp280_compensate_P_int64(adc); + + return (fxp_t)(((uint64_t)compensated << (POINTPOS - 8L)) / 100); +} diff --git a/src/bmp280_comp.h b/src/bmp280_comp.h new file mode 100644 index 0000000..0ea3dd8 --- /dev/null +++ b/src/bmp280_comp.h @@ -0,0 +1,23 @@ +#ifndef BMP280_COMP_H +#define BMP280_COMP_H + +#include + +#include + +/*!@brief Calculate temperature from BMP280 raw sensor value. + * @returns The temperature in °C. + */ +fxp_t bmp280_comp_temperature(int32_t adc); + +/*!@brief Calculate relative humidity from BMP280 raw sensor value. + * @returns The relative humidity in %. + */ +fxp_t bmp280_comp_humidity(int32_t adc); + +/*!@brief Calculate pressure from BMP280 raw sensor value. + * @returns The pressure in hPa. + */ +fxp_t bmp280_comp_pressure(int32_t adc); + +#endif // BMP280_COMP_H diff --git a/src/clock.c b/src/clock.c index 59eac28..e7d2cb3 100644 --- a/src/clock.c +++ b/src/clock.c @@ -1,6 +1,7 @@ #include #include "clock.h" +#include "libopencm3/stm32/f0/rcc.h" void init_clock(void) { @@ -26,6 +27,9 @@ void init_clock(void) // DMA1 is used for ADC data transfer rcc_periph_clock_enable(RCC_DMA1); + + // I2C1 is used for communication with the BMP280 + rcc_periph_clock_enable(RCC_I2C1); } diff --git a/src/main.c b/src/main.c index bb7a1d3..1e1ed76 100644 --- a/src/main.c +++ b/src/main.c @@ -16,12 +16,19 @@ #include "power_switch.h" #include "measurement.h" #include "deepsleep.h" +#include "bmp280.h" #include "pinout.h" #include "config.h" volatile int wait_frame = 1; +static enum { + BMP280_NOT_PRESENT, + BMP280_IDLE, + BMP280_MEASURING, + BMP280_READOUT +} bmp280_state; /* Set up systick to fire freq times per second */ static void init_systick(int freq) @@ -160,6 +167,18 @@ static void report_status(struct MeasurementResult *meas_data) rs485_enqueue(":"); rs485_enqueue(charge_control_is_discharge_blocked() ? "1" : "0"); + rs485_enqueue(":"); + + if(bmp280_state != BMP280_NOT_PRESENT) { + fxp_format(bmp280_get_temperature(), number, 2); + rs485_enqueue(number); + rs485_enqueue(":"); + + fxp_format(bmp280_get_pressure(), number, 2); + rs485_enqueue(number); + } else { + rs485_enqueue("-1:-1"); + } rs485_enqueue("\n"); } @@ -198,6 +217,19 @@ static void report_averaged(struct MeasurementResult *meas_data) rs485_enqueue(":"); rs485_enqueue(charge_control_is_discharge_blocked() ? "1" : "0"); + rs485_enqueue(":"); + + if(bmp280_state != BMP280_NOT_PRESENT) { + fxp_format(bmp280_get_temperature(), number, 2); + rs485_enqueue(number); + rs485_enqueue(":"); + + fxp_format(bmp280_get_pressure(), number, 2); + rs485_enqueue(number); + } else { + rs485_enqueue("-1:-1"); + } + rs485_enqueue("\n"); } @@ -223,6 +255,34 @@ static void low_power_mode(uint32_t duration_sec) } +static void handle_bmp280(uint64_t timebase_ms) +{ + switch(bmp280_state) { + case BMP280_NOT_PRESENT: + // do nothing + break; + + case BMP280_IDLE: + if(timebase_ms % 1000 == 137) { + bmp280_start_measurement(); + bmp280_state = BMP280_MEASURING; + } + break; + + case BMP280_MEASURING: + if(timebase_ms % 10 == 0 && bmp280_is_measurement_complete()) { + bmp280_state = BMP280_READOUT; + } + break; + + case BMP280_READOUT: + bmp280_readout_and_compensate(); + bmp280_state = BMP280_IDLE; + break; + } +} + + int main(void) { //uint32_t cpuload = 0; @@ -249,6 +309,12 @@ int main(void) led_chplex_init(); led_chplex_on(LED_CHPLEX_IDX_SOLAR_ON); + if(bmp280_init()) { + bmp280_state = BMP280_IDLE; + } else { + bmp280_state = BMP280_NOT_PRESENT; + } + init_systick(1000); rs485_enqueue("LNSC-2420 v" VERSION " initialized.\n"); @@ -283,6 +349,9 @@ int main(void) report_averaged(&meas_data); } + // update BMP280 + handle_bmp280(timebase_ms); + measurement_wait_for_completion(); measurement_finalize(&meas_data); diff --git a/src/pinout.h b/src/pinout.h index 492001d..84a24eb 100644 --- a/src/pinout.h +++ b/src/pinout.h @@ -43,4 +43,11 @@ #define RS485_TX_PIN GPIO6 #define RS485_RX_PIN GPIO7 +/* BMP280 I²C */ + +#define BMP280_I2C_PORT GPIOA + +#define BMP280_I2C_SCL GPIO9 +#define BMP280_I2C_SDA GPIO10 + #endif // PINOUT_H