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

Revision:
0:32b684f1caad
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Wed Apr 11 01:56:50 2012 +0000
@@ -0,0 +1,264 @@
+#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();
+        }
+    }
+}
+