BMP388 pressure sensor

Hi all!

Newbie on this forum! I have been playing with my Arduino Uno for a while and I managed to interface with a DHT22 and BMP388 sensor and print values on a LCD. Using the libraries it worked out as planned. But now I want to challenge my understanding of the sensors and general interfacing with SPI/I2C a bit more.

I wanted to use just the Wire.h library reading out data from the BMP388. I managed to read out the device ID and I managed to write to registers and read the values back. But to read out the 24bit data as a burst read(as suggested by the datasheet). Challenged me a bit too much. I would like to find out what is the best way to tackle this "problem". I know this sensor might be a bit too complex to start with. If there is any other suggestions for a slightly easier sensor with I2C or SPI, I am more than happy to take in any suggestions.

Thanks for reading!

Post your best attempt at the code, using code tags as described in "How to use this forum", and explain what goes wrong.

I am sorry. Maybe my question was not really clear. I have code that works including the library perfectly fine. But to enhance the learning process (especially in coding) I try to send the commands over I2C message by message. Which gives me some good feedback if I look on the serial monitor and using Wire.h
Now I find the BMP388 a relatively complex sensor.
My question is, without giving me hints how to code. I see that its a 24 bit sensor with data in 3 different registers. The datasheet suggests to do a so called "burst read" and not to read the data byte by byte because of the possibility of faulty readings. What would be your suggestion? Is there anyone with more knowledge about this sensor?
I have tried to look in the libraries. But those are still a bit hard to figure out, even though I am able to use them.

With I2C you have no choice but to read the data byte by byte. The data sheet may be suggesting to use an autoincrement address option for reading sequential bytes, but I have no interest in reading it.

Perhaps you are confusing I2C with SPI transfers, which use an entirely different protocol, different pins and different internal Arduino hardware.

Thank you for your reply. In the datasheet there is something written about auto increment of the register if a certain message is send. If I am not wrong it should be able to do so with quite a standard follow up of messages. See chapter 5.2.2. of the datasheet. Now what I want to try is to shift in bytes in the right order, but that part I am still trying to figure out.

BST-BMP388-DS001-01.pdf (1.96 MB)

Example of using I2C autoincrement to read three 16 bit values from an inertial sensor:

Wire.beginTransmission(MPU_addr1);
  Wire.write(0x3B);  //send starting register address
  Wire.endTransmission(false); //restart for read
  Wire.requestFrom(MPU_addr1, 6, true); //get six bytes accelerometer data

  xa = Wire.read() << 8 | Wire.read();
  ya = Wire.read() << 8 | Wire.read();
  za = Wire.read() << 8 | Wire.read();

Hi rmvansomeren,

It took me quite a while to work out what the BMP388 was doing, as the datasheet was somewhat vague.

The issue with using the I2C Wire library, is that its standard buffer size is 32 bytes. This limits the number of bytes that can be read in a single burst from the barometer's FIFO. Moreover, this is further complicated by the fact that if the burst is terminated before all the FIFO data is read, the BMP388 moves it's FIFO address pointer back to the head of the last temperature/pressure measurement whose transmission was interrupted. This incomplete transfer is then resent on the next FIFO read.

This situation actually increases the complexity of the microcontroller's BMP388 receiver, as it also has to track back to account for the fact that the barometer's FIFO address pointer has been moved. An easier way is to either use SPI that can handle the 512 bytes of data in a single transfer, or alternatively use I2C to continously read individual temperature and pressure measurements until the BMP388's FIFO is empty. This method however requires the overhead of continously sending the I2C address of the device for each temperature and pressure read, but it does simplify the code.

I've implemented this simplified approach in my BMP388_DEV library: GitHub - MartinL1/BMP388_DEV: An Arduino compatible, non-blocking, I2C/SPI library for the Bosch BMP388 barometer. Includes both interrupt and FIFO operation..

Hi all,

Thank you for your replies!

I managed to get all the calibration data for both pressure and temperature measurements.
And I also managed to get data out of the temp and baro registers. What I read in the datasheet is the fact that you can do a burst read of the data registers. This way I receive 3 bytes of data. For the moment I am not using the FIFO. Its a bit too complicated for my level op programming. Now I get 24 bits of raw data. My next question is how can I make it usable values? How do I apply the calibration coefficients to my raw data?

Martin L I used your library and it works perfectly. This coding is purely to enhance my coding skills an my understanding of this sensor and I2C. The problem with SPI is the fact that my breakout board from QC robots is mainly configures for I2C. I asked for them to clearify the header pins on the breakout board. But the only thing I got as a reply is the Datasheet of the sensor and the pinouts of the I2C connector...

Thanks for helping me in the right direction!

So I managed to do my Calibration coefficient calculations and now I am trying to output the temperature. At the moment I have to put the heater on since the output is -78.xx degrees. As I am debugging I see that the first byte of data is always 0x00. Which to me seems highly unlikely for a 24 bit sensor.
Below is a piece of my code where I try to read out the 3 data registers starting from register 0x07.

Wire.requestFrom(BaroSensAdd, 3);// request 1 byte from the BMP388
  if (Wire.available() <= 3) {//if the byte is available
    Data1 = Wire.read();//get byte 1
    Data2 = Wire.read();//get byte 2
    Data3 = Wire.read();//get byte 3
    Serial.println("Byte 1");// print text
    Serial.println(Data1, HEX); // show byte 1
    Serial.println("Byte 2");// print text
    Serial.println(Data2, HEX);// show byte 2
    Serial.println("Byte 3");// print text
    Serial.println(Data3, HEX);//show byte 3
    CombinedData = (Data3 << 16) | (Data2 << 8) | (Data1); //
    Serial.println("Combined Data: ");
    Serial.println(CombinedData, 10);
    CombinedRawData = CombinedData;
    Serial.println("Combined data double");
    Serial.println(CombinedRawData);
    CompensateTemp();
    delay(5000);//delay
  }

This won’t work unless the variables are declared as long:

    CombinedData = (Data3 << 16) | (Data2 << 8) | (Data1);

Thanks! That seemed to be the issue. I got some quite accurate readings now. Is there any reason why it needs a long? Just for learning purposes?

P.s. now I am moving on to pressure measurements

So a new issue presents. I get a pressure reading of 7235584 in long format on the serial port. After calibration with the temperature reading and calibration formulas from the datasheet I get a reading of 578215.43 which seems a bit too much for a pressure reading. I checked all the calibration parameters and formulas. See the code

void CompensatePress() {
  float PartialData_P1 = Parameter_P6 * T_lin;
  float PartialData_P2 = Parameter_P7 * T_lin * T_lin;
  float PartialData_P3 = Parameter_P8 * T_lin * T_lin * T_lin;
  float Partial_out_1 = Parameter_P5 + PartialData_P1 + PartialData_P2 + PartialData_P3;
  PartialData_P1 = Parameter_P2 * T_lin;
  PartialData_P2 = Parameter_P3 * T_lin * T_lin;
  PartialData_P3 = Parameter_P4 * T_lin * T_lin * T_lin;
  float Partial_out_2 = (float)CombinedPressData * (Parameter_P1 + PartialData_P1 + PartialData_P2 + PartialData_P3);;
  PartialData_P1 = (float)CombinedPressData * (float)CombinedPressData;
  PartialData_P2 = Parameter_P9 + Parameter_P10 * T_lin;
  PartialData_P3 = PartialData_P1 * PartialData_P2;
  float PartialData_P4 = PartialData_P3 + (float)CombinedPressData * (float)CombinedPressData * (float)CombinedPressData * Parameter_P11;
  float Pressure_2 = Partial_out_1 + Partial_out_2 + PartialData_P4;
  Serial.println("Pressure");
  Serial.println(Pressure_2);
}

What am I missing here...T_lin is the temperature reading after calibration.
Hope you can help me out!

Is there any reason why it needs a long

Of course there is. With the default integer variable of 16 bits, this operation always results in zero.

(Data3 << 16)

What am I missing here.

You forgot to post all the code. No one can make any sense of that useless snippet.

ok thank you for your explanation!

I did not want to throw just 400 lines of code “over the fence”. Please keep in mind I am still learning so I know the code can be done in a way more efficient way. But that will come later.
Anyway here is my complete code: I am sorry I had to remove a few lines because of the fact I exceeded 9000 characters:

byte ChipIDByte = 0;
byte MSBpressData = 0;
byte MidPressData = 0;
byte SensorStatus = 0;
byte ErrorStatus = 0;
byte OSRRegData = 0;
byte FifoConfigStatus = 0;
byte PwrCtrlStatus = 0;

long Data1 = 0;
long Data2 = 0;
long Data3 = 0;
long Data4 = 0;
long Data5 = 0;
long Data6 = 0;
/*
   Bytes to store pressure and temperature calibration data
*/
byte NVM_PAR_T1_1 = 0;
byte NVM_PAR_T1_2 = 0;
byte NVM_PAR_T2_1 = 0;
byte NVM_PAR_T2_2 = 0;
byte NVM_PAR_T3 = 0;
byte NVM_PAR_P1_1 = 0;
byte NVM_PAR_P1_2 = 0;
byte NVM_PAR_P2_1 = 0;
byte NVM_PAR_P2_2 = 0;
byte NVM_PAR_P3 = 0;
byte NVM_PAR_P4 = 0;
byte NVM_PAR_P5_1 = 0;
byte NVM_PAR_P5_2 = 0;
byte NVM_PAR_P6_1 = 0;
byte NVM_PAR_P6_2 = 0;
byte NVM_PAR_P7 = 0;
byte NVM_PAR_P8 = 0;
byte NVM_PAR_P9_1 = 0;
byte NVM_PAR_P9_2 = 0;
byte NVM_PAR_P10 = 0;
byte NVM_PAR_P11 = 0;

unsigned int CombinedData = 0;
//unsigned int NVM_PAR_T1_COMBINED = 0;
//unsigned int NVM_PAR_T2_COMBINED = 0;
float NVM_PAR_T1_COMBINED = 0.0;
float NVM_PAR_T2_COMBINED = 0.0;
//float NVM_PAR_T3 = 0.0;
/*
   Combined bytes of pressure calibration data
*/
unsigned int NVM_PAR_P1_COMBINED = 0;
unsigned int NVM_PAR_P2_COMBINED = 0;
unsigned int NVM_PAR_P5_COMBINED = 0;
unsigned int NVM_PAR_P6_COMBINED = 0;
unsigned int NVM_PAR_P9_COMBINED = 0;

float Parameter_T1;
float Parameter_T2;
float Parameter_T3;
float Parameter_P1;
float Parameter_P2;
float Parameter_P3;
float Parameter_P4;
float Parameter_P5;
float Parameter_P6;
float Parameter_P7;
float Parameter_P8;
float Parameter_P9;
float Parameter_P10;
float Parameter_P11;
float T_lin;
long CombinedRawData;
long UncompPress;
long CombinedPressData;

void setup() { // put your setup code here, to run once:
  Wire.begin();//initiate the Wire library
  Serial.begin(9600); //initiate serial comm with Arduino
  delay(100); // time to setup everything
  Wire.beginTransmission(BaroSensAdd);// send the sensor Address
  Wire.write(PwrCtrlReg);// send the register to write to
  Wire.write(NormalMode);// send the value to the register
  Wire.endTransmission();// stop transmission
  delay(100);// give sensor time to process
  //  Wire.beginTransmission(BaroSensAdd);
  //  Wire.write(OSR_REG);// send the register to write to
  //  Wire.write(0x52);// send the value to the register
  //  Wire.endTransmission();// stop transmission
  delay(100);// give sensor time to process
  GetTempCalibration();
  GetPresCalibration();
  CalibrateTemp();
  CalibratePress();
  delay(5000);
}

void loop() {// put your main code here, to run repeatedly:

  /*
   * The first transmission of data gets temperature 
   */
  Wire.beginTransmission(BaroSensAdd);// start sending a mess70)age to the sensor
  Wire.write(TempDataReg);//try to read out the chip ID
  Wire.endTransmission();//send the end of transmission
  Wire.requestFrom(BaroSensAdd, 3);// request 1 byte from the BMP388
  if (Wire.available() <= 3) {//if the byte is available
    Data1 = Wire.read();//get byte 1
    Data2 = Wire.read();//get byte 2
    Data3 = Wire.read();//get byte 3
    Serial.println("Byte 1");// print text
    Serial.println(Data1, HEX); // show byte 1
    Serial.println("Byte 2");// print text
    Serial.println(Data2, HEX);// show byte 2
    Serial.println("Byte 3");// print text
    Serial.println(Data3, HEX);//show byte 3
    long CombinedData = (Data3 << 16) | (Data2 << 8) | (Data1); //
    Serial.println("Combined Data: ");
    Serial.println(CombinedData, 10);
    CombinedRawData = CombinedData;
    Serial.println("Combined data double");
    Serial.println(CombinedRawData);
    CompensateTemp();
    delay(100);
  }
    /*
   *  This transmisison wil get pressure data
   */
  Wire.beginTransmission(BaroSensAdd);// start sending a mess70)age to the sensor
  Wire.write(DataReg0);//try to read out the chip ID
  Wire.endTransmission();//send the end of transmission
  Wire.requestFrom(BaroSensAdd, 3);// request 1 byte from the BMP388
  if (Wire.available() <= 3) {
    Serial.println("pressure data");
    Data4 = Wire.read();//get byte 1
    Data5 = Wire.read();//get byte 2
    Data6 = Wire.read();//get byte 3
    Serial.println("Byte 1");// print text
    Serial.println(Data4, HEX); // show byte 1
    Serial.println("Byte 2");// print text
    Serial.println(Data5, HEX);// show byte 2
    Serial.println("Byte 3");// print text
    Serial.println(Data6, HEX);//show byte 3
    long CombinedPress = (Data6 << 16) | (Data5 << 8) | (Data4); //
    Serial.println(CombinedPress);
    CombinedPressData = CombinedPress;
    CompensatePress();
    delay(5000);//delay
  }

}


void CalibrateTemp() {
  Parameter_T1 = (float)NVM_PAR_T1_COMBINED / powf(2.0f, -8.0f);
  Parameter_T2 = (float)NVM_PAR_T2_COMBINED / powf(2.0f, 30.0f);
  Parameter_T3 = (float)NVM_PAR_T3 / powf(2.0f, 48.0f);
  Serial.println ("Trim Parameters Temp");
  Serial.println (Parameter_T1);
  Serial.println (Parameter_T2, 10);
  Serial.println (Parameter_T3, 15);
}
/*
 * Calculations to compensate temperature
 */
void CompensateTemp() {
  float PartialData1 = (float)CombinedRawData - Parameter_T1;
  float PartialData2 = PartialData1 * Parameter_T2;
  float Temperature = PartialData2 + (PartialData1 * PartialData1) * Parameter_T3;
  T_lin = Temperature;
  Serial.println("Temperature");
  Serial.println(Temperature);
}

/*
 * This function will calculate calibration coefficients
 */
void CalibratePress() {
  Parameter_P1 = ((float)NVM_PAR_P1_COMBINED - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f);
  Parameter_P2 = ((float)NVM_PAR_P2_COMBINED - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f);
  Parameter_P3 = (float)NVM_PAR_P3 / powf(2.0f, 32.0f);
  Parameter_P4 = (float)NVM_PAR_P4 / powf(2.0f, 37.0f);
  Parameter_P5 = (float)NVM_PAR_P5_COMBINED / powf(2.0f, -3.0f);
  Parameter_P6 = (float)NVM_PAR_P6_COMBINED / powf(2.0f, 6.0f);
  Parameter_P7 = (float)NVM_PAR_P7 / powf(2.0f, 8.0f);
  Parameter_P8 = (float)NVM_PAR_P8 / powf(2.0f, 15.0f);
  Parameter_P9 = (float)NVM_PAR_P9_COMBINED / powf(2.0f, 48.0f);
  Parameter_P10 = (float)NVM_PAR_P10 / powf(2.0f, 48.0f);
  Parameter_P11 = (float)NVM_PAR_P11 / powf(2.0f, 65.0f);

  Serial.println ("Trim Parameters Press");
  Serial.println (Parameter_P1, 15);
  Serial.println (Parameter_P2, 15);
  Serial.println (Parameter_P3, 15);
  Serial.println (Parameter_P4, 15);
  Serial.println (Parameter_P5, 15);
  Serial.println (Parameter_P6, 15);
  Serial.println (Parameter_P7, 15);
  Serial.println (Parameter_P8, 15);
  Serial.println (Parameter_P9, 15);
  Serial.println (Parameter_P10, 15);
  Serial.println (Parameter_P11, 18);
}
/*
 * This function is based on the formulast out of the datasheet
 * and will calibrate pressure based on the Temperature
 */
void CompensatePress() {
  float PartialData_P1 = Parameter_P6 * T_lin;
  float PartialData_P2 = Parameter_P7 * T_lin * T_lin;
  float PartialData_P3 = Parameter_P8 * T_lin * T_lin * T_lin;
  float Partial_out_1 = Parameter_P5 + PartialData_P1 + PartialData_P2 + PartialData_P3;
  PartialData_P1 = Parameter_P2 * T_lin;
  PartialData_P2 = Parameter_P3 * T_lin * T_lin;
  PartialData_P3 = Parameter_P4 * T_lin * T_lin * T_lin;
  float Partial_out_2 = (float)CombinedPressData * (Parameter_P1 + PartialData_P1 + PartialData_P2 + PartialData_P3);;
  PartialData_P1 = (float)CombinedPressData * (float)CombinedPressData;
  PartialData_P2 = Parameter_P9 + Parameter_P10 * T_lin;
  PartialData_P3 = PartialData_P1 * PartialData_P2;
  float PartialData_P4 = PartialData_P3 + (float)CombinedPressData * (float)CombinedPressData * CombinedPressData * Parameter_P11;
  float Pressure_2 = Partial_out_1 + Partial_out_2 + PartialData_P4;
}

I checked all the calibration parameters and formulas.

What you absolutely MUST do is to declare all the variables to be of the correct type (signed/unsigned int, long int, float, double, etc.) to carry out the calculation, as required by the formulas.

In order to accomplish that task, you should work with a small program that contains only the required data and variables to verify that the calibration function performs as expected. If you run into problems, post the minimal code, describe the problem clearly, and forum members will help.

Wow that was exactly what was needed. I did not give my calibration variables the proper type. If I compare the values of what I get with the library and my own code. They are identical now. Thanks a lot for putting me in the right direction.

Yaay!