13.3.BMP280による気圧、温度計測
プロジェクト概要
BMP280を使用した気圧、温度計測プロジェクトです。データシートによると以下の精度を持ちます。
気圧±1 hPa絶対精度
温度±1.0°C精度
高度±1メートル精度
I2Cアドレスは7 ビットは 111011x です。 上位 6 ビットは固定で最後のビットでアドレスを切り替えることができます。BME280/BMP280は、SDOピンの接続に応じて、2 つの I2C アドレスを選択できます。SDO が High の場合、I2C アドレスは 0x77 です。SDO が Low の場合、I2C アドレスは 0x76 です。Adafruit BME280 モジュールでは、SDO ピンが High に引き上げられているため、初期状態ではアドレスは 0x77 になります。SDO を GND に接続して、アドレスを 0x76 に変更できます。SDO ピンをフローティングのまますることはできません。 フローティングのままにすると、I²C アドレスは未定義になります。
本プロジェクトでも、温度と気圧をLCDに表示しています。また、同時にオンボード温度センサーの値も表示しています。
部品リスト
Adafruit BMP280 I2C基板 1 マルツ
GROVEの16 x 2 LCD 1 スイッチサイエンス
配線図

ソースリスト
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/rtc.h"
#include "hardware/adc.h"
//#define PICO_DEFAULT_LED_PIN 25
#define LED_PIN PICO_DEFAULT_LED_PIN
#define I2C_PORT i2c0
#define I2C_SDA 8
#define I2C_SCL 9
//ADc
const float ConversionFactor = 3.3f / (1 << 12);
前章参照
// ------
void InitAdc()
{
adc_init();
adc_set_temp_sensor_enabled(true);
}
float ReadOnBoardTemperature()
{
adc_select_input(4);
float tempV = (float)adc_read() * ConversionFactor;
float tempC = 27.0f - (tempV - 0.706f) / 0.001721f;
return tempC;
}
//以下前章参照
void InitRtc()
//bmp280 --------------------------------
#define BMP280_ADDR 0x77
// hardware registers
#define REG_CONFIG _u(0xF5)
#define REG_CTRL_MEAS _u(0xF4)
#define REG_RESET _u(0xE0)
#define REG_TEMP_XLSB _u(0xFC)
#define REG_TEMP_LSB _u(0xFB)
#define REG_TEMP_MSB _u(0xFA)
#define REG_PRESSURE_XLSB _u(0xF9)
#define REG_PRESSURE_LSB _u(0xF8)
#define REG_PRESSURE_MSB _u(0xF7)
// calibration registers
#define REG_DIG_T1_LSB _u(0x88)
#define REG_DIG_T1_MSB _u(0x89)
#define REG_DIG_T2_LSB _u(0x8A)
#define REG_DIG_T2_MSB _u(0x8B)
#define REG_DIG_T3_LSB _u(0x8C)
#define REG_DIG_T3_MSB _u(0x8D)
#define REG_DIG_P1_LSB _u(0x8E)
#define REG_DIG_P1_MSB _u(0x8F)
#define REG_DIG_P2_LSB _u(0x90)
#define REG_DIG_P2_MSB _u(0x91)
#define REG_DIG_P3_LSB _u(0x92)
#define REG_DIG_P3_MSB _u(0x93)
#define REG_DIG_P4_LSB _u(0x94)
#define REG_DIG_P4_MSB _u(0x95)
#define REG_DIG_P5_LSB _u(0x96)
#define REG_DIG_P5_MSB _u(0x97)
#define REG_DIG_P6_LSB _u(0x98)
#define REG_DIG_P6_MSB _u(0x99)
#define REG_DIG_P7_LSB _u(0x9A)
#define REG_DIG_P7_MSB _u(0x9B)
#define REG_DIG_P8_LSB _u(0x9C)
#define REG_DIG_P8_MSB _u(0x9D)
#define REG_DIG_P9_LSB _u(0x9E)
#define REG_DIG_P9_MSB _u(0x9F)
// number of calibration registers to be read
#define NUM_CALIB_PARAMS 24
struct bmp280_calib_param {
// temperature params
uint16_t dig_t1;
int16_t dig_t2;
int16_t dig_t3;
// pressure params
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;
};
struct bmp280_calib_param Bmp280Params;
void bmp280_init() {
// use the "handheld device dynamic" optimal setting (see datasheet)
uint8_t buf[2];
// 500ms sampling time, x16 filter
const uint8_t reg_config_val = ((0x04 << 5) | (0x05 << 2)) & 0xFC;
// send register number followed by its corresponding value
buf[0] = REG_CONFIG;
buf[1] = reg_config_val;
i2c_write_blocking(I2C_PORT, BMP280_ADDR, buf, 2, false);
// osrs_t x1, osrs_p x4, normal mode operation
const uint8_t reg_ctrl_meas_val = (0x01 << 5) | (0x03 << 2) | (0x03);
buf[0] = REG_CTRL_MEAS;
buf[1] = reg_ctrl_meas_val;
i2c_write_blocking(I2C_PORT, BMP280_ADDR, buf, 2, false);
}
void bmp280_read_raw(int32_t* temp, int32_t* pressure) {
// BMP280 data registers are auto-incrementing and we have 3 temperature and
// pressure registers each, so we start at 0xF7 and read 6 bytes to 0xFC
// note: normal mode does not require further ctrl_meas and config register writes
uint8_t buf[6];
uint8_t reg = REG_PRESSURE_MSB;
i2c_write_blocking(I2C_PORT, BMP280_ADDR, ®, 1, true); // true to keep master control of bus
i2c_read_blocking(I2C_PORT, BMP280_ADDR, buf, 6, false); // false - finished with bus
// store the 20 bit read in a 32 bit signed integer for conversion
*pressure = (buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4);
*temp = (buf[3] << 12) | (buf[4] << 4) | (buf[5] >> 4);
}
void bmp280_reset() {
// reset the device with the power-on-reset procedure
uint8_t buf[2] = { REG_RESET, 0xB6 };
i2c_write_blocking(I2C_PORT, BMP280_ADDR, buf, 2, false);
}
// intermediate function that calculates the fine resolution temperature
// used for both pressure and temperature conversions
int32_t bmp280_convert(int32_t temp, struct bmp280_calib_param* params) {
// use the 32-bit fixed point compensation implementation given in the
// datasheet
int32_t var1, var2;
var1 = ((((temp >> 3) - ((int32_t)params->dig_t1 << 1))) * ((int32_t)params->dig_t2)) >> 11;
var2 = (((((temp >> 4) - ((int32_t)params->dig_t1)) * ((temp >> 4) - ((int32_t)params->dig_t1))) >> 12) * ((int32_t)params->dig_t3)) >> 14;
return var1 + var2;
}
int32_t bmp280_convert_temp(int32_t temp, struct bmp280_calib_param* params) {
// uses the BMP280 calibration parameters to compensate the temperature value read from its registers
int32_t t_fine = bmp280_convert(temp, params);
return (t_fine * 5 + 128) >> 8;
}
int32_t bmp280_convert_pressure(int32_t pressure, int32_t temp, struct bmp280_calib_param* params) {
// uses the BMP280 calibration parameters to compensate the pressure value read from its registers
int32_t t_fine = bmp280_convert(temp, params);
int32_t var1, var2;
uint32_t converted = 0.0;
var1 = (((int32_t)t_fine) >> 1) - (int32_t)64000;
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((int32_t)params->dig_p6);
var2 += ((var1 * ((int32_t)params->dig_p5)) << 1);
var2 = (var2 >> 2) + (((int32_t)params->dig_p4) << 16);
var1 = (((params->dig_p3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + ((((int32_t)params->dig_p2) * var1) >> 1)) >> 18;
var1 = ((((32768 + var1)) * ((int32_t)params->dig_p1)) >> 15);
if (var1 == 0) {
return 0; // avoid exception caused by division by zero
}
converted = (((uint32_t)(((int32_t)1048576) - pressure) - (var2 >> 12))) * 3125;
if (converted < 0x80000000) {
converted = (converted << 1) / ((uint32_t)var1);
} else {
converted = (converted / (uint32_t)var1) * 2;
}
var1 = (((int32_t)params->dig_p9) * ((int32_t)(((converted >> 3) * (converted >> 3)) >> 13))) >> 12;
var2 = (((int32_t)(converted >> 2)) * ((int32_t)params->dig_p8)) >> 13;
converted = (uint32_t)((int32_t)converted + ((var1 + var2 + params->dig_p7) >> 4));
return converted;
}
void bmp280_get_calib_params(struct bmp280_calib_param* params) {
// raw temp and pressure values need to be calibrated according to
// parameters generated during the manufacturing of the sensor
// there are 3 temperature params, and 9 pressure params, each with a LSB
// and MSB register, so we read from 24 registers
uint8_t buf[NUM_CALIB_PARAMS] = { 0 };
uint8_t reg = REG_DIG_T1_LSB;
i2c_write_blocking(I2C_PORT, BMP280_ADDR, ®, 1, true); // true to keep master control of bus
// read in one go as register addresses auto-increment
i2c_read_blocking(I2C_PORT, BMP280_ADDR, buf, NUM_CALIB_PARAMS, false); // false, we're done reading
// store these in a struct for later use
params->dig_t1 = (uint16_t)(buf[1] << 8) | buf[0];
params->dig_t2 = (int16_t)(buf[3] << 8) | buf[2];
params->dig_t3 = (int16_t)(buf[5] << 8) | buf[4];
params->dig_p1 = (uint16_t)(buf[7] << 8) | buf[6];
params->dig_p2 = (int16_t)(buf[9] << 8) | buf[8];
params->dig_p3 = (int16_t)(buf[11] << 8) | buf[10];
params->dig_p4 = (int16_t)(buf[13] << 8) | buf[12];
params->dig_p5 = (int16_t)(buf[15] << 8) | buf[14];
params->dig_p6 = (int16_t)(buf[17] << 8) | buf[16];
params->dig_p7 = (int16_t)(buf[19] << 8) | buf[18];
params->dig_p8 = (int16_t)(buf[21] << 8) | buf[20];
params->dig_p9 = (int16_t)(buf[23] << 8) | buf[22];
}
bool ReadBmp280Data(float *temperature, float *pressure)
{
int32_t temperatureInt;
int32_t pressureInt;
bmp280_read_raw(&temperatureInt, &pressureInt);
int32_t temperatureConv = bmp280_convert_temp(temperatureInt, &Bmp280Params);
int32_t pressureConv = bmp280_convert_pressure(pressureInt, temperatureInt, &Bmp280Params);
*pressure = pressureConv/100.0f;
*temperature = temperatureConv/100.0f;
return true;
}
//以下前章参照
//lcd -------------------------------
#define LCD_ADDR 0x27
int main()
{
stdio_init_all();
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
gpio_put(LED_PIN, 0);
i2c_init(I2C_PORT, 400*1000);
gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
gpio_pull_up(I2C_SDA);
gpio_pull_up(I2C_SCL);
WaitTerminalStartup(30*1000);
printf("\nTerminal connected\n");
ScanI2CBus();
printf("I2C Scan completed\n");
lcd_init();
InitAdc();
InitRtc();
bmp280_init();
bmp280_get_calib_params(&Bmp280Params);
char buf[128];
datetime_t nowdt;
int presec = -1;
while (1) {
rtc_get_datetime(&nowdt);
if(presec != nowdt.sec)
{
float bTemp = ReadOnBoardTemperature();
float bmpTemp, bmpPress;
ReadBmp280Data(&bmpTemp, &bmpPress);
sprintf(buf, "%02d:%02d:%02d %4.0fhPa", nowdt.hour,nowdt.min, nowdt.sec, bmpPress);
lcd_set_cursor(0, 0);
lcd_string(buf);
//0123456789012345
//BM:23.1 AD:12.5
sprintf(buf, "BM:%4.1f AD:%4.1f", bmpTemp, bTemp);
lcd_set_cursor(1, 0);
lcd_string(buf);
printf("%4d/%02d/%02d %02d:%02d:%02d bmp:%4.1fC adc:%4.1fC Pressure:%4.0fhPa\n", nowdt.year, nowdt.month, nowdt.day,
nowdt.hour,nowdt.min, nowdt.sec, bmpTemp, bTemp, bmpPress);
}
presec = nowdt.sec;
sleep_ms(200);
}
return 0;
}

上記ターミナル画面のように温度センサー値にくらべて5℃程度高い
ですが、変動がほぼありません。計測は安定しています。気圧も変動は少なく、
正しく計測されています。
BMP280での温度、気圧は、内部レジスタの値から補正をします。そのため、BMP280を初期化後、bmp280_get_calib_params()で補正値を得ます。
コメントをお書きください