Exploring Smart Remote Control Airplanes using the Intel® Curie™ Module

Technology kit for remote controlled airplane.

Introduction:

Remote control airplanes are a fun hobby whether you are young or old.  The technology and kits offered today have evolved a lot over the years yielding smaller footprints, lower costs, and more integrated electronics.  This article explores emulating a three channel smart remote control airplane example with a focus on the hardware interface and software.

The Arduino 101* (branded Genuino 101* outside the U.S.) with the Intel® Curie™ module is a good choice for this example given the low latency microcontroller-like characteristics of the module, low power consumption, PWM peripheral, and integrated accelerometer and gyroscope.  The Arduino 101 will form the heart of the solution as it provides a mechanism to gather user input from a remote control, manage the control of a DC motor that represents the propeller, and two Servo motors representing the rudder and elevator controls respectively. The rudder controls the left and right “steering” of the plane and the elevator controls the climb and dive of the plane.

Since the Intel Curie module also comes packaged with an accelerometer and gyroscope, we will also incorporate an advanced “learning/beginner” mode. Once selected, this mode will detect dramatic/steep changes to rates of climb/decent or turns and automatically counter the movement to prevent crashes.

 

Hardware Interface:

The various hardware components of this prototype are:

  • 1 x Arduino 101 (https://www.arduino.cc/en/Main/ArduinoBoard101)
  • 2 x Micro Servos (https://www.sparkfun.com/products/9065)
  • 1 x ArduMoto Motor Shield (https://www.sparkfun.com/products/9815)
  • 1 x Brushed DC Motor (https://www.sparkfun.com/products/11696)
  • 1 x R415x 2.4GHz Receiver (https://hobbyking.com/media/file/672761531X1606554X3.pdf)
  • 2.4GHz Transmitter (https://www.rcplanet.com/ParkZone_2_4GHz_4_Channel_Transmitter_Spektrum_DSM_p/pkz3341.htm?gclid=CLz6uPnvsNICFVCBfgodFcUKqw)

The brushed DC motor is used as the throttle, in conjunction with the Ardumoto shield, which provides the h- bridge drivers for energizing the motor and inputs for controlling the motor speed and direction. The hardware connections are listed below:

  1. The motor terminals connect to the motor A port screw terminals on the shield.
  2. The Arduino 101 is able to control the motor speed using the Pin 3 PWMA pin as indicated on the shield.
    1. The motor only needs to spin in the clockwise direction and is configured by grounding Pin 12 DIRA on the shield.
  3. The rudder and elevator are controlled using servos with the control wires connected to PWM Pins 9 and 6 respectively.
  4. The remote control receiver output pin is connected as a Digital Input to Pin 2 on the Arduino 101.
  5. All components are powered by +5V.
  6. The Arduino 101 device is powered with an external power supply.
Conceptual model of the plane.

Conceptual model of the plane

 

Hardware diagram for remote controlled airplane.

Hardware Diagram

 

Processing Data From the Receiver:

Data Stream Format:

 

cPPM Data Stream

cPPM Data Stream

The transmitter used in this example contains left and right sticks for user input.  The transmitter sends the stick position values to the receiver where it is then output to the Arduino 101 device for processing. The receiver output connects to the Arduino 101 using a single digital I/O pin.  The receiver outputs data in a cPPM (Combined Pulse Position Modulation) format that the Arduino 101 can decode to determine the channel values sent from the transmitter.  The data stream contains a start pulse and 6 channel pulses as shown in the oscilloscope capture above.

 

Start Pulse

Start Pulse

The start pulse is the longest pulse in the data stream and is the beginning of a frame.  The start pulse for the receiver in this example measures around 12.48ms as shown by the blue cursors in the oscilloscope capture above.

 

Channel Pulses

Channel Pulses

After the start pulse, there are six channel pulses that will have varying pulse widths depending on the position of the left and right sticks on the transmitter.  The channel 1 pulse width in this example measures 1.16ms as shown by the blue cursors in the oscilloscope capture above. 

 

Decoding the data:

To decode the data stream, the pulses are measured to detect the start pulse and then measure the time for each channel pulse.  The resources used on the Arduino 101 to decode the data stream are a Timer and Interrupt on Pin Change.  The Timer is used to count the number of microseconds of each edge and the interrupt is used to detect edge transitions.  To initialize the Interrupt on Pin Change and Timer, see the setup() function code below:

#define cPPM_PIN 2
unsigned long startT=0;
void setup() {
   pinMode(cPPM_PIN, INPUT);
   attachInterrupt(digitalPinToInterrupt(cPPM), isr, CHANGE);
   startT=micros();
 } 

The interrupt service routine will trigger on each edge transition in the data stream to detect the start pulse and measure each channel pulse width using the Timer.  The start pulse is detected to determine the start of the current data stream frame and then each channel pulse width is measured to determine the channel value.  The pulse width is measured by subtracting the previous timer value from the current timer value during each interrupt.  The channel values are continuously updated in the isr and are stored in the channel array.  The interrupt service routine isr() implementation is shown in the code below.

#define SOF_TIME 11000
#define NUM_CHANNELS 6
unsigned long endT=0;
unsigned long pwT=0;
int cppm=0;
bool sof=false;
int count=0;
int channel[6];
void isr()
{
    //Save End Time
    endT = micros();

    //Read cPPM Pin
    cppm = digitalRead(cPPM_PIN);

    //Measure Pulsewidth Time
    pwT = endT - startT;
    startT = endT;

    //SOF Pulse Detect
    if (cppm == LOW && pwT > SOF_TIME)
    {
      sof = true;
    }
    
    //Channel Pulse Detect
    else if (cppm == LOW && sof)
    {
      channel[count++] = pwT;
      if (count == NUM_CHANNELS)
      {
        sof = false;
        count = 0;
      }
    }
} 

 

Correlating the data:

After decoding the data stream, the data is correlated to determine the transmitter left/right stick to channel number and channel minimum and maximum pulse width times.  After experimenting with the ranges of the left and right sticks on the transmitter, a correlation table is shown below.  This table is used in the next sections where controlling the airplane with the rudder, elevator, and throttle are discussed.

ChannelControlStickDirectionMin Value(us) Max Value(us) 
1RudderRightLeft/Right790 Left1615 Right
2ElevatorRightUp/Down790 Down1627 Up
3ThrottleLeftUp/Down790 0%1630 100%
5Flight IntelligenceRightPush790 Enabled1630 Disabled

Below is some test code that can be put in the main loop to print out the channel values.

for (int i=0; i < NUM_CHANNELS; i++)
    Serial.println(channel[i]);
Serial.println("");
delay(250);

 

 

Controlling the Rudder and Elevator:

The rudder steers the airplane left or right and the elevator allows the airplane to climb or descend.  Individual servos are used to move the rudder and elevator.  The servo control lines are connected to the Arduino 101 using two PWM pins.  The Arduino 101 Servo library is used to quickly interface to the servos.  For example, the servos are initialized for rudder and elevator control in the setup() function below.

#include <Servo.h>
#define RUDDER_PIN  9
#define ELEVATOR_PIN 6
Servo rudder;
Servo elevator;
void setup() {
  rudder.attach(RUDDER_PIN);
  elevator.attach(ELEVATOR_PIN);
 }

After the servos are initialized, the main loop is used to map the channel values measured in the isr() to servo position values.  From the correlation table above, the minimum and maximum channel values are mapped to the minimum and maximum position values that the servo should move.  This interpolation is easy to do with the map() function.  The servos are actuated by calling the write() function.  The example main loop code for rudder and elevator code is shown below.

#define RUDDER_CHANNEL      0
#define ELEVATOR_CHANNEL    1

#define RUDDERVAL_MIN     790
#define RUDDERVAL_MAX     1615
#define RUDDERSERVO_MIN   0
#define RUDDERSERVO_MAX   180

#define ELEVATORVAL_MIN   790
#define ELEVATORVAL_MAX   1627
#define ELEVATORSERVO_MIN 0
#define ELEVATORSERVO_MAX 180

int rudderVal=0;
int elevatorVal=90;

void loop() {
rudderVal   = map(channel[0],RUDDERVAL_MIN,RUDDERVAL_MAX,RUDDERSERVO_MIN,RUDDERSERVO_MAX);
    elevatorVal = map(channel[1],ELEVATORVAL_MIN,ELEVATORVAL_MAX,ELEVATORSERVO_MIN,ELEVATORSERVO_MAX);
  rudder.write(rudderVal);
  elevator.write(elevatorVal);
  delay(100);
}

 

Controlling the Throttle:

The throttle is used to control the air speed of the airplane and is connected to an Arduino 101 PWM pin.    Increasing the throttle, increases the PWM duty cycle which causes the motor to spin the propeller faster.  Decreasing the throttle, decreases the PWM duty cycle which causes the motor to spin the propeller slower.  The example implementation below in the main loop shows how to accomplish simple throttle control.  A similar channel mapping interpolation is used to determine the throttle percentage to PWM duty cycle value.  This value is simply passed in a call to analogWrite() to adjust the motor speed.

#define THROTTLE_PIN 3
#define THROTTLE_CHANNEL    2
#define THROTTLEVAL_MIN   790
#define THROTTLEVAL_MAX   1630
#define THROTTLEMOTOR_MIN 0
#define THROTTLEMOTOR_MAX 255
int throttleVal=0;
void loop() {
    throttleVal = map(channel[THROTTLE_CHANNEL],THROTTLEVAL_MIN,THROTTLEVAL_MAX,THROTTLEMOTOR_MIN,THROTTLEMOTOR_MAX);
  analogWrite(THROTTLE_PIN, throttleVal);
  delay(100);
}

 

Flight Intelligence:

One of the challenges when flying remote control airplanes is getting used to the sensitivity of the control sticks on the transmitter.  It is very easy to apply too much stick during a flight which can cause you to lose control of the airplane.  Using the onboard accelerometer and gyroscope, closed loop flight intelligence could be added to measure and correct steep banking angles during turns and steep dive angles while descending or climbing.  Correcting these conditions could prevent the plane from stalling or crashing.  To enter or exit the flight intelligence mode, the right control stick is pushed in to toggle the mode.  This correlates to channel five as indicated in the channel correlation table discussed earlier.    

Measuring the Angles:

To measure the angles of the airplane during flight, real time data received from the sensors are converted to roll and pitch values.  A roll value gives the angle when the airplane is turning left or right.  The pitch value gives the angle when climbing or descending.  The sign of the angle measured determines the direction for a given pitch or roll value.  Please see the table below for correlating roll and pitch values to the airplane movements.  Please also note that the values assume the front orientation of the Arduino 101 board is the side where the ICSP header resides.

Left TurnNegative Roll
Right TurnPositive Roll
ClimbingNegative Pitch
DescendingPositive Pitch

Correcting the Angles:

When detecting that roll or pitch threshold angles are exceeded, the system can override the user’s input and actuate the rudder or elevator servos intelligently to reverse the roll or pitch angles back below the threshold while holding the throttle steady.  The implementation is achievable using the CurieIMU library to read the sensor data and the Madgwick filter to compute the pitch and roll angles.  An example flowchart, initialization of the sensors, and a modified implementation of the main loop are shown below. 

 

An example flowchart, initialization of the sensors, and a modified implementation of the main loop.

//Initialization
#include <CurieIMU.h>
#include <MadgwickAHRS.h>
#define RATE                    25    //Hz
#define ACCEL_RANGE             2     //G
#define GYRO_RANGE              250   //Degrees/Second
void setup() {
CurieIMU.begin();
CurieIMU.setGyroRate(RATE);
CurieIMU.setAccelerometerRate(RATE);
filter.begin(RATE);
CurieIMU.setAccelerometerRange(ACCEL_RANGE);
CurieIMU.setGyroRange(GYRO_RANGE);
}

//Main Loop
#define ROLL_THRESHOLD          15
#define PITCH_THRESHOLD         25
#define CENTER_ELEVATOR         90
#define CENTER_RUDDER            90
#define UP_ELEVATOR                  CENTER_ELEVATOR+30
#define LEFT_RUDDER                  CENTER_RUDDER-30
#define RIGHT_RUDDER               CENTER_RUDDER+30
#define HALF_THROTTLE              128
void loop() {
  int aix, aiy, aiz;
  int gix, giy, giz;
  float ax, ay, az;
  float gx, gy, gz;
  int roll, pitch;

  //If Flight Intelligence Disabled, Regular User Control
  if (channel[FLIGHTMODE_CHANNEL] > FLIGHTMODE_THRESHOLD)
  {
    rudderVal   = map(channel[RUDDER_CHANNEL],RUDDERVAL_MIN,RUDDERVAL_MAX,RUDDERSERVO_MIN,RUDDERSERVO_MAX);
    elevatorVal = map(channel[ELEVATOR_CHANNEL],ELEVATORVAL_MIN,ELEVATORVAL_MAX,ELEVATORSERVO_MIN,ELEVATORSERVO_MAX);
    throttleVal = map(channel[THROTTLE_CHANNEL],THROTTLEVAL_MIN,THROTTLEVAL_MAX,THROTTLEMOTOR_MIN,THROTTLEMOTOR_MAX);
  }
  //If Flight Intelligence Enabled
  else
  {
    //Measure Pitch and Roll Angles
    CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz);
    ax = convertRawAcceleration(aix);
    ay = convertRawAcceleration(aiy);
    az = convertRawAcceleration(aiz);
    gx = convertRawGyro(gix);
    gy = convertRawGyro(giy);
    gz = convertRawGyro(giz);
    filter.updateIMU(gx, gy, gz, ax, ay, az);
    roll = (int)filter.getRoll();
    pitch = (int)filter.getPitch();

    //If Steep Dive
    if (pitch >= 0 && abs(pitch) >= PITCH_THRESHOLD)
    {
      //Apply Up Elevator
      elevatorVal = UP_ELEVATOR;

      //Apply 50% Throttle
      throttleVal = HALF_THROTTLE;
    }

    //If Steep Climb
    else if (pitch < 0 && abs(pitch) >= PITCH_THRESHOLD)
    {
      //Apply Center Elevator
      elevatorVal = CENTER_ELEVATOR;

      //Apply 50% Throttle
      throttleVal = HALF_THROTTLE;
    }

    //If Right Turn
    else if (roll >= 0 && abs(roll) >= ROLL_THRESHOLD)
    {
      //Apply Left Rudder
      rudderVal = LEFT_RUDDER;

      //Apply 50% Throttle
      throttleVal = HALF_THROTTLE;
    }

    //If Left Turn
    else if (roll < 0 && abs(roll) >= ROLL_THRESHOLD)
    {
      //Apply Right Rudder
      rudderVal = RIGHT_RUDDER;

      //Apply 50% Throttle
      throttleVal = HALF_THROTTLE;
    }

    //Else Flight Mode User Control
    else
    {
      //Map Channel Values to Control Values
      rudderVal   = map(channel[RUDDER_CHANNEL],RUDDERVAL_MIN,RUDDERVAL_MAX,RUDDERSERVO_MIN,RUDDERSERVO_MAX);
      elevatorVal = map(channel[ELEVATOR_CHANNEL],ELEVATORVAL_MIN,ELEVATORVAL_MAX,ELEVATORSERVO_MIN,ELEVATORSERVO_MAX);
      throttleVal = map(channel[THROTTLE_CHANNEL],THROTTLEVAL_MIN,THROTTLEVAL_MAX,THROTTLEMOTOR_MIN,THROTTLEMOTOR_MAX); 
  }
  rudder.write(rudderVal);
  elevator.write(elevatorVal);
  analogWrite(THROTTLE_PIN, throttleVal);
  delay(100);
}

 

Conclusion:

We discussed about the hardware and software used in an example smart three channel remote control airplane.  We laid the groundwork and learned about how to process the receiver data and how to use the motor and servo components to control the airplane.  We concluded by adding real time flight intelligence using the sensors onboard the Intel Curie module to keep the airplane flying under control.  The concepts discussed in this article can also be applied to other types of projects that use remote control, servos, and motor control.  

 

About the Author:

Mike Rylee is a Software Engineer at Intel Corporation with a background in developing embedded systems and apps for Android*, Windows*, iOS*, and Mac*.  He currently works on Internet of Things projects.

 

Notices:

No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.

Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.

This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule, specifications and roadmaps.

The products and services described may contain defects or errors known as errata which may cause deviations from published specifications. Current characterized errata are available on request.

Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or by visiting www.intel.com/design/literature.htm.

Intel, the Intel logo, and Intel RealSense are trademarks of Intel Corporation in the U.S. and/or other countries.

 

*Other names and brands may be claimed as the property of others

**This sample source code is released under the Intel Sample Source Code License Agreement

© 2017 Intel Corporation.

For more complete information about compiler optimizations, see our Optimization Notice.