trouble understanding ppm code

Hi. I've been writing code for an arduino that will act as an RC receiver that writes a PPM-signal to another arduino wich decodes the signal. I will post the code below. When i print the values for the different channels on the decoder i don't get a value between 1000 and 2000 for the "roll" which is in "pulseLen[4]" and is the last channel. Instead i get a flickering value around 12. If i set the number of channels to 5 i get the values i want, though i suspect this solution is not what i want since i only want to use 4 channels on the transmitter. I have been gathering code from tutorials and snippets and having a hard time to point out the problem. But maybe it has something to do with how i handle the pulses in respect to the frame space?

Hope someone is patient enough to help me out. Thanks

Also. If someone wants to explain how the code inside "ISR(TIMER1_COMPA_vect)" on the receiver works i would be very grateful.

Receiver code:

/*

   This code get values from a transmitter and sends them to another arduino with PPM

*/

#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>

#define channelNum      4
#define PPMPin          2
#define PPM_FrameLen    27000 
#define PPM_PulseLen    400     
#define clockMultiplier 2

RF24 radio(7, 8);

int ppm[channelNum];
unsigned long lastReceiveTime = 0;

const uint64_t pipe = 0xE8E8F0F0E1LL;

struct RF24Data {
  byte throttle,
       yaw,
       pitch,
       roll,
       AUX1,
       AUX2;
} data;


void setup() {

  resetData();
  setupPPM();

  Serial.begin(115200);

  radio.begin();
  radio.setAutoAck(false);
  radio.setChannel(108);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.openReadingPipe(1, pipe);
  radio.startListening();

}

void loop() {

  receiveData();
  unsigned long now = millis();

  if (now - lastReceiveTime > 1000) {
    resetData();
  }

  setPPMValues();

  printData();

}

/***********PPM OUT*************/
ISR(TIMER1_COMPA_vect) {

  static boolean state = true;

  TCNT1 = 0; // timer’s start value saved by  TCNT1 register (Timer Counter)

  if (state) {
    //end pulse
    digitalWrite(PPMPin, 0);
    OCR1A = PPM_PulseLen * clockMultiplier; // set compare match register to desired timer count
    state = false;
  }
  else {
    //start pulse
    static byte currChannelNum;
    static unsigned int calc_rest;

    digitalWrite(PPMPin, 1);
    state = true;

    if (currChannelNum >= channelNum) {
      currChannelNum = 0;
      calc_rest += PPM_PulseLen;
      OCR1A = (PPM_FrameLen - calc_rest) * clockMultiplier;
      calc_rest = 0;
    }
    else {
      OCR1A = (ppm[currChannelNum] - PPM_PulseLen) * clockMultiplier;
      calc_rest += ppm[currChannelNum];
      currChannelNum++;
    }
  }

}

void receiveData() {

  while (radio.available()) {

    radio.read(&data, sizeof(data));
    lastReceiveTime = millis();
  }

}

void resetData() {

  data.throttle = 0;
  data.yaw      = 127;
  data.pitch    = 127;
  data.roll     = 127;
  data.AUX1     = 0;
  data.AUX2     = 0;

  setPPMValues();

}

void setPPMValues() {

  ppm[0] = map(data.throttle, 0, 255, 1000, 2000);
  ppm[1] = map(data.yaw,      0, 255, 1000, 2000);
  ppm[2] = map(data.pitch,    0, 255, 1000, 2000);
  ppm[3] = map(data.roll,     0, 255, 1000, 2000);

}

void setupPPM() {

  pinMode(PPMPin, OUTPUT);
  digitalWrite(PPMPin, 0);

  cli();      //disable global interrupts
  TCCR1A = 0; //set entire TCCR1A register to 0
  TCCR1B = 0; //set entire TCCR1B register to 0

  OCR1A = 100;             // compare match register (not very important, sets the timeout for the first interrupt
  TCCR1B |= (1 << WGM12);  //turn on CTC mode
  TCCR1B |= (1 << CS11);   // 8 prescaler: .5 uSeconds at 16Mhz
  TIMSK1 |= (1 << OCIE1A); //enable timer compare interrupt
  sei();                   //enable global interrupt

}

void printData() {
  Serial.print("Throttle: "); Serial.print(data.throttle);  Serial.print("    ");
  Serial.print("Yaw: ");      Serial.print(data.yaw);       Serial.print("    ");
  Serial.print("Pitch: ");    Serial.print(data.pitch);     Serial.print("    ");
  Serial.print("Roll: ");     Serial.print(data.roll);      Serial.print("    ");
  Serial.print("Aux1: ");     Serial.print(data.AUX1);      Serial.print("    ");
  Serial.print("Aux2: ");     Serial.print(data.AUX2);      Serial.print("\n");
}

PPM decoder:

#include <Servo.h>


#define channelNum 4 //Number of channels, if i set this to 5 i get the correct value for the last 
                                  channel, but i only use 4 channels: throttle, yaw, pitch and roll.

int PPMPin = 2;
const int frameSpace = 8000; //4channels * 2000

volatile long currentTime;
volatile long lastSpike;
volatile byte currentChannel = 0;
volatile int pulseLen[channelNum + 1];
volatile int lastChannelFlag = false;

Servo ESC0;

int ESCPins[] = {4, 7, 8, 12};

void setup() {

  Serial.begin(115200);
  pinMode(PPMPin, INPUT);
  digitalWrite(PPMPin, LOW);

  attachInterrupt(digitalPinToInterrupt(PPMPin), spike, RISING);
  lastSpike = micros();

  ESC0.attach(ESCPins[0]);
}

void loop() {

  if (lastChannelFlag) {
    lastChannel();
    writeToESC();
    displayChannels();
  }

}

void spike() {
  currentTime = micros();
  pulseLen[currentChannel] = currentTime - lastSpike;
  if (pulseLen[currentChannel] > frameSpace) {
    currentChannel = 0;
  }

  lastSpike = currentTime;
  currentChannel++;

  if (currentChannel == channelNum) {
    lastChannelFlag = true;
  }

}

void lastChannel() {
  while (digitalRead(PPMPin) == HIGH) {
    currentTime = micros();
    pulseLen[currentChannel] = currentTime - lastSpike;

    currentChannel = 0;
    lastSpike = currentTime;
    lastChannelFlag = false;
  }

}

void writeToESC() {

}

void displayChannels() {

  Serial.print("Throttle: "); Serial.print(pulseLen[1]);      Serial.print("    ");
  Serial.print("Yaw: ");      Serial.print(pulseLen[2]);      Serial.print("    ");
  Serial.print("Pitch: ");    Serial.print(pulseLen[3]);      Serial.print("    ");
  Serial.print("Roll: ");     Serial.print(pulseLen[4]);      Serial.print("\n");

}

lefeh:
Hi. I've been writing code for an arduino that will act as an RC receiver that writes a PPM-signal to another arduino wich decodes the signal. I will post the code below. When i print the values for the different channels on the decoder i don't get a value between 1000 and 2000 for the "roll" which is in "pulseLen[4]" and is the last channel. Instead i get a flickering value around 12. If i set the number of channels to 5 i get the values i want, though i suspect this solution is not what i want since i only want to use 4 channels on the transmitter. I have been gathering code from tutorials and snippets and having a hard time to point out the problem. But maybe it has something to do with how i handle the pulses in respect to the frame space?

Hope someone is patient enough to help me out. Thanks

Also. If someone wants to explain how the code inside "ISR(TIMER1_COMPA_vect)" on the receiver works i would be very grateful.

ISR(TIMER1_COMPA_vect) are complex and confusing to most. This may help you. Here is some code that does what you are requesting. I am using this non blocking code to control my RC Arduino projects I've easily adapted this to use for your pinout.
It uses the same ISR based interrupts you are attempting to use and I've adapted into a class with easily accessible callbacks similar to what attachInterrupt has.
Attached is the Interrupts.h class library files I've created

Working and Tested Decoder code:

#include "Interrupts.h"
InterruptsClass Interrupt;
typedef union
{
  struct {
    volatile uint16_t Throttle;
    volatile uint16_t Yaw;
    volatile uint16_t Pitch;
    volatile uint16_t Roll;
  };
  volatile uint16_t pulseLen[4];
} ControlsUnion;

ControlsUnion RC; // place to hold the retrieved values

int ESCPins[] = {4, 7, 8, 12}; // your pins to detect PPM

// This callback is needed to handle pin specific callbacks
void callPinInterrupts(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  Interrupt.PinCallBack(Time, PinsChanged, Pins);
}
//Pin Specific Callbacks:
//This sets up the pins and adds code to the interrupt which is triggered each time the pin rises or falls
// *** Any pin including analog pins can be used!!! ***
void ESCPin0(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  uint16_t t =  Interrupt.RCRemote(ESCPins[0], Time, Pins, true);
  if (t) RC.pulseLen[0] = t - 1000;
}
void ESCPin1(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  uint16_t t =   Interrupt.RCRemote(ESCPins[1], Time, Pins, true);
  if (t) RC.pulseLen[1] = t - 1000;
}
void ESCPin2(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  uint16_t t =   Interrupt.RCRemote(ESCPins[2], Time, Pins, true);
  if (t) RC.pulseLen[2] = t - 1000;
}
void ESCPin3(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  uint16_t t =   Interrupt.RCRemote(ESCPins[3], Time, Pins, true);
  if (t) RC.pulseLen[3] = t - 1000;
}

void setup() {
  Serial.begin(115200); //115200
  // Initialize the interrupts:
  Interrupt.onInterrupt(callPinInterrupts);
  Interrupt.onPin(ESCPins[0], INPUT, ESCPin0);
  Interrupt.onPin(ESCPins[1], INPUT, ESCPin1);
  Interrupt.onPin(ESCPins[2], INPUT, ESCPin2);
  Interrupt.onPin(ESCPins[3], INPUT, ESCPin3);
}

void loop() {
  int t = 100;// 10 times a second
  static unsigned long SpamTimer;
  if ((millis() - SpamTimer) >= (t)) {
    SpamTimer = millis();
    Serial.print("Throttle = ");    Serial.print(RC.Throttle);
    Serial.print("\t Yaw = ");    Serial.print(RC.Yaw);
    Serial.print("\t Pitch = ");    Serial.print(RC.Pitch);
    Serial.print("\t Roll = ");    Serial.print(RC.Roll);
    Serial.println();
  }
}

Output:

 Throttle = 263    Yaw = 500    Pitch = 496    Roll = 504

Z

Interrupts.cpp (6.6 KB)

Interrupts.h (5.54 KB)

Hi. Thanks for the answer. But the ESC pins are for later output to the electric speed controllers on the drone i'm building. The PPMPin (pin 2) is where i read the PPM-signal. Plus, i'd rather not use libraries and try to work with this code and understand it completely since i've come this far with it.

I should've commented more to avoid misunderstandings. I'm sorry.

lefeh:
Hi. Thanks for the answer. But the ESC pins are for later output to the electric speed controllers on the drone i'm building. The PPMPin (pin 2) is where i read the PPM-signal. Plus, i'd rather not use libraries and try to work with this code and understand it completely since i've come this far with it.

I should've commented more to avoid misunderstandings. I'm sorry.

My apologies I assumed you had 4 inputs. I see you are only using 1 input correct?
Do you have all your pulses generate one after another from that input then you delay 8000 uS before starting again?
Z

I have reviewed your code further and see where you can improve.
The following function freezes your code until the pulse is received. by simply changing one thing you can capture the falling edge in the interrupt and simplify everything

void lastChannel() {
  while (digitalRead(PPMPin) == HIGH) {
//...
 }
}

Changed

attachInterrupt(digitalPinToInterrupt(PPMPin), spike, RISING);

to

attachInterrupt(digitalPinToInterrupt(PPMPin), spike, CHANGE);

now your function spike can handle the timing:

void spike() {
  static unsigned long EdgeTime;
  currentTime = micros();
  if (pulseLen[currentChannel] > frameSpace) {
    currentChannel = 0;
  }
  if(currentChannel == channelNum)return;
  if (digitalRead(PPMPin) == HIGH) {  //Pulse went HIGH 
    EdgeTime = currentTime;           //store the start time
  } else {                            // Pulse Went low calculate the duration
    uint16_t dTime = currentTime - EdgeTime; // Calculate the change in time
    if ((900 < dTime) && (dTime < 2100)){    // Time is within proper RC range 
      pulseLen[currentChannel++] = dTime;    // Store the time then (++ is after variable) incrament the currentchannel by 1
    } 
  }
}

This code compiles and should work although I have no way to test it.
added lots of notes for you :slight_smile:

#include <Servo.h>


#define channelNum 4 //Number of channels, if i set this to 5 i get the correct value for the last 
                 //                 channel, but i only use 4 channels: throttle, yaw, pitch and roll.

int PPMPin = 2;
const int frameSpace = 8000; //4channels * 2000

volatile long currentTime;
volatile long lastSpike;
volatile byte currentChannel = 0;
volatile int pulseLen[channelNum + 1];
volatile int lastChannelFlag = false;

Servo ESC0;

int ESCPins[] = {4, 7, 8, 12};

void setup() {

  Serial.begin(115200);
  pinMode(PPMPin, INPUT);
  digitalWrite(PPMPin, LOW);

  attachInterrupt(digitalPinToInterrupt(PPMPin), spike, CHANGE);
  lastSpike = micros();

  ESC0.attach(ESCPins[0]);
}

void loop() {

  if (currentChannel = channelNum) {
    writeToESC();
    displayChannels();
  }

}

void spike() {
  static unsigned long EdgeTime;
  currentTime = micros();
  if (pulseLen[currentChannel] > frameSpace) {
    currentChannel = 0;
  }
  if(currentChannel == channelNum)return;
  if (digitalRead(PPMPin) == HIGH) {  //Pulse went HIGH 
    EdgeTime = currentTime;           //store the start time
  } else {                            // Pulse Went low calculate the duration
    uint16_t dTime = currentTime - EdgeTime; // Calculate the change in time
    if ((900 < dTime) && (dTime < 2100)){    // Time is within proper RC range 
      pulseLen[currentChannel++] = dTime;    // Store the time then (++ is after variable) incrament the currentchannel by 1
    } 
  }
}

void writeToESC() {

}

void displayChannels() {
  Serial.print("Throttle: "); Serial.print(pulseLen[1]);      Serial.print("    ");
  Serial.print("Yaw: ");      Serial.print(pulseLen[2]);      Serial.print("    ");
  Serial.print("Pitch: ");    Serial.print(pulseLen[3]);      Serial.print("    ");
  Serial.print("Roll: ");     Serial.print(pulseLen[4]);      Serial.print("\n");
}

Hope this helps :slight_smile:
Z

zhomeslice:
My apologies I assumed you had 4 inputs. I see you are only using 1 input correct?
Do you have all your pulses generate one after another from that input then you delay 8000 uS before starting again?
Z

Those 4 pins are for later output for the ESC's. The only input i use on the decoder is pin 2 where all the channels come in a stream of pulses. I currently only use ESC0 for testing the motor.

Thanks for the answer, I will try to implement this and come back with results!

I tried the code but now when i display the channels i get this:

Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    itch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 11

For about half a second and then it freezes.

The current code is:

#include <Servo.h>

#define channelNum 4

int PPMPin = 2;
const int frameSpace = 8000; //4ch * 2000

volatile long currentTime;
volatile byte currentChannel = 0;
volatile int pulseLen[channelNum + 1];

void setup() {

  Serial.begin(115200);
  pinMode(PPMPin, INPUT);
  digitalWrite(PPMPin, LOW);

  attachInterrupt(digitalPinToInterrupt(PPMPin), spike, CHANGE);

}

void loop() {
  displayChannels();
}

void spike() {
  static unsigned long edgeTime;
  currentTime = micros();
 
  if (pulseLen[currentChannel] > frameSpace) {
    currentChannel = 0;
  }

  currentChannel++;

  if (currentChannel == channelNum)return;

  if(digitalRead(PPMPin) == HIGH) {
    edgeTime = currentTime;
  }
  else {
    uint16_t duration = currentTime - edgeTime;
    if((900 < duration) && (duration < 2100)) {
      pulseLen[currentChannel++] = duration;
    }
  }

}


void displayChannels() {

  Serial.print("Throttle: "); Serial.print(pulseLen[1]);      Serial.print("    ");
  Serial.print("Yaw: ");      Serial.print(pulseLen[2]);      Serial.print("    ");
  Serial.print("Pitch: ");    Serial.print(pulseLen[3]);      Serial.print("    ");
  Serial.print("Roll: ");     Serial.print(pulseLen[4]);      Serial.print("\n");

}

lefeh:
I tried the code but now when i display the channels i get this:

Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0

Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    itch: 1104    Roll: 0
Throttle: 1100    Yaw: 1096    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 1100    Pitch: 1104    Roll: 0
Throttle: 1100    Yaw: 11




For about half a second and then it freezes.

The current code is:



#include <Servo.h>

#define channelNum 4

int PPMPin = 2;
const int frameSpace = 8000; //4ch * 2000

volatile long currentTime;
volatile byte currentChannel = 0;
volatile int pulseLen[channelNum + 1];

void setup() {

Serial.begin(115200);
 pinMode(PPMPin, INPUT);
 digitalWrite(PPMPin, LOW);

attachInterrupt(digitalPinToInterrupt(PPMPin), spike, CHANGE);

}

void loop() {
 displayChannels();
}

void spike() {
 static unsigned long edgeTime;
 currentTime = micros();

if (pulseLen[currentChannel] > frameSpace) {
   currentChannel = 0;
 }

currentChannel++;

if (currentChannel == channelNum)return;

if(digitalRead(PPMPin) == HIGH) {
   edgeTime = currentTime;
 }
 else {
   uint16_t duration = currentTime - edgeTime;
   if((900 < duration) && (duration < 2100)) {
     pulseLen[currentChannel++] = duration;
   }
 }

}

void displayChannels() {

Serial.print("Throttle: "); Serial.print(pulseLen[1]);      Serial.print("    ");
 Serial.print("Yaw: ");      Serial.print(pulseLen[2]);      Serial.print("    ");
 Serial.print("Pitch: ");    Serial.print(pulseLen[3]);      Serial.print("    ");
 Serial.print("Roll: ");     Serial.print(pulseLen[4]);      Serial.print("\n");

}

We may be spanning the serial port. Interrupts and serial com require some control. This has caused me problems in the past. Try adding a spam timer for the serial output:

void displayChannels() {
  static unsigned long SpamTimer;
  if ((millis() - SpamTimer) >= (100)) {
     SpamTimer = millis();
     Serial.print("Throttle: "); Serial.print(pulseLen[1]);      
     Serial.print("    ");
     Serial.print("Yaw: ");      Serial.print(pulseLen[2]);      
     Serial.print("    ");
     Serial.print("Pitch: ");    Serial.print(pulseLen[3]);      
     Serial.print("    ");
     Serial.print("Roll: ");     Serial.print(pulseLen[4]);      
     Serial.print("\n");
  }
}

Z

Well i found the problem! The code is apparently working as it should but it was that i chose to display the values inside the "if(lastChannelFlag)" statement that messed it up. If i just call "displayChannels()" outside that block it shows everything as i want it. Many hours went into finding that but i learned a lot about the code and I can follow it a lot better now for the rest of the project!

Thanks for the answers and for the effort you put in. I will still take your code into consideration to try and optimize the code!

lefeh:
Well i found the problem! The code is apparently working as it should but it was that i chose to display the values inside the "if(lastChannelFlag)" statement that messed it up. If i just call "displayChannels()" outside that block it shows everything as i want it. Many hours went into finding that but i learned a lot about the code and I can follow it a lot better now for the rest of the project!

Thanks for the answers and for the effort you put in. I will still take your code into consideration to try and optimize the code!

Your Welcome,
Post back her if you have more questions about the code. I'll keep it tagged to send me emails if you post something
Z