Quantcast
Channel: MSP430 Technical Forums
Viewing all articles
Browse latest Browse all 2077

RC PPM encode and decode

$
0
0
PPM (pulse position modulation) is a common protocol used with radio control. It used to be the over that air protocol, but has been mostly replaced with PCM (pulse code modulation). It lives on in the form of trainer ports, radio to tx module link, and even some modern PCM receivers can output PPM (typically on the battery connector). It is also a handy way to drive up to 8 servos using a single pin of the microcontroller (and a CD4017 decoder). PPM is sort of a concatenation of several (often 8) RC servo signals in succession. The time between narrow (a few hundred microseconds) pulses determines with width of each successive servo pulse. A duration of more than about 4000 microseconds designates the end of a frame. The typical total frame duration is 20,000 microseconds (20 ms).

ppm.jpg

The PPM transmit code uses a single timer capture/compare unit in toggle mode. The initial state is set with OUTMOD_0 to allow for active low or active high output. The elegantly simple MSP430 timer makes this code very compact and efficient.
 
static void init_ppm_tx(unsigned pol)           // - Initialize PPM transmission
{                                               // pol = 0: idle low, pulse high
                                                // pol = 1: idle high, pulse low
                                                //
    P2DIR |= BIT0;                              // PPM output on P2.0
    P2SEL |= BIT0;                              // Enable timer compare output
    P2SEL2 &= ~BIT0;                            //
                                                //
    TA1CCTL0 = OUTMOD_0 | (pol ? OUT : 0);      // Set initial state of output (polarity)
    TA1CCTL0 = OUTMOD_4 | CCIE;                 // Set timer output to toggle mode, enable interrupt
    TA1CCR0 = TA1R + 1000;                      // Set initial interrupt time
}                                               //
                                                //
static unsigned st[8];                          // Servo transmit times
                                                //
static const unsigned pulse_duration = 200;     // Duration of on time of each pulse
static const unsigned frame_duration = 20000;   // Total duration of a complete frame
                                                //
#pragma vector = TIMER1_A0_VECTOR               // - ISR for PPM transmission
__interrupt void isr_ccr0(void)                 //
{                                               //
    static unsigned state = 0;                  // State / pulse index
    static unsigned td = 0;                     // Total duration of channel pulses
                                                //
    if(state < (sizeof(st) / sizeof(st[0])) * 2 + 1) { // Check if not done with all times
        if(state & 1) {                         // Alternate between rising & falling edges
                                                // Setup the time until the next rising edge
            const unsigned t = st[state >> 1];  // Get time from array
            TA1CCR0 += (t - pulse_duration);    // Add to falling edge time, compensate for pulse width
            td += t;                            // Update total frame duration
        } else {                                //
            TA1CCR0 += pulse_duration;          // Add pulse duration to rising edge time
        }                                       //
        ++state;                                // Increment state
    } else {                                    // Final pulse in frame (off time only)
        TA1CCR0 += (frame_duration - pulse_duration - td); // Set rising edge time to make desired frame duration
        td = 0;                                 // Reset total frame time
        state = 0;                              // Reset state
    }                                           //
}                                               //
                                                //
The PPM decoder is a bit more complicated. It checks for vaild pulse widths and ignores anything out of spec. Two capture/compare units are used. The first handles measuring the time between edges, and the second provides a timeout that detects an idle line (to prevent timer wraparound from creating alias pulses). Either the rising of falling edge can be used.
 
static void init_ppm_rx(unsigned edge)          // - Initialize PPM reception
{                                               // edge = 0: capture on rising edge
                                                // edge = 1: capture on falling edge
                                                //
    P2DIR &= ~BIT1;                             // PPM input on P2.1
    P2SEL |= BIT1;                              // Enable time capture input
    P2SEL2 &= ~BIT1;                            //
                                                //
    TA1CCTL1 = (edge ? CM_2 : CM_1) | CCIS_0 | CAP | CCIE; // CCR1 capture mode, enable interrupt
    TA1CCTL2 = CCIE;                            // CCR2 enable interrupt
}                                               //
                                                //
static unsigned sr[8];                          // Servo receive times
static unsigned rx_frame = 0;                   // Incremented after every received frame
                                                //
static const unsigned min_pw = 1500 - 1100;     // 400 us minimum
static const unsigned max_pw = 1500 + 1100;     // 2600 us maximum
static const unsigned min_reset = 4000;         // minimum time between frames
static const unsigned rx_timeout = 24000;       // maximum time between pulses
                                                //
#pragma vector = TIMER1_A1_VECTOR               // - ISR for PPM reception
__interrupt void isr_ccr12(void)                //
{                                               //
    static unsigned pt, et;                     // Previous time, elapsed time
    static unsigned state;                      // Received pulse index / state
    static unsigned pd[8];                      // Received pulse durations - the size of this array must
                                                //  match what the transmitter is sending
                                                //
    switch(TA1IV) {                             //
        case 0x02:                              // - CCR1
            et = TA1CCR1 - pt;                  // Calculate elapsed time since last edge
            pt = TA1CCR1;                       // Save current edge time
            if(et > min_reset) {                // Check for a pulse that is too long to be a channel
                                                // Check if all pulses received, and no more
                if(state == sizeof(pd) / sizeof(pd[0])) {
                    memcpy(sr, pd, sizeof(pd)); // Copy to foreground array
                    ++rx_frame;                 // Increment frame count
                }                               //
                state = 0;                      // Begin next frame
            } else if(et < min_pw || et > max_pw) { // Check if pulse is out of range
                state = 0x80;                   // Go to idle state
            } else {                            // Save pulse if room in array
                if(state < sizeof(pd) / sizeof(pd[0]))
                    pd[state] = et;             //
                if(state < 0x40) ++state;       // Next state - limit max value in case of noisy input
            }                                   //
            TA1CCR2 = pt + rx_timeout;          // Reset timeout
            break;                              //
        case 0x04:                              // - CCR2
            state = 0x81;                       // Go to timeout state
            break;                              //
    }                                           //
}                                               //
                                                //
Here is a simple test program that prints the pulse times. The times are represented as the deviation from 1500 microseconds. That is the typical center time for a RC servo. Each field is a fixed width with a sign followed by 3 digits. Jumper P2.0 to P2.1 to feed the PPM output to the PPM input.
 
terminal.png
 
#include <msp430.h> 
#include <stdlib.h>

static void putc(char c)                        // -  Put char to serial
{                                               //
    while(!(IFG2 & UCA0TXIFG));                 //
    UCA0TXBUF = c;                              //
}                                               //
                                                //
static void put_pw(unsigned pw)                 // - Print pulse width to serial
{                                               // Sign and three digits
    unsigned n;                                 // Deviation from 1500 us
    if(pw < 1500) {                             // If less than 1500
        n = 1500 - pw;                          // Calculate deviation
        putc('-');                              // Print sign
    } else {                                    // Equal to or more than 1500
        n = pw - 1500;                          // Calculate deviation
        putc((pw == 1500) ? ' ' : '+');         // Print sign
    }                                           //
    div_t d = div(n, 100);                      //
    putc(d.quot + '0');                         // First digit - hundreds
    d = div(d.rem, 10);                         //
    putc(d.quot + '0');                         // Second digit - tens
    putc(d.rem + '0');                          // Third digit - ones
}                                               //
                                                //
static const unsigned long smclk_freq = 8000000UL; // SMCLK frequency in hertz
static const unsigned long bps = 9600UL;        // Async serial bit rate
                                                //
int main(void)                                  //
{                                               //
    const unsigned long brd = (smclk_freq + (bps >> 1)) / bps;  // Bit rate divisor
                                                //
    WDTCTL = WDTPW | WDTHOLD;                   //
                                                //
    DCOCTL = 0;                                 // Run DCO at 8 MHz
    BCSCTL1 = CALBC1_8MHZ;                      //
    DCOCTL  = CALDCO_8MHZ;                      //
                                                //
    P1DIR = 0;                                  //
    P1SEL = BIT1 | BIT2;                        // Enable UART pins
    P1SEL2 = BIT1 | BIT2;                       //
                                                //
    P2DIR = 0;                                  //
    P2SEL = 0;                                  //
    P2SEL2 = 0;                                 //
                                                //
    TA1CTL = TASSEL_2 | ID_3 | MC_2;            // Setup timer 1 for SMCLK / 8, continuous mode
                                                //
    init_ppm_rx(0);                             // Initialize PPM receive
                                                //
    init_ppm_tx(0);                             // Initialize PPM transmit
                                                //
                                                // Initialize UART
    UCA0CTL1 = UCSWRST;                         // Hold USCI in reset to allow configuration
    UCA0CTL0 = 0;                               // No parity, LSB first, 8 bits, one stop bit, UART (async)
    UCA0BR1 = (brd >> 12) & 0xFF;               // High byte of whole divisor
    UCA0BR0 = (brd >> 4) & 0xFF;                // Low byte of whole divisor
    UCA0MCTL = ((brd << 4) & 0xF0) | UCOS16;    // Fractional divisor, oversampling mode
    UCA0CTL1 = UCSSEL_2;                        // Use SMCLK for bit rate generator, release reset
                                                //
                                                //
    st[0] = 1000;                               // Setup servo transmit times
    st[1] = 1200;                               //
    st[2] = 1400;                               //
    st[3] = 1500;                               //
    st[4] = 1600;                               //
    st[5] = 1800;                               //
    st[6] = 2000;                               //
    st[7] = 1520;                               //
                                                //
    _enable_interrupts();                       // Enable all interrupts
                                                //
    for(;;) {                                   //
        unsigned n;                             //
                                                // Print received pulse times to serial
        for(n = 0; n < sizeof(sr) / sizeof(sr[0]); ++n)
            put_pw(sr[n]);                      //
        putc('\r'); putc('\n');                 //
    }                                           //
                                                //
    return 0;                                   //
}                                               //
A bargraph can be displayed with some custom software.

graph.png
Attached File  servo_test.cpp   3.59KB   0 downloads


Viewing all articles
Browse latest Browse all 2077

Trending Articles