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.
This commit is contained in:
Thomas Kolb 2022-10-08 20:50:08 +02:00
parent 2275edb475
commit 2f0f6a01f2
7 changed files with 357 additions and 0 deletions

159
src/bmp280.c Normal file
View file

@ -0,0 +1,159 @@
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/gpio.h>
#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;
}

18
src/bmp280.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef BMP280_H
#define BMP280_H
#include <stdbool.h>
#include <stdint.h>
#include <fxp.h>
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

77
src/bmp280_comp.c Normal file
View file

@ -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);
}

23
src/bmp280_comp.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef BMP280_COMP_H
#define BMP280_COMP_H
#include <stdint.h>
#include <fxp.h>
/*!@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

View file

@ -1,6 +1,7 @@
#include <libopencm3/stm32/rcc.h>
#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);
}

View file

@ -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);

View file

@ -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