0

I'm having a bit of an odd problem here. I'm programming a self-built quadcopter using an Arduino Uno for control. One of the sensor's that I'm using is Adafruit's GPS breakout. The motors are controlled with electronic speed controllers (ESCs) that controlled identically to normal servo motors. Everything is connected together with a custom Arduino shield. The GPS is using SoftwareSerial on pins 3 and 4, and the ESCs are connected on pins 5-8. Most of the GPS code is directly copied from Adafruit's documentation.

What I've found is that even when the throttle is held steady (at any power level), the motors experience quick bursts of power far beyond normal whenever there is communication between the GPS module and the Arduino. For example, the GPS updates on 1Hz intervals and so the motors get a burst of speed about every second. Has anyone else experienced something like this or could someone at least point me in the right direction?

Here's an abbreviated version of the code that I'm currently using:

#include <Servo.h>
#include <Wire.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

Servo ESC1;
Servo ESC2;
Servo ESC3;
Servo ESC4;
const int maxThrottle = 2000;
const int minThrottle = 1000;

SoftwareSerial softwareSerial(4, 3);
Adafruit_GPS GPS(&softwareSerial);
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences. 
#define GPSECHO false
bool usingInterrupt = false;
uint32_t timer = millis();

void setup() {
    ESC1.attach(5);
    ESC2.attach(8);
    ESC3.attach(6);
    ESC4.attach(7);
    setThrottleAll(minThrottle);

    GPS.begin(9600);
    GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
    GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
    GPS.sendCommand(PGCMD_ANTENNA);
    useInterrupt(true);
}
int throttleCommand = 20;
void loop() {
    // Map throttle command percentage to raw throttle values that are sent to the ESCs
    int throttle = map(throttleCommand, 0, 100, minThrottle, maxThrottle);
    setThrottle(1, throttle);
    setThrottle(2, throttle);
    setThrottle(3, throttle);
    setThrottle(4, throttle);
}

// GPS interrupt
SIGNAL(TIMER0_COMPA_vect) {
    char c = GPS.read();
    // if you want to debug, this is a good time to do it!
    #ifdef UDR0
        if (GPSECHO)
            if (c) UDR0 = c;
                // writing direct to UDR0 is much much faster than Serial.print 
                // but only one character can be written at a time. 
    #endif
}
void useInterrupt(boolean v) {
    if (v) {
        // Timer0 is already used for millis() - we'll just interrupt somewhere
        // in the middle and call the "Compare A" function above
        OCR0A = 0xAF;
        TIMSK0 |= _BV(OCIE0A);
        usingInterrupt = true;
    }
    else {
        // do not call the interrupt function COMPA anymore
        TIMSK0 &= ~_BV(OCIE0A);
        usingInterrupt = false;
    }
}

void setThrottleAll(int throttle) {
    setThrottle(1, throttle);
    setThrottle(2, throttle);
    setThrottle(3, throttle);
    setThrottle(4, throttle);
}
void setThrottle(int index, int throttle) {
    if (index == 1)
        ESC1.writeMicroseconds(throttle);
    if (index == 2)
        ESC2.writeMicroseconds(throttle);
    if (index == 3)
        ESC3.writeMicroseconds(throttle);
    if (index == 4)
        ESC4.writeMicroseconds(throttle);
}
  • 1
    SoftwareSerial combined with a real time flight controller sounds like a bad idea. You may also have areas in the message code that can block. This project requires careful engineering from one end to the other. – Chris Stratton May 23 '16 at 0:23
  • @ChrisStratton Is there a different way of communicating with the GPS module that doesn't block? Would the hardware serial pins 0 and 1 be a better choice? – petschekr May 23 '16 at 0:34
  • Servo.h uses a timer and will experience glitches if interrupts are disabled frequently/for a long time. – BrettAM May 23 '16 at 0:39
  • Realistically, an Arduino is not an appropriate platform for a flight controller - pretty much all projects have long abandoned them, and even if you are going to use an ATmega you really need to use only code specifically written for this purpose rather than typical Arduino libraries. For example, you want to make sure that you are using hardware PWM channels for all outputs and you want to make sure that your control loop cannot under any combination of circumstances get stuck anywhere. Speaking of loops, a lot more is needed than throttling the motors. – Chris Stratton May 23 '16 at 0:50
  • Ah, I understand. The reason I thought it was possible was because of other projects like AeroQuad and BlueCopter. Some people have even gotten quads to fly with a Raspberry Pi as the flight controller (and a totally non-realtime stock Raspbian OS). On the last point, my actual code is much more complex. I just kept the relevant parts because I've narrowed down the issue to the GPS module. The rest is much more than just throttling motors! – petschekr May 23 '16 at 1:02
1

The bad news is that you are using the absolute worst libraries (SoftwareSerial and Adafruit_GPS), and your program structure is based on the awful Adafruit_GPS example.

The good news is that there are much better alternatives: NeoSWSerial and NeoGPS. Still, there's no doubt that you are near the limits of this processor, especially with Wire and timer-based Servos. It can be an "interesting challenge" or a "beginner's nightmare."

Suggestions:

1) Consider using the HardwareSerial object, Serial, for the GPS interface. Your excerpt does not show what Serial is currently used for, but if it's debug print statements... don't. Instead, use a software serial port for debug statements (use a serial-to-USB adapter on those pins to connect it to your PC). This would have a tremendous impact.

2) If you really really can't use Serial for the GPS (are you sure???), consider using an AltSoftSerial instance on pins 8 & 9, as this is much better than the dreaded SoftwareSerial. Next best is NeoSWSerial on any pins, including 4&3 that you are using now. There is absolutely no reason to shoot yourself in the foot with SoftwareSerial.

3) As other's have commented, use HW PWM... let the machine do the work for you.

4) Consider using the faster and smaller NeoGPS library. And restructure your program to look like the NeoGPS example programs, because their sequencing is much more efficient. The Adafruit_GPS example uses a timer interrupt... to read a character from the input buffer... that was stored by a SoftwareSerial interrupt... that was on for the entire character time of ~1ms. What a phenomenal waste!

5) Use interrupt-driven GPS parsing. All three Neo__Serial libraries (HW, IC and SW) allow NeoGPS to handle the GPS characters during the rx interrupt, avoiding character loss because the port was not read in time (input buffer overflow). And because just a little work is performed on each character, there is no big chunk of processing when a sentence is complete. This also uses half the interrupts and saves copying the characters multiple times, in and out of the serial input buffer... into the Adafruit double-buffered sentences, where it gets searched multiple times with strcmp and strchr. Ugh. No, I meant to say UGH!

6) NeoGPS will also free up hundreds of bytes of RAM, which you may be able to trade for some more speed, elsewhere.

It's definitely a tight squeeze. Either reallocate your pins and use better libraries, or get a bigger hammer.      :-)

  • Wow, thank you so much for that thorough answer! I had no idea that there were alternatives to the stock Adafruit libraries for their hardware. I'll definitely take a look at it. – petschekr May 24 '16 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.