Menu

PPM to Servo test

Features
bendh
2012-07-26
2018-12-17
  • bendh

    bendh - 2012-07-26

    Hallo,
    I want to test a PPM signal.
    Do you have an example PPM-in to 4 Servos out.
    Please excuse my english.

    thanks Bernd

     
    • dvdouden

      dvdouden - 2012-10-29

      First of all, sorry for the late reply!

      Here's a quick writeup of what you're looking for, I haven't tested it (haven't even compiled it) so I'm not sure it works, but it should give you a fairly good hint in the right direction.

      What you basically want is tie PPM-input directly to Servo Out

      #include <ServoOut.h>
      #include <Timer1.h>
      
      #define CHANNELS 4
      #define SERVOS 4
      
      uint16_t g_channels[CHANNELS];                // output buffer for PPMIn
      uint8_t  g_workIn[PPMIN_WORK_SIZE(CHANNELS)]; // we need to have a work buffer for the PPMIn class
      
      rc::PPMIn g_PPMIn(g_channels, g_workIn, CHANNELS);
      
      uint8_t  g_pinsOut[SERVOS] = {2, 3, 4, 5};      // Output pins
      uint16_t g_servos[SERVOS];                      // Input buffer for servoOut, microseconds
      uint8_t  g_workOut[SERVOOUT_WORK_SIZE(SERVOS)]; // we need to have a work buffer for the ServoOut class
      
      rc::ServoOut g_ServoOut(g_pinsOut, g_servos, g_workOut, SERVOS);
      
      void setup()
      {
          // Initialize timer1, this is required for all features that use Timer1
          // (PPMIn/PPMOut/ServoIn/ServoOut)
          rc::Timer1::init();
      
          // We use pin 8 as PPM input pin
          pinMode(8, INPUT);
      
          // We use pin change interrupts to detect changes in the signal
          // If you're unfamiliar with how this works, please look up some
          // article or tutorial on the subject.
      
          // only allow pin change interrupts for PB0 (digital pin 8)
          PCMSK0 = (1 << PCINT0);
      
          // enable pin change interrupt 0
          PCICR = (1 << PCIE0);
      
          // set up the output pins
          for (uint8_t i = 0;  i < SERVOS; ++i)
          {
              pinMode(g_pinsOut[i], OUTPUT);
      
              // put them low
              digitalWrite(g_pinsOut[i], LOW);
      
              // fill servo output buffer (input for ServoOut)
              g_servos[i] = 1000;
          }
      
          // start listening
          g_PPMIn.start();
          // start outputting
          g_ServoOut.start();
      }
      
      void loop()
      {
          // update incoming values
          g_PPMIn.update();
      
          if (g_PPMIn.isStable())
          {
              // do magic, incoming values available in g_channels in microseconds.
      
              // update the input buffer
              for (uint8_t i = 0;  i < SERVOS; ++i)
              {
                  // fill ServoOut input buffer directly from PPMIn
                  g_servos[i] = g_channels[i];
              }
      
              // tell ServoOut there are new values available in its input buffer
              g_ServoOut.update();
          }
      }
      
       
  • Anonymous

    Anonymous - 2014-09-14

    Hi!
    I stumbled upon your library a few hours ago, since I'm trying to send a PPM signal from a transmitter to a receiver and to feed 3 servos. I used the example you gave (on top of my comment to servo pinOut 4), but the outcome was a very fast non-stop throwing up-and-down of just one of the servos (i think). I tried to move it with the joystick from the receiver side, but it barely changed the outcome, it didn't stop with the fast throttling up-and-down.
    What do you think is the problem, how can i fix it ?
    PS:I'm just starting to use and learn about microcontrollers, PPM, PWM and C++, I know that's one of the problems :)

    Regards, George

     
    • dvdouden

      dvdouden - 2014-09-15

      Hi George,

      Try using one servo first, if that works fine then the servos are probably causing a lot of noise on the power lines. I've seen this happen using some cheap micro servos. The moment the one of the servos starts moving it will draw quite some current and this is interfering with the servo signals. You can fix this by adding a small electrolytic capacitor between the power and ground lines of the servos. Most receivers I've seen also have an electrolytic cap for this purpose.

       
  • Anonymous

    Anonymous - 2014-09-15

    Hi!
    I tried your advice, to use one servo only but it's not moving at all now.
    Maybe I didn't understand your lib since I don't know C++ (I'm a Java developer).
    What I'm trying to do is make a robotic hand controlled by joysticks and a microcontroller sending PPM transmit signal to a transmitter, and the transmitter to send the signal to another microcontroller. And the controller to decode the PPM and feed the servos with it.
    If You can spend the time to look through what I'm uploading on both sides, and maybe tell me what I'm doing wrong, this will be of great help for me !

    Regards, George

    Transmitter and Joystick side:

    //PPM OUT
    
    #include <PPMOut.h>
    #include <Timer1.h>
    
    #define CHANNELS 1
    
    uint8_t  g_pins[CHANNELS] = {0}; // Input pins for the Joystick pot  ????
    uint16_t g_input[CHANNELS];                   // Input buffer in microseconds
    uint8_t  g_work[PPMOUT_WORK_SIZE(CHANNELS)];  // we need to have a work buffer for the PPMOut class
    
    // PPMOut requires two buffers:
    //     Input buffer containing input samples in microseconds
    //     Work buffer of ((channels + 1) * 2) elements for internal calculations and frame buffering
    // This setup removes any limit on the number of channels you want, and makes sure the library doesn't use more
    // memory than it really needs, since the client code supplies the buffers.
    rc::PPMOut g_PPMOut(CHANNELS, g_input, g_work, CHANNELS);
    
    void setup()
    {
        // Initialize timer1, this is required for all features that use Timer1
        // (PPMIn/PPMOut/ServoIn/ServoOut)
        rc::Timer1::init();
    
        for (uint8_t i = 0;  i < CHANNELS; ++i)
        {
            // set up input pins
            pinMode(g_pins[i], INPUT);
    
            // fill input buffer, convert raw values to microseconds
            g_input[i] = map(analogRead(g_pins[i]), 0, 1024, 1000, 2000);
        }
    
        // initialize PPMOut with some settings
        g_PPMOut.setPulseLength(448);   // pulse length in microseconds
        g_PPMOut.setPauseLength(10448); // length of pause after last channel in microseconds
        // note: this is also called the end of frame, or start of frame, and is usually around 10ms
    
        // start PPMOut, use pin 9 (pins 9 and 10 are preferred)
        g_PPMOut.start(9);
    }
    
    void loop()
    {
        // update the input buffer
        for (uint8_t i = 0;  i < CHANNELS; ++i)
        {
            // fill input buffer, convert raw values to microseconds
            g_input[i] = map(analogRead(g_pins[i]), 0, 1024, 1000, 2000);
        }
    
        // tell PPMOut there are new values available in the input buffer
        g_PPMOut.update();
    }
    

    Receiver and Servos side:

    //PPM IN
    #include <PPMIn.h>
    #include <ServoOut.h>
    #include <Timer1.h>
    
    #define CHANNELS 1
    #define SERVOS 1
    
    uint16_t g_channels[CHANNELS];                // output buffer for PPMIn
    uint8_t  g_workIn[PPMIN_WORK_SIZE(CHANNELS)]; // we need to have a work buffer for the PPMIn class
    
    rc::PPMIn g_PPMIn(g_channels, g_workIn, CHANNELS);
    
    uint8_t  g_pinsOut[SERVOS] = {3};      // Output pins
    uint16_t g_servos[SERVOS];                      // Input buffer for servoOut, microseconds
    uint8_t  g_workOut[SERVOOUT_WORK_SIZE(SERVOS)]; // we need to have a work buffer for the ServoOut class
    
    rc::ServoOut g_ServoOut(g_pinsOut, g_servos, g_workOut, SERVOS);
    
    void setup()
    {
        // Initialize timer1, this is required for all features that use Timer1
        // (PPMIn/PPMOut/ServoIn/ServoOut)
        rc::Timer1::init();
    
        // We use pin 8 as PPM input pin
        pinMode(8, INPUT);
    
        // We use pin change interrupts to detect changes in the signal
        // If you're unfamiliar with how this works, please look up some
        // article or tutorial on the subject.
    
        // only allow pin change interrupts for PB0 (digital pin 8)
        PCMSK0 = (1 << PCINT0);
    
        // enable pin change interrupt 0
        PCICR = (1 << PCIE0);
    
        // set up the output pins
        for (uint8_t i = 0;  i < SERVOS; ++i)
        {
            pinMode(g_pinsOut[i], OUTPUT);
    
            // put them low
            digitalWrite(g_pinsOut[i], LOW);
    
            // fill servo output buffer (input for ServoOut)
            g_servos[i] = 1000;
        }
    
        // start listening
        g_PPMIn.start();
        // start outputting
        g_ServoOut.start();
    }
    
    void loop()
    {
        // update incoming values
        g_PPMIn.update();
    
        if (g_PPMIn.isStable())
        {
            // do magic, incoming values available in g_channels in microseconds.
    
            // update the input buffer
            for (uint8_t i = 0;  i < SERVOS; ++i)
            {
                // fill ServoOut input buffer directly from PPMIn
                g_servos[i] = g_channels[i];
            }
    
            // tell ServoOut there are new values available in its input buffer
            g_ServoOut.update();
        }
    }
    
     
    • dvdouden

      dvdouden - 2014-09-16

      Your code looks okay at first glance. How did you wire your hardware? What kind of transmitter and receiver are you using? Depending on the type of hardware you may have to set the number of channels to match the number of channels on the RX/TX. My Esky TX/RX combo for example always expects a 6 channel PPM signal, even if I only want to use just three channels.

      But anyway, if all you want to do at the receiving side is control a bunch of servo's and you already have an RC receiver, then you might as well just hook the servos directly to the RX, no need to add an extra microcontroller, unless you want to do all sorts of post processing :)

       
  • Anonymous

    Anonymous - 2014-10-21

    I too am looking to control some servos wirelessly. Here's my setup.

    Transmit side: Atmega 328p Arduino Pro Mini 5v. Uses the PPM_OUT to generate a 2 channel PPM signal that is transmitted via a 433mhz rf transmitter directly connected to the ppm output pin as designated by your suggested PPM_out example in your library.

    Receive side: Atmega 328p Arduino Pro Mini 5v. Taking the TX ppm input from a 433mhz receiver. Than it should generate appropriate servo signal, and push it out to the two servo's.

    When I use the above code I get no servo movement whatsoever. I can see my led blinking on the TX side, and seems that a PPM signal is being transmitted, and received. Some guidance here would be much appreciated.

    This is the first in a series of RC projects I am looking to do with increasing number of channels. But first wanted to start with something simple.

     
    • dvdouden

      dvdouden - 2014-10-21

      Hi,

      I suggest you test the two parts on the RX side (PPMIn and ServoOut) individually to narrow down the cause of the problem.
      Verify that you actually receive what you transmit, for example by lighting up the on board LED when a channel has a specific value, or when it's value is below or above a certain threshold.
      Verify that the servos can actually be controlled by hardcoding the values in g_servos to various values. You can "animate" them by incrementing the value in the loop function and making the code sleep for a dozen milliseconds.

      so something like this

      void loop()
      {
          // update the input buffer
          for (uint8_t i = 0;  i < SERVOS; ++i)
          {
              ++g_servos[i];
              if (g_servos[i] > 2000)
                  g_servos[i] = 1000;
          }
          // tell ServoOut there are new values available in its input buffer
          g_ServoOut.update();
          delay(16);
      }
      

      If that works properly, then at least the servo control works fine.

      As for the RX testing:

      void loop()
      {
          // update incoming values
          g_PPMIn.update();
      
          if (g_PPMIn.isStable())
          {
              // turn led on.
          }
          else
          {
              // turn led off.
          }
      }
      

      and

      void loop()
      {
          // update incoming values
          g_PPMIn.update();
      
          if (g_PPMIn.isStable())
          {
              if ( g_channels[0] > 1500 )
              {
                  // turn led on.
              }
              else
              {
                  // turn led off.
              }
          }
      }
      

      That should at least give you some idea which piece of code doesn't work as expected.

       
  • Anonymous

    Anonymous - 2014-11-12

    So I went through and did the suggested testing. And it fails both to write the hardcoded values from the first posted test you gave, and fails to light the led for a stable ppm signal (at least that's what I assume the test is for)

    I got rid of my wireless reciever, transmitter pair, and just used a cross over wire from the PPM signal being generated on the first pro mini 5v 328p to the second, meant to act as the receiver. When I did a simple serial monitor test on the second one to check what the incoming stream looked like, I got a bunch of ones and zeros whose pattern would change as I moved the joystick around on the transmitter pro mini. So I assume that means that I am in fact generating a PPM signal.

    I verified my servos work, that I have them properly wired, both according to the suggested pinout in your ServoOut sketch, and by running them through an independent live test with a previous virtual wire library based sketch.

    So it seems that I am either not generating the appropriate PPM signal. Which I am simply using the PPMOut example exactly how it is, and also modified for my 2 channel setup.

    So it seems more likely and issue on the PPMin/ServoOut side. I am using this as my code on the receiver side:

    ~~~~~~~~~~~~~

    include <PPMIn.h>

    include <ServoOut.h>

    include <Timer1.h>

    define CHANNELS 2

    define SERVOS 2

    uint16_t g_channels[CHANNELS]; // output buffer for PPMIn
    uint8_t g_workIn[PPMIN_WORK_SIZE(CHANNELS)]; // we need to have a work buffer for the PPMIn class

    rc::PPMIn g_PPMIn(g_channels, g_workIn, CHANNELS);

    uint8_t g_pinsOut[SERVOS] = {2, 3}; // Output pins
    uint16_t g_servos[SERVOS]; // Input buffer for servoOut, microseconds
    uint8_t g_workOut[SERVOOUT_WORK_SIZE(SERVOS)]; // we need to have a work buffer for the ServoOut class

    rc::ServoOut g_ServoOut(g_pinsOut, g_servos, g_workOut, SERVOS);

    void setup()
    {
    // Initialize timer1, this is required for all features that use Timer1
    // (PPMIn/PPMOut/ServoIn/ServoOut)
    rc::Timer1::init();

    // We use pin 8 as PPM input pin
    pinMode(8, INPUT);
    
    // We use pin change interrupts to detect changes in the signal
    // If you're unfamiliar with how this works, please look up some
    // article or tutorial on the subject.
    
    // only allow pin change interrupts for PB0 (digital pin 8)
    PCMSK0 = (1 << PCINT0);
    
    // enable pin change interrupt 0
    PCICR = (1 << PCIE0);
    
    // set up the output pins
    for (uint8_t i = 0;  i < SERVOS; ++i)
    {
        pinMode(g_pinsOut[i], OUTPUT);
    
        // put them low
        digitalWrite(g_pinsOut[i], LOW);
    
        // fill servo output buffer (input for ServoOut)
        g_servos[i] = 1000;
    }
    
    // start listening
    g_PPMIn.start();
    // start outputting
    g_ServoOut.start();
    

    }

    void loop()
    {
    // update incoming values
    g_PPMIn.update();

    if (g_PPMIn.isStable())
    {
        // do magic, incoming values available in g_channels in microseconds.
    
        // update the input buffer
        for (uint8_t i = 0;  i < SERVOS; ++i)
        {
            // fill ServoOut input buffer directly from PPMIn
            g_servos[i] = g_channels[i];
        }
    
        // tell ServoOut there are new values available in its input buffer
        g_ServoOut.update();
    }
    

    }

    I am using this on the Transmitter side to generate the PPM.
    

    / ---------------------------------------------------------------------------
    This software is in the public domain, furnished "as is", without technical
    support, and with no warranty, express or implied, as to its usefulness for
    any purpose.

    ppmout_example.pde
    Demonstrate Pulse Position Modulation Output functionality

    Author: Daniel van den Ouden
    Project: ArduinoRCLib
    Website: http://sourceforge.net/p/arduinorclib/
    -------------------------------------------------------------------------/

    include <PPMOut.h>

    include <Timer1.h>

    define CHANNELS 2

    uint8_t g_pins[CHANNELS] = {A0, A1}; // Input pins
    uint16_t g_input[CHANNELS]; // Input buffer in microseconds
    uint8_t g_work[PPMOUT_WORK_SIZE(CHANNELS)]; // we need to have a work buffer for the PPMOut class

    // PPMOut requires two buffers:
    // Input buffer containing input samples in microseconds
    // Work buffer of ((channels + 1) * 2) elements for internal calculations and frame buffering
    // This setup removes any limit on the number of channels you want, and makes sure the library doesn't use more
    // memory than it really needs, since the client code supplies the buffers.
    rc::PPMOut g_PPMOut(CHANNELS, g_input, g_work, CHANNELS);

    void setup()
    {
    // Initialize timer1, this is required for all features that use Timer1
    // (PPMIn/PPMOut/ServoIn/ServoOut)
    rc::Timer1::init();

    for (uint8_t i = 0;  i < CHANNELS; ++i)
    {
        // set up input pins
        pinMode(g_pins[i], INPUT);
    
        // fill input buffer, convert raw values to microseconds
        g_input[i] = map(analogRead(g_pins[i]), 0, 1024, 1000, 2000);
    }
    
    // initialize PPMOut with some settings
    g_PPMOut.setPulseLength(448);   // pulse length in microseconds
    g_PPMOut.setPauseLength(10448); // length of pause after last channel in microseconds
    // note: this is also called the end of frame, or start of frame, and is usually around 10ms
    
    // start PPMOut, use pin 9 (pins 9 and 10 are preferred)
    g_PPMOut.start(10);
    

    }

    void loop()
    {
    // update the input buffer
    for (uint8_t i = 0; i < CHANNELS; ++i)
    {
    // fill input buffer, convert raw values to microseconds
    g_input[i] = map(analogRead(g_pins[i]), 0, 1024, 1000, 2000);
    }

    // tell PPMOut there are new values available in the input buffer
    g_PPMOut.update();
    

    }
    ~~~~~~~~~~

    Any help you care to give is greatly appreciated.

    Thanks,
    Andrew

     
    • dvdouden

      dvdouden - 2014-11-12

      I'm missing the pin change interrupt handler in your receiver side code:

      // Interrupt handling code below, this needs cleaning
      
      static uint8_t lastB = 0; // last read value of PINB
      
      // Pin change port 0 interrupt
      ISR(PCINT0_vect)
      {    
          uint8_t newB = PINB;
      
          // we've hardcoded the bitmask here (1 << 0), the portable way to get it is
          // digitalPinToBitMask(pinnr), but you really don't want to call that in
          // the interrupt handler.
          // we test if the specified pin has changed (not really necessary, but
          // think about what would happen if you were monitoring multiple pins)
          if ((lastB & (1 << 0)) != (newB & (1 << 0)))
          {
              // and we tell PPMIn the pin has changed
              g_PPMIn.pinChanged(newB & (1 << 0));
          }
      
          lastB = newB;
      }
      

      Arduino pin 8 on the Atmega328 (uno/nano) corresponds with PB0 (port B, bit 0). Hence the pin B and (1 << 0) bits of code. This interrupt handler is called whenever pin 8 changes. Without it, the PPMIn code would never receive any notifications that the input pin has changed, and thus would never receive a signal.

      I'll see if I can test your code tonight. I just received a new Nano yesterday after I blew up my old one, so I should be able to test it :)

       
  • Anonymous

    Anonymous - 2014-11-12

    Thanks so much.

    Not missing, just my novice hardware programming/understanding. Of course now that you explain it, it's "oh yeah, totally makes sense." but i'm not sure I would have ever come to the conclusion on my own. Well not at least for a few more weeks of starring at it...

    I'll also muck around with it a bit more today and see what I come up with. It's become a matter of principal now to get this working, one of those projects you can't let go regardless the amount of time.

    Thanks

     
    • dvdouden

      dvdouden - 2014-11-12

      You're welcome :)

       
  • Anonymous

    Anonymous - 2014-11-12

    So my initial testing of adding the interrupt handler is that I still had no movement on the servos.

    I stumbled upon this piece of code in a forum somewhere that measures the different time values of an incoming PPM signal. So I ran some tests on the receiver pro mini (328p based) by uploading the code below to it, and recording the different results for the various joystick movements on the Transmitter/PPM generation side. Again using a simple crossover wire for the transmit method from one pro-mini to the next.

    The results from the serial monitor (I summarized them with the min max values, you can see the full results in the attached file.

    Joystick Center: min/max per channel:   592/659   839/922   frameLength: 4204
    Joystick Left:   min/max per channel:   439/922   443/923   frameLength: 3660
    Joystick Right:  min/max per channel:   595/1352  594/1352  frameLength: 4704
    Joystick Up:     min/max per channel:   390/919   390/919   frameLength: 3440
    Joystick Down:   min/max per channel:   776/1352  775/1352  frameLength: 4868
    

    The PPM timing test code:

    // Arduino Code to get PPM timings - v1.2  //
    /////////////////////////////////////////////
    // written by Dave B.         [2010.09.09] //
    /////////////////////////////////////////////
    // comments, bugs, ideas to davedave1@gmx.net
    // first 6 lines should be adjusted to individual setup
    #define channelnumber 2     // Number of channels
    #define framecount 100      // How many frames to capture?   tested with 100 frames for 4 channels on atmega328
    #define minsync 300        // Minimum syncpulselength     values above 3000 should work for normal PPM
    int inputpin = 8;           // PPM input pin
    int inverted = 0;           // use "inverted = 1"  for inverted signals
    long serialspeed = 115200;  // tested with 115200 Baud, needs to match driver settings and serial monitor setting
    
    long minchannel[channelnumber];
    long maxchannel[channelnumber];
    
    long synclength[framecount];                       // syncvalues
    int channel[channelnumber*framecount];             // Channel values
    long framelength[framecount];                      // frame start times
    int counter = 0;                                   // counter for measuring timings
    int outcounter = 0;                                // counter for output measured timings
    int synced = 0;                                    // used boolean after found sync, start get all frames from pattern
    
    void setup()
    {
        Serial.begin(serialspeed);                       // open serial connection
        pinMode(inputpin, INPUT);                        // set input pin for input
        synced = 0;
    
        for (int channelcounter = 0; channelcounter < channelnumber; channelcounter++){
            minchannel[channelcounter] = 50000;
            maxchannel[channelcounter] = 0;
        }
    
    }
    
    void loop()
    {
        synclength[counter] = 0;                                                                         //
        if (synced == 0){                                                                                // if not already in sync
            if ( inverted == 0 ){                                                                          // if inverted signal selected is false
                synclength[counter] = pulseIn(inputpin, HIGH);                                               // wait for next HIGH pulse
            }
            else{                                                                                          // else inverted is selected
                synclength[counter] = pulseIn(inputpin, LOW);                                                // wait for next LOW pulse
            }
        }
        if ((synclength[counter] > minsync)or(synced == 1)){                                              // if syncpulse found or already synced
            synced = 1;                                                                                     // set synced as "true"
            framelength[counter] = micros();                                                                // get start time
            synclength[counter] = 0;                                                                        // reset synclength which was used to find syncpulse ( [0] should work )
            if ( inverted == 0 ){                                                                           // if inverted signal selected
                for (int channelcounter = 0; channelcounter < channelnumber; channelcounter++){               // loop for measuring all channels
                    channel[(counter * channelnumber) + channelcounter]=pulseIn(inputpin, HIGH);
                }
                synclength[counter]  = pulseIn(inputpin, HIGH);                                               // get syncpulse length
            }
            else{                                                                                           // else inverted is selected
                for (int channelcounter = 0; channelcounter < channelnumber; channelcounter++){               // loop for measuring all channels
                    channel[(counter * channelnumber) + channelcounter]=pulseIn(inputpin, LOW);
                }
                synclength[counter]  = pulseIn(inputpin, LOW);                                                 // get syncpulse length
            }
            framelength[counter] = micros() - framelength[counter] ;                                         // keep difference between start- and endtime
            if (counter >= (framecount-1)){                                                                    // if captured enough frames
                synced = 0;                                                                                      // set synced to "false"
                outcounter = 0;                                                                                  // start output with second (third) frame, the first (first to) get measured bad
                while (outcounter < framecount){                                                                 // loop for all captured frames
                    Serial.print("FrameNumber: ");                                                                 // output measured timings
                    Serial.print(outcounter+1);
                    Serial.print(" ");
                    Serial.print("    ");
                    for (int channelcounter = 0; channelcounter < channelnumber; channelcounter++){                // output all channels and pulses
                        Serial.print(channel[(outcounter * channelnumber) + channelcounter]);
                        Serial.print("   ");
                        if (minchannel[channelcounter] > channel[(outcounter * channelnumber) + channelcounter]){
                            minchannel[channelcounter] = channel[(outcounter * channelnumber) + channelcounter];
                        }
                        if (maxchannel[channelcounter] < channel[(outcounter * channelnumber) + channelcounter]){
                            maxchannel[channelcounter] = channel[(outcounter * channelnumber) + channelcounter];
                        }
                    }
                    Serial.print(synclength[outcounter]);                                                          // output syncpulse length
                    Serial.print("    ");
                    Serial.print("frameLength: ");
                    Serial.print(framelength[outcounter]);
                    Serial.println("");                                                                            // output NewLine()
                    outcounter++;                                                                                  // increase counter for output frames
                }
                Serial.print("min/max per channel:   ");
                for (int channelcounter = 0; channelcounter < channelnumber; channelcounter++){
                    Serial.print(minchannel[channelcounter]);
                    Serial.print("/");
                    Serial.print(maxchannel[channelcounter]);
                    Serial.print("   ");
                }
                Serial.println("");                                                                              // output NewLine()
                Serial.println("");                                                                              // output NewLine()
                counter = 0;                                                                                     // reset counter for measuring the next sequence of frames
            }
            else{                                                                                             // else (not enough frames captured)
                counter++;                                                                                       // increase counter for measuring the next frame
            }
        } 
    }
    
     
    • dvdouden

      dvdouden - 2014-11-12

      pulseIn isn't incredibly accurate. It relies on the number of clockcycles between pin changes, any interrupt that occurs while monitoring for pin changes will throw the result off by at least a microsecond, if not more. The amount of interrupts will be fairly constant though, so the results will be fairly consistent, but not extremely accurate. Any jitter you see in the pulse lengths on the actual channels may also be due to the analogIn on the transmitter side; those readings are hardly ever stable.

      The difference in frame length for left/right/up/down movement is because RC PPM signals do not have a fixed frame width. Longer pulses on the channels will increase the total frame width, shorter pulses will decrease it. Some "real" transmitters work like this, others don't (and use a fixed frame width/variable start of frame pulse).

      But the frame length is way to small. If the transmitter code you posted earlier is what you used to get the results in the attachment then the frame length should be way larger. It should be somewhere between 12000 and 15000. The synclength (3rd number) also should be consistently around 10K. The other two numbers should be between 500 and 1500.

      #define minsync 300
      

      That should've been at least 3000. With a length of 300 microseconds the code will not be able to differentiate between channels data and the start/end frame marker.

      See https://sourceforge.net/p/arduinorclib/wiki/PPM%20Signal/
      Check out this video for PPM in action: https://www.youtube.com/watch?v=-ufBy22aJQA

       
      • dvdouden

        dvdouden - 2014-11-12

        I just loaded the PPMOut example to an Arduino Mega (slightly modified to make it work on that board) with 4 channels and a constant 1500 uS value per channel. I connected that to and Arduino Nano with a slightly modified PPMIn example to output the incoming values on serial port:

        1505 1502 1502 1503 
        1507 1500 1503 1503 
        1507 1500 1503 1503 
        1503 1503 1503 1503 
        1503 1503 1503 1503 
        1503 1503 1503 1504 
        1503 1503 1503 1503 
        1507 1500 1502 1503 
        1503 1503 1503 1503 
        1503 1503 1503 1503 
        1503 1503 1503 1503 
        1503 1503 1503 1503
        

        That looks fairly solid.

        I then ran the PPM testing code you posted (with minsync = 3000, channelnumber = 4) and the result were weird. Here's an excerpt:

        FrameNumber: 57     1048   1048   1048   1048   6457    frameLength: 12944
        FrameNumber: 58     1048   1048   1048   1048   6008    frameLength: 12492
        FrameNumber: 59     1048   1047   1048   1048   5559    frameLength: 12044
        FrameNumber: 60     1048   1048   1048   1048   5117    frameLength: 11596
        FrameNumber: 61     1048   1048   1048   1048   4673    frameLength: 11144
        FrameNumber: 62     1048   1047   1048   1048   4219    frameLength: 10700
        FrameNumber: 63     1048   1048   1048   1048   3775    frameLength: 10248
        FrameNumber: 64     1048   1041   1048   1048   3332    frameLength: 9796
        FrameNumber: 65     1048   1047   1047   1048   2884    frameLength: 9352
        FrameNumber: 66     1048   1048   1048   1048   2440    frameLength: 8900
        FrameNumber: 67     1048   1048   1048   1048   1992    frameLength: 8452
        FrameNumber: 68     1048   1048   1048   1048   1548    frameLength: 8000
        

        As you can see, the channel values are pretty solid (you need to add 448, the pause width, to get the actual value, about 1496). But you can see the frame length decreasing. I can also see this happening on the transmitting arduino since I'm outputting the PPM signal on pin 13 (LED) and I can see the LED dimming slightly in about two seconds and then go full brightness again.
        I also see this happening when I run it on the Arduino Nano if I run the PPMOut code on there. So I guess that's definitely a bug :(

        and oh crap

        https://sourceforge.net/p/arduinorclib/tickets/57/

        That's it

        In PPMOut.h, replace the define of PPM_OUT_WORK_SIZE with this:

        define PPMOUT_WORK_SIZE(channels) (((channels) + ((channels) + 1) * 2) * sizeof(uint16_t))
        

        In PPMOut.cpp, add a comma to the first line of this snippet and add the following two lines after it:

        m_timings(const_cast<uint16_t*>(m_channelTimings) + p_maxChannels),
        m_mask(0),
        m_port(0)
        

        Now it should be stable...

        FrameNumber: 81     1036   1043   1043   1044   9915    frameLength: 16392
        FrameNumber: 82     1044   1043   1044   1043   9915    frameLength: 16392
        FrameNumber: 83     1043   1043   1040   1043   9915    frameLength: 16392
        FrameNumber: 84     1043   1043   1041   1043   9915    frameLength: 16392
        FrameNumber: 85     1044   1043   1043   1043   9916    frameLength: 16392
        FrameNumber: 86     1043   1043   1044   1043   9915    frameLength: 16392
        FrameNumber: 87     1043   1044   1043   1043   9922    frameLength: 16392
        FrameNumber: 88     1043   1043   1043   1043   9922    frameLength: 16392
        FrameNumber: 89     1044   1043   1043   1043   9922    frameLength: 16392
        FrameNumber: 90     1043   1043   1043   1043   9922    frameLength: 16392
        FrameNumber: 91     1043   1044   1044   1043   9922    frameLength: 16392
        FrameNumber: 92     1044   1043   1043   1043   9922    frameLength: 16392
        FrameNumber: 93     1043   1043   1043   1044   9922    frameLength: 16392
        FrameNumber: 94     1043   1044   1043   1043   9922    frameLength: 16392
        FrameNumber: 95     1043   1043   1044   1043   9922    frameLength: 16392
        FrameNumber: 96     1043   1044   1043   1043   9922    frameLength: 16400
        FrameNumber: 97     1043   1043   1044   1043   9916    frameLength: 16388
        FrameNumber: 98     1043   1043   1044   1043   9916    frameLength: 16392
        FrameNumber: 99     1043   1044   1043   1043   9915    frameLength: 16392
        FrameNumber: 100     1043   1037   1044   1043   9915    frameLength: 16392
        

        I should've patched this a long time ago...

         
        • Anonymous

          Anonymous - 2014-11-13

          So I made the suggested changes to the library files, and got a stable stream that allowed me to control my servo on pin 2 of the Atmega 328. However for whatever reason the other channel is either not getting through, or some other conflict is occurring, because I get no joy trying to control the servo on pin 3. I can verify that both servos are properly powered with an external supply, have filter caps, etc. When I switch the signal wires on the servos I can verify again that any servo hooked to pin 2 will work just fine. But 0 control whatsoever of anything on pin 3.

          I even Switched the servoOut pins to 4 and 5 and then I just got no control at all.

          Seems like a port conflict I am guessing. I'll try and see if I get more luck from other pins. But when I looked at the port pinout I saw no reason why 3, 4, and 5 should not work.

          In either case it was wonderful to see some movement. Even tested via my 433 TX , RX unit and again the signal for pin 2 at least worked like a charm.

          I'll keep you posted if I discover anything else interesting.

           
        • Anonymous

          Anonymous - 2014-11-13

          So I have discovered that for whatever reason there is a port conflict with using d3, d4, or d5... When i switched the servoOut pin to D10, I had full reliable control of both servos. So beautiful to see the wonders of PPM control that allows for beautifully smooth control methods compared to the ultra clunky virtual wire library...

          Soon I ought to have a full pan tilt emote control security camera in place. Yay! Thanks for all the help! Love this library!

           
          • dvdouden

            dvdouden - 2014-11-13

            Awesome, good to hear that it's working now and glad to be of help!

            It's weird that pins 2-3 weren't working. That used to work fine: https://www.youtube.com/watch?v=wCi2PpY_LIs

            Pins 0-7 are all on hardware port D, pins 8-13 are on port B, maybe there's some issues in the ServoOut code after all...

             
            • Anonymous

              Anonymous - 2014-11-13

              D2 worked fine, just not D3, D$, or D5. But yeah, you get the idea. Perhaps it's an Atmega 328p thing.

               

Anonymous
Anonymous

Add attachments
Cancel





Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.