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.
main.cpp@0:32b684f1caad, 2012-04-11 (annotated)
- Committer:
- jwolter
- Date:
- Wed Apr 11 01:56:50 2012 +0000
- Revision:
- 0:32b684f1caad
Initial release
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
jwolter | 0:32b684f1caad | 1 | #include "mbed.h" |
jwolter | 0:32b684f1caad | 2 | #include "usbhid.h" |
jwolter | 0:32b684f1caad | 3 | |
jwolter | 0:32b684f1caad | 4 | |
jwolter | 0:32b684f1caad | 5 | /* PPM reader by John Wolter from RC_Simulator by Jafar Qutteineh*/ |
jwolter | 0:32b684f1caad | 6 | /* This program takes the PPM Signal (Pulse Position Modulation) from your RC transmitter. Includes a calibration routine |
jwolter | 0:32b684f1caad | 7 | for |
jwolter | 0:32b684f1caad | 8 | */ |
jwolter | 0:32b684f1caad | 9 | |
jwolter | 0:32b684f1caad | 10 | /*Setup: Just connect the GND and PPM signal of your transmitter to the GND and P11 respectively. |
jwolter | 0:32b684f1caad | 11 | Put a switch between P5 and Vout for calibration mode.*/ |
jwolter | 0:32b684f1caad | 12 | |
jwolter | 0:32b684f1caad | 13 | /*How it works: |
jwolter | 0:32b684f1caad | 14 | -A PPM signal is a stream of low (0V) and high (5V) inputs which indicate the value of multiple channels. |
jwolter | 0:32b684f1caad | 15 | -The PPM signal consists of a bunch of variable length low pulses separated by fixed length high pulses (or vice versa). |
jwolter | 0:32b684f1caad | 16 | -A set of pulses which represent one set of channel values is called a frame. |
jwolter | 0:32b684f1caad | 17 | -The overall length of the frame is fixed (20 ms for example). |
jwolter | 0:32b684f1caad | 18 | -The value of each channel is determined by the length of its pulse. (0.5 to 1.5 ms for example) |
jwolter | 0:32b684f1caad | 19 | -The end of the frame is filled with a variable length pulse to fill the frame. This pulse is much longer than the |
jwolter | 0:32b684f1caad | 20 | channel pulses so that it acts at the marker for the start of a new frame |
jwolter | 0:32b684f1caad | 21 | -A single pin is configured as InturreptIn pin. whenever the PPM signal goes down Inturrept SignalRise() or SignalFall() is fired. |
jwolter | 0:32b684f1caad | 22 | -SignalRise or SignalFall keep track of timing: |
jwolter | 0:32b684f1caad | 23 | a) If Pulse width is <300 uS it's most probably a glitch. |
jwolter | 0:32b684f1caad | 24 | b) If pulse width is >300 uS but less than < 2000 uS this is a correct channel. Read it and wait for next channel |
jwolter | 0:32b684f1caad | 25 | c) If Pulse width is >MinSync uS this is a new frame, start again. |
jwolter | 0:32b684f1caad | 26 | d) Timing will be different from transmitter to another, you may want to play a little bit here |
jwolter | 0:32b684f1caad | 27 | */ |
jwolter | 0:32b684f1caad | 28 | |
jwolter | 0:32b684f1caad | 29 | |
jwolter | 0:32b684f1caad | 30 | DigitalOut led1(LED1); |
jwolter | 0:32b684f1caad | 31 | DigitalOut led2(LED2); |
jwolter | 0:32b684f1caad | 32 | DigitalOut led3(LED3); |
jwolter | 0:32b684f1caad | 33 | DigitalOut led4(LED4); |
jwolter | 0:32b684f1caad | 34 | InterruptIn PPMsignal(p11); // connect PPM signal to this, you can change to anyother pin |
jwolter | 0:32b684f1caad | 35 | InterruptIn CalSwitch(p5); // Switch to initiate calibration mode |
jwolter | 0:32b684f1caad | 36 | LocalFileSystem local("local"); // Create the local filesystem under the name "local" |
jwolter | 0:32b684f1caad | 37 | Serial PC(USBTX,USBRX); // I used this for debuging |
jwolter | 0:32b684f1caad | 38 | Timer timer; |
jwolter | 0:32b684f1caad | 39 | Timer timer2; |
jwolter | 0:32b684f1caad | 40 | |
jwolter | 0:32b684f1caad | 41 | const int NChannels=6; // This is the number of channels in your PPM signal |
jwolter | 0:32b684f1caad | 42 | unsigned char CurChannel=0; //This will point to the current channel in PPM frame. |
jwolter | 0:32b684f1caad | 43 | int Channels[NChannels]={0,0,0,0,0,0}; //This where the channels value is stored until frame is complete. |
jwolter | 0:32b684f1caad | 44 | unsigned int Times[NChannels]={0,0,0,0,0,0}; //This where the channel pulse time value is stored until frame is complete. |
jwolter | 0:32b684f1caad | 45 | float C0s[NChannels]; // Zeroth order coefficient for converting times to channels |
jwolter | 0:32b684f1caad | 46 | float C1s[NChannels]; // First order coefficient for converting times to channels |
jwolter | 0:32b684f1caad | 47 | int MsgCount[6]={0,0,0,0,0,0}; // Counter for analyzing data flow, i.e. number of errors, etc. |
jwolter | 0:32b684f1caad | 48 | |
jwolter | 0:32b684f1caad | 49 | bool CanUpdate=false; // Once PPM frame is complete this will be true |
jwolter | 0:32b684f1caad | 50 | bool CalMode=false; // Once calibraton switch is pressed this will be true |
jwolter | 0:32b684f1caad | 51 | int FrameCount=0; |
jwolter | 0:32b684f1caad | 52 | int i; |
jwolter | 0:32b684f1caad | 53 | int TimeElapsed =0; //Keeps track of time between PPM interrupts |
jwolter | 0:32b684f1caad | 54 | int TimeElapsed2 =0; //Keeps track of time between button interrupts |
jwolter | 0:32b684f1caad | 55 | int TLow; |
jwolter | 0:32b684f1caad | 56 | int THigh; |
jwolter | 0:32b684f1caad | 57 | char dum1,dum2; |
jwolter | 0:32b684f1caad | 58 | |
jwolter | 0:32b684f1caad | 59 | /* Here are the parameters that might need to be adjusted depending on your setup */ |
jwolter | 0:32b684f1caad | 60 | const bool debug=true; |
jwolter | 0:32b684f1caad | 61 | const bool VerboseDebug=true; |
jwolter | 0:32b684f1caad | 62 | int MinSync = 6000; // Minimum time of the sync pulse (us) |
jwolter | 0:32b684f1caad | 63 | int ShortTime = 800; // If the pulse time for a channel is this short, something is wrong (us) |
jwolter | 0:32b684f1caad | 64 | int TimeMin = 1000; // The minimum pulse time for a channel should be this long (us) |
jwolter | 0:32b684f1caad | 65 | int TimeMax = 2000; // The maximum pulse time for a channel should be this long (us) |
jwolter | 0:32b684f1caad | 66 | int ChannelMin = -127; // Desired minimum value for outputs |
jwolter | 0:32b684f1caad | 67 | int ChannelMax = 127; // Desired maximum value for outputs |
jwolter | 0:32b684f1caad | 68 | const int JCCount=4; //Number of joystick channels |
jwolter | 0:32b684f1caad | 69 | unsigned char JoystickChannels[JCCount]={0,1,2,3}; // List of joystick channels |
jwolter | 0:32b684f1caad | 70 | |
jwolter | 0:32b684f1caad | 71 | //Raise_Message is just a function that helped me in development process. You can ignore it all togather. |
jwolter | 0:32b684f1caad | 72 | void Raise_Message (unsigned char Err_Code, int info) { |
jwolter | 0:32b684f1caad | 73 | switch (Err_Code) { |
jwolter | 0:32b684f1caad | 74 | case 0: |
jwolter | 0:32b684f1caad | 75 | MsgCount[0]++; |
jwolter | 0:32b684f1caad | 76 | // led1 = 1; |
jwolter | 0:32b684f1caad | 77 | // PC.printf ("%i\n",info); |
jwolter | 0:32b684f1caad | 78 | break; |
jwolter | 0:32b684f1caad | 79 | case 1: |
jwolter | 0:32b684f1caad | 80 | MsgCount[1]++; |
jwolter | 0:32b684f1caad | 81 | // led2 = 1; |
jwolter | 0:32b684f1caad | 82 | // PC.printf ("Broke@ %i\n",info); |
jwolter | 0:32b684f1caad | 83 | break; |
jwolter | 0:32b684f1caad | 84 | case 2: |
jwolter | 0:32b684f1caad | 85 | MsgCount[2]++; |
jwolter | 0:32b684f1caad | 86 | // led3 = 1; |
jwolter | 0:32b684f1caad | 87 | // PC.printf ("Set ok\n"); |
jwolter | 0:32b684f1caad | 88 | break; |
jwolter | 0:32b684f1caad | 89 | case 3: |
jwolter | 0:32b684f1caad | 90 | MsgCount[3]++; |
jwolter | 0:32b684f1caad | 91 | // led4 = 1; |
jwolter | 0:32b684f1caad | 92 | // PC.printf ("%i\n",info); |
jwolter | 0:32b684f1caad | 93 | break; |
jwolter | 0:32b684f1caad | 94 | case 255: |
jwolter | 0:32b684f1caad | 95 | MsgCount[4]++; |
jwolter | 0:32b684f1caad | 96 | // PC.printf("Initalized sucessfully \n"); |
jwolter | 0:32b684f1caad | 97 | break; |
jwolter | 0:32b684f1caad | 98 | default: |
jwolter | 0:32b684f1caad | 99 | MsgCount[5]++; |
jwolter | 0:32b684f1caad | 100 | // PC.printf("I shouldn't be here \n"); |
jwolter | 0:32b684f1caad | 101 | } |
jwolter | 0:32b684f1caad | 102 | |
jwolter | 0:32b684f1caad | 103 | } |
jwolter | 0:32b684f1caad | 104 | |
jwolter | 0:32b684f1caad | 105 | //Here were all the work decoding the PPM signal takes place |
jwolter | 0:32b684f1caad | 106 | void SignalRise() { |
jwolter | 0:32b684f1caad | 107 | led4=1-led4; |
jwolter | 0:32b684f1caad | 108 | TimeElapsed = timer.read_us(); // Since we are measuring from signal rise to signal rise, note that TimeElapsed includes the fixed separator time as well |
jwolter | 0:32b684f1caad | 109 | if (TimeElapsed < ShortTime) { |
jwolter | 0:32b684f1caad | 110 | Raise_Message(0,TimeElapsed); |
jwolter | 0:32b684f1caad | 111 | return; //Channel timing too short; ignore, it's a glitch. Don't move to the next channel |
jwolter | 0:32b684f1caad | 112 | } |
jwolter | 0:32b684f1caad | 113 | __disable_irq(); |
jwolter | 0:32b684f1caad | 114 | timer.reset(); |
jwolter | 0:32b684f1caad | 115 | if ((TimeElapsed > MinSync ) && (CurChannel != 0)) { //somehow before reaching the end of PPM frame you read "New" frame signal??? |
jwolter | 0:32b684f1caad | 116 | //Ok, it happens. Just ignore this frame and start a new one |
jwolter | 0:32b684f1caad | 117 | Raise_Message (1,CurChannel); //incomplete channels set |
jwolter | 0:32b684f1caad | 118 | CurChannel=0; |
jwolter | 0:32b684f1caad | 119 | } |
jwolter | 0:32b684f1caad | 120 | if ((TimeElapsed > MinSync ) && (CurChannel == 0)) { |
jwolter | 0:32b684f1caad | 121 | Raise_Message (2,CurChannel); //New frame started |
jwolter | 0:32b684f1caad | 122 | __enable_irq(); // This is good. You've received "New" frame signal as expected |
jwolter | 0:32b684f1caad | 123 | return; |
jwolter | 0:32b684f1caad | 124 | } |
jwolter | 0:32b684f1caad | 125 | |
jwolter | 0:32b684f1caad | 126 | // Process current channel. This is a correct channel in a correct frame so far |
jwolter | 0:32b684f1caad | 127 | //Channels[CurChannel]= (TimeElapsed-1000)*255/1000; // Normalize reading (Min: 900us Max: 1900 us). This is my readings, yours can be different |
jwolter | 0:32b684f1caad | 128 | Channels[CurChannel]= C0s[CurChannel] + C1s[CurChannel]*TimeElapsed; // Normalize reading (Min: 900us Max: 1900 us). This is my readings, yours can be different |
jwolter | 0:32b684f1caad | 129 | Times[CurChannel] = TimeElapsed; |
jwolter | 0:32b684f1caad | 130 | CurChannel++; |
jwolter | 0:32b684f1caad | 131 | |
jwolter | 0:32b684f1caad | 132 | if (CurChannel==NChannels ) { // great!, you've a complete correct frame. Set CanUpdate and start a new frame |
jwolter | 0:32b684f1caad | 133 | FrameCount++; |
jwolter | 0:32b684f1caad | 134 | CurChannel=0; |
jwolter | 0:32b684f1caad | 135 | Raise_Message(3,0); // Successful frame! |
jwolter | 0:32b684f1caad | 136 | CanUpdate= true; |
jwolter | 0:32b684f1caad | 137 | led1=1-led1; // blink the LED |
jwolter | 0:32b684f1caad | 138 | } |
jwolter | 0:32b684f1caad | 139 | __enable_irq(); |
jwolter | 0:32b684f1caad | 140 | } |
jwolter | 0:32b684f1caad | 141 | |
jwolter | 0:32b684f1caad | 142 | // Interrupt for calibration button press |
jwolter | 0:32b684f1caad | 143 | void CalSwitchRise() { |
jwolter | 0:32b684f1caad | 144 | TimeElapsed2 = timer2.read(); // Since we are measuring from signal rise to signal rise, note that TimeElapsed includes the fixed separator time as well |
jwolter | 0:32b684f1caad | 145 | if (TimeElapsed2 > 0.3) { |
jwolter | 0:32b684f1caad | 146 | __disable_irq(); |
jwolter | 0:32b684f1caad | 147 | CalMode=true; |
jwolter | 0:32b684f1caad | 148 | timer2.reset(); |
jwolter | 0:32b684f1caad | 149 | __enable_irq(); |
jwolter | 0:32b684f1caad | 150 | } |
jwolter | 0:32b684f1caad | 151 | } |
jwolter | 0:32b684f1caad | 152 | |
jwolter | 0:32b684f1caad | 153 | void Initalize () { |
jwolter | 0:32b684f1caad | 154 | __disable_irq(); |
jwolter | 0:32b684f1caad | 155 | PC.baud(9600); // set baud rate |
jwolter | 0:32b684f1caad | 156 | if (debug) {PC.printf("\n\nInitializing...\n");} |
jwolter | 0:32b684f1caad | 157 | PPMsignal.mode (PullUp); |
jwolter | 0:32b684f1caad | 158 | PPMsignal.rise(&SignalRise); //Attach SignalRise routine to handle PPMsignal rise |
jwolter | 0:32b684f1caad | 159 | CalSwitch.rise(&CalSwitchRise); //Attach CalSwitchRise routine to handle CalSwitch rise |
jwolter | 0:32b684f1caad | 160 | timer.start(); |
jwolter | 0:32b684f1caad | 161 | timer2.start(); |
jwolter | 0:32b684f1caad | 162 | Raise_Message(255,0); // return successful |
jwolter | 0:32b684f1caad | 163 | led1 = 0; |
jwolter | 0:32b684f1caad | 164 | led2 = 0; |
jwolter | 0:32b684f1caad | 165 | led3 = 0; |
jwolter | 0:32b684f1caad | 166 | led4 = 0; |
jwolter | 0:32b684f1caad | 167 | //Initialize all channels to produce "sane" outputs (depending on your definition of sanity ;-). |
jwolter | 0:32b684f1caad | 168 | C1s[0] = 1.0*(ChannelMax-ChannelMin)/(TimeMax-TimeMin); |
jwolter | 0:32b684f1caad | 169 | C0s[0] = 1.0*ChannelMin - TimeMin*C1s[0]; |
jwolter | 0:32b684f1caad | 170 | for (i=1; i<NChannels; i++) |
jwolter | 0:32b684f1caad | 171 | { |
jwolter | 0:32b684f1caad | 172 | C1s[i]=C1s[0]; |
jwolter | 0:32b684f1caad | 173 | C0s[i]=C0s[0]; |
jwolter | 0:32b684f1caad | 174 | } |
jwolter | 0:32b684f1caad | 175 | if (debug) { |
jwolter | 0:32b684f1caad | 176 | PC.printf("Coefficients read from file:\n"); |
jwolter | 0:32b684f1caad | 177 | for(i=0; i<NChannels; i++) |
jwolter | 0:32b684f1caad | 178 | { |
jwolter | 0:32b684f1caad | 179 | PC.printf("Channel %d - C0=%6f, C1=%6f\n",i,C0s[i],C1s[i]); |
jwolter | 0:32b684f1caad | 180 | } |
jwolter | 0:32b684f1caad | 181 | } |
jwolter | 0:32b684f1caad | 182 | // Initialize joystick channels using saved calibration information |
jwolter | 0:32b684f1caad | 183 | FILE *fpi = fopen("/local/coefs.txt", "r"); // Open coefficient file on the local file system for reading |
jwolter | 0:32b684f1caad | 184 | for(i=0; i<JCCount; i++) |
jwolter | 0:32b684f1caad | 185 | { |
jwolter | 0:32b684f1caad | 186 | fscanf(fpi, "%f%c%f%c",&C0s[JoystickChannels[i]],&dum1,&C1s[JoystickChannels[i]],&dum2); |
jwolter | 0:32b684f1caad | 187 | } |
jwolter | 0:32b684f1caad | 188 | fclose(fpi); |
jwolter | 0:32b684f1caad | 189 | if (debug) { |
jwolter | 0:32b684f1caad | 190 | PC.printf("Coefficients read from file:\n"); |
jwolter | 0:32b684f1caad | 191 | for(i=0; i<JCCount; i++) |
jwolter | 0:32b684f1caad | 192 | { |
jwolter | 0:32b684f1caad | 193 | PC.printf("Channel %d - C0=%6f, C1=%6f\n",JoystickChannels[i],C0s[JoystickChannels[i]],C1s[JoystickChannels[i]]); |
jwolter | 0:32b684f1caad | 194 | } |
jwolter | 0:32b684f1caad | 195 | PC.printf("...Initialization complete.\n"); |
jwolter | 0:32b684f1caad | 196 | } |
jwolter | 0:32b684f1caad | 197 | timer.reset(); |
jwolter | 0:32b684f1caad | 198 | timer2.reset(); |
jwolter | 0:32b684f1caad | 199 | __enable_irq(); |
jwolter | 0:32b684f1caad | 200 | } |
jwolter | 0:32b684f1caad | 201 | |
jwolter | 0:32b684f1caad | 202 | int main() { |
jwolter | 0:32b684f1caad | 203 | //unsigned int *pOut = Times; |
jwolter | 0:32b684f1caad | 204 | int *pOut = Channels; |
jwolter | 0:32b684f1caad | 205 | |
jwolter | 0:32b684f1caad | 206 | Initalize(); |
jwolter | 0:32b684f1caad | 207 | //wait(5); |
jwolter | 0:32b684f1caad | 208 | |
jwolter | 0:32b684f1caad | 209 | |
jwolter | 0:32b684f1caad | 210 | while (1) { |
jwolter | 0:32b684f1caad | 211 | if (CanUpdate) { // We have a new frame to read |
jwolter | 0:32b684f1caad | 212 | __disable_irq(); |
jwolter | 0:32b684f1caad | 213 | CanUpdate=false; |
jwolter | 0:32b684f1caad | 214 | |
jwolter | 0:32b684f1caad | 215 | //Here is where the response to the PPM inputs should go. |
jwolter | 0:32b684f1caad | 216 | |
jwolter | 0:32b684f1caad | 217 | if (VerboseDebug) {PC.printf("%6d, %6d, %6d, %6d, %6d, %6d\n",pOut[0],pOut[1],pOut[2],pOut[3],pOut[4],pOut[5]); } |
jwolter | 0:32b684f1caad | 218 | //if (VerboseDebug) {PC.printf("%6f, %6f, %6f, %6f, %6f, %6f\n",pOut[0],pOut[1],pOut[2],pOut[3],pOut[4],pOut[5]); } |
jwolter | 0:32b684f1caad | 219 | __enable_irq(); |
jwolter | 0:32b684f1caad | 220 | |
jwolter | 0:32b684f1caad | 221 | } |
jwolter | 0:32b684f1caad | 222 | if (CalMode) { // Calibration mode triggered |
jwolter | 0:32b684f1caad | 223 | __disable_irq(); |
jwolter | 0:32b684f1caad | 224 | wait(1.); // Debounce |
jwolter | 0:32b684f1caad | 225 | led2 = 1; |
jwolter | 0:32b684f1caad | 226 | for(i=0; i<JCCount; i++) |
jwolter | 0:32b684f1caad | 227 | { |
jwolter | 0:32b684f1caad | 228 | PC.printf("Push joystick channel %2d to the LOW position and press the calibrate button\n",JoystickChannels[i]+1); |
jwolter | 0:32b684f1caad | 229 | __enable_irq(); |
jwolter | 0:32b684f1caad | 230 | CalMode=false; |
jwolter | 0:32b684f1caad | 231 | while (!CalMode) {wait(0.1);} // Wait until the calibrate button is pressed. |
jwolter | 0:32b684f1caad | 232 | CanUpdate=false; |
jwolter | 0:32b684f1caad | 233 | while (!CanUpdate) {wait(0.02);} // Wait until a new PPM frame is acquired. |
jwolter | 0:32b684f1caad | 234 | __disable_irq(); |
jwolter | 0:32b684f1caad | 235 | TLow = Times[JoystickChannels[i]]; // Save the time value in the low position. |
jwolter | 0:32b684f1caad | 236 | wait(1.); // Debounce |
jwolter | 0:32b684f1caad | 237 | PC.printf("Push joystick channel %2d to the HIGH position and press the calibrate button\n",JoystickChannels[i]+1); |
jwolter | 0:32b684f1caad | 238 | __enable_irq(); |
jwolter | 0:32b684f1caad | 239 | CalMode=false; |
jwolter | 0:32b684f1caad | 240 | while (!CalMode) {wait(0.1);} // Wait until the calibrate button is pressed. |
jwolter | 0:32b684f1caad | 241 | CanUpdate=false; |
jwolter | 0:32b684f1caad | 242 | while (!CanUpdate) {wait(0.02);} // Wait until a new PPM frame is acquired. |
jwolter | 0:32b684f1caad | 243 | __disable_irq(); |
jwolter | 0:32b684f1caad | 244 | THigh = Times[JoystickChannels[i]]; // Save the time value in the low position. |
jwolter | 0:32b684f1caad | 245 | wait(1.); // Debounce |
jwolter | 0:32b684f1caad | 246 | C1s[i] = 1.0*(ChannelMax-ChannelMin)/(THigh-TLow); |
jwolter | 0:32b684f1caad | 247 | C0s[i] = 1.0*ChannelMin - TLow*C1s[i]; |
jwolter | 0:32b684f1caad | 248 | PC.printf("Coefficients: C0=%6f, C1=%6f\n",C0s[i],C1s[i]); |
jwolter | 0:32b684f1caad | 249 | } |
jwolter | 0:32b684f1caad | 250 | FILE *fpo = fopen("/local/coefs.txt", "w"); // Open coefficient file on the local file system for writing |
jwolter | 0:32b684f1caad | 251 | for(i=0; i<JCCount; i++) |
jwolter | 0:32b684f1caad | 252 | { |
jwolter | 0:32b684f1caad | 253 | fprintf(fpo, "%6f,%6f\n",C0s[JoystickChannels[i]],C1s[JoystickChannels[i]]); |
jwolter | 0:32b684f1caad | 254 | } |
jwolter | 0:32b684f1caad | 255 | fclose(fpo); |
jwolter | 0:32b684f1caad | 256 | PC.printf("Calibration completed.\n"); |
jwolter | 0:32b684f1caad | 257 | led2=0; |
jwolter | 0:32b684f1caad | 258 | CalMode=false; |
jwolter | 0:32b684f1caad | 259 | CanUpdate=false; |
jwolter | 0:32b684f1caad | 260 | __enable_irq(); |
jwolter | 0:32b684f1caad | 261 | } |
jwolter | 0:32b684f1caad | 262 | } |
jwolter | 0:32b684f1caad | 263 | } |
jwolter | 0:32b684f1caad | 264 |