Using BMP280 sensor to control servos. Need some guidance with coding

Hello all,

I am new to Arduino and programing, so I hope to get some guidance here for my project. For some of you, it might be rather simple. Thanks in advance for your time.
I am trying to build a model rocket that reach a specific height like maybe 800 feet. My idea is that the rocket would deploy airbrakes at a preset altitude to slow the rocket down before reaching the intended altitude, so the rocket will reach an altitude more closer to the target altitude rather than flying way over it.
I am using existing libraries for the BMP280 sensor and the servos, but I have trouble taking the altitude readings from the sensor to control the servos. I am not sure I know how to make the connection codes between the reading from the sensor to the servos.
BMP280 Sensor and the servos are both functional on their own, so my wiring should be good. Therefore, I think the coding is more likely the problem. I am using a Mega 2560 board.
Below is the code that I have so far. I hope I can get some help here for my project and learn more about Arduino too.
Thanks!

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>

#include <Servo.h>

Adafruit_BMP280 bmp;

int pushButton = 2;
int servoPin1 = 9;
int servoPin2 = 10;
int servoPin3 = 11;
int greenLed = 13;
int redLed = 12;

Servo Myservo1; //define servos
Servo Myservo2;
Servo Myservo3;
int servoPos=0;

void setup() {
Serial.begin(9600);
  Serial.println(F("BMP280 test"));

  //if (!bmp.begin(BMP280_ADDRESS_ALT, BMP280_CHIPID)) {
  if (!bmp.begin()) {
    Serial.println(F("Could not find a valid BMP280 sensor, check wiring or "
                      "try a different address!"));
    while (1) delay(10);
  }
    bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /* Operating Mode. */
                  Adafruit_BMP280::SAMPLING_X2,     /* Temp. oversampling */
                  Adafruit_BMP280::SAMPLING_X16,    /* Pressure oversampling */
                  Adafruit_BMP280::FILTER_X16,      /* Filtering. */
                  Adafruit_BMP280::STANDBY_MS_500); /* Standby time.*/
  
  pinMode(pushButton,INPUT);
  pinMode(greenLed,OUTPUT);
  Myservo1.attach(servoPin1);
  Myservo2.attach(servoPin2);
  Myservo3.attach(servoPin3);

 
}

void loop() {
 Serial.print(F("Temperature = "));
    Serial.print(bmp.readTemperature());
    Serial.println(" *C");

    Serial.print(F("Pressure = "));
    Serial.print(bmp.readPressure());
    Serial.println(" Pa");

    Serial.print(F("Approx altitude = "));
    Serial.print(bmp.readAltitude(1013.25)); //Adjusted to local forecast! 
    Serial.println(" m");

    Serial.println();
    delay(1000);


  /*if(digitalRead(pushButton)==LOW){*/          //push button to test airbrake flaps

  if(Serial.print(F("Approx altitude >= 235"))) {
    
    Myservo1.write(90);
    Myservo2.write(90);
    Myservo3.write(90);
    
    
    digitalWrite(greenLed, HIGH);
    digitalWrite(redLed, LOW);
  
  }
  else{
   
    Myservo1.write(0);
    Myservo2.write(0);
    Myservo3.write(0);
    
    digitalWrite(greenLed, LOW);
    digitalWrite(redLed, HIGH);
    
     }
 
}

Read the forum guidelines to see how to properly post code.
Use the IDE autoformat tool (ctrl-t or Tools, Auto format) before posting code in code tags.
You can edit your original post to post the code correctly. Highlight the code and click the </> button on the tool bar above. It is that easy.

How is the BMP connected to the Mega?
Post a schematic showing how the wiring should be.
Post photos showing how the wiring really is.

What does the code do? How is that different from what you want?

How long do you expect this flight to last?

Hi @mikami2020

The Bosch BMP280 is a bit of a strange device, in that Bosch designed it with only a "measuring" bit in the status register. This bit goes high when a measurement is in progress, otherwise it's low.

Using this "measuring" bit is really problematic from a programming perspective, since what you really want to know is when a conversion is "ready" or "complete" and not simply when a measurement is taking place. I'll hasten to add that Bosch's later barometers switched to using a "ready" bit instead, which is so much more convenient to work with.

The Adafruit doesn't really address this issue with their BMP280 library, since you'll notice that their example code just include a blocking delay() function that's longer than the barometer's specificed conversion time. Blocking functions aren't ideal for real-time applications.

To address this issue I wrote my own BMP280 library: https://github.com/MartinL1/BMP280_DEV, which you can find in the Arduino IDE's library manager.

Using the BMP280_DEV library, it's possible to use non-blocking code and poll the barometer to check if the results are ready. In the barometer's NORMAL mode, this will return a results at a rate set by the barometer's standby time. (It does this by detecting the "measuring" bit's rising and falling edges in software).

Here's an example operating the BMP280 with I2C in NORMAL mode with non-blocking code:

/////////////////////////////////////////////////////////////////////////////////
// BMP280_DEV - I2C Communications, Default Configuration, Normal Conversion
/////////////////////////////////////////////////////////////////////////////////

#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library

float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)

void setup() 
{
  Serial.begin(115200);                           // Initialise the serial port
  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
}

void loop() 
{
  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
  {
    Serial.print(temperature);                    // Display the results    
    Serial.print(F("*C   "));
    Serial.print(pressure);    
    Serial.print(F("hPa   "));
    Serial.print(altitude);
    Serial.println(F("m"));  
  }
}

Using this code you could just add your altitude check and servo code within the barometer's if () statement.

1 Like

I see no difference between a signal that is active low, and a signal that is active high.
Maybe it's less intuitive, but as convenient as its inverted counterpart.

Following a non-blocking approach to make the data available is absolutely preferable.

That's exactly what I thought, until I started trying take barometer measurements at the standby interval in its free-running normal mode.

The problem is that the "measuring" bit is transient and doesn't go high immediately after initiating a measurement. Therefore unless you detect the bit's transitions, you've got no way of knowing whether a measurement has completed or not.

Bosch remedied this in their later barometers such as the BMP388 with a "drdy" or data ready bit that simply goes high when a conversion is complete.

You want to catch the moment, when a new measurement is available,
so the transition between not-ready and ready has to be detected anyway,
regardless of its basic polarity.

Unlike the BMP280, with the BMP388 it's only necessary to poll the "ready" bit's level.

There's a subtle and but fundamental difference between the BMP280 and BMP388 barometers' status bits in both meaning and timing.

I've also checked Bosch's BMP280 library code and they provide no solution to reading their own barometer in normal mode at the standby timing interval.

Do you know if the BME280 suffers from this quirk? It seems to be more readily available than the BMP388 (or at least it seems that way.)

The BME280's registers look the same as BMP280's , so I'd imagine it does.

I haven't looked at Bosch's latest product range, but I believe the BMP388 has now been superseded by BMP390.

Thank you for the info.

John

Thanks for the properly post code guidelines. I've fixed the original post to post the code correctly.
I will post a picture of the wiring later today.

As for the code, I intended to use it like this.

  1. Have the BMP280 sensor detect the altitude as the rocket is going up.
  2. When the sensor detects an altitude higher than my preset altitude, say 770 ft, a signal will send to control the servos.
  3. The servos will then turn 90 degrees to pull open the flaps for a few seconds to increase the frontal surface area of the rocket to increase drag to slow down the rocket. And after a few seconds, the servo should return back to 0 degree and close the flaps.

I tried to test this by using the code I post, and I wrote the following lines hoping that it would achieve the goal. My serial monitor showed the altitude reading to be somewhere around -14.5 meter. I stood on a chair, also lifted the sensor to above my head, and the altitude reading changed; however, the servos didn't move. I tried putting the sensor on the floor as well; the reading changed, but the servo also didn't move.

Using Rocksim simulation result, the time to apogee is around 7.5 seconds.

Testing the barometer's altitude requires a comparison with the threshold:

if (bmp.readAltitude(1013.25) > -14.5)

Thanks for creating and sharing this library.
I think this will be very helpful to my project. I will definitely tried it and share the result here.
Thanks again! :raised_hands:

Hi @mikami2020

Using the Adafruit library, If you replace the line:

if(Serial.print(F("Approx altitude >= -14.5"))) {

with

if (bmp.readAltitude(1013.25) > -14.5) {

Do your servos now react when the threshold is exceeded?

1 Like

I see. Let me also try doing this.
Your answers are very informational (although I don't understand everything yet) and very helpful to me!

Hello Martin,

Your code worked! Today, I changed the line to the code that you suggested and set the altitude to -9; when the altitude readings changed up and down, the flaps opened and closed as intended.
I am going to try the library that you suggested in the earlier post too.
Do you think the DEV library that you created would be a better suit for my project goal? I am afraid I didn't understand too well about blocking functions vs non-blocking.
Thanks again for your time and your very helpful responses!

My apology for troubling you again. I tried to use the library and add altitude check and the servo code to it. However, I got an error message when I compiled the codes. I hope you can assist me on this. Below is the code and also the error message.

/////////////////////////////////////////////////////////////////////////////////
// BMP280_DEV - I2C Communications, Default Configuration, Normal Conversion
/////////////////////////////////////////////////////////////////////////////////
#include <Servo.h>
#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library

int pushButton = 2;
int servoPin1 = 9;
int servoPin2 = 10;
int servoPin3 = 11;
int greenLed = 13;

Servo Myservo1; //define servos
Servo Myservo2;
Servo Myservo3;

int servoPos=0;
float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)

void setup() 
{
  Serial.begin(115200);                           // Initialise the serial port
  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
  pinMode(greenLed,OUTPUT);
  Myservo1.attach(servoPin1);
  Myservo2.attach(servoPin2);
  Myservo3.attach(servoPin3);
}

void loop() 
{
  digitalWrite(greenLed, HIGH);
  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
  {
    Serial.print(temperature);                    // Display the results    
    Serial.print(F("*C   "));
    Serial.print(pressure);    
    Serial.print(F("hPa   "));
    Serial.print(altitude);
    Serial.println(F("m"));  
  }
  if (bmp280.getAltitude(1013.23f) > -14.45) {
    
    Myservo1.write(90);
    Myservo2.write(90);
    Myservo3.write(90);
  
  }
  else{
   
    Myservo1.write(0);
    Myservo2.write(0);
    Myservo3.write(0);
    
     }
}

Arduino: 1.8.16 (Mac OS X), Board: "Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)"

/var/folders/jz/z7fc17697dzbdj3cmb_sjd9c0000gn/T/arduino_modified_sketch_764643/BMP280_I2C_Normal.ino: In function 'void loop()':
BMP280_I2C_Normal:48:34: error: cannot bind non-const lvalue reference of type 'float&' to an rvalue of type 'float'
if (bmp280.getAltitude(1013.23f) > -14.45) {
^
In file included from /var/folders/jz/z7fc17697dzbdj3cmb_sjd9c0000gn/T/arduino_modified_sketch_764643/BMP280_I2C_Normal.ino:5:0:
/Users/karenma/Documents/Arduino/libraries/BMP280_DEV/BMP280_DEV.h:158:11: note: initializing argument 1 of 'uint8_t BMP280_DEV::getAltitude(float&)'
uint8_t getAltitude(float &altitude); // Get an altitude measurement
^~~~~~~~~~~
exit status 1
cannot bind non-const lvalue reference of type 'float&' to an rvalue of type 'float'

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Hi @mikami2020

If the Adafruit library works for you then I'd stick with that.

The blocking vs non-blocking is simply whether the code sits and waits for an event to happen (blocking) vs allowing the code continue to run and service the event whenever it occurs (non-blocking). Non-blocking code achieves this either by periodically polling (checking), or using interrupts.

Blocking code usually works OK if the microcontroller has a single task to perform, non-blocking becomes essential when it has to juggle between multiple tasks. Blocking code is usually characterised by the use of the delay() function in the loop(), rather than using design patterns that keeps the loop() running, for example:

// Execute code in the loop() every second (1000 milliseconds)
unsigned long currentTime, lastTime;
unsigned long interval = 1000;                    // 1000 milliseconds

void setup(){}

void loop() 
{
  currentTime = millis();                    // Get the current time
  if (currentTime - lastTime >= interval)    // If the difference between current and last times exceeds the interval...
  { 
    //
    // Put your code to execute every second here...
    //
    lastTime = currentTime;                  // Update lastTime to currentTime
  }  
}