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:
- The motor terminals connect to the motor A port screw terminals on the shield.
- The Arduino 101 is able to control the motor speed using the Pin 3 PWMA pin as indicated on the shield.
- The motor only needs to spin in the clockwise direction and is configured by grounding Pin 12 DIRA on the shield.
- The rudder and elevator are controlled using servos with the control wires connected to PWM Pins 9 and 6 respectively.
- The remote control receiver output pin is connected as a Digital Input to Pin 2 on the Arduino 101.
- All components are powered by +5V.
- The Arduino 101 device is powered with an external power supply.
Processing Data From the Receiver:
Data Stream Format:
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.
Channel | Control | Stick | Direction | Min Value(us) | Max Value(us) |
---|---|---|---|---|---|
1 | Rudder | Right | Left/Right | 790 Left | 1615 Right |
2 | Elevator | Right | Up/Down | 790 Down | 1627 Up |
3 | Throttle | Left | Up/Down | 790 0% | 1630 100% |
5 | Flight Intelligence | Right | Push | 790 Enabled | 1630 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 Turn | Negative Roll |
Right Turn | Positive Roll |
Climbing | Negative Pitch |
Descending | Positive 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.
//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.