0
votes

Arduino drone project, whenever output is "loaded" ("open-circuit" signal-in pin to ESC or even a cap to ground), the input "steering" command starts to glitch (go to really low values << 1000).

The motor speed is a function of both the steering command, as well as the throttle. (In this test-case with just one motor as seen in the code below, unMotorSpeed = unThrottleIn +/- unSteeringIn)

When hooked up to a scope, the physical input signals (steering and throttle coming from the receiver) are great, and I even swapped the input pins to make sure there wasn't a problem between the receiver and the arduino. The problem seems to be coming from the software, but it just doesn't make sense, since when there isn't a "load" attached, the input and output values are all fine and clean. (I put "load" in quotes because sometimes it's essentially an open circuit --> super high impedance input signal to the electronic speed controller (ESC), which I don't even ground to complete a circuit).

Would anyone be able to check out the code and see if I'm missing something?

At this point, a somewhat quick workaround would be to simply not write those new glitchy values to the motor and keep the old speed values for whenever the new speed is significantly lower than the old speed (and these are at over 50khz, so clearly a huge jump in just one step is a bit crazy).

Note: In the code, the total output of unMotorSpeed leaves from the servoThrottle pin. Just the original naming that I didn't end up changing... it's clear if you read the code and see all the variables.

UPDATE: Weird thing is, I just ran my setup without any changes and EVERYTHING WORKED...I reflashed the arduino multiple times to make sure it wasn't some lucky glitch, and it kept on working, with everything set up. Then I dropped my remote on the ground and moved a few of the wires on the breadboard around, and things went back to their wonky ways after putting the setup back together. Idk what to do!

// --> starting code found at: rcarduino.blogspot.com
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
with.html


#include <Servo.h>

// Assign your channel in pins
#define THROTTLE_IN_PIN 3
#define STEERING_IN_PIN 2


// Assign your channel out pins
#define THROTTLE_OUT_PIN 9
//#define STEERING_OUT_PIN 9


// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
//Servo servoSteering;


// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2

// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;

// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
//uint32_t ulAuxStart;

void setup()
{
  Serial.begin(9600);

  // attach servo objects, these will generate the correct
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers 
  servoThrottle.attach(THROTTLE_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  attachInterrupt(digitalPinToInterrupt(THROTTLE_IN_PIN), calcThrottle,CHANGE);
  attachInterrupt(digitalPinToInterrupt(STEERING_IN_PIN), calcSteering,CHANGE);
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained
  // between calls to loop.
  static uint16_t unThrottleIn;
  static uint16_t unSteeringIn;
  static uint16_t difference;
  static uint16_t unMotorSpeed; // variable that stores overall motor speed
  static uint8_t bUpdateFlags; // local copy of update flags

  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;

    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.

    if(bUpdateFlags & THROTTLE_FLAG)
    {
      unThrottleIn = unThrottleInShared;
    }

    if(bUpdateFlags & STEERING_FLAG)
    {
      unSteeringIn = unSteeringInShared;
    }

    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;

    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }

  //Serial.println(unSteeringIn);

  // do any processing from here onwards
  // only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
  // variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by
  // the interrupt routines and should not be used in loop

  // the following code provides simple pass through
  // this is a good initial test, the Arduino will pass through
  // receiver input as if the Arduino is not there.
  // This should be used to confirm the circuit and power
  // before attempting any custom processing in a project.

  // we are checking to see if the channel value has changed, this is indicated 
  // by the flags. For the simple pass through we don't really need this     check,
  // but for a more complex project where a new signal requires significant processing
  // this allows us to only calculate new values when we have new inputs, rather than
  // on every cycle.

///// if-else chain commented out to determine/prove problem with steering signal --> buggy! 


  if(unSteeringIn < 1400) // if steering joystick moved left
  {
  difference = 1400 - unSteeringIn;
  if(unThrottleIn - difference >= 0)
    unMotorSpeed = unThrottleIn - difference;
  }
  else if(unSteeringIn > 1550) //if steering joystick moved right (needs to be tweaked, but works for now)
  {
    difference = unSteeringIn - 1600;
    if(unThrottleIn + difference < 2000)
      unMotorSpeed = unThrottleIn + difference;
  }
  else
  {
    unMotorSpeed = unThrottleIn;  
  }

  //Serial.println(unMotorSpeed);
  //Serial.println(unSteeringIn);
  //Serial.println(unThrottleIn);

  if(bUpdateFlags)
  {
    //Serial.println(servoThrottle.readMicroseconds());
    if(servoThrottle.readMicroseconds() != unMotorSpeed)
    {
      servoThrottle.writeMicroseconds(unMotorSpeed);
      Serial.println(unMotorSpeed);
    }
  }

  bUpdateFlags = 0;
}

// simple interrupt service routine
void calcThrottle()
{
  // if the pin is high, its a rising edge of the signal pulse, so lets record its value
  if(digitalRead(THROTTLE_IN_PIN) == HIGH)
  {
    ulThrottleStart = micros();
  }
  else
  {
    // else it must be a falling edge, so lets get the time and subtract the time of the rising edge
    // this gives use the time between the rising and falling edges i.e. the pulse duration.
    unThrottleInShared = (uint16_t)(micros() - ulThrottleStart); // pulse duration

    // use set the throttle flag to indicate that a new throttle signal has been received
    bUpdateFlagsShared |= THROTTLE_FLAG;
  }
}

void calcSteering()
{
  if(digitalRead(STEERING_IN_PIN) == HIGH)
  {
    ulSteeringStart = micros();
  }
  else
  {
    unSteeringInShared = (uint16_t)(micros() - ulSteeringStart); // pulse duration
    bUpdateFlagsShared |= STEERING_FLAG;
  }
}
1
Could you be more specific about the nature of the glitch ? What range of values do you expect and what range of values do you observe ? You could maybe add a software low-pass filter, only accept values for pulses of a certain realistic width.Tim
You might want to trim down this question - it is somewhat verbose and confused. For example, in the context of SO I would immediatly interpret the first sentence as "Whenever the output of my Arduino project (i.e. the compiled code) is loaded (i.e. burned to flash)..." and thereafter it starts to make no sense. The terms output and loaded are ambiguous in this context, but you obviously did not intend the interpretation I made. Even in the sense of "load" as an electrical load, "open-circuit" is in fact no load at all, so I am unclear as to what you mean.Clifford
What is the valid range of unSteeringIn. Your statics are all initialised to zero, but it looks like not all paths result in them being assigned a valid value before they are used. You might want to initialise them with real values.Clifford
So the values that the motors respond to are within roughly 1000-2000. 1000 being fully off, 1500 being half-speed, and 2000 being full-speed/fully on. When I put any sort of "load" at the output, the steering values seen at unSteeringIn (not the physical input values going to steering pin from the receiver), start glitching randomly with low values like 50, 300, 700, etc., even when I'm operating at around 1900. It looks like the arduino is catching false, quick pulses randomly... I'm just not sure why it's only with unSteeringIn and not unThrottleIn, even when I swap the input pins.MS9T1
@MS9T1 : Do not add information relevant to the question in comments - update the question instead - that is you respond to comments by improving the question, not by adding more comments; this is not a discussion forum. Beware that you might get an answer from a software development expert of which there are many here but whom is not otherwise an expert in Ardunio or RC servos or drone control of which there are far fewer. As such you need to provide all necessary domain specific information.Clifford

1 Answers

0
votes

You should read the documentation of AttachInterrupt() - in the section "About Interrupt Service Routines" it gives information on how certain functions behave when called from an interrupt. For micros() it states:

micros() works initially, but will start behaving erratically after 1-2 ms.

I believe that means after the ISR has been running for more than 1ms, rather than just 1 ms in general, so may not apply in this case, but you might need to consider how you are doing the timing in the ISR. That's a problem with Arduino - terrible documentation!

One definite problem which may be a cause is the fact that unSteeringInShared is non-atomic. It is a 16 bit value on 8 bit hardware so requires multiple instructions to read and write and the process can be interrupted. It is therefore possible to read one byte of the value in the loop() context and then have both bytes changed by the interrupt context before you read the second byte, so you then up with two halves of two different values.

To resolve this problem you could either disable interrupts while reading:

noInterrupts() ;
unSteeringIn = unSteeringInShared ;
interrupts() ;

Or you can spin-lock the read:

do
{
    unSteeringIn = unSteeringInShared ;

} while( unSteeringIn != unSteeringInShared ) ;

You should do the same for unThrottleInShared too, although why you do not see any problem with that is unclear - this is perhaps not the problem you are currently observing, but is definitely a problem in any case.

Alternatively if 8 bit resolution is sufficient you could encode the input as an atomic 8 bit value thus:

uint8_t unSteeringInShared ;


...


int32_t timeus = micros() - ulSteeringStart - 1000 ;
if( timeus < 0 )
{
    unSteeringInShared = 0 ;
}
else if( timeus > 1000 )
{
    unSteeringInShared = 255;
}
else
{
    unSteeringInShared = (uint8_t)(time * 255 / 1000) ;
}

Of course changing your scale from 1000 to 2000 to 0 to 255 will need changes to the rest of the code. For example to convert a value x in the range 0 to 255 to a a servo pulse width:

 pulsew = (x * 1000 / 256) + 1000 ;