PPM and servo PWM control at the same time

EDIT: On second thought, can this be moved to the programming questions forum? This involves servos but probably not in the way this section meant…

I’m having some issues. I have a foolproof code setup (modified from some example I found ages ago to not use timer1) to read the ppm signal coming from a drone style radio rx.

That part works fine, mostly. Some jitter but that could be the radio.

Then, I want to read this ppm signal, extract all the things, which I can do, and then manipulate, do whatever, and then send it out to two servos. I’m reading the ppm signal and using it to smooth, limit, and control a pan-tilt servo setup on a drone.

My issue is that I seem to be fighting some jitter. What I imagine is going on is that my timer interrupts for the servo library and the interrupts for reading the rising edge change on the ppm signal are hitting each other and causing minute microsecond delays to the servo signal output, causing jitter. Same with the input, could also be causing the jitter.

I know there are arduino based systems that control servos and escs and also read ppm in without issue, so… How does that happen? What am I missing?

I’ve resorted to adding a significant ‘dead spot’ to the pan-tilt inputs to stop the jitter when centered but even when getting a solid manually entered variable, if the ppm system is working, the servo jumps ever so slightly, annoying when there is a camera attached… Ignore the ADC stuff, haven’t worked on that yet.

//plane central controller


//libraries
#include <TaskScheduler.h>
#include <eRCaGuy_Timer2_Counter.h>
#include <Servo.h>
//-----


//ADC stuff
#define SELPIN A6 //Selection Pin 
#define DATAOUT 11//MOSI 
#define DATAIN  12//MISO 
#define SPICLOCK  13//Clock 
int readvalue;  
//-----



//set pins on stuff
int panservo = 10;
int tiltservo = 9;
//servo vars
Servo pan;
Servo tilt;
int panleftlimit = 900;
int panrightlimit = 2100;
int tiltdownlimit = 1200;
int tiltuplimit = 2200;
int panpwm = 1530;
int tiltpwm = 1530;
//-----

//ppm channel setting
int panchannel = 8;
int tiltchannel = 9;
int navlightchannel = 6;
int gearbrakeschannel = 4;
//-----


//Pin connected to PPM input
const int ppm_pin = 2;
//ppm value array
volatile int ppm[16];
//-----


//scheduler stuff
void servodrivercallback();
void heartbeatcallback();
void navlightflashercallback();
Task servodriver(20, TASK_FOREVER, &servodrivercallback);   //servo driver clock
Task heartbeat(500, TASK_FOREVER, &heartbeatcallback); //heartbeat clock
Task navlightflasher(100, TASK_FOREVER, &navlightflashercallback); //nav light flash callback
Scheduler runner;   //scheduler object
//-----


//pin vars
int heartled = 4;
int navpsuenable = 8;
int strobeled = 5;
int alarmled = 21;
//-----



//general vars
bool heartbeatflag = false;
bool navledpowersetting = true;
int strobeflashcounter = 0;
int strobeflashdelay = 20;

//-----




void setup() {
  timer2.setup();
  
  analogReference(EXTERNAL);  //so no booms happen
  pinMode(heartled,OUTPUT); //heartbeat led pinmode
  pinMode(alarmled,OUTPUT); //alarm led pinmode
  pinMode(strobeled,OUTPUT); //strobe led pinmode
  pinMode(SELPIN,OUTPUT);    //Selection Pin 
  pinMode(DATAOUT,OUTPUT);    //MOSI 
  pinMode(DATAIN,INPUT);     //MISO 
  pinMode(SPICLOCK,OUTPUT);   //Clock 
  pinMode(tiltservo,OUTPUT); //servo tilt
  pinMode(panservo,OUTPUT); //servo pan

  Serial.begin(57600);

  
  //inputs
  pinMode(ppm_pin, INPUT);
  
  //interrupts
  attachInterrupt(ppm_pin - 2, read_ppm, CHANGE);

  //servo stuff
  pan.attach(panservo);
  tilt.attach(tiltservo);


   //scheduler stuff
  runner.addTask(servodriver);  //add task to.. thing
  runner.addTask(heartbeat);    //add task to.. thing
  runner.addTask(navlightflasher);
  servodriver.enable();         //enable it
//  heartbeat.enable();           //enable this too
  navlightflasher.enable();

  //pin setting stuff
  digitalWrite(navpsuenable, !navledpowersetting);
  digitalWrite(alarmled, HIGH);
  delay(100);
  digitalWrite(alarmled, LOW);

}

void loop() {
 runner.execute();  //runs scheduler
}

page 2

void servodrivercallback() {

  panpwm = map(ppm[panchannel],1644,1404,panleftlimit,panrightlimit);
  tiltpwm = map(ppm[tiltchannel],1380,1644,tiltuplimit,tiltdownlimit);

  if(panpwm > 1400 && panpwm < 1600) {
    panpwm = 1500;
  }
  if(tiltpwm > 1585 && tiltpwm < 1785) {
    tiltpwm = 1685;
  }

  pan.write(panpwm);
  tilt.write(tiltpwm);

  //debug stuff
  Serial.print("PAN: ");
  Serial.print(panpwm);
  Serial.print("  ");
  Serial.print("TILT: ");
  Serial.print(tiltpwm);
  Serial.print("  ");
  Serial.print("NAVLED:  ");
  Serial.print(ppm[navlightchannel]);
  Serial.print("  ");
  Serial.print("BRAKES:  ");
  Serial.print(ppm[gearbrakeschannel]);
  Serial.print("  ");
  Serial.print("PPMPAN:  ");
  Serial.print(ppm[panchannel]);
  Serial.print("  ");
  Serial.print("PPMTILT:  ");
  Serial.print(ppm[tiltchannel]);
  Serial.println("");
  
}


void heartbeatcallback() {          //just flash the led to make sure everything is still alive
  heartbeatflag = !heartbeatflag;
  if(navledpowersetting == true) {
  digitalWrite(heartled,heartbeatflag);
  }
  if(navledpowersetting == false) {
  digitalWrite(heartled, LOW);
  }
}


void navlightflashercallback() {
  navledpowersetting = false;  //start at zero
  if(ppm[navlightchannel] > 1600) {  //check ppm channel for light control
    navledpowersetting = true;
  }
  digitalWrite(navpsuenable, !navledpowersetting);  //set psu state
  
  if(navledpowersetting == false) {  //check that nav power is on. if its not, just write an off
  digitalWrite(strobeled, LOW);
  digitalWrite(heartled, LOW);
  }
  
  if(navledpowersetting == true) {  //if it is on, then do your flashing work
    digitalWrite(strobeled, LOW);  //default to off
    digitalWrite(heartled, LOW);
    if(strobeflashcounter == 1 || strobeflashcounter == 4) {  //if it is 2 of the delay times, turn on, otherwise its off
      digitalWrite(strobeled, HIGH);
      digitalWrite(heartled, HIGH);
    }
  }
  strobeflashcounter++;
  
  if(strobeflashcounter >= strobeflashdelay) {  //reset timer after a set delay to repeat the sequence
    strobeflashcounter = 0;
  }
}

void read_ppm(){  //leave this alone
  static unsigned int pulse;
  static unsigned long counter;
  static byte channel;
  static unsigned long lastmicros;

  counter = timer2.get_count() - lastmicros;
  lastmicros = timer2.get_count();

  if(counter < 1020){  //must be a pulse if less than 510us
    pulse = counter;
  }
  else if(counter > 3820){  //sync pulses over 1910us
    channel = 0;
  }
  else{  //servo values between 510us and 2420us will end up here
    ppm[channel] = (counter + pulse)/2;
    channel++;
  }
}



int read_adc(int channel){
  int adcvalue = 0;
  byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3)

  //allow channel selection
  commandbits|=((channel-1)<<3);

  digitalWrite(SELPIN,LOW); //Select adc
  // setup bits to be written
  for (int i=7; i>=3; i--){
    digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);    
  }

  digitalWrite(SPICLOCK,HIGH);    //ignores 2 null bits
  digitalWrite(SPICLOCK,LOW);
  digitalWrite(SPICLOCK,HIGH);  
  digitalWrite(SPICLOCK,LOW);

  //read bits from adc
  for (int i=11; i>=0; i--){
    adcvalue+=digitalRead(DATAIN)<<i;
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  digitalWrite(SELPIN, HIGH); //turn off device
  return adcvalue;
}

Can you post the whole program as a single entity. Attach your .ino file if it is too long to include in your Reply.

A general description of how the the different parts of the program are intended to work would also be very useful.

Why are you using the Taskscheduler library?

...R

Hello!

Sorry about that. I posted it as two pages because that’s how I had it tabbed off in the IDE.

I am using task scheduler because its a nice way for me to make sure the strobe lights and the servo updates all happen at a nice time relative to everything else.

So the basic flow of the parts of the program I care about are simply: read_ppm uses the pin change interrupt to see the small pulses and work its way through the ppm signal and compares it to timer2 using the other library to get the signal timing and convert it into an array.

Then I simply use the normal servo library to send that to the servos, updated at 50hz via task scheduler. These two things independently work fine but when working together the servo starts to jitter.

//plane central controller


//libraries
#include <TaskScheduler.h>
#include <eRCaGuy_Timer2_Counter.h>
#include <Servo.h>
//-----


//ADC stuff
#define SELPIN A6 //Selection Pin 
#define DATAOUT 11//MOSI 
#define DATAIN  12//MISO 
#define SPICLOCK  13//Clock 
int readvalue;  
//-----



//set pins on stuff
int panservo = 10;
int tiltservo = 9;
//servo vars
Servo pan;
Servo tilt;
int panleftlimit = 900;
int panrightlimit = 2100;
int tiltdownlimit = 1200;
int tiltuplimit = 2200;
int panpwm = 1530;
int tiltpwm = 1530;
//-----

//ppm channel setting
int panchannel = 8;
int tiltchannel = 9;
int navlightchannel = 6;
int gearbrakeschannel = 4;
//-----


//Pin connected to PPM input
const int ppm_pin = 2;
//ppm value array
volatile int ppm[16];
//-----


//scheduler stuff
void servodrivercallback();
void heartbeatcallback();
void navlightflashercallback();
Task servodriver(20, TASK_FOREVER, &servodrivercallback);   //servo driver clock
Task heartbeat(500, TASK_FOREVER, &heartbeatcallback); //heartbeat clock
Task navlightflasher(100, TASK_FOREVER, &navlightflashercallback); //nav light flash callback
Scheduler runner;   //scheduler object
//-----


//pin vars
int heartled = 4;
int navpsuenable = 8;
int strobeled = 5;
int alarmled = 21;
//-----



//general vars
bool heartbeatflag = false;
bool navledpowersetting = true;
int strobeflashcounter = 0;
int strobeflashdelay = 20;

//-----




void setup() {
  timer2.setup();
  
  analogReference(EXTERNAL);  //so no booms happen
  pinMode(heartled,OUTPUT); //heartbeat led pinmode
  pinMode(alarmled,OUTPUT); //alarm led pinmode
  pinMode(strobeled,OUTPUT); //strobe led pinmode
  pinMode(SELPIN,OUTPUT);    //Selection Pin 
  pinMode(DATAOUT,OUTPUT);    //MOSI 
  pinMode(DATAIN,INPUT);     //MISO 
  pinMode(SPICLOCK,OUTPUT);   //Clock 
  pinMode(tiltservo,OUTPUT); //servo tilt
  pinMode(panservo,OUTPUT); //servo pan

  Serial.begin(57600);

  
  //inputs
  pinMode(ppm_pin, INPUT);
  
  //interrupts
  attachInterrupt(ppm_pin - 2, read_ppm, CHANGE);

  //servo stuff
  pan.attach(panservo);
  tilt.attach(tiltservo);


   //scheduler stuff
  runner.addTask(servodriver);  //add task to.. thing
  runner.addTask(heartbeat);    //add task to.. thing
  runner.addTask(navlightflasher);
  servodriver.enable();         //enable it
//  heartbeat.enable();           //enable this too
  navlightflasher.enable();

  //pin setting stuff
  digitalWrite(navpsuenable, !navledpowersetting);
  digitalWrite(alarmled, HIGH);
  delay(100);
  digitalWrite(alarmled, LOW);

}

void loop() {
 runner.execute();  //runs scheduler
}

void servodrivercallback() {

  panpwm = map(ppm[panchannel],1644,1404,panleftlimit,panrightlimit);
  tiltpwm = map(ppm[tiltchannel],1380,1644,tiltuplimit,tiltdownlimit);

  if(panpwm > 1400 && panpwm < 1600) {
    panpwm = 1500;
  }
  if(tiltpwm > 1585 && tiltpwm < 1785) {
    tiltpwm = 1685;
  }

  pan.write(panpwm);
  tilt.write(tiltpwm);

  //debug stuff
  Serial.print("PAN: ");
  Serial.print(panpwm);
  Serial.print("  ");
  Serial.print("TILT: ");
  Serial.print(tiltpwm);
  Serial.print("  ");
  Serial.print("NAVLED:  ");
  Serial.print(ppm[navlightchannel]);
  Serial.print("  ");
  Serial.print("BRAKES:  ");
  Serial.print(ppm[gearbrakeschannel]);
  Serial.print("  ");
  Serial.print("PPMPAN:  ");
  Serial.print(ppm[panchannel]);
  Serial.print("  ");
  Serial.print("PPMTILT:  ");
  Serial.print(ppm[tiltchannel]);
  Serial.println("");
  
}


void heartbeatcallback() {          //just flash the led to make sure everything is still alive
  heartbeatflag = !heartbeatflag;
  if(navledpowersetting == true) {
  digitalWrite(heartled,heartbeatflag);
  }
  if(navledpowersetting == false) {
  digitalWrite(heartled, LOW);
  }
}


void navlightflashercallback() {
  navledpowersetting = false;  //start at zero
  if(ppm[navlightchannel] > 1600) {  //check ppm channel for light control
    navledpowersetting = true;
  }
  digitalWrite(navpsuenable, !navledpowersetting);  //set psu state
  
  if(navledpowersetting == false) {  //check that nav power is on. if its not, just write an off
  digitalWrite(strobeled, LOW);
  digitalWrite(heartled, LOW);
  }
  
  if(navledpowersetting == true) {  //if it is on, then do your flashing work
    digitalWrite(strobeled, LOW);  //default to off
    digitalWrite(heartled, LOW);
    if(strobeflashcounter == 1 || strobeflashcounter == 4) {  //if it is 2 of the delay times, turn on, otherwise its off
      digitalWrite(strobeled, HIGH);
      digitalWrite(heartled, HIGH);
    }
  }
  strobeflashcounter++;
  
  if(strobeflashcounter >= strobeflashdelay) {  //reset timer after a set delay to repeat the sequence
    strobeflashcounter = 0;
  }
}

void read_ppm(){  //leave this alone
  static unsigned int pulse;
  static unsigned long counter;
  static byte channel;
  static unsigned long lastmicros;

  counter = timer2.get_count() - lastmicros;
  lastmicros = timer2.get_count();

  if(counter < 1020){  //must be a pulse if less than 510us
    pulse = counter;
  }
  else if(counter > 3820){  //sync pulses over 1910us
    channel = 0;
  }
  else{  //servo values between 510us and 2420us will end up here
    ppm[channel] = (counter + pulse)/2;
    channel++;
  }
}



int read_adc(int channel){
  int adcvalue = 0;
  byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3)

  //allow channel selection
  commandbits|=((channel-1)<<3);

  digitalWrite(SELPIN,LOW); //Select adc
  // setup bits to be written
  for (int i=7; i>=3; i--){
    digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);    
  }

  digitalWrite(SPICLOCK,HIGH);    //ignores 2 null bits
  digitalWrite(SPICLOCK,LOW);
  digitalWrite(SPICLOCK,HIGH);  
  digitalWrite(SPICLOCK,LOW);

  //read bits from adc
  for (int i=11; i>=0; i--){
    adcvalue+=digitalRead(DATAIN)<<i;
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  digitalWrite(SELPIN, HIGH); //turn off device
  return adcvalue;
}

I don't know anything about that Taskscheduler library so I can't make sense of your code.

I'm guessing that the problem is that some part of the code (probably the ISR) takes too long and interferes with the Servo library.

You may (or may not) be interested in Several Things at a Time which shows how to manage timing without a library. IMHO that makes the code much more transparent.

...R

I'll take a look into that, thank you!

What the task scheduler library does is simply call routines at intervals set in setup, at least as far as I use it that's all it does. It calls heartbeat every half second, servo updater every 20ms and strobe flasher every 100ms.

I'm making a new ppm reader system that then also triggers the servos with a blocking routine with delays during the dead period in the sync pulse. I'll post the outcome of that here if it works.

itman496:
What the task scheduler library does is simply call routines at intervals

Yes. But what is going on under the hood :slight_smile:

...R

The way to deal with ISRs within TaskScheduler framework is to have the actual action of the ISR wrapped a one-time executing Task which runs immediately. The actual ISR should restart() the task, which will be executed immediately during next scheduling pass. This was you are out of the ISR and have all the timers running, serial available, and you are out of the ISR as fast as possible.

Check this code:

Line 253:

PCintPort::attachInterrupt(PIN_MOTION_ACTIVATION, &MotionDetected, FALLING);

a pin interrupt is assigned to MotionDetected method:

This is all what is does:

void MotionDetected() {
  tMotion.restart();
}

Respective task:

Task tMotion(0, 1, &MotionDetectedCallback, &ts);

and this is where the actual processing occurs:

void MotionDetectedCallback() {

  tTimeout.restartDelayed();   // Set overall timeout to put device to sleep if inactive
  tDistance1.enableIfNot();    // Start distance measuring

  if ( tScream.isEnabled()  || tFlash.isEnabled() || tWink.isEnabled() ) return;

  tWink.set(TASK_WINK, 1, NULL, &WinkOnEnable, &WinkOnDisable);
  tWink.enable();
}