Go Down

Topic: Wireless (nrf24l01) altimeter for RC plane (completed project with code) (Read 3603 times) previous topic - next topic

scottyjr


TwoChain

Hi All,

I was hoping to find some time to put together some more detailed information regarding the full project, but it has been over a month now and I just never seem to do it. Therefore, I will just post my code here for you to look at. If you have any questions, please let me know. Be warned, I am certainly not an expert, so what you will be seeing is likely not an example of "good coding". However, it works, and I have had some great results.

Here is a summary of what it does:

1. The transmitter, with battery, weighs 32 g.
2. The range is at least 1 km (direct line of sight).
3. Reads altitude(in ft since this was the request made by the person I prepared it for).
4. Temperature in Celsius. I realize it is odd to report altitude in feet and temp. in Celsius.  We have a saying in Canada that we are moving towards the metric system inch by inch...
5. The maximum altitude is recorded and displayed where the temperature is normally displayed (the two values are cycled every second).
6. The transmitter is attached to the plane, and the plane is placed on the ground. A button on the receiver is then pressed which causes the Pro Mini to record the measured altitude. This altitude is then subtracted from all subsequent readings, thus "zeroing" the altimeter.
7. My dad (the person this altimeter was made for) has his RC plane equipped with a servo to drop parachutes (and other other things...). Since it is hard to look at the altimeter while flying, I have allowed the user to pre-set an altitude at which an alarm will sound. By flicking a switch, the LCD displays the "triggerHeight" instead of the usual altitude/temperature/max height. When this switch is on, a potentiometer can be used to adjust the "triggerHeight" in increments of 50 ft. See my code for how I did this. When the plane flies above this set point, the alarm sounds, and the RC plane operator can drop his payload. If the operator does not have payload dropping functionality, it is still pretty useful since you can tell when you have reached a certain altitude without looking at the display.
8. I will soon be adding a gps unit to allow real time display of velocity. I think I will have velocity displayed instead of temperature, since I haven't found temperature to change much (and it is not very exciting...). I will update this thread when that is complete (still waiting for the gps in the mail...).


My transmitter consists of:
nrf24l01+pa+lna
Arduino Pro Mini (3.3 V)
bmp85
3.7 V LiPo battery.

My receiver contains:
1) LCD screen (like this): http://www.ebay.ca/itm/1602-16x2-Character-LCD-Display-Module-HD44780-Controller-Blue-Blacklight-/170817946781?pt=LH_DefaultDomain_0&hash=item27c58b049d&_uhb=1
2) 10 k potentiometers. One to control the LCD contrast, and one to set the "drop height" (see the code for more detail or feel free to ask).
3) Alarm (sounds when the "drop height" has been reached).
4) Switch (to allow the drop height to be displayed on the LCD screen vs. the height/temp).
5) Button (used to zero the altimeter).

Transmitter Code:
Code: [Select]
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>

RF24 radio(9,10);
const uint64_t pipe = 0xE8E8F0F0E1LL;


#include <Wire.h>
#include <Adafruit_BMP085.h>
Adafruit_BMP085 bmp;
float Actual;
float Temp;
float data[2]; // 2 element array to hold temp and altitude

void setup(void)
{
  Serial.begin(57600);
  radio.begin();
  radio.openWritingPipe(pipe);

  if (!bmp.begin())
      {
Serial.println("BMP085 could not be found");
while (1) {}
      } 
}

void loop(void)
{
  data[0] = bmp.readAltitude();
  data[1] = bmp.readTemperature();
      for (int i = 1; i < 3; i++)
      {
        radio.write(data, sizeof(data));
      }
  delay(500);
}



Receiver code:
Code: [Select]
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>
int Temp;
float Zero = 100;
float Actual;
int height;
unsigned long time;
float data[2]; // 2 element array to hold the temp and pressure data
int MaxHeight;
boolean TempOrMaxHeight = true;
int tarePin = 14;
int setTriggerHeight = 15;
int triggerHeight = 16;
int alarmPin = 17;
int val = 0;
float var;
int beepingHeight = 1000; //I set this at 1000 so that the unit does not start beeping as soon as it is turned on.
int i = 0;

RF24 radio(9,10);
const uint64_t pipe = 0xE8E8F0F0E1LL;

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 7, 6, 4, 3, 5);

void setup(void)
{
  //Code for radio begins
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(1,pipe);
  radio.startListening();
  //Ends
 
  //Code for LCD begin
  pinMode(tarePin,INPUT);
  pinMode(triggerHeight, INPUT);
  pinMode(setTriggerHeight,INPUT);
  pinMode(alarmPin, OUTPUT);
 
  Zero = 100;  // I just set this to give myself a baseline when testing
  lcd.begin(16, 2);
     
      //This will show up, then scroll off the screen when the unit is first turned on.
      lcd.print("TwoChain inc.");
      delay(2000);
        for (int positionCounter = 0; positionCounter < 16; positionCounter++)
        {
        // scroll one position left:
        lcd.scrollDisplayLeft();
        // wait a bit:
        delay(300);
        } 
      lcd.clear();
}
  //Code for LCD ends

void loop(void)
{
  //Code for radio begins
  if (radio.available())
  {
    time = millis(); // Check time when last signal was detected. I do this to help identify if my plane has flown out of range.
    bool done = false; 
    while (!done)
    {
      done = radio.read(data, sizeof(data));
    }
  // Ends
 
 
  digitalWrite(triggerHeight, HIGH); // I am using the internal pull up resistor to keep this pin high until a button is pressed.
  // Code for LCD
    if (digitalRead(tarePin) == LOW) // Check when tare button has been pressed
    {
      Zero = Actual; // Set "Zero" to the last measured altiitude. It will then be continually subtracted from the measured altitude.
      MaxHeight = 0;
      i = 0; // i is also reset to zero if the "zero" button is pressed.
      lcd.clear();
      lcd.print("Calibrated!");
      delay(2000);
      lcd.clear();
      for (int i = 0; i < 3;i++) // This loop is not necessary, but I find it exciting!
      {
      lcd.print("Ready for ");
      lcd.setCursor(0,1);
      lcd.print("takeoff!!!");
      delay(500);
      lcd.clear();
      delay(500);
      }
    }
   
    while (digitalRead(triggerHeight) == LOW) // If my "set switch" is pressed, the user can set the altitude at which the alarm will trigger.
    {
      lcd.clear();
      val = analogRead(setTriggerHeight);
      var =(1000-val*24)/100; // I am switching between a float and int, and playing with the math, to make the "alarm altitude" increase in 50 f increments.
      beepingHeight = var*50+500;
      lcd.print("Alarm will sound");
      lcd.setCursor(0,1);
      lcd.print("at ");
      lcd.print(beepingHeight);
      lcd.print(" ft");
      delay(500);
     }
   
   
    Actual = data[0]; // Convert the float to an int cause there is no point in reporting the extra decimal places due to variations in pressure
    lcd.clear();
    lcd.print("Altitude ");
    height = (Actual-Zero)*3.28; // Multiplying by 3.28 to convert to ft.
    lcd.print(height);
    lcd.print(" ft");
   
    lcd.setCursor(0,1); // Begin the temp and max height display
    if (height > MaxHeight) // Continually store the maximum altitude to be displayed later on.
    {
     MaxHeight = height;
    }
       
    if (TempOrMaxHeight == true)
    {
    lcd.print("Temp ");
    lcd.print(data[1]);
    lcd.print(" ");
    lcd.print((char)223);
    lcd.print("C");
    }
    else
    {
    lcd.print("Max Alt. ");
      lcd.print(MaxHeight);
    lcd.print(" ft");
    }
    TempOrMaxHeight = !TempOrMaxHeight; // This is to keep the LCD display continually switching from displaying temp. and max altitude.
    delay(500);
   
  }
 
  else
  {
    if (millis()-time > 5000) // Check how long it has been since last successful signal was detected.
     {
    lcd.clear();
    lcd.print("No Signal!");   
    lcd.setCursor(0,1);
    lcd.print("Out of Range!");
    delay(1000);
    }
  }
 
  if (height > beepingHeight && i < 1) // Sound an alarm if the max altitude is reached.
  {
   tone(alarmPin,750,2000);
   i = 1;
  }
 
 
}



A video of this unit will be posted on youtube. I will update this thread when the video has been uploaded. In the meantime, there are some nice videos of the RC planes dropping their payloads (and some other odd things...) on the same channel: http://www.youtube.com/user/cosmohunter1

Finally, I would like to thank the many people on this forum who helped me with this project. In particular,
terryking228, Crofter, and Vasek42 for providing crucial input early on to help me get my wireless communication working, which was by far the most challenging part of this project for me.

I would be happy to answer any questions (if I can) that you may have.

Dustin

terryking228

Dustin,
This is WAY Cool!  You have the main project success tool: Persistence!

Which antennas did you use? How were they mounted..?

Thanks for the great description..
Regards, Terry King terry@yourduino.com  - Check great prices, devices and Arduino-related boards at http://YourDuino.com
HOW-TO: http://ArduinoInfo.Info

TwoChain

Hi Terry,

Thanks a lot, and thanks for your assistance with my project!

I used the antenna that comes with the NRF24L01+PA+LNA module. For the receiver, I have the antenna sticking out of a project box (you can see it at 6:42 of the video poster here: http://www.youtube.com/watch?v=RNMemNLEfaQ). For the transmitter, I actually just have the Pro Mini, NRF, and BMP085 bundled together in bubble wrap. While it doesn't look aesthetically pleasing, weight is critical on these RC planes, so I figured this was the best option.

I actually just received a gy-neo6mv2 GPS unit in the mail last night, so I will playing around with it this weekend. My goal is to be able to transmit altitude (from the BMP unit) and speed (from the GPS unit). Once this is done, I think my project will be complete. I will post an update once this is done.

Thanks,

Dustin

mistergreen

Very cool.
I saw the whole video. You need a IP camera so you can see the video live & see where you're going.


terryking228

Quote
actually just have the Pro Mini, NRF, and BMP085 bundled together in bubble wrap.


People often have problems with unreliability when they start using  nRF24L01 units. It SEEMS this is often connection, and power problems.  What can we learn from what you did?? 

What is the power source in each of the units you made?  How long are the wires to the RF unit, and what are the wires like?

Did you end up with added capacitor(s) on the +3.3V - Gnd connections at the RF unit? 

Have you had instances in which the units just "did not come up working" but they did work after power cycling etc?

Do you usually have a set sequence of which unit is powered up first?

Thanks! I want to update http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo with whatever I can learn.

And tell your father he is an awesome Aircraft builder and always ends up flying again. 

"Any landing where you can walk over and pick up the pieces with one hand is a good landing"

:P
Regards, Terry King terry@yourduino.com  - Check great prices, devices and Arduino-related boards at http://YourDuino.com
HOW-TO: http://ArduinoInfo.Info

TwoChain

Hi Terry,

First of all, I am happy to announce that I have successfully incorporated a gy-neo6mv2 GPS unit (like this one: http://www.ebay.ca/itm/GY-NEO6MV2-Flight-Controller-GPS-Module-For-Arduino-EEPROM-MWC-APM-2-5-/390647042336) to the transmitter part of my project. I can now transmit both altitude and speed. I just got back from driving around with my wife to verify the speed readings are correct (they are!). I have made some modifications to my code (such as displaying speed instead of temperature, and showing the max speed and max altitude when the "reset" button is pressed, unless it is held down for > 6 s, in which case it does reset everything). For some reason the GPS unit did not initially work when I tried to run it with the NRF. After a lot of playing around, I got it to work by making the GPS code run repeatedly for 1 s, followed by the NRF code running for 0.5 s (you will see this in my code). Once I have finished putting appropriate annotation into my code, I will post it. The whole transmitter now weighs ~ 50 g (the GPS antenna is quite heavy), which is acceptable for the RC plane it will be used for.

As for your questions, here are my answers:

1) Power source: The receiver is powered by a 9 V battery. The transmitter is powered by a 3.7V 150mAh Li-Po. Google "RC Champ battery" to see the exact battery.
2) Wires to the RF unit: I have used the cheapest jumper cables I could find on ebay. Since they were quite long (20 cm), I cut them in half and soldered them back together (now probably ~ 7-10 cm).
3) When breadboading the circuit, I did use capacitors. However, when I was assembling everything to my Pro Mini, it became too annoying, so I left them out. Fortunately, it all seems to still work fine.
4) Intermittent functioning: Yes, this can be an issue. Sometimes if the transmitter is not positioned correctly, it can lose connection with the receiver.I usually just have to touch the antenna, or give the whole transmitter unit a nudge, and then it starts working . I built in a timer into the receiver code to let me know if it has been out of communication for more than  5 s (see code). However, when my transmitter is wrapped up in bubble wrap, it is very reliable. This makes me think the occasional lost connection (when not wrapped up) is due to a lose wire, or something of the sort, that does not occur when the unit is wrapped up.
5) Order of turning on: It definitely does not matter what order I use to turn on the unit.


I hope that is useful, and I would be very happy if any of this helps you with your wikispace link. For anyone else reading this, the link Terry posted is very useful. It helped me enormously when learning how to write code for the RF unit.

Thanks,

Dustin

TwoChain

Hello All,

I have now incorporated a GPS unit into my project so that I can transmit speed, as well as altitude (from my BMP085 unit). I have modified the transmitter and received code to allow for the following modifications:

1) Speed is now reported where temperature used to be reported.
2) The receiver unit no longer cycles through to show maximum altitude. Instead, if the "reset" button is pressed for less than 6 s, both the maximum altitude and maximum speed are displayed. If the button is pressed for longer than 6 s, the unit is reset (max speed and altitude are reset, and the unit is "zeroed").

I did run into an unexpected problem when trying to incorporate the GPS unit into the transmitter. For some reason, when I first tried to add the GPS unit, and the associated code, I wasn't able to get any data from the GPS. I have no idea why this occurs. After A LOT of playing around, I found that I could get it to work if I had the GPS code run repeatedly for 1 s, followed by the NRF code running for 0.5 s. Without doing this, it seems like the GPS unit just does not pick up any data. You will see where I made this modification in the code.

Here is the new code for both units:

Transmitter Code:
Code: [Select]
// Libraries

//For NRF
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>
// End

// For BMP085
#include <Wire.h>
#include <Adafruit_BMP085.h>
// End

// For GPS
#include <SoftwareSerial.h>
#include <TinyGPS.h>

// End

//-----------------------------------------------------------

// Constants

//For GPS
unsigned long time;
float kmhspeed;
float test;
float flat, flon;
long courses;
// End


// For NRF
float data[3]; // 3 element array to hold temp, altitude, and speed
const uint64_t pipe = 0xE8E8F0F0E1LL;
// End

//-----------------------------------------------------------

// Declarations

//For GPS
SoftwareSerial gpsSerial(4, 3);
TinyGPS gps;
// End

// For NRF
RF24 radio(9,10);
// End

// For BMP085
Adafruit_BMP085 bmp;
// End

//-----------------------------------------------------------


void setup(void)
{
   Serial.begin(115200); // connect serial
// For NRF
  radio.begin();
  radio.openWritingPipe(pipe);
// End

// For BMP085
  if (!bmp.begin())
      {
Serial.println("Could not find a valid BMP085 sensor, check wiring!");
while (1) {}
      } 
// End

//For GPS
gpsSerial.begin(9600); // connect gps sensor
// End

}

void loop(void)
{
  // For GPS
 
time = millis(); // I have included this to make the program run through the GPS stuff a few times before moving on to the NRF stuff. Without doing this, I am unable to get data from the GPS unit when paired with the NRF. This is not just an issue with the NRF, since I am unable to even print GPS data to the serial monitor without doing this.

do
{
 
  while(gpsSerial.available())
  {
   if(gps.encode(gpsSerial.read()))
   {
    gps.f_get_position(&flat, &flon);
    kmhspeed = gps.f_speed_kmph();
   }
  }
 
} while (millis()-time < 1000); // Had to have this run continuously for 1 s before moving onto the NRF code, otherwire the GPS code does not seem to work. I chose 1 s based on trial and error.
  // End

time = millis();

do
{
   
//For NRF and BMP085
  data[0] = bmp.readAltitude();
  data[1] = bmp.readTemperature();
  data[2] = kmhspeed;
      for (int i = 1; i < 3; i++)
      {
        radio.write(data, sizeof(data));
      }

} while (millis()-time < 500);
// End
}


Receiver Code:
Code: [Select]

//Code for Radio
//Begins
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>
RF24 radio(9,10);
const uint64_t pipe = 0xE8E8F0F0E1LL;
float data[3]; // 3 element array to hold the temp, pressure, and speed data
// Ends

//Code for LCD
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 7, 6, 4, 3, 5);
//Ends

// Constants
//Begins
int Temp;
float Zero = 100;
float Actual;
int height;
unsigned long time;
unsigned long timeTwo;
int maxHeight = 0;
boolean TempOrMaxHeight = true;
int tarePin = 14;
int setTriggerHeight = 15;
int triggerHeight = 16;
int alarmPin = 17;
int val = 0;
float var;
int beepingHeight = 1000; //I set this at 1000 so that the unit does not start beeping as soon as it is turned on.
int i = 0;
float kmph;
float currentSpeed = 0;
float maxSpeed = 0;
//Ends



void setup(void)
{
  //Code for Radio
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(1,pipe);
  radio.startListening();
  //Ends
 
  //Code for LCD
  pinMode(tarePin,INPUT);
  pinMode(triggerHeight, INPUT);
  pinMode(setTriggerHeight,INPUT);
  pinMode(alarmPin, OUTPUT);
  Zero = 100;  // I just set this to give mysefl a baseline when testing
  lcd.begin(16, 2);
     
      //This will show up, then scroll off the screen when the unit is first turned on.
      lcd.print("TwoChain inc.");
      delay(2000);
        for (int positionCounter = 0; positionCounter < 16; positionCounter++)
        {
        // scroll one position left:
        lcd.scrollDisplayLeft();
        // wait a bit:
        delay(300);
        } 
      lcd.clear();
     
  // Ends
}

void loop(void)
{
  //Code for radio begins
  if (radio.available())
  {
    time = millis(); // Check time when last signal was detected.
    bool done = false; 
    while (!done)
    {
      done = radio.read(data, sizeof(data));
    }
  // Ends
 
 
  digitalWrite(triggerHeight, HIGH); // I am using the internal pull up resistor to keep this pin high until a button is pressed.
  // Code for LCD
   
    if (digitalRead(tarePin) == LOW) // Check when tare button has been pressed
    {
      timeTwo=millis();
    do // If the resent button is pressed down for less than 6 seconds, the max speed and height are reported. If the button is held for more than 6 s, the values are reset.
       {     
        lcd.clear();
        lcd.print("Max Alt. ");
        lcd.print(maxHeight); // Subtracting these two gives the "tared" altitude.
        lcd.print(" ft");
        lcd.setCursor(0,1); // Begin the speed display
        lcd.print("MaxS ");
        lcd.print(maxSpeed);
        lcd.print(" ");
        lcd.print("km/h");
        delay(6000);
        digitalRead(tarePin);
       
        if (digitalRead(tarePin) == LOW)
        {
        Zero = Actual; // Set "Zero" to the last measured altiitude
        maxHeight = 0;
        maxSpeed = 0;
        i = 0; // i is also reset to zero if the "zero" button is pressed.
        lcd.clear();
        lcd.print("Calibrated!");
        delay(2000);
        lcd.clear();
        for (int i = 0; i < 3;i++) // This loop is not necessary, but I find it exciting!
        {
          lcd.print("Ready for ");
          lcd.setCursor(0,1);
          lcd.print("takeoff!!!");
          delay(500);
          lcd.clear();
          delay(500);
        }
          digitalWrite(tarePin, HIGH);
       }
       }while (digitalRead(tarePin) == LOW);
    }
     
   
    while (digitalRead(triggerHeight) == LOW) // If my "set switch" is pressed, the user can set the altitude at which the alarm will trigger.
    {
      lcd.clear();
      val = analogRead(setTriggerHeight);
      var =(1000-val*24)/100; // I am switching between a float and int, and playing with the math, to make the "alarm altitude" increase in 50 f increments.
      beepingHeight = var*50+500;
      lcd.print("Alarm will sound");
      lcd.setCursor(0,1);
      lcd.print("at ");
      lcd.print(beepingHeight);
      lcd.print(" ft");
      delay(500);
     }
   
   
    Actual = data[0]; // Convert the float to an int cause there is no point in reporting the extra decimal places due to variations in pressure
    lcd.clear();
    lcd.print("Altitude ");
    height = (Actual-Zero)*3.28; // Multiplying by 3.28 to convert to ft.
    lcd.print(height); // Subtracting these two gives the "tared" altitude.
    lcd.print(" ft");
   
    currentSpeed = data[2];
    lcd.setCursor(0,1); // Begin the speed display
    lcd.print("Spd. ");
    lcd.print(currentSpeed);
    lcd.print(" ");
    lcd.print("km/h");
   
    if (height > maxHeight) // Continually store the maximum altitude to be displayed later on.
    {
     maxHeight = height;
    }
       
    if (currentSpeed > maxSpeed) // Continually store the maximum altitude to be displayed later on.
    {
     maxSpeed = currentSpeed;
    }
   
    delay(500);
  }
 
  else
  {
    if (millis()-time > 5000) // Check how long it has been since last successful signal was detected.
     {
    lcd.clear();
    lcd.print("No Signal!");   
    lcd.setCursor(0,1);
    lcd.print("Out of Range!");
    delay(1000);
    }
  }
 
  if (height > beepingHeight && i < 1) // Sound an alarm if the max altitude is reached.
  {
   tone(alarmPin,750,2000);
   i = 1; // Set i = 1 so that the alarm does not sound again until the unit is reset.
  }
 
}



Please let me know if you have any questions.

Thanks,

Dustin

Stanley

I've done this before.. pls hv a look at my blog entry below...

I've to split up the GPS NMEA sentences into 3 fragments as RF24 only have a max payload of 32 Bytes

Hv to do with the software serial not fast enough ... you may try AltSoftSerial https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html from PJRC or swap the h/w and s/w serial....

http://arduino-for-beginners.blogspot.com/2013/06/arduino-serial-over-nrf24l01.html


TwoChain

Hi Stanley,

Thanks for the post. I didn't have to split up the GPS NMEA, and it works just fine. The only issue I ran into was having to delay the program in key places, as I explained earlier in this thread. Maybe this is because I am only sending the speed, and not everything else. I was actually surprised at how smoothly the whole thing came together...

When you say you did this before, are you referring to sending GPS data wirelessly, or making a wireless altimeter/speedometer. I believe you need to use something like the BMP085 for measuring altitude, since you can't get reliable altitude from a GPS unit.

Thanks,

Dustin

colind

Hi.

First of all thanks very much for sharing your experiences with these transmitters.

I'm hoping to do something similar to this and I just had one question regarding the code.  In the first example without GPS you have an array of 2 floats. Then you have a loop which seems to write the same data twice. I know that there is a 32 byte packet limit on the NRF24L01 , but i thought a float was 4 bytes.
Would something like

radio.write(&data, sizeof(data))

work too in this case?

Did you use this loop routine to send smaller packets which are more likely to be received intact?

Then in the GPS example, the data array contains 3 floats, but it seems that in the loop only 2 are sent.

In my application I'll have 6  integers to send (using a teensy -so 32 bits I think) so I'm wondering the best way to do this. Getting my head round the code is step 1.

Many thanks for any pointers/ help.
Colin

gary-watkins

Hi Dustin
I am very impressed with your project and wanted to try to emulate it. But your post had everything apart from the wiring diagram. Will you be posting it as i would love to see it to.

Go Up