Pulse Position Library Questions.

Status
Not open for further replies.

dereckbc

Well-known member
OK a little background. I am trying to make a 6-channel RC PPM Encoder by using a donor Joy Stick with the guts ripped out and just use the 6 Analog Sliders in the Joystick for data. I have several code versions that will work on the Arduino Nano, Uno and Mega, platform. OK fast forward to now. A few members here have convinced me to look into using a Teensy 3.2 with the Pulse Position Libray. Basically saying the Teensy platform was easier to use. I do not see that yet.

So I look at the Pulse Position Libray on Get Hub. All I see is Greek. With the Arduino platform I understand every line of code and what it does. The Pulse Position Library I do not see much that I understand. I do not see anyplace that sets up the analog channels, sets up the output PPM pin, no read or write from analog, notta nothing

Can someone help a Newb out and show me where to get started. and fill in some blanks. I am aware of this on PJRC

THX

Dereck
 
Last edited:
Is this the same project?

https://forum.pjrc.com/threads/38611-ISR-Timer-Question

Look, I want to help you. But we do have a general expectation here to post (or link to) complete code when you talk about it.


Regarding your question, the PulsePosition library only does PPM. It doesn't do anything with analog signals. If you want to convert analog to PPM, *you* add the code which gets the analog signals (usually with analogRead) and then you call the PulsePosition library functions to cause the PPM output. PulsePosition is one piece of the project. You combine it together with other stuff to achieve your goals. Does that make any sense?
 

Yes Sir


Look, I want to help you. But we do have a general expectation here to post (or link to) complete code when you talk about it.

Thus why I did not paste the text above. Corrected below.


Regarding your question, the PulsePosition library only does PPM. It doesn't do anything with analog signals. If you want to convert analog to PPM, *you* add the code which gets the analog signals (usually with analogRead) and then you call the PulsePosition library functions to cause the PPM output. PulsePosition is one piece of the project. You combine it together with other stuff to achieve your goals. Does that make any sense?
Yes I got the big picture of what you said. I need to write a Sketch and integrate the cpp and h files. I think I can setup at least pin assignments like the Analog channel inputs, which pin is the Digital output. But I have no clue how to marry it all together. Anyway here is some Arduino code I know that works on a Nano. Loaded and tested on scope.

Couple of things lured me into at least looking into using the Teensy like easier to work with and more precise. Well easier part, not to me, precise I do not know. I am hoping jitter will be improved.

Code:
// PPM Encoder Joystick to Futaba Trainer Port
// For use with Arduino Nano V3.0
// Ian Johnston 29/04/2010
// Version 1.1

int AI_Pin_AEL = 6;    // Ana In - Aeleron potentiometer (Ana In Ch.0 playing up?)
int AI_Pin_ELE = 1;    // Ana In - Elevator potentiometer
int AI_Pin_THR = 2;    // Ana In - Throttle potentiometer
int AI_Pin_RUD = 3;    // Ana In - Rudder potentiometer
int AI_Pin_TIpot = 4;    // Ana In - TI potentiometer
int AI_Raw_AEL;        // Ana In raw var - 0->1023
int AI_Raw_ELE;        // Ana In raw var - 0->1023
int AI_Raw_THR;        // Ana In raw var - 0->1023
int AI_Raw_RUD;        // Ana In raw var - 0->1023
int AI_Raw_TIpot;        // Ana In raw var - 0->1023
int AI_AEL;           // Ana In var - 0->1023 compensated
int AI_ELE;           // Ana In var - 0->1023 compensated
int AI_THR;           // Ana In var - 0->1023 compensated
int AI_RUD;           // Ana In var - 0->1023 compensated
int AI_TIpot;           // Ana In var - 0->1023 compensated
int Aeleron_uS = 700;     // Ana In Ch.0 uS var - Aeleron
int Elevator_uS = 700;    // Ana In Ch.1 uS var - Elevator
int Throttle_uS = 700;    // Ana In Ch.2 uS var - Throttle
int Rudder_uS = 700;      // Ana In Ch.3 uS var - Rudder
int TI_uS = 700;          // Ana In Ch.4 uS var - TI
int TIsw_uS = 700;        // Ana In Ch.4 uS var - TI Switch

int Fixed_uS = 100;       // PPM frame fixed LOW phase
int pulseMin = 700;          // pulse minimum width minus start in uS
int pulseMax = 2100;      // pulse maximum width in uS

float DualrateMultAel = 0.9; // Dual rate mult
float DualrateMultEle = 0.9; // Dual rate mult
float DualrateMultThr = 0.9; // Dual rate mult
float DualrateMultRud = 0.9; // Dual rate mult
float DualrateMultTI = 0.9; // Dual rate mult
int DualrateAdjAel = 0;   // Dual rate mid adjustment
int DualrateAdjEle = 0;      // Dual rate mid adjustment
int DualrateAdjThr = 0;      // Dual rate mid adjustment
int DualrateAdjRud = 0;      // Dual rate mid adjustment
int DualrateAdjTI = 0;      // Dual rate mid adjustment

int outPinPPM = 10;       // digital pin 10
int outPinTEST = 8;       // digital pin 8
int inPinD6 = 6;          // digital pin 6
int inPinD11 = 11;        // digital pin 11

ISR(TIMER1_COMPA_vect) {
    ppmoutput(); // Jump to ppmoutput subroutine
}

void setup() {

  // Serial.begin(9600) ; // Test

  pinMode(outPinPPM, OUTPUT);   // sets the digital pin as output
  pinMode(outPinTEST, OUTPUT);  // sets digital pin as output, added dc
  pinMode(inPinD6, INPUT);      // sets the digital pin as input
  digitalWrite(inPinD6, HIGH);  // turn on pull-up resistor
  pinMode(inPinD11, INPUT);     // sets the digital pin as input
  digitalWrite(inPinD11, HIGH); // turn on pull-up resistor

   // Setup timer
  TCCR1A = B00110001; // Compare register B used in mode '3'
  TCCR1B = B00010010; // WGM13 and CS11 set to 1
  TCCR1C = B00000000; // All set to 0
  TIMSK1 = B00000010; // Interrupt on compare B
  TIFR1  = B00000010; // Interrupt on compare B
  OCR1A = 22500; // 22.5mS PPM output refresh
  OCR1B = 1000;
  
}


void ppmoutput() { // PPM output sub

// test pulse and Channel 1 start- used to trigger scope. Modified so Test Pulse
// and Channel 1 Pulse start at same time
  digitalWrite(outPinPPM, LOW); //Starts Channel 1 Output Pulse
 digitalWrite(outPinTEST, LOW); // Starts Test Sync Pulse
 delayMicroseconds(100);    // Hold, Test Sync Pin for 100 us
 digitalWrite(outPinTEST, HIGH);

 // Channel 1 - Aeleron
 
  delayMicroseconds(Fixed_uS - 100);    // Hold Channel 1 minus 100 us used in Test Sync Pulse
  digitalWrite(outPinPPM, HIGH);
  delayMicroseconds(Aeleron_uS);  // Hold for Aeleron_uS microseconds         

 // Channel 2 - Elevator 
  digitalWrite(outPinPPM, LOW);
  delayMicroseconds(Fixed_uS);    // Hold
  digitalWrite(outPinPPM, HIGH);
  delayMicroseconds(Elevator_uS); // Hold for Elevator_uS microseconds      

 // Channel 3 - Throttle
  digitalWrite(outPinPPM, LOW);
  delayMicroseconds(Fixed_uS);    // Hold
  digitalWrite(outPinPPM, HIGH);
  delayMicroseconds(Throttle_uS); // Hold for Throttle_uS microseconds      

 // Channel 4 - Rudder
  digitalWrite(outPinPPM, LOW);
  delayMicroseconds(Fixed_uS);    // Hold
  digitalWrite(outPinPPM, HIGH);
  delayMicroseconds(Rudder_uS);   // Hold for Rudder_uS microseconds

 // Channel 5 - TI Switch
  digitalWrite(outPinPPM, LOW);
  delayMicroseconds(Fixed_uS);    // Hold
  digitalWrite(outPinPPM, HIGH);
  delayMicroseconds(TIsw_uS);     // Hold for TIsw_uS microseconds        

 // Channel 6 - TI pot
  digitalWrite(outPinPPM, LOW);
  delayMicroseconds(Fixed_uS);    // Hold
  digitalWrite(outPinPPM, HIGH);
  delayMicroseconds(TI_uS);       // Hold for TI_uS microseconds  

 // Synchro pulse
  digitalWrite(outPinPPM, LOW);
  delayMicroseconds(Fixed_uS);    // Hold
  digitalWrite(outPinPPM, HIGH);  // Start Synchro pulse

}


void loop() { // Main loop

 // Read analogue ports
   AI_Raw_AEL = analogRead(AI_Pin_AEL);
   AI_Raw_ELE = analogRead(AI_Pin_ELE);
   AI_Raw_THR = analogRead(AI_Pin_THR);
   AI_Raw_RUD = analogRead(AI_Pin_RUD);
   AI_Raw_TIpot = analogRead(AI_Pin_TIpot);

 // Compensate for discrepancy in pot inputs including centering offset.
 // Also use this to invert inputs if necessary (swap x1 & y1)
 // y=mx+c, x to y scales to x1 to y1
   AI_AEL = map(AI_Raw_AEL, 0, 1023, 1200, 0) - 100; // Invert Aeleron pot and slight centre offset
   AI_ELE = map(AI_Raw_ELE, 0, 1023, 1200, 0) - 120; // Invert Elevator pot and slight centre offset
   AI_THR = map(AI_Raw_THR, 0, 1023, 0, 1023) + 0;  // Throttle
   AI_RUD = map(AI_Raw_RUD, 0, 1023, 0, 1023) + 0;  // Rudder
   AI_TIpot = map(AI_Raw_TIpot, 0, 1023, 1023, 0) + 0;  // Thermal Intelligence pot (TI)
   
 // Map analogue inputs to PPM rates for each of the channels
   Aeleron_uS = (AI_AEL * DualrateMultAel) + pulseMin + DualrateAdjAel;
   Elevator_uS = (AI_ELE * DualrateMultEle) + pulseMin + DualrateAdjEle;
   Throttle_uS = (AI_THR * DualrateMultThr) + pulseMin + DualrateAdjThr;
   Rudder_uS = (AI_RUD * DualrateMultRud) + pulseMin + DualrateAdjRud;
   TI_uS = (AI_TIpot * DualrateMultTI) + pulseMin + DualrateAdjTI;

 // Check limits
  if (Aeleron_uS <= 700) Aeleron_uS = 700;     // Min
  if (Aeleron_uS >= 2100) Aeleron_uS = 2100;   // Max   
  if (Elevator_uS <= 700) Elevator_uS = 700;   // Min
  if (Elevator_uS >= 2100) Elevator_uS = 2100; // Max 
  if (Throttle_uS <= 700) Throttle_uS = 700;   // Min
  if (Throttle_uS >= 2100) Throttle_uS = 2100; // Max 
  if (Rudder_uS <= 700) Rudder_uS = 700;       // Min
  if (Rudder_uS >= 2100) Rudder_uS = 2100;     // Max   
  if (TI_uS <= 700) TI_uS = 700;               // Min
  if (TI_uS >= 2100) TI_uS = 2100;             // Max 
   
  if (digitalRead(inPinD6) == 0) { // Low rate
         DualrateMultAel = 0.5;
        DualrateMultEle = 0.5;
        DualrateMultThr = 0.9;
        DualrateMultRud = 0.7;
        DualrateMultTI = 0.9;
        DualrateAdjAel = 200;
        DualrateAdjEle = 200;
        DualrateAdjThr = 0;
        DualrateAdjRud = 100;
        DualrateAdjTI = 0;
  }
  
  if (digitalRead(inPinD6) == 1) { // Normal/high rate
        DualrateMultAel = 0.9;
        DualrateMultEle = 0.9;
        DualrateMultThr = 0.9;
        DualrateMultRud = 0.9;
        DualrateMultTI = 0.9;
        DualrateAdjAel = 0;
        DualrateAdjEle = 0;
        DualrateAdjThr = 0;
        DualrateAdjRud = 0;
        DualrateAdjTI = 0;
  }
  
  if (digitalRead(inPinD11) == 1) { // TI Switch    
        TIsw_uS = 2100;
  } else {
        TIsw_uS = 700;
  }

}
 
Last edited:
I recommend you try File > Examples > PulsePosition > LoopBack.

Please understand this is *not* a complete solution for your needs. It's a demo of how PulsePositon can help you. Hopefully you can see from the code that it transmits PPM data and also tries to receive. If you connect those 2 pins together, you should receive the same data it's sending. Give it a try.

When you see it's working, then try connecting that PPM signal to whatever you're trying to control. Again, remember this is just a demo. But you can use the demo to test whether PulsePositon is sending PPM data which your hardware can use. Perhaps edit the LoopBack example slightly to send more channels, or send different numbers. Can you see the effects with your hardware connected?

I know you probably wish to talk about how to achieve your entire project, but please do yourself a favor and use the LoopBack example. This is how you make projects, by using libraries or code samples to achieve the parts you need. First you get the individual parts working. Then you try to use them together. Especially if you're a beginner, it's important to test the pieces and learn how they work, *before* to put them together into a more complex system!
 
Personally I would first try to see if this works for you versus the IntervalTimer stuff of your new thread.

Why, because assuming the PulsePosition Output code works for you, you can probably simply the number of channels you need to output, and then when something changes, you simply tell that channel the new width and code will handle the issues associated with interrupts and dealing with volatile data... Also the timings will be much more accurate.

Or I would maybe keep it real simple, and in your main loop. A long time ago, a few of us, worked on a remote control, originally it was setup to be RC and developed used a BasicAtomPro in basic... DIY-Remote-with-updates.jpg
Picture is actually of later version.

The code was originally setup to read in all of the input devices and do the next output. Was too slow, so setup to only read maybe one or two analog devices per pass, Simple state value, which worked like a champ...
 
Kurt thank you for the encouragement. Main reason I considered using the Library was the accuracy and precision. I understand what you mean by only changing the value of a PPM pulse only when a new integer is generated by the ADC. Problem is I have not wrapped my head around what is going on with the PPM library and how to use it. The code with the Timers I understand, and have modified it to fit my needs.

I am an engineer so I know the math, just electrical, not code. So I have to ask and question the Accuracy and Precision for my application. I only need 1 micro-second accuracy as every thing my code will do is done in whole integers of milli-seconds. For a PPM RC Encoder the important interval is the FRAME TIMING of 50 Hz or .020 seconds. Does not matter it it is actually .02001 or .01998 as long as it is the same error every frame pulse so as not to create jitter. The actual pulses range from 1ms to 2 ms wide x 6 channels so they can only occupy a maximum of 12 ms of every 20 ms frame. So a pulse is a min 1 ms and max 2 ms divided up 1023 times. A count error of a few of 5 to 10 is not going to be a issue with pulse width. The critical time is the 20 ms timer. If it is accurate, I think that might be all that counts.

So correct me if I am wrong here. True it maybe the Pulse Position library is more accurate and precise, but for my application does not buy me anything. Something tells me the straight simple code I understand is within .1% accurate, and going to .001% does not gain much of anything.

Only thing I think it might improve is Latency in stick movements that are not perceivable with a delay no greater than .020 seconds to a human.
 
Problem is I have not wrapped my head around what is going on with the PPM library and how to use it.

The output part of PulsePosition does pretty much the same thing as that program's timer and ppmoutput() function. Instead of putting the data into fixed variables Aeleron_uS, you use the write functions PulsePosition provides. Imagine if you were going to turn that ppmoutput() code into a library for everyone in the world to use. Fixed names for variables isn't a very flexible way for people to have any number of outputs, is it?

That's what PulsePosition does. How it works internally involves complex hardware details. That's why it's a library, so you can just use it with the simple functions, without needing to look at it's very complex internal code.
 
Paul or anyone who knows, How do I make the pulseposition library output PPM on 2 pins? Do I just add a line like:

PPMOut.begin(Pin Number);

I have my doubts because in my program I have two lines

PPMOut.begin(10); // PPM output on Pin 10
PPMOut.begin(10, 8); // I have no idea what this really does

However that only puts out PPM on pin 10, and pin 8 acts like a Shift Register output. I used it for for a sync signal on a scope. I want two identical PPM outputs to drive two devices.
 
Paul or anyone who knows, How do I make the pulseposition library output PPM on 2 pins? Do I just add a line like:

PPMOut.begin(Pin Number);

I have my doubts because in my program I have two lines

PPMOut.begin(10); // PPM output on Pin 10
PPMOut.begin(10, 8); // I have no idea what this really does

However that only puts out PPM on pin 10, and pin 8 acts like a Shift Register output. I used it for for a sync signal on a scope. I want two identical PPM outputs to drive two devices.
I answered (Guessed) on your other thread!
 
Paul or anyone who knows, How do I make the pulseposition library output PPM on 2 pins? Do I just add a line like:
As Paul suggested look at File > Examples > PulsePosition > LoopBack
you'll need to define
PulsePositionOutput out1;
PulsePositionOutput out2;
and for each, out1.begin(9); out2.begin(10); etc.
only a subset of the pins support PPM out, see https://www.pjrc.com/teensy/td_libs_PulsePosition.html
(@Paul, does that web page need to be updated for T3.5/3.6?)
 
Last edited:
Thanks Manitou I figured it out after reading Kurt's reply. By dumb luck I made it pin 9 which just happens to be one of the pins that can be used. I knew it could be done, just was not sure how.

So thanks Kurt and Manitou. Now I am off to figure out how to add TRIM to the code so I can shift the map output from say 0, 8191, 1000, 2000 to something like 0,8191,1010, 2010 and changing Dead Band center from 1500 to 1510 to establish a new center point.
 
KISS principle

Hi, I have been having similar problems understanding PPM solutions, and eventually came up with my own, which has had some discussion on RCGroups. It works very well, and is very simple. This is the AVR (Arduino) code for it.

Code:
/*
 *  Use Timer 1 and fast PWM mode to generate a PPM output signal on pin 10 (OC1B)
 *  all above timings are doubled due to 0.5uS clock
 */

volatile unsigned int channelMap[9] = {3000, 3040, 3060, 3080, 3100, 3120, 3140, 3160, 17400}; 
volatile unsigned int ch = 0;
void setup() {
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;

  TIFR1  |= (1 << OCF1A);  // clear any pending interrupts;
  TCCR1A |= (1 << COM1B1); // Toggle on B compare
  TCCR1A |= (1 << WGM10) | (1 << WGM11);   // Fast PWM mode, TOP = OCR1A
  TCCR1B |= (1 << WGM12) | (1 << WGM13);   // Fast PWM mode, TOP = OCR1A
  TCCR1B |= (1 << CS11);   // 8 prescaler
  TIMSK1 |= (1 << TOIE1);  // enable overflow vector
  DDRB   |= (1 << DDB2); //turn on output pin 10 = OC1B
  OCR1B = 600; // for separator pulse of 300us - doubled for 8 prescaler
  interrupts(); // enable all interrupts
}

ISR(TIMER1_OVF_vect) { //Timer overflow ISR
  OCR1A = channelMap[ch];
  ch = ( ch++ > 8) ? 0 : ch;
}

void loop() {
  // put your main code here, to run repeatedly:
  // remember to momentarily disable interrupts when updating 16bit channel variables
  // or do it during the sync, ie when ch==8
  // Calculate channelMap[8] (sync) by subtracting channel values from 20ms
}

As you can see it couldn't get much simpler. All that is required in the loop() block is to set the pulse widths in an array (from analogReads, or however you wish to do it), and the hardware does the rest.

I have only just started delving into the joy that is Teensy, and getting into the spec sheet. So far it looks to me as though the processor has a similar set of registers to manage timers, and does similar sorts of things, but all with different names of course. Still working my way through all that, but it seems to me that the Teensy must be able to do something similar, and as simply as is possible with the Arduino. Yes it's specific to my needs and may not be universally applicable (I'm building an R/C transmitter encoder based on Teensy. It was going to be Arduino but I was seduced by two numbers, "32bit" and "150Mhz").

My issue is that some solutions I've seen for various requirements (not just PPM) seem to be way too complicated, and could be implemented by making more efficient use of the hardware. The spec sheet for the Teensy is over 1800 pages, so there must be some good stuff in there! I started programming in the late '70s, Cobol and IBM mainframes, and we had to be efficient in those days, resources like memory and disk space were scarce and expensive! Old habits die hard I guess...

;-) Ian
 
but it seems to me that the Teensy must be able to do something similar, and as simply as is possible with the Arduino.

The code inside PulsePosition actually does something pretty similar to this AVR code, at least on the transmit side. For reception, input capture is used (which AVR only has 1 capture pin per 16 bit timer, instead of 8 on Teensy 3.x).

But the code is more complex, as it could also be on AVR if the CPU weren't so slow. One of the main differences is the timer prescaler is *not* used, as the AVR code does to simplify all the math to only 16 bit numbers. Instead, the pulses are handled with 32 bit counts of the timer running at that full 48 or 60 MHz F_BUS speed. So you actually get 21 or 17 ns resolution on the pulse widths, instead of rounding to the nearest 500 ns.

I started programming in the late '70s, Cobol and IBM mainframes, and we had to be efficient in those days, resources like memory and disk space were scarce and expensive! Old habits die hard I guess...

There's still a tremendous need for efficient design, but the trade-offs have changed greatly since the 1970s, or even the early 2000s. These modern built in peripherals have amazing capabilities compared to older chips, but often it takes more complex code to fully utilize them and to do so in ways that have the least conflicts when paired with other libs.
 
Hi, I have been having similar problems understanding PPM solutions, and eventually came up with my own, which has had some discussion on RCGroups. It works very well, and is very simple. This is the AVR (Arduino) code for it.

Care to share a link to that thread?

Pretty sure you are the Ian I have been looking for about a year now with no luck. Pretty sure I took a lot of inspiration off you with this project I found. Is that yours? Sure would like to talk to you. I am not a code writer, I am an EE with a lot of Power, RF, and Telecom background, and I have a working model using the Teensy, and if you look you will see some of your code in mine above. What I would really like to do is add some of the other functions like Trims, Rates, and some Bling like you did with Project 19.

I pretty much copied your Project 18 and converted it to Teensy. I did not post the project RCG but did on Watt Flyer if you want to take a look. Could not of done it without the help from Paul and a few others here. Thx

4GJSwTE.jpg
 
Last edited:
Status
Not open for further replies.
Back
Top