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.
Diff: main.cpp
- 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(); + } + } +} +