4 channel RC reading jitter

I'm trying to read the 4 channel RC using attachInterrupt() at pin 9-12 and sending it through serial1 once all four channels duty cycle are read. The duty cycle is range from 1000us to 2000us. However some jitter happen in data collected, such as

1583 1518 1558 1515
1583 1518 1558 1515
1583 1518 1558 1515
1583 1518 1558 1515
5893 60934 5893 60170
5893 60934 5893 60170
5893 60934 5893 60170
5893 60934 5893 60170
1586 1517 1558 1515
1583 1518 1558 1514
1583 1518 1558 1514
1583 1518 1558 1514
1587 1517 1558 1514
1541 1560 1473 1515
1541 1560 1473 1515
1541 1560 1473 1515
1550 551 1492 1515
1550 551 1492 1515
1550 551 1492 1515
1550 551 1492 1515
1564 1537 1520 1515
1564 1537 1520 1515

It looks like interrupts clashes and latency happen. I read that it is common problem in arduino, and one of remedies is using RCArduinoFastLib, but that is only applied to Atmega controller. Is there examples to do hardware timer capture in ARM controller? Any idea are welcome.

Hi,
Post your code, Its probably something simple

Duane B

rcarduino.blogspot.com

// Assign your channel in pins
#define AILE_IN_PIN 9 
#define AUX1_IN_PIN 10
#define ELEV_IN_PIN 11
#define RUDD_IN_PIN 12


// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define AILE_FLAG 1
#define AUX1_FLAG 2
#define ELEV_FLAG 4
#define RUDD_FLAG 8

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

volatile uint16_t unAileInShared;
volatile uint16_t unAux1InShared;
volatile uint16_t unElevInShared;
volatile uint16_t unRuddInShared;

uint32_t ulAileStart;
uint32_t ulAux1Start;
uint32_t ulElevStart;
uint32_t ulRuddStart;

//global variable
uint16_t AUX1time;
uint16_t RUDDtime;
uint16_t ELEVtime;
uint16_t AILEtime;


void setup()
{
  Serial.begin(115200);  
 
  Serial1.begin(115200);  

  attachInterrupt(AILE_IN_PIN, calcAile,CHANGE); 
  attachInterrupt(AUX1_IN_PIN, calcAux1,CHANGE); 
  attachInterrupt(ELEV_IN_PIN, calcElev,CHANGE);
  attachInterrupt(RUDD_IN_PIN, calcRudd,CHANGE);  
}

void loop()
{  

  if(bUpdateFlagsShared==0x0F)
  {

    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables
    AILEtime = unAileInShared;
    AUX1time = unAux1InShared;
    ELEVtime = unElevInShared;
    RUDDtime = unRuddInShared;
    bUpdateFlagsShared = 0;
    interrupts();
    
    // send the bluetooth whenever all servos are ready
    Serial1.write(MSB(AILEtime)); 
    Serial1.write(LSB(AILEtime));
    
    Serial1.write(MSB(AUX1time)); 
    Serial1.write(LSB(AUX1time));
    
    Serial1.write(MSB(ELEVtime)); 
    Serial1.write(LSB(ELEVtime));
    
    Serial1.write(MSB(RUDDtime)); 
    Serial1.write(LSB(RUDDtime));
    Serial1.print("\n");       
  }   
}


// simple interrupt service routine
void calcAile()
{
  // if the pin is high, its a rising edge of the signal pulse, so lets record its value
  if(digitalRead(AILE_IN_PIN) == HIGH)  
  { 
    ulAileStart = 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.
    unAileInShared = (uint16_t)(micros() - ulAileStart);
    // use set the throttle flag to indicate that a new throttle signal has been received
    bUpdateFlagsShared |= AILE_FLAG;
  }
}

void calcAux1()
{
  if(digitalRead(AUX1_IN_PIN)==HIGH)  //PORTD&0x20 
  { 
    ulAux1Start = micros();
  }
  else
  {
    unAux1InShared = (uint16_t)(micros() - ulAux1Start);
    bUpdateFlagsShared |= AUX1_FLAG;
  }
}

void calcElev()
{
  if(digitalRead(ELEV_IN_PIN)==HIGH)  //PORTD&0x40
  { 
    ulElevStart = micros();
  }
  else
  {
    unElevInShared = (uint16_t)(micros() - ulElevStart);
    bUpdateFlagsShared |= ELEV_FLAG;
  }
}

void calcRudd()
{
  if(digitalRead(RUDD_IN_PIN)==HIGH)
  { 
    ulRuddStart = micros();
  }
  else
  {
    unRuddInShared = (uint16_t)(micros() - ulRuddStart);
    bUpdateFlagsShared |= RUDD_FLAG;
  }
}

byte LSB(int val) {
  return (byte) (val & 0x00FF);
}

byte MSB(int val) {
  return (byte) ((val & 0xFF00) >> 8);
}

Hi,
The code looks fine, assuming that nointerrupts works, to test this -

  1. Try adding nointerrupts(); to the end of your set up function, if it works as expected you should have no output.

Let us know

Duane B

Hi,

In addition to adding nointerrupts to the end of the setup function, change your if test to

if(bUpdateFlagsShared)

that will tell us if interrupts on any of the pins are not being disabled and if so which pins

Duane B

rcarduino.blogspot.com

hi Duane,

Sorry for late reply, I've tested with noInterrupts() , the function looks to disable all the interrupts no problem. I've also tested with another sketch that do rc relay, the code is as follow:

#include <Servo.h>

// Assign your channel in pins
#define AILE_IN_PIN 9 
#define AUX1_IN_PIN 10
#define ELEV_IN_PIN 11
#define RUDD_IN_PIN 12

// Servo drive pin
#define SERVO_PIN1  4
#define SERVO_PIN2  5
#define SERVO_PIN3  6
#define SERVO_PIN4  7

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define AILE_FLAG 1
#define AUX1_FLAG 2
#define ELEV_FLAG 4
#define RUDD_FLAG 8

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

volatile boolean bAileUpdate; 
volatile boolean bAux1Update; 

// 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 unAileInShared;
volatile uint16_t unAux1InShared;
volatile uint16_t unElevInShared;
volatile uint16_t unRuddInShared;

// 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 ulAileStart;
uint32_t ulAux1Start;
uint32_t ulElevStart;
uint32_t ulRuddStart;

//global variable
uint16_t AUX1time;
uint16_t RUDDtime;
uint16_t ELEVtime;
uint16_t AILEtime;

Servo aileServo;   //cannot declared as volatile
Servo aux1Servo; 

void setup()
{
  Serial.begin(115200);  
 
  Serial1.begin(115200);  
    
  attachInterrupt(AILE_IN_PIN, calcAile,CHANGE); 
  attachInterrupt(AUX1_IN_PIN, calcAux1,CHANGE); 
  attachInterrupt(ELEV_IN_PIN, calcElev,CHANGE);
  attachInterrupt(RUDD_IN_PIN, calcRudd,CHANGE);

  aileServo.attach(SERVO_PIN1); 
  aux1Servo.attach(SERVO_PIN2);  
  
  // trim servo input
  aileServo.writeMicroseconds(1555);
  aux1Servo.writeMicroseconds(1505);

  
}

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 unAuxIn;
  
  //static uint8_t bUpdateServo;
  
  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared==0x0F)
  {

    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables 
    AILEtime = unAileInShared;
    AUX1time = unAux1InShared;
    ELEVtime = unElevInShared;
    RUDDtime = unRuddInShared;
    bUpdateFlagsShared = 0;
    interrupts(); 
    bAileUpdate = true; 
    bAux1Update = true; 

    Serial.println(AILEtime); 
    Serial.print("\t");
    Serial.println(AUX1time); 
    Serial.print("\t");
    Serial.println(ELEVtime); 
    Serial.print("\t");
    Serial.println(bUpdateFlagsShared); 
    Serial.print("\t");
    Serial.println(t1);     
   }   
  
  if(bAileUpdate)
  {
    // fluctuating Servo/ELEVtime
    if(aileServo.readMicroseconds()!=AILEtime)
    {
      aileServo.writeMicroseconds(AILEtime);
    }
    bAileUpdate = false; 
    
  }
  
  if(bAux1Update)//if(bUpdateServo&0x01)
  {
   
    if(aux1Servo.readMicroseconds()!=AUX1time)
    {
      aux1Servo.writeMicroseconds(AUX1time);
    }
    bAux1Update = false; 
    
  }
  
}


// simple interrupt service routine
void calcAile()
{
  // if the pin is high, its a rising edge of the signal pulse, so lets record its value
  if(digitalRead(AILE_IN_PIN) == HIGH)  //optimizing tbd... digitalRead(AILE_IN_PIN) == HIGH
  { 
    ulAileStart = 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.
    unAileInShared = (uint16_t)(micros() - ulAileStart);
    // use set the throttle flag to indicate that a new throttle signal has been received
    bUpdateFlagsShared |= AILE_FLAG;
  }
}

void calcAux1()
{
  if(digitalRead(AUX1_IN_PIN)==HIGH)  
  { 
    ulAux1Start = micros();
  }
  else
  {
    unAux1InShared = (uint16_t)(micros() - ulAux1Start);
    bUpdateFlagsShared |= AUX1_FLAG;
  }
}

void calcElev()
{
  if(digitalRead(ELEV_IN_PIN)==HIGH)  //PORTD&0x40
  { 
    ulElevStart = micros();
  }
  else
  {
    unElevInShared = (uint16_t)(micros() - ulElevStart);
    bUpdateFlagsShared |= ELEV_FLAG;
  }
}

void calcRudd()
{
  if(digitalRead(RUDD_IN_PIN)==HIGH)
  { 
    ulRuddStart = micros();
  }
  else
  {
    unRuddInShared = (uint16_t)(micros() - ulRuddStart);
    bUpdateFlagsShared |= RUDD_FLAG;
  }
}

}

The result of the above code is that the controlled servo jittering, due to some random jump in read in rc pulse width, for instance:
1588 1519
1585 1519
1587 1519
1584 1519
587 519
1584 1519
1588 1519
1584 1519
1587 1519

Hi,
I have just put together a loop back sketch which simply outputs six servos and reads them back in again as if they are RC Channels.

Right now it uses pinChangeInt on the Arduino UNO. I will convert it for Due this evening, in the meantime if you want to have a go at converting it you can find the post here -

Duane B

rcarduino.blogspot.com

Hi,
I have managed to replicate the situation -

Expected results -
1304 1402 0 1102 1600 1195
1304 1402 0 1102 1600 1195
1304 1402 0 1102 1599 1195
1304 1401 0 1102 1599 1194
1304 1401 0 1102 1599 1194
1305 1401 0 1102 1599 1194

At this point the final channel looses 1000 us

1305 1401 0 1101 1599 194
1305 1402 0 1101 1599 194
1304 1402 0 1101 1599 194
1304 1402 0 1101 1600 194

which soon turns up on the first channel

2304 1402 0 1102 1600 194
2304 1402 0 1102 1600 195
2304 1402 0 1102 1600 195
2304 1402 0 1102 1600 195
2304 1402 0 1102 1599 195

Finally everything gets back in synch

1304 1402 0 1101 1600 1194
1304 1402 0 1102 1600 1195
1304 1402 0 1102 1600 1195
1304 1402 0 1102 1599 1195

Having used the same code pattern extensively I am wondering if this might be an internal error in the way that Arduino Due interrupts are implemented, its very similar to a synchronisation problem I have had in the past when multiplexing RC Channels into fewer pins (i.e. six channels read through two pins) the problem seems to effect adjacent channels i.e. 1 and 2 or 6 and 1 (wrap around) never 2 and 4.

The test code is here for anyone else that wants to try, its a simple loop back where a servo output is fed back in as a channel input.

I will continue to look into this tonight.

Duane B

rcarduino.blogspot.com

Hi,
It looks as if the error comes from a combination of interrupts and the way in which micros is implemented, you can find more information and a suggested fix to the Due cores here -

http://arduino.cc/forum/index.php/topic,162787.0.html

Try the code in the post above with the fix to the micros function and let us know how you get on by posting in the topic linked above

Once you have this fix in place, the 4 channel code should also be fine

Duane B

rcarduino.blogspot.com

8 Channel RC Reading/writing/loopback code now up here -

Duane B

rcarduino.blogspot.com