1

Greetings to all members of the Forum. I would like to express my deepest gratitude, with respect to the title of this post. The last week I am trying to capture and decode a PPM Signal from an RC transmitter. Although I have seen many different approaches and codes online, using methods like Input Capture Mode and Pin Change Interrupts I would like to seek some help on my approach. Most of the codes I have found online present glitches and instabilities while trying to read the time between the rising pulses. I have tried to read and understand how to set up the Input Capture registers on the Atmega 32u4 chip, aka Arduino Pro Micro / Lepnardo, but I cannot find a proper way of defining the registers as well as the ISR (_vec) function. In the next few lines I will try to explain the code I have written so far, but I will need some help with respect to the initialization of registers. Please bare with me, as any help will be much appreciated. Without further ado, let me introduce you to the following lines.


#define STICK_CENTER 1500
#define STICK_HALFWAY 500
#define THRESHOLD 15 

#define MIN_PULSE_WIDTH (STICK_CENTER - STICK_HALFWAY - 15)
#define MAX_PULSE_WIDTH (STICK_CENTER + STICK_HALFWAY + 15)

#define RC_CHANNELS_COUNT 8

#define PPM_CAPTURE_PIN 4
#define NEWFRAME_PULSE_WIDTH 2000
#define TIMER_COUNT_DIVIDER 2

void rc_setup_ppm() {
  setupPins();
  initTimer();
}

void setupPins(void) {
  // Set up the Input Capture pin
  pinMode(PPM_CAPTURE_PIN, INPUT);
  digitalWrite(PPM_CAPTURE_PIN, 1); // enable the pullup
}

void initTimer(void) {
  cli();
  
  TIFR = (1 << ICF1); //Clear any Interrupt flags 
  PRR |= (0 << PRTIM1); //Enable Timer1
  TCCR1A = 0; //Clear TCCR1A
  TCCR1B = 0; //Clear TCCR1B
  TCCR1C = 0; //Clear TCCR1C
  TCCR1B = (1 << ICNC1) | (1 << ICES1) | (1 << WGM13) | (1 << WGM12) | (0 << CS12) | (1 << CS11) | (0 << CS10);
  TIMSK1 |= (1 << ICIE1); //Enable input capture
  TIFR1 = (1 << ICF1); //Clear interrupt flag

 sei();
}

uint16_t adjust(uint16_t diff, uint8_t chan) {
 
  return diff / TIMER_COUNT_DIVIDER;
}

ISR(TIMER1_CAPT_vect) {
  union twoBytes {
    uint16_t word;
    uint8_t  byte[2];
  } timeValue;

  uint16_t now, diff;
  static uint16_t last = 0;
  static uint8_t chan = 0;

  timeValue.byte[0] = ICR1L;    // grab captured timer value (low byte)
  timeValue.byte[1] = ICR1H;    // grab captured timer value (high byte)

  now = timeValue.word;
  diff = now - last;
  last = now;

  //all numbers are microseconds multiplied by TIMER_COUNT_DIVIDER (as prescaler is set to 1/8 of 16 MHz)
  if (diff > (NEWFRAME_PULSE_WIDTH * TIMER_COUNT_DIVIDER)) {
    chan = 0;  // new data frame detected, start again
  }
  else {
    if (diff > (MIN_PULSE_WIDTH * TIMER_COUNT_DIVIDER - THRESHOLD)
        && diff < (MAX_PULSE_WIDTH * TIMER_COUNT_DIVIDER + THRESHOLD)
        && chan < RC_CHANNELS_COUNT)
    {
      rc_values[chan] = adjust(diff, chan); //store detected value
    }
    chan++; //no value detected within expected range, move to next channel
  }
}

Given the above I would like to help setting the initialization routine of the registers. Some information about my signal:

PPM includes 8 different channels as a summed signal. In total 9 pulses while each one, has a time duration from 998 - 2012 microsecs.

The time frame of each pulse is easy to be changed though the transmitter settings. Also the total period of the frame can be changed. By the first I mean the HIGH time of each pulse, and by the second I mean the total frame period after the 8 channels have been scanned. Given these, the time periods are 26.5 mills and 350 microsecs.

Enough with these. Going back to the actual code you will see the cli(); and sei(); functions. Do I really have to set these during that part of the code? Also do I need to reset or delete any active flags? Should I include any flags (e.g. Overflow flag after the counter has reached its peack value)

At this point I would also like to mention that I am using a prescaler of 1/8, which results to a tick period of apx. 0.5 microsecs. Also, I am capturing only the rising part of the pulses. And I am using an Input canceler, but given the datasheet specifications I have seen that the cancel frame is 4 periods long. Is there any possible way to use a low latency filtering scheme?

Please take some time to read this post, as I am confident enough that any responses will help others too clarify these topics. I know that I have included many different aspects and I feel sorry for this.

Many thanks, Koutsoukos Theodosios

6
  • I feel like you're about to pull a rabbit out of a hat or challenge us to a duel or something. In all seriousness, you'll probably have better luck if you cut this down.
    – timemage
    Feb 23, 2021 at 1:23
  • Sorry for the contents of my post. I would like to seek advices only to the Initialization of the input capture mode. Only the registers and how to use these. Feb 23, 2021 at 8:33
  • I could probably write something about how to setup input capture. But in your question you mention that you're trying to do this with 8 different signals. The 32u4 only has 2 input capture pins. That would seem to be something of a problem for you, if I understand correctly.
    – timemage
    Feb 23, 2021 at 12:24
  • Dear timemage, thank you for your reply. It was something I wrote wrong, so please forgive me. I meant that the ppm signal contains 8 different signals, (9 pulses in total). Feb 23, 2021 at 13:20
  • If "after the 8 channels have been scanned" means after "after 8 pulses..." you can edit your question to say that. Clarifications to questions asked here normally supposed to edited into the question.
    – timemage
    Feb 23, 2021 at 13:30

1 Answer 1

2

Do I really have to set these during that part of the code?

No, you don't. I would, however, change the order of the last initializations:

  • Update TIFR1 right before TIMSK1 in order to avoid an interrupt to be triggered immediately.

  • Set TCCR1B last in order for the timer to start only when it has been fully configured.

Also do I need to reset or delete any active flags?

AFAIK the Arduino core does not use the Timer 1 interrupts. You can take control of all the bits in the registers (e.g. TIMSK1 = (1 << ICIE1);) instead of only some selected bits. It shouldn't make a difference though.

Should I include any flags (e.g. Overflow flag after the counter has reached its peak value)

Do not worry about overflows. The computation diff = now - last is done modulo 216 and, owing to the rules of modular arithmetics, gives the correct result even across an overflow.

And I am using an Input canceler, but given the datasheet specifications I have seen that the cancel frame is 4 periods long.

This creates a delay of “four system clock cycles”, or 0.25 µs. Do you really care about such a small latency? Please note that, since all edges suffer the same latency, it doesn't affect the calculated differences.

Now, if I may, a couple of comments about your code:

  pinMode(PPM_CAPTURE_PIN, INPUT);
  digitalWrite(PPM_CAPTURE_PIN, 1); // enable the pullup

This can be simplified an made more explicit:

  pinMode(PPM_CAPTURE_PIN, INPUT_PULLUP);

Then,

  union twoBytes {
    uint16_t word;
    uint8_t  byte[2];
  } timeValue;
  timeValue.byte[0] = ICR1L;    // grab captured timer value (low byte)
  timeValue.byte[1] = ICR1H;    // grab captured timer value (high byte)
  now = timeValue.word;

This used to be the way to read 16-bit registers a long time ago. Since many years now, the C compiler knows how to do it, you just have to issue:

  now = ICR1;
4
  • This contains most of what I was writing, so I'm just going to vote this up.
    – timemage
    Feb 23, 2021 at 14:44
  • PRR |= (0 << PRTIM1); -> PRR0 &= ~(1 << PRTIM1);
    – timemage
    Feb 23, 2021 at 14:50
  • TCCR1C = 0; does nothing useful.
    – timemage
    Feb 23, 2021 at 14:54
  • 1
    Dear Edgar, timemage, many thanks for your replies. Especially to the the one from Edger, which included everything I was asking for. Just amazed by the simplicity and the way you did transmit all the information. Cheers! Feb 23, 2021 at 22:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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