This program reads a PPM signal. It is based on RC_Simulator by Jafar Qutteineh. Additions include a number of settings to customize the routine to individual needs, as well as a calibration routine for joystick/slider controls.

Dependencies:   mbed

main.cpp

Committer:
jwolter
Date:
2012-04-11
Revision:
0:32b684f1caad

File content as of revision 0:32b684f1caad:

#include "mbed.h"
#include "usbhid.h"


/* PPM reader by John Wolter from RC_Simulator by Jafar Qutteineh*/
/* This program takes the PPM Signal (Pulse Position Modulation) from your RC transmitter.  Includes a calibration routine
   for 
*/
   
/*Setup: Just connect the GND and PPM signal of your transmitter to the GND and P11 respectively.
         Put a switch between P5 and Vout for calibration mode.*/

/*How it works: 
    -A PPM signal is a stream of low (0V) and high (5V) inputs which indicate the value of multiple channels.  
    -The PPM signal consists of a bunch of variable length low pulses separated by fixed length high pulses (or vice versa).
    -A set of pulses which represent one set of channel values is called a frame.
    -The overall length of the frame is fixed (20 ms for example).
    -The value of each channel is determined by the length of its pulse. (0.5 to 1.5 ms for example)
    -The end of the frame is filled with a variable length pulse to fill the frame.  This pulse is much longer than the 
     channel pulses so that it acts at the marker for the start of a new frame
    -A single pin is configured as InturreptIn pin. whenever the PPM signal goes down Inturrept SignalRise() or SignalFall() is fired.
    -SignalRise or SignalFall keep track of timing:
        a) If Pulse width is <300 uS it's most probably a glitch.
        b) If pulse width is >300 uS but less than < 2000 uS this is a correct channel. Read it and wait for next channel
        c) If Pulse width is >MinSync uS this is a new frame, start again.
        d) Timing will be different from transmitter to another, you may want to play a little bit here
*/   
   

DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);
DigitalOut led4(LED4);
InterruptIn PPMsignal(p11); // connect PPM signal to this, you can change to anyother pin
InterruptIn CalSwitch(p5); // Switch to initiate calibration mode
LocalFileSystem local("local");  // Create the local filesystem under the name "local"
Serial PC(USBTX,USBRX);   // I used this for debuging
Timer timer;        
Timer timer2;        

const int NChannels=6; // This is the number of channels in your PPM signal
unsigned char CurChannel=0; //This will point to the current channel in PPM frame. 
int Channels[NChannels]={0,0,0,0,0,0}; //This where the channels value is stored until frame is complete.
unsigned int Times[NChannels]={0,0,0,0,0,0}; //This where the channel pulse time value is stored until frame is complete.
float C0s[NChannels]; // Zeroth order coefficient for converting times to channels
float C1s[NChannels]; // First order coefficient for converting times to channels
int MsgCount[6]={0,0,0,0,0,0}; // Counter for analyzing data flow, i.e. number of errors, etc.

bool CanUpdate=false;       // Once PPM frame is complete this will be true
bool CalMode=false;       // Once calibraton switch is pressed this will be true
int FrameCount=0;
int i;
int TimeElapsed =0; //Keeps track of time between PPM interrupts
int TimeElapsed2 =0; //Keeps track of time between button interrupts
int TLow;
int THigh;
char dum1,dum2;

/* Here are the parameters that might need to be adjusted depending on your setup */
const bool debug=true;
const bool VerboseDebug=true;
int MinSync = 6000; // Minimum time of the sync pulse (us)
int ShortTime = 800; // If the pulse time for a channel is this short, something is wrong (us)
int TimeMin = 1000; // The minimum pulse time for a channel should be this long (us)
int TimeMax = 2000; // The maximum pulse time for a channel should be this long (us)
int ChannelMin = -127; // Desired minimum value for outputs
int ChannelMax = 127; // Desired maximum value for outputs
const int JCCount=4; //Number of joystick channels
unsigned char JoystickChannels[JCCount]={0,1,2,3}; // List of joystick channels

//Raise_Message is just a function that helped me in development process. You can ignore it all togather.
void Raise_Message (unsigned char Err_Code, int info) {
    switch (Err_Code) {
        case 0:
            MsgCount[0]++;
//            led1 = 1;
//            PC.printf ("%i\n",info);
            break;
        case 1:
            MsgCount[1]++;
//            led2 = 1;
//            PC.printf ("Broke@ %i\n",info);
            break;
        case 2:
            MsgCount[2]++;
//            led3 = 1;
//            PC.printf ("Set ok\n");
            break;
        case 3:
            MsgCount[3]++;
//            led4 = 1;
//            PC.printf ("%i\n",info);
            break;
        case 255:
            MsgCount[4]++;
//            PC.printf("Initalized sucessfully \n");
            break;
        default:
            MsgCount[5]++;
//            PC.printf("I shouldn't be here \n");
    }

}

//Here were all the work decoding the PPM signal takes place
void SignalRise() {
    led4=1-led4;
    TimeElapsed = timer.read_us(); // Since we are measuring from signal rise to signal rise, note that TimeElapsed includes the fixed separator time as well
    if (TimeElapsed < ShortTime) { 
        Raise_Message(0,TimeElapsed);
        return; //Channel timing too short; ignore, it's a glitch. Don't move to the next channel
    }
    __disable_irq();
    timer.reset();  
    if ((TimeElapsed > MinSync ) && (CurChannel != 0)) {   //somehow before reaching the end of PPM frame you read   "New" frame signal???
                                                         //Ok, it happens. Just ignore this frame and start a new one        
        Raise_Message (1,CurChannel);          //incomplete channels set
        CurChannel=0;              
    }
    if ((TimeElapsed > MinSync ) && (CurChannel == 0)) {
        Raise_Message (2,CurChannel);          //New frame started
      __enable_irq();       // This is good. You've received "New" frame signal as expected
        return;
    }

    // Process current channel. This is a correct channel in a correct frame so far
    //Channels[CurChannel]= (TimeElapsed-1000)*255/1000; // Normalize reading (Min: 900us Max: 1900 us). This is my readings, yours can be different
    Channels[CurChannel]= C0s[CurChannel] + C1s[CurChannel]*TimeElapsed; // Normalize reading (Min: 900us Max: 1900 us). This is my readings, yours can be different
    Times[CurChannel] = TimeElapsed;
    CurChannel++;
    
    if (CurChannel==NChannels ) {  // great!, you've a complete correct frame. Set CanUpdate and start a new frame
        FrameCount++;
        CurChannel=0;
        Raise_Message(3,0); // Successful frame!
        CanUpdate= true;
        led1=1-led1; // blink the LED
    }
    __enable_irq();
}

// Interrupt for calibration button press
void CalSwitchRise() {
    TimeElapsed2 = timer2.read(); // Since we are measuring from signal rise to signal rise, note that TimeElapsed includes the fixed separator time as well
    if (TimeElapsed2 > 0.3) {
        __disable_irq();
        CalMode=true;
        timer2.reset();  
        __enable_irq();
    }
}

void Initalize () {
    __disable_irq();
    PC.baud(9600); // set baud rate
    if (debug) {PC.printf("\n\nInitializing...\n");}
    PPMsignal.mode (PullUp);
    PPMsignal.rise(&SignalRise); //Attach SignalRise routine to handle PPMsignal rise
    CalSwitch.rise(&CalSwitchRise); //Attach CalSwitchRise routine to handle CalSwitch rise
    timer.start();
    timer2.start();
    Raise_Message(255,0); // return successful
    led1 = 0;
    led2 = 0;
    led3 = 0;
    led4 = 0;
    //Initialize all channels to produce "sane" outputs (depending on your definition of sanity ;-).
    C1s[0] = 1.0*(ChannelMax-ChannelMin)/(TimeMax-TimeMin);
    C0s[0] = 1.0*ChannelMin - TimeMin*C1s[0];
    for (i=1; i<NChannels; i++)
    {
        C1s[i]=C1s[0];
        C0s[i]=C0s[0];
    }    
    if (debug) {
        PC.printf("Coefficients read from file:\n");
        for(i=0; i<NChannels; i++)
        {
            PC.printf("Channel %d - C0=%6f, C1=%6f\n",i,C0s[i],C1s[i]);
        }
    }
    // Initialize joystick channels using saved calibration information
    FILE *fpi = fopen("/local/coefs.txt", "r");  // Open coefficient file on the local file system for reading
    for(i=0; i<JCCount; i++)
    {
        fscanf(fpi, "%f%c%f%c",&C0s[JoystickChannels[i]],&dum1,&C1s[JoystickChannels[i]],&dum2);
    }
    fclose(fpi);
    if (debug) {
        PC.printf("Coefficients read from file:\n");
        for(i=0; i<JCCount; i++)
        {
            PC.printf("Channel %d - C0=%6f, C1=%6f\n",JoystickChannels[i],C0s[JoystickChannels[i]],C1s[JoystickChannels[i]]);
        }    
        PC.printf("...Initialization complete.\n");
    }
    timer.reset();
    timer2.reset();
    __enable_irq();
}

int main() {
    //unsigned int *pOut = Times;
    int *pOut = Channels;
    
    Initalize();
    //wait(5);
    

    while (1) {
        if (CanUpdate) {        // We have a new frame to read
            __disable_irq();
            CanUpdate=false;
            
            //Here is where the response to the PPM inputs should go.
            
            if (VerboseDebug) {PC.printf("%6d,  %6d,  %6d,  %6d,  %6d,  %6d\n",pOut[0],pOut[1],pOut[2],pOut[3],pOut[4],pOut[5]); }
            //if (VerboseDebug) {PC.printf("%6f,  %6f,  %6f,  %6f,  %6f,  %6f\n",pOut[0],pOut[1],pOut[2],pOut[3],pOut[4],pOut[5]); }
            __enable_irq();

        }
        if (CalMode) { // Calibration mode triggered
            __disable_irq();
            wait(1.); // Debounce
            led2 = 1;
            for(i=0; i<JCCount; i++)
            {
                PC.printf("Push joystick channel %2d to the LOW position and press the calibrate button\n",JoystickChannels[i]+1);
                __enable_irq();
                CalMode=false;
                while (!CalMode) {wait(0.1);} // Wait until the calibrate button is pressed.
                CanUpdate=false;
                while (!CanUpdate) {wait(0.02);} // Wait until a new PPM frame is acquired.
                __disable_irq();
                TLow = Times[JoystickChannels[i]]; // Save the time value in the low position.
                wait(1.); // Debounce
                PC.printf("Push joystick channel %2d to the HIGH position and press the calibrate button\n",JoystickChannels[i]+1);
                __enable_irq();
                CalMode=false;
                while (!CalMode) {wait(0.1);} // Wait until the calibrate button is pressed.
                CanUpdate=false;
                while (!CanUpdate) {wait(0.02);} // Wait until a new PPM frame is acquired.
                __disable_irq();
                THigh = Times[JoystickChannels[i]]; // Save the time value in the low position.
                wait(1.); // Debounce
                C1s[i] = 1.0*(ChannelMax-ChannelMin)/(THigh-TLow);
                C0s[i] = 1.0*ChannelMin - TLow*C1s[i];
                PC.printf("Coefficients: C0=%6f, C1=%6f\n",C0s[i],C1s[i]);
            }
            FILE *fpo = fopen("/local/coefs.txt", "w");  // Open coefficient file on the local file system for writing
            for(i=0; i<JCCount; i++)
            {
                fprintf(fpo, "%6f,%6f\n",C0s[JoystickChannels[i]],C1s[JoystickChannels[i]]);
            }
            fclose(fpo);
            PC.printf("Calibration completed.\n");
            led2=0;
            CalMode=false;
            CanUpdate=false;
            __enable_irq();
        }
    }
}