NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Sensors, Actuators, and Robotics » Radio Control Interface - a PPM decoder

January 13, 2010
by icarus
icarus's Avatar
so i'm trying to decode a ppm signal, basically i have a pin with a ppm signal

that carries 3-4 channels. From hours of research, I've found that the information within the signal is contained in the measured time interval between a series of pulses. the time interval for each individual channel is no larger than 0ms-3ms. after all the channels have been sent, there is a longer delay, I've heard over 4ms but under 20ms, used to indicate the data is starting over at ch 1. several forums have stated code examples using the 16-bit timer/counter and an interrupt to measure the interval, but I'm confused as to how that feature works and the "formal" way the code should be written. I have lots of experience in C++, but I cannot wrap my head around how to program to use the interupt feature, or even if thats what i need...

any help is much appreciated, thank you

January 18, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi icarus,

It sounds like a pretty cool project you are working on. I think you came to the right conclusion in your research that the information is encoded the pulses of a single line. Your best bet to tackling this project is to start by trying to detect the time between pulses of your signal line. To get started with this idea I suggest looking into the Interrupts Tutorial. It deals with interrupts in general, but specifically uses a pin change interrupt to find the rising edge of a clock line. You should be able to combine that with the concepts found in the Real Time Clock Project in order to measure the times between the pulses.

Once you find the rising edges of your signal line the time between them will help you decode the signal into the different channels. Let us know how you get along with your project. It should be pretty fun.

Humberto

January 19, 2010
by icarus
icarus's Avatar

Thanks for the post!

I've made great progress in my project. I did just that, using the Input capture function of the MCU and a pin change interrupt on the rising edge of the pulse to trigger a capture. I wrote the code to the best of my ability, with some help from example codes, and loaded it onto the chip. However, when i tested it, it did not work. the source of my problem turned out to be a voltage of less than 0.5 V on the ppm signal. so I'm going to hook the signal line to an oscilloscope to see where to go from there. otherwise, the code seems to be working and I'm making good progress. I'll try to post a copy of my code here in the next few days and also some updates on what all this is for and the results of the oscilloscope test.

-Icarus

January 31, 2010
by icarus
icarus's Avatar

By gosh I've got it!

So, this is an update on my progress into my project. Ive been working on interfacing standard servos with a Hobby-zone Super cub airplane. This is a huge advancement for anyone with a hobby-zone plane, as their servos are "dumb"; they do not have the circuitry to accept a pwm signal. Instead they run 5 wires, 2 for motor leads and 3 for a pot that indicates position. this setup has low quality and poor response, so an interface would be awesome.

Conveniently, most hobby-zone airplanes have an x-port jack on their Rx. On the x-port there are 4 pins, positive, ground, the ppm control line, and throttle. so i got to thinking... if i can use the ATmega168 to read the ppm signal, i can output a pwm signal and control my standard servos. this is where this thread comes in, i need a little help at first but now Ive got into it and made a working prototype.

I did the oscilloscope test about 2 weeks ago and got all the info on my ppm source. here are the results.

The ppm line is 0-5v TTL. so its easy to hook up. the pulses are .325ms the frame is 24.6 ms. channel 1 is throttle, increasing it enlarges the time from 1ms to 2ms channel 2 is elevator. its range is 1.16 to 1.82ms up- elevator increases its value channel 3 is rudder. its range is 1.2 to 1.82 ms. right increases its value. ACT switch produces non uniform pulses the x-port button seems to be frequency modulated, right now it screws up my code when pressed.

with this information i wrote this code to interpret the ppm signal and reproduce the elevator commands. The code uses the timer counter 0 found in the real time clock example. beefed up to keep time in hundredths of a microsecond. i then use an int0 interrupt to record the time between rising edges of the pulses. after all that, i had figure a way to record the individual channels in their respective variables. this is done with an array in the ISR. anyways heres my code, i tested it and it works with a futaba s3101 servo on the elevator channel.

// xport_servo.c
// for NerdKits with ATmega168
// by Icarus

#define F_CPU 14745600

#include <stdio.h>
#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
// pD2= xport in
// PB2 - servo signal (OC1B)

// This variable is marked "volatile" because it is modified
// by an interrupt handler.  Without the "volatile" marking,
// the compiler might just assume that it doesn't change in 
// the flow of any given function (if the compiler doesn't
// see any code in that function modifying it -- sounds 
// reasonable, normally!).
//
// But with "volatile", it will always read it from memory 
// instead of making that assumption.
volatile int32_t the_time;
volatile int32_t pulse_width;
volatile int32_t dataline[3];
volatile int8_t frame = 1;
volatile int8_t i = 0;
uint16_t pos = PWM_START;
uint16_t ch_2_pos;

ISR(INT0_vect){
//these lines capture the time from rising edge to rising edge
// then reset the timer to keep accurate time
pulse_width = the_time;
the_time = 0;
//this if statement sets the value of the first pulse to channel 1  
if (pulse_width >1000){
i=0;
}

// this statement weeds out false readings of 0 and pulses over the
// maximum value of the channel.
if ((pulse_width < 500)&(pulse_width>0)) {  
//this sets the pointer to i, which should be the channel number 0 - 2
    dataline[i] = pulse_width;
//incriment the channel after one has been set  
    i++;
// resets the channel number each cycle 
    if (i==3){
    i=0;
    // this is used to determine that the data line has been updated
    frame = 1 ;
    }

}
//this is probably not necicarry but it ensures the global interupt
//register is set
sei();

}

void PulseTimer_Setup() {
  // setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/8
  // which is 14745600/8, or 102400 increments per second
  TCCR0B |= (1<<CS01);
  // set TOP to 1023
  // because it counts 0, 1, 2, ... 8, 9, 0, 1, 2 ...
  // so 0 through 9 equals 10 events
  OCR0A = 9;
  // enable interrupt on compare event
  // (102400 / 104 = 10240 per second)
  TIMSK0 |= (1<<OCIE0A);
}

// the_time will store the elapsed time
// in hundredths of a micro second second.
// (100,000 = 1 second)
//

SIGNAL(SIG_OUTPUT_COMPARE0A) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a micro second has elapsed (0.000001 seconds).
   the_time++;

}

void init_ppm_pin(){

  //make PD2 input pin

  PORTD |= (1<<PD2);

  //Enable External interupt INT0- This enables interrupts on pin
  //PD2 
  EIMSK |= (1<<INT0);

  //Set the interupt to fire on the rising edge
  EICRA |= (1<<ISC00);
  EICRA |= (1<<ISC01);
}

void pwm_set(uint16_t x) {
  OCR1B = x;
}

#define PWM_MIN 1300
#define PWM_MAX 4450
#define PWM_START 2765

void pwm_init() {
  // setup Timer1 for Fast PWM mode, 16-bit
  // COM1B1 -- for non-inverting output
  // WGM13, WGM12, WGM11, WGM10 -- for Fast PWM with OCR1A as TOP value
  // CS11 -- for CLK/8 prescaling

  OCR1A = 36864;    // sets PWM to repeat pulse every 20.0ms
  pwm_set(PWM_START);
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

  // each count is 8/14745600 = 0.5425us.
  // so 1.0ms = 1843.2
  //    1.5ms = 2764.8
  //    2.0ms = 3686.4
  //   20.0ms = 36864
}

int main() {
//this prevents the initialization from being interupted
  cli();

  PulseTimer_Setup();

  init_ppm_pin();

  pwm_init();

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  //these lines are used to idicate program resets in the hyperterminal
  //(when it resets, they are displayed indicating reboot)
  printf_P(PSTR(" initiializing... "));
  delay_ms(2000);
  printf_P(PSTR(" done \r\n"));
  delay_ms(2000);

  // set pb2 as an output (for the servo)
  DDRB |= (1<<PB2);

  // turn on interrupt handle
  sei();

  while(1) {

    if (frame == 1){

        // dataline[1] min = 200 max= 350
        //range = 150 points
        //
        // servo range min= 1843.2 max= 3686.4
        //range = 1843 points
        // 1843/150 = 12.29 round to 12
        // 1 point of dataline range= 12 points of servo range

        //subtract dataline min
        //multiply by 12
        // then add servo min 
        ch_2_pos = (((dataline[1]-200)*12)+1843);
        pos = ch_2_pos;

        //print data to computer for analysis
        printf_P(PSTR("channel 1: %d "),dataline[0]);
        printf_P(PSTR("channel 2: %d "),dataline[1]);
        printf_P(PSTR("channel 3: %d "),dataline[2]);
        printf_P(PSTR("servo pos: %d \r\n"),pos);
        }
    //check that pos is within servo limits
    if(pos > PWM_MAX) pos = PWM_MAX;
    if(pos < PWM_MIN) pos = PWM_MIN;
    //output servo position
    pwm_set(pos);
   }
  return 0;
}

Now that i have a stable platform to interface standard servos, i can begin work on expanding it to 2 servos and eventually an autopilot... but thats for another day.

if you have any questions or comments, especially an ideas to improve the code please, dont hesitate to post! i check the forum frequently and will reply ASAP :)

-Icarus

Post a Reply

Please log in to post a reply.

Did you know that reading a double floating point variable with scanf requires "%lf" for "long float"? Learn more...