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