Experimental Arduino RC Plane Build Log

Would you use the hardware and/or software designed as part of this project for your own RC planes?

  • Yes

    Votes: 10 100.0%
  • No

    Votes: 0 0.0%

  • Total voters
    10

Power_Broker

Active member
Update #22.)

Finally got around to testing out the connections on the new PCB; everything works!!!

If you make your own and want to test things out yourself, the code for testing the previous version of the PCB will work (see earlier posts for the source code downloads). The only addition is the testing of the 3DR radios on Teensy UART port 2 (Serial2). You test the 3DR radios exactly the same as the XBees (just use a serial terminal of your choice instead of the XCTU application).

Since I didn't go over the board testing in detail, if anyone has any questions, please ask! I'm more than happy to explain anything in more detail if needed.
 

Power_Broker

Active member
Update #23.)

Real quick update, been working on the code since the PCB has been validated. I just finished implementing a custom GPS NMEA string parser with the Teensy. It's real simple since I'm only extracting lat/long from GPGLL sentences and nothing else. I'll probably only need lat/long and such a simple parser will keep serial data processing fast.

Also, I accidentally overdischarged another battery while testing the electronics/code. I moved to a "shore power" solution involving a 12V wall plug so that no more batteries get ruined.
 

Power_Broker

Active member
Update #24.)

Another quick software update. I'm probably going to have a lot of these software updates until I get things solidly figured out. I also won't be posting any flight controller software until I can get a good first draft (at least).

Been working on the on-board telemetry datalogging portion of the flight controller code. The flight controller (Teensy 3.5) has a microSD slot with a 4pin SDIO interface (MUCH faster than a normal SD card interface). Because of these amazing features built into my controller, I decided to use it for logging things like altitude, airspeed, latitude, longitude, and anything else I see fit (completely and easily customizable).

Plain-text files (.txt) are pretty standard for datalogging in Arduino projects, but to make data extraction and graphing easier post-flight, I decided to go with a spreadsheet (.csv) instead. The great thing is that a .csv is just as easy to insert and save data as a .txt is. The main advantage of a .csv is that you can use Excel to view, manipulate, and graph data. Note: A .csv is the pretty much the same thing as a .txt, but with a different extension AND a .csv is formatted as "Comma Separated Values".

For instance, a .txt might have info like this:

altitude airspeed
10 34
11 49
12 40

but a .csv would have the same info in a different format:

altitude,airspeed
10,34
11,49
12,40


I did some testing and got the datalogging functionality to work thanks to some example code written by Bill Greiman in this forum post.
 

Power_Broker

Active member
Update #25.)

I've been messing with the on-board GPS today. I'm using a cheap U-BLOX NEO-6M module from Amazon (here). By default, the GPS baud rate is 9600 with a lat/long refresh rate of 1Hz. In order to do real navigation, I found some example code online that will change the GPS settings to communicate at 115200 baud with a refresh rate of 10Hz (MUCH faster). This higher refresh rate will help the accuracy of the GPS based telemetry and future autopilot systems on the plane.

You might ask, "Why not use the U-Center desktop application to set the GPS configuration?" The answer is that I tried, but after doing some research, the GPS will reset to the factory default settings when power cycled. That means that I have to change the GPS settings manually through the on-board Teensy upon every plane startup.

Here's the link to the example code that changes the GPS settings to 115200 baud with lat/long refresh rate of 10Hz: Link

I tested the code on my Teensy and it worked! I'll be inserting parts of that code into my main flight controller code so that I can get 10Hz refresh rate every time I turn the plane on.
 

Power_Broker

Active member
Update #26.)

I ran some tests on how fast I could save telemetry data to the SD card via the on-board Teensy's SD card slot. Turns out that although the nominal time to save the telemetry data was 3ms, there were random lag spikes of around 10ms and even a couple of random spikes of 200ms. These lag spikes will unfortunately tie up the processor too long and could cause glitches and timing issues (possibly causing the plane to crash). Because of this, I've decided to save data without using an SD card entirely.

I'm going to use the XBee radios differently for this purpose. Instead of using XBees for sensor control, I'm going to use them for telemetry data going from the plane to the ground station Teensy. That Teensy will then communicate with a program running on the flight laptop and save the data onto a .csv that way.

Because the XBees aren't full duplex radios, I'm going to have to use the 3DR radios to send both flight and sensor control commands.
 

Power_Broker

Active member
Update 27.)

In order to make my flight controller code more readable and portable, I've decided to write separate libraries for some of the different features I want to implement.

Today I wrote the first of these custom libraries. This one is used to both configure the on board GPS and extract lat/long data from the GPS serial stream.

You can download this library and try it out for yourself from my GitHub page here.
 

Power_Broker

Active member
Update 28.)

Just dropped my second custom Arduino library. This new one helps transfer data between the ground station and the plane (data will flow in both directions) quickly and reliably. Here are some really cool features:

- Transfers 16-bit values
- Packets can have variable length --> only send what ya need
- works with hardware UART and software defined serial ports
- works at all baud rates
- use a start byte, payload length (in bytes), payload, 8-bit checksum of payload, and end byte
- payload length is always a multiple of 3 bytes​
- every 16-bit that is sent is first split into a MSB and LSB​
- the value is assigned an 8-bit message ID​
- all three of these values (message ID, MSB, LSB - respectively) are inserted into the payload before the entire packet is sent​

Check out the library here.
 

Power_Broker

Active member
Update #29.)

Today I integrated the 3DR radios into the ground station hardware and did some basic testing. The connection between the ground station and plane works!

I did some testing at 115200 baud transferring the basic string "Hi" from the ground station to the plane as fast as I could reliably. Seems that with no delay, there were some errors. In order to get rid of the few errors, I started to introduce small delays in the code - starting with 1 microsecond and slightly increasing the delay duration. Turns out the smallest delay that keeps basic errors from occurring is 2 milliseconds.

Later I'll start testing the radios by transferring real plane commands between the ground station and plane.
 

Power_Broker

Active member
Update #30.)

I'm starting to overhaul the code for both the ground station and the flight controller. As I update these programs as well as the ground station Python GUI, I'll be adding and updating them in a new GitHub repository here.

Right now I only have the ground station code up, but I'll be adding the other programs soon.
 

Power_Broker

Active member
Update #31.)

Found and fixed a few bugs in the ground station code. Updated both the ground station and flight controller code and posted the updated ones on my GitHub site.

Been messing around with the 3DR/SiK radio settings. I ended up going with a radio configuration that's optimized for speed while risking packet corruption. I figure since my serial data parsing library will protect my airplane from corrupt packets, I'm safe to experiment with jamming the airwaves with as much serial data as fast as possible. This is one of the reasons this is an "experimental" UAV - we'll see how it goes.

For anyone interested in the settings I'm using for these radios, here they are:

SuperSpeedSettings.PNG
 

Power_Broker

Active member
Small note: Parts of this project involves the possible use of USB to UART converters (aka FTDI converters). You can get cheap converters (like I did) that use chips from the company Prolific, but their latest drivers do not work for Windows 10. In order to get my converters to work, I followed the instructions at this website and fixed the problem!

If y'all try to get these cheap converters to work, check out the link to get rid of Code 10 errors.
 

Power_Broker

Active member
Here's a lesson learned:

Development on the plane can last hours at a time. Often during these work sessions - everything including radios and servos are powered and take quite a large power draw over an extended period of time. Up until a month or so ago, I was using my LiPo batteries to power things as I work. Unfortunately, I accidentally overdischarged and ruined several batteries while I worked because I didn't realize how low they were getting.

To keep from ruining more batteries, I use a "shore power" concept where I take a 12V LED power switch bank and connect both the ground station and the plane to separate power output ports on the device. This way I can power everything off a 12V supply for as long as I want without worrying about ruining my batteries. Below are some pics of my setup:

IMG_4333.JPG



IMG_4332.JPG
 

Power_Broker

Active member
Update #32.)

Today I ordered one of these slip rings. I want to use it for the camera/sensor gimbal if/when I design and 3D print my own. If you're not familiar with slip rings, they basically allow you to wire connections between two rotating parts without having to worry about rotating too far.

617Pq1oqenL._SL1301_.jpg
 

Power_Broker

Active member
Update #33.)

The GPS library is still in the works, but I had a MAJOR update and has been greatly improved compared to my previous version. Right now you can use the library to very efficiently grab lat, long, UTC hour, UTC minute, and UTC second of fix. I also switched from parsing GLL sentences to RMC sentences. RMC includes info such as date/UTC time of fix, course over ground, and speed over ground while GLL pretty much only has lat and long.

Here's the link: GPS Library
 

Power_Broker

Active member
Update #34.)

Tested the integration of the ground station and flight controller code. Today I tested the command radio link and GPS parsing (running simultaneously) and it worked with a slight library edit. Seems that I had the macro "DATA_LEN" defined in both libraries. At first I got yelled at by the compiler for redefining a macro, but in the Air Comms library, I changed it to "AIR_DATA_LEN" and everything works!!

Also, in Update #6.) I mentioned how I needed to change all the libraries that I use for the plane that take advantage of I2C buses. Turns out that the class constructorfor the servo driver library I use is overloaded to take an optional Wire object argument. That way, I can pass "Wire2" as a Wire object to the constructor without having to edit the source code. Yay!

Here is an example:
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(&Wire2, 0x40);

The only problem is I still need to edit the libraries for my LiDAR and IMU sensors, unfortunately.
 

JTarmstr

Elite member
Update #34.)

Tested the integration of the ground station and flight controller code. Today I tested the command radio link and GPS parsing (running simultaneously) and it worked with a slight library edit. Seems that I had the macro "DATA_LEN" defined in both libraries. At first I got yelled at by the compiler for redefining a macro, but in the Air Comms library, I changed it to "AIR_DATA_LEN" and everything works!!

Also, in Update #6.) I mentioned how I needed to change all the libraries that I use for the plane that take advantage of I2C buses. Turns out that the class constructorfor the servo driver library I use is overloaded to take an optional Wire object argument. That way, I can pass "Wire2" as a Wire object to the constructor without having to edit the source code. Yay!

Here is an example:
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(&Wire2, 0x40);

The only problem is I still need to edit the libraries for my LiDAR and IMU sensors, unfortunately.

Wow this is getting complex, following with interest.
 

Power_Broker

Active member
Update #35.)

The code for the first test flight is FINISHED! :D;)

That being said, I will continue to work on, update, improve, and expand the functionality of the code. I'll be adding sensor integration algorithms, autopilots, telemetry reporting algorithms, etc.... but for this first test flight, it'll just be a plain an simple: "can a person fly it with sticks alone?"

I'm going to post the Arduino code inline over the next two posts (I'll run into a character limit error if I don't post them separately lol).
 

Power_Broker

Active member
Ground Station (Hand Controller Code):

C++:
#include <AirComms.h>




#define PWR_LED_PIN 13

#define PITCH_COMMAND               0
#define ROLL_COMMAND                1
#define YAW_COMMAND                 2
#define THROTTLE_COMMAND            3
#define AUTOPILOT_TOGGLE_COMMAND    4
#define LIMITER_TOGGLE_COMMAND      4
#define LANDING_GEAR_TOGGLE_COMMAND 5
#define FLAPS_TOGGLE_COMMAND        5

#define PITCH_ANALOG_PIN    3
#define ROLL_ANALOG_PIN     2
#define YAW_ANALOG_PIN      0
#define THROTTLE_ANALOG_PIN 1

#define PITCH_RATE_PIN  0
#define ROLL_RATE_PIN   1
#define YAW_RATE_PIN    2

#define AILERON_OFFSET  0
#define ELEVATOR_OFFSET 0
#define RUDDER_OFFSET   0
#define THROTTLE_OFFSET 0

#define THROTTLE_MAX            170 //full throttle
#define AILERON_MAX_HIGHRATES   320 //roll left
#define ELEVATOR_MAX_HIGHRATES  360 //nose up
#define RUDDER_MAX_HIGHRATES    330 //nose left

#define THROTTLE_MIN            50  //no throttle
#define AILERON_MIN_HIGHRATES   230 //roll right
#define ELEVATOR_MIN_HIGHRATES  220 //nose down
#define RUDDER_MIN_HIGHRATES    210 //nose right

#define AILERON_MAX_LOWRATES  325
#define ELEVATOR_MAX_LOWRATES 350
#define RUDDER_MAX_LOWRATES   292

#define AILERON_MIN_LOWRATES  289
#define ELEVATOR_MIN_LOWRATES 264
#define RUDDER_MIN_LOWRATES   264

#define THROTTLE_MIN_ADC  35880
#define AILERON_MIN_ADC   35300
#define ELEVATOR_MIN_ADC  36620
#define RUDDER_MIN_ADC    37330

#define THROTTLE_MAX_ADC  61220
#define AILERON_MAX_ADC   60180
#define ELEVATOR_MAX_ADC  62300
#define RUDDER_MAX_ADC    62170




//create instances of the AirComms class
AirComms commandsRadio;
AirComms telemetryRadio;

/////////////////////////////////////////////////////////////////////////////////////////
/*
 * Command Array Anatomy:
 *
 * --------------------------------------------------------------------------------------
 * Index Number - Command Type                    - Command Format
 * --------------------------------------------------------------------------------------
 *      0       - pitch                           - 16-Bit Analog
 *      1       - roll                            - 16-Bit Analog
 *      2       - yaw                             - 16-Bit Analog
 *      3       - throttle                        - 16-Bit Analog
 *      4       - Autopilot Toggle (MSB)          - 8-bit  Boolean
 *              - Pitch/Roll Limiter Toggle (LSB) - 8-bit  Boolean
 *      5       - Landing Gear Toggle (MSB)       - 8-bit  Boolean
 *              - Flaps Toggle (LSB)              - 8-bit  Boolean
 *      6       - Unused                          - Unused
 *      7       - Unused                          - Unused
 *      8       - Unused                          - Unused
 *      9       - Unused                          - Unused
 *      10      - Unused                          - Unused
 *      11      - Unused                          - Unused
 *      12      - Unused                          - Unused
 *      13      - Unused                          - Unused
 *      14      - Unused                          - Unused
 *      15      - Unused                          - Unused
 *      16      - Unused                          - Unused
 *      17      - Unused                          - Unused
 *      18      - Unused                          - Unused
 *      19      - Unused                          - Unused
 */
/////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////
/*
 * Telemetry Array Anatomy:
 *
 * --------------------------------------------------------------------------------------
 * Index Number - Telemetry Type                  - Telemetry Format
 * --------------------------------------------------------------------------------------
 *      0       - Unused                          - Unused
 *      1       - Unused                          - Unused
 *      2       - Unused                          - Unused
 *      3       - Unused                          - Unused
 *      4       - Unused                          - Unused
 *      5       - Unused                          - Unused
 *      6       - Unused                          - Unused
 *      7       - Unused                          - Unused
 *      8       - Unused                          - Unused
 *      9       - Unused                          - Unused
 *      10      - Unused                          - Unused
 *      11      - Unused                          - Unused
 *      12      - Unused                          - Unused
 *      13      - Unused                          - Unused
 *      14      - Unused                          - Unused
 *      15      - Unused                          - Unused
 *      16      - Unused                          - Unused
 *      17      - Unused                          - Unused
 *      18      - Unused                          - Unused
 *      19      - Unused                          - Unused
 */
/////////////////////////////////////////////////////////////////////////////////////////




/////////////////////////////////////////////////////////////////////////////////////////
//structs to handle control surface commands
struct controlSurfaces
{
  byte command_index;
  byte analog_pin;
  byte rate_pin;
  uint16_t _offset;
  uint16_t high_rates_surface_max;
  uint16_t high_rates_surface_min;
  uint16_t low_rates_surface_max;
  uint16_t low_rates_surface_min;
  uint16_t ADC_max;
  uint16_t ADC_min;
};

struct controlSurfaces ailerons
{
  ROLL_COMMAND,           //command_index
  ROLL_ANALOG_PIN,        //analog_pin
  ROLL_RATE_PIN,          //rate_pin
  AILERON_OFFSET,         //_offset
  AILERON_MAX_HIGHRATES,  //high_rates_surface_max
  AILERON_MIN_HIGHRATES,  //high_rates_surface_min
  AILERON_MAX_LOWRATES,   //low_rates_surface_max
  AILERON_MIN_LOWRATES,   //low_rates_surface_min
  AILERON_MAX_ADC,        //ADC_max
  AILERON_MIN_ADC         //ADC_min
};

struct controlSurfaces elevator
{
  PITCH_COMMAND,          //command_index
  PITCH_ANALOG_PIN,       //analog_pin
  PITCH_RATE_PIN,         //rate_pin
  ELEVATOR_OFFSET,        //_offset
  ELEVATOR_MAX_HIGHRATES, //high_rates_surface_max
  ELEVATOR_MIN_HIGHRATES, //high_rates_surface_min
  ELEVATOR_MAX_LOWRATES,  //low_rates_surface_max
  ELEVATOR_MIN_LOWRATES,  //low_rates_surface_min
  ELEVATOR_MAX_ADC,       //ADC_max
  ELEVATOR_MIN_ADC        //ADC_min
};

struct controlSurfaces rudder
{
  YAW_COMMAND,            //command_index
  YAW_ANALOG_PIN,         //analog_pin
  YAW_RATE_PIN,           //rate_pin
  RUDDER_OFFSET,          //_offset
  RUDDER_MAX_HIGHRATES,   //high_rates_surface_max
  RUDDER_MIN_HIGHRATES,   //high_rates_surface_min
  RUDDER_MAX_LOWRATES,    //low_rates_surface_max
  RUDDER_MIN_LOWRATES,    //low_rates_surface_min
  RUDDER_MAX_ADC,         //ADC_max
  RUDDER_MIN_ADC          //ADC_max
};

struct controlSurfaces throttle
{
  THROTTLE_COMMAND,       //command_index
  THROTTLE_ANALOG_PIN,    //analog_pin
  0,                      //rate_pin
  THROTTLE_OFFSET,        //_offset
  THROTTLE_MAX,           //high_rates_surface_max
  THROTTLE_MIN,           //high_rates_surface_min
  0,                      //low_rates_surface_max
  0,                      //low_rates_surface_min
  THROTTLE_MAX_ADC,       //ADC_max
  THROTTLE_MIN_ADC        //ADC_max
};
/////////////////////////////////////////////////////////////////////////////////////////




//array to identify which datafields need to be sent to the plane
bool commandsArray[AIR_DATA_LEN] = {false};

//analog pin assignments
const byte yawPin       = 0;
const byte throttlePin  = 1;
const byte rollPin      = 2;
const byte pitchPin     = 3;

//loop total delay in ms
const byte delayTime    = 10;

//variables for control surface values
unsigned int pitchValue     = 0;
unsigned int rollValue      = 0;
unsigned int yawValue       = 0;
unsigned int throttleValue  = 0;




void setup()
{
  //turn on the Teensy PWR LED
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);

  //setup rate switch inputs
  pinMode(PITCH_RATE_PIN, INPUT_PULLUP);  //pitch rates
  pinMode(ROLL_RATE_PIN, INPUT_PULLUP);   //roll rates
  pinMode(YAW_RATE_PIN, INPUT_PULLUP);    //rudder rates

  //take analog readings at a resolution of 16-bits
  analogReadResolution(16);

  //initialize serial ports
  Serial.begin(2000000);
  Serial4.begin(115200);  //3DR radio
  Serial5.begin(9600);    //XBee radio

  //initialize radios
  commandsRadio.begin(Serial4);
  telemetryRadio.begin(Serial5);
}




void loop()
{
  //DAQ from joystick/switches
  updateCommands();

  /*Serial.print(commandsRadio.outgoingArray[0]); Serial.print(" ");
  Serial.print(commandsRadio.outgoingArray[1]); Serial.print(" ");
  Serial.print(commandsRadio.outgoingArray[2]); Serial.print(" ");
  Serial.print(commandsRadio.outgoingArray[3]); Serial.print(" ");
  Serial.println();*/

  //send data to plane IFC
  sendData();

  //wait for IFC to "catch up"
  delay(delayTime);
}




void updateCommands()
{
  //read and map joystick data
  //mapping normalized to max and min joystick values

  updateCommand(ailerons);
  updateCommand(elevator);
  updateCommand(rudder);
  updateCommand(throttle);

  return;
}




void updateCommand(controlSurfaces controlSurface)
{
  //value to be sent to plane
  uint16_t commandValue = 0;
 
  //read the analog voltage of the potentiometer
  uint16_t analogValue = analogRead(controlSurface.analog_pin);

  /*Serial.print("Command_Index: "); Serial.println(controlSurface.command_index);
  Serial.print("ADC_min: "); Serial.println(controlSurface.ADC_min);
  Serial.print("Analog: "); Serial.println(analogValue);
  Serial.print("ADC_max: "); Serial.println(controlSurface.ADC_max);
  Serial.print("high_rates_surface_min: "); Serial.println(controlSurface.high_rates_surface_min);
  Serial.println();*/

  //throttle is processed opposite of all other commands
  if(controlSurface.command_index == THROTTLE_COMMAND)
  {
    //convert to a servo command format
    commandValue = map(analogValue,                             //value to be mapped
                      controlSurface.ADC_min,                 //input minimum expected value
                      controlSurface.ADC_max,                 //input maximum expected value
                      controlSurface.high_rates_surface_min,  //output minimum value
                      controlSurface.high_rates_surface_max   //output maximum value
                      ) + controlSurface._offset;             //add the offset (bias)
  }
  else
  {
    //convert to a servo command format
    commandValue = map(analogValue,                             //value to be mapped
                      controlSurface.ADC_min,                 //input minimum expected value
                      controlSurface.ADC_max,                 //input maximum expected value
                      controlSurface.high_rates_surface_max,  //output maximum value
                      controlSurface.high_rates_surface_min   //output minimum value
                      ) + controlSurface._offset;             //add the offset (bias)
  }
 
  commandValue = constrain(commandValue,                            //value to be constrained
                            controlSurface.high_rates_surface_min,  //minimum value
                            controlSurface.high_rates_surface_max   //maximum value
                            );

  /*Serial.print("Command_Index: "); Serial.println(controlSurface.command_index);
  Serial.print("Value: "); Serial.println(commandValue);
  Serial.print("high_rates_surface_min: "); Serial.println(controlSurface.high_rates_surface_min);
  Serial.print("high_rates_surface_max: "); Serial.println(controlSurface.high_rates_surface_max);
  Serial.print("_offset: "); Serial.println(controlSurface._offset);
  Serial.println();*/

  //set the respective commandArray index to true so that the servo data will be sent to
  //the plane
  commandsArray[controlSurface.command_index] = true;

  //update the AirComms out buffer
  commandsRadio.outgoingArray[controlSurface.command_index] = commandValue;

  return;
}




void sendData()
{
  //send commands to the plane
  commandsRadio.sendData(commandsArray);

  //reset boolean array
  resetCommandsArray();
 
  return;
}




void resetCommandsArray()
{
  for(byte i=0; i<AIR_DATA_LEN; i++)
  {
    commandsArray[i] = false;
  }
 
  return;
}
 

Power_Broker

Active member
Flight Controller (On-Board) Code:

C++:
#include <Wire.h>
#include <AirComms.h>
#include <neo6mGPS.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include <Adafruit_PWMServoDriver.h>
#include <LIDARLite.h>
#include <Servo.h>



/////////////////////////////////////////////////////////////////////////////////////////
//macros
#define DEBUG_PORT            Serial
#define COMMAND_PORT          Serial2
#define GPS_PORT              Serial3
#define TELEM_PORT            Serial4

#define PITOT_PIN             A9

#define ANALOG_RESOLUTION     16

#define ELEVATOR_INDEX        0
#define AILERON_INDEX         1
#define RUDDER_INDEX          2
#define THROTTLE_INDEX        3
#define NOSE_GEAR_INDEX       5

#define THROTTLE_PIN          28
#define NOSE_GEAR_PIN         4
#define R_AILERON_PIN         3
#define L_AILERON_PIN         2
#define ELEVATOR_PIN          1
#define RUDDER_PIN            0

#define THROTTLE_MAX          170 //full throttle
#define AILERON_MAX           320 //roll left
#define ELEVATOR_MAX          360 //nose up
#define RUDDER_MAX            330 //nose left

#define THROTTLE_MIN          50  //no throttle
#define AILERON_MIN           230 //roll right
#define ELEVATOR_MIN          220 //nose down
#define RUDDER_MIN            210 //nose right

#define MAXPITCHUP            -20
#define UNSAFEPITCHUP         -35

#define MAXPITCHDOWN          10
#define UNSAFEPITCHDOWN       30

#define MAXROLL_RIGHT         -35
#define UNSAFEROLL_RIGHT      -45
#define MAXROLL_LEFT          35
#define UNSAFEROLL_LEFT       45

#define PITCH_CORRECTION_MIN  0
#define PITCH_CORRECTION_MAX  30

#define ROLL_CORRECTION_MIN   0
#define ROLL_CORRECTION_MAX   10

#define SERVO_MIN             0
#define SERVO_MAX             0
/////////////////////////////////////////////////////////////////////////////////////////




/////////////////////////////////////////////////////////////////////////////////////////
//class initializations
Adafruit_BNO055 bno = Adafruit_BNO055(55);
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(&Wire2, 0x40);
LIDARLite myLidarLite;
Servo throttle;
AirComms commandsRadio;
AirComms telemetryRadio;

/////////////////////////////////////////////////////////////////////////////////////////
/*
 * Command Array Anatomy:
 *
 * --------------------------------------------------------------------------------------
 * Index Number - Command Type                    - Command Format
 * --------------------------------------------------------------------------------------
 *      0       - pitch                           - 16-Bit Analog
 *      1       - roll                            - 16-Bit Analog
 *      2       - yaw                             - 16-Bit Analog
 *      3       - throttle                        - 16-Bit Analog
 *      4       - Autopilot Toggle (MSB)          - 8-bit  Boolean
 *              - Pitch/Roll Limiter Toggle (LSB) - 8-bit  Boolean
 *      5       - Landing Gear Toggle (MSB)       - 8-bit  Boolean
 *              - Flaps Toggle (LSB)              - 8-bit  Boolean
 *      6       - Unused                          - Unused
 *      7       - Unused                          - Unused
 *      8       - Unused                          - Unused
 *      9       - Unused                          - Unused
 *      10      - Unused                          - Unused
 *      11      - Unused                          - Unused
 *      12      - Unused                          - Unused
 *      13      - Unused                          - Unused
 *      14      - Unused                          - Unused
 *      15      - Unused                          - Unused
 *      16      - Unused                          - Unused
 *      17      - Unused                          - Unused
 *      18      - Unused                          - Unused
 *      19      - Unused                          - Unused
 */
/////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////
/*
 * Telemetry Array Anatomy:
 *
 * --------------------------------------------------------------------------------------
 * Index Number - Telemetry Type                  - Telemetry Format
 * --------------------------------------------------------------------------------------
 *      0       - Unused                          - Unused
 *      1       - Unused                          - Unused
 *      2       - Unused                          - Unused
 *      3       - Unused                          - Unused
 *      4       - Unused                          - Unused
 *      5       - Unused                          - Unused
 *      6       - Unused                          - Unused
 *      7       - Unused                          - Unused
 *      8       - Unused                          - Unused
 *      9       - Unused                          - Unused
 *      10      - Unused                          - Unused
 *      11      - Unused                          - Unused
 *      12      - Unused                          - Unused
 *      13      - Unused                          - Unused
 *      14      - Unused                          - Unused
 *      15      - Unused                          - Unused
 *      16      - Unused                          - Unused
 *      17      - Unused                          - Unused
 *      18      - Unused                          - Unused
 *      19      - Unused                          - Unused
 */
/////////////////////////////////////////////////////////////////////////////////////////





/////////////////////////////////////////////////////////////////////////////////////////
//sensor variables
byte LiDAR_Counter = 0;
int altitude = 0;
float rollAngle = 0;
float pitchAngle = 0;
float convertedRoll = 0;
float convertedPitch = 0;
int convertedAltitude = 0;
float velocity = 0; /* m/s */
float latitude = 0;
float longitude = 0;
/////////////////////////////////////////////////////////////////////////////////////////


uint32_t currentTime;
uint32_t futureTime;
int hi;

void setup()
{
  //turn on the Teensy PWR LED
  pinMode(13,OUTPUT);
  digitalWrite(13,HIGH);
 
  //initialize serial ports
  DEBUG_PORT.begin(2000000);    //PC debugging
  //while(!DEBUG_PORT);
 
  COMMAND_PORT.begin(115200);   //Command comms
  while(!COMMAND_PORT);
 
  GPS_PORT.begin(9600);         //GPS comms
  while(!GPS_PORT);
 
  TELEM_PORT.begin(9600);       //Telem comms
  while(!TELEM_PORT);
 

  //initialize radios
  commandsRadio.begin(Serial2);
  telemetryRadio.begin(Serial4);

  //initialize GPS
  myGPS.begin(Serial3, Serial);
 
  //initialize the IMU and wait for it to "boot up"
  if(!bno.begin())
  {
    //there was a problem detecting the BNO055 ... check your connections
    Serial.println("Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!");
    //while(1);
  }
  else
  {
    Serial.println("BNO055 Successfully Configured");
    delay(1000);
    bno.setExtCrystalUse(true);
  }

  //initialize the servo driver and set the frequency of all outputs to 50Hz
  pwm.begin();
  pwm.setPWMFreq(50);

  //initialize each individual servo to their respective center position
  pwm.setPWM(RUDDER_PIN, 0, (RUDDER_MAX+RUDDER_MIN)/2);
  pwm.setPWM(ELEVATOR_PIN, 0, (ELEVATOR_MAX+ELEVATOR_MIN)/2);
  pwm.setPWM(L_AILERON_PIN, 0, (AILERON_MAX+AILERON_MIN)/2);
  pwm.setPWM(R_AILERON_PIN, 0, (AILERON_MAX+AILERON_MIN)/2);
 
  //initialize the ESC
  throttle.attach(THROTTLE_PIN);
  throttle.write(THROTTLE_MIN);

  //initialize the LiDAR for long distance readings
  myLidarLite.begin(0, true);
  myLidarLite.configure(0);

  //set resolution for analog reads (such as for reading pitot tube pressure)
  analogReadResolution(ANALOG_RESOLUTION);
}




void loop()
{
  //receive data from the ground station if available
  if(getCommands())
  {
    throttle.write(constrain(commandsRadio.incomingArray[THROTTLE_INDEX], THROTTLE_MIN, THROTTLE_MAX));
    
    pwm.setPWM(ELEVATOR_PIN, 0, constrain(commandsRadio.incomingArray[ELEVATOR_INDEX], ELEVATOR_MIN, ELEVATOR_MAX));
    pwm.setPWM(RUDDER_PIN, 0, constrain(commandsRadio.incomingArray[RUDDER_INDEX], RUDDER_MIN, RUDDER_MAX));
    pwm.setPWM(R_AILERON_PIN, 0, constrain(commandsRadio.incomingArray[AILERON_INDEX], AILERON_MIN, AILERON_MAX));
    pwm.setPWM(L_AILERON_PIN, 0, constrain(commandsRadio.incomingArray[AILERON_INDEX], AILERON_MIN, AILERON_MAX));

    /*Serial.print("Throttle: "); Serial.println(constrain(commandsRadio.incomingArray[THROTTLE_INDEX], THROTTLE_MIN, THROTTLE_MAX));
    Serial.print("Elevator: "); Serial.println(constrain(commandsRadio.incomingArray[ELEVATOR_INDEX], ELEVATOR_MIN, ELEVATOR_MAX));
    Serial.print("Rudder: "); Serial.println(constrain(commandsRadio.incomingArray[RUDDER_INDEX], RUDDER_MIN, RUDDER_MAX));
    Serial.print("Aileron: "); Serial.println(constrain(commandsRadio.incomingArray[AILERON_INDEX], AILERON_MIN, AILERON_MAX));*/
  }

  //receive data from the GPS if available
  if(myGPS.grabData_LatLong() == NEW_DATA)
  {
    //debugging prints
    /*Serial.print("UTC HOUR: "); Serial.println(myGPS.GPS_data[UTC_HOUR_POS]);
    Serial.print("UTC MIN: "); Serial.println(myGPS.GPS_data[UTC_MINUTE_POS]);
    Serial.print("UTC SEC: "); Serial.println(myGPS.GPS_data[UTC_SECOND_POS]);
    Serial.print("LAT (dd): "); Serial.println(myGPS.GPS_data[LAT_POS], 10);
    Serial.print("LON (dd): "); Serial.println(myGPS.GPS_data[LON_POS], 10);*/
  }
 
  //read IMU data
  getEulerAngles();

  //read altimeter data
  readLiDAR();

  //find true altitude
  convertedAltitude = altitude * cos(convertedRoll) * cos(convertedPitch);
  //Serial.print("Converted Altitude: "); Serial.println(convertedAltitude);

  //read airspeed data
  readPitot();
}




bool getCommands()
{
  //get new data if available
  int report = commandsRadio.getData();

  ///////////////////////////////////////////////////////////////////////////////////////
  //optional debugging prints
  if(report == 1)
  {
    /*Serial.println("Success!");
    Serial.print("\tNew Data: ");
    for(byte i=0; i<AIR_DATA_LEN; i++)
    {
      if(i != 0)
      {
        Serial.print(" ");
      }
      
      Serial.print(commandsRadio.incomingArray[i]);
    }
    Serial.println();*/
  }
  /*else if(report == NO_DATA)
  {
    Serial.println("\t\tNo Data Available");
  }
  else if(report == SERIAL_BUFF_ERROR)
  {
    Serial.println("\t\tERROR - SERIAL_BUFF_ERROR");
  }
  else if(report == END_BYTE_ERROR)
  {
    Serial.println("\t\tERROR - END_BYTE_ERROR");
  }
  else if(report == CHECKSUM_ERROR)
  {
    Serial.println("\t\tERROR - CHECKSUM_ERROR");
  }
  else if(report == TIMEOUT_ERROR)
  {
    Serial.println("\t\tERROR - TIMEOUT_ERROR");
  }
  else if(report == PAYLOAD_ERROR)
  {
    Serial.println("\t\tERROR - PAYLOAD_ERROR");
  }
  else
  {
    Serial.print("\t\tERROR - UNKNOWN ERROR OCCURRED: ");
    Serial.println(report);
  }*/

  if(report == 1)
  {
    return true;
  }
  else
  {
    return false;
  }
}




void getEulerAngles()
{
  //get a new sensor event
  sensors_event_t event;
  bno.getEvent(&event);

  //get pitch and roll angles in degrees
  rollAngle = event.orientation.y;
  pitchAngle = event.orientation.z;

  //convert Euler angles from degrees to radians - ONLY USED FOR LiDAR CORRECTION
  convertedRoll = (rollAngle) * (M_PI / 180);
  convertedPitch = (pitchAngle) * (M_PI / 180);
}




void readLiDAR()
{
  //get altitude readings - including periodic bias correction
  if(LiDAR_Counter >= 100)
  {
    altitude = myLidarLite.distance();
    LiDAR_Counter = 0;
  }
  else
  {
    altitude = myLidarLite.distance(false);
    LiDAR_Counter = LiDAR_Counter + 1;
  }

  //Serial.print("Raw LiDAR Distance: "); Serial.println(altitude);
}




void readPitot()
{
  //velocity =  //(m/s)
  uint32_t pitotValue = analogRead(PITOT_PIN);

  //Serial.print("Pitot Value: "); Serial.println(pitotValue);
 
  return;
}
 

Power_Broker

Active member
Update #34.)

Since my last post there has been a LOT of work done on the software and a little bit on the hardware side.

After I made the decision to finalize the working code for my first test flight, I did some in depth testing and found a couple of small bugs. I decided to start fixing and altering the code and ended up starting the process to completely overhaul how the software works. I realized that the sketches for both the hand controller and (especially) the flight controller were long and complicated. This isn't what I want. I'm envisioning a complete plane avionics design that is easy to build, code, and reconfigure. The plan is to implement many of the autopilot features found in ArduPilot (or even better/different features) in a way that is more intuitive - especially when it comes to editing the software files manually (editing the actual source files in ArduPilot is extremely difficult unless you have a lot of C++ experience). Thinking of all this gave me an idea.

What if I create a library for my plane? But not just a normal library - it'll be a master library to include all other libraries (both custom and external) needed for the core autonomous RC plane functions while also providing the user an intuitive interface with these functions and data. This master library is called ArdUAV and serves as an abstraction of the hardcore processes such as parsing GPS data, transferring and parsing serial data over radio link, polling data from sensors, setting servo positions, etc.

Although these core processes are abstracted, the data being used in these processes are fully available to the user. Also, not only are all flight critical pieces of data available to the user (i.e. lat/long, airspeed, etc), but the abstracted functions are also fully available to the user. This gives unparalleled control of the plane to the user in only a couple lines of code! The user can easily and quickly design a sketch that can differ from other sketches using the same library. For example, if you want to take every elevator servo command received from the hand controller and add the value of 5 before updating the servo position, you can do ALL of that in literally 2 or 3 lines of code as long as you're using ArdUAV!

Another reason why I wanted to create ArdUAV was to make plane and hand controller configuration easier. Before I created this library, I had to rewrite every macro, constant, and core functions. This meant a lot of error prone and time consuming copy and pasting. Now, I have only two files that need to be edited once. These files are IFC_Tools.h for the flight controller and GS_Tools.h for the hand controller. Each of these serve as a sort of config file for the library. For instance, if you want to change the serial port the telemetry radio is connected to on the plane from Serial1 to Serial2, all you have to do is change "#define TELEM_PORT 1" to "#define TELEM_PORT 2" in IFC_Tools.h and the library automatically reconfigures the rest of the code for you using preprocessor conditionals (#if statements).

Another big software update is that I'm changing how the wireless transfer of serial data is handled in my AirComms custom library. Instead of periodically polling the radio's serial input buffer for new data, I've decided to use hardware serial events. The details on how to use serial events with Arduinos can be found here. Basically, the serialEvent#() function can automatically detect incoming serial bytes and process them as they arrive. This is a MUCH faster, efficient, and safer way of processing serial data.

I still have yet to test the new updates to the AirComms library - probably get around to it this weekend.

There have also been a couple of hardware updates. The first of which is the FPV goggle setup I'm using. Beforehand I had the el cheapo foam goggles from hobbyking, but I recently got a Quanum Fatshark Genesis. It's plug and play with my current 5.8GHz FPV transmitter, thank goodness!


Fatshark-Quanum-Genesis-no0.jpg


I've also decided to edit the flight controller PCB to include the second row of pins on the BNO055 IMU. This will allow me to use the interrupt pin on the IMU for automated data transfer (as opposed to polling the IMU for data). I'm also considering using the analog signal from the LiDAR altimeter instead of I2C. This should make acquiring altitude data a bit faster. Lastly, I'm thinking about doing away with the servo driver and using the servo library on the Teensy for servo control instead.
 
Last edited: