skathi
Posts: 12
Joined: Thu Aug 02, 2018 9:08 am

PPM signal creating

Thu Aug 02, 2018 9:18 am

Hey, I'm new Raspberry Pi user, and I'm trying to write software PPM signal code(python), but I've got some problems. How can I separate channels one pack from another ? Is there any protocols for single channel duration? Does anybody have any PPM signal modulation code example?
P.S. Thank you in advance)
P.S.S Sorry for my bad English

drgeoff
Posts: 11820
Joined: Wed Jan 25, 2012 6:39 pm

Re: PPM signal creating

Thu Aug 02, 2018 1:47 pm

What is PPM? Peak Programme Meter?
Quis custodiet ipsos custodes?

scotty101
Posts: 4223
Joined: Fri Jun 08, 2012 6:03 pm

Re: PPM signal creating

Thu Aug 02, 2018 3:40 pm

I think the most likely meaning of PPM is Pulse Position Modulation.
Often used with Servos and RC control applications.

skathi, you are going to have to provide a bit more information. What are you trying to interface with? Are you sending or recieving a PPM signal?

From my time doing stuff with quadcopters, i remember some C code to separate the 8 channels of data from each other in the Ardupilot source code.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

skathi
Posts: 12
Joined: Thu Aug 02, 2018 9:08 am

Re: PPM signal creating

Fri Aug 31, 2018 12:22 pm

thank you very much for your reply, but I've already modulated it.
PPM is Pulse Position Modulation. It's for sending signal to my pixracer.

User avatar
joan
Posts: 15549
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: PPM signal creating

Fri Aug 31, 2018 1:30 pm

Here is a pigpio Python script to generate PPM.

Code: Select all

#!/usr/bin/env python

# PPM.py
# 2016-02-18
# Public Domain

import time
import pigpio

class X:

   GAP=100
   WAVES=3

   def __init__(self, pi, gpio, channels=8, frame_ms=27):
      self.pi = pi
      self.gpio = gpio

      if frame_ms < 5:
         frame_ms = 5
         channels = 2
      elif frame_ms > 100:
         frame_ms = 100

      self.frame_ms = frame_ms

      self._frame_us = int(frame_ms * 1000)
      self._frame_secs = frame_ms / 1000.0

      if channels < 1:
         channels = 1
      elif channels > (frame_ms // 2):
         channels = int(frame_ms // 2)

      self.channels = channels

      self._widths = [1000] * channels # set each channel to minimum pulse width

      self._wid = [None]*self.WAVES
      self._next_wid = 0

      pi.write(gpio, pigpio.LOW)

      self._update_time = time.time()

   def _update(self):
      wf =[]
      micros = 0
      for i in self._widths:
         wf.append(pigpio.pulse(0, 1<<self.gpio, self.GAP))
         wf.append(pigpio.pulse(1<<self.gpio, 0, i))
         micros += (i+self.GAP)
      # off for the remaining frame period
      wf.append(pigpio.pulse(0, 1<<self.gpio, self._frame_us-micros))

      self.pi.wave_add_generic(wf)
      wid = self.pi.wave_create()
      self.pi.wave_send_using_mode(wid, pigpio.WAVE_MODE_REPEAT_SYNC)
      self._wid[self._next_wid] = wid

      self._next_wid += 1
      if self._next_wid >= self.WAVES:
         self._next_wid = 0

      
      remaining = self._update_time + self._frame_secs - time.time()
      if remaining > 0:
         time.sleep(remaining)
      self._update_time = time.time()

      wid = self._wid[self._next_wid]
      if wid is not None:
         self.pi.wave_delete(wid)
         self._wid[self._next_wid] = None

   def update_channel(self, channel, width):
      self._widths[channel] = width
      self._update()

   def update_channels(self, widths):
      self._widths[0:len(widths)] = widths[0:self.channels]
      self._update()

   def cancel(self):
      self.pi.wave_tx_stop()
      for i in self._wid:
         if i is not None:
            self.pi.wave_delete(i)

if __name__ == "__main__":

   import time
   import PPM
   import pigpio

   pi = pigpio.pi()

   if not pi.connected:
      exit(0)

   pi.wave_tx_stop() # Start with a clean slate.

   ppm = PPM.X(pi, 6, frame_ms=20)

   updates = 0
   start = time.time()
   for chan in range(8):
      for pw in range(1000, 2000, 5):
         ppm.update_channel(chan, pw)
         updates += 1
   end = time.time()
   secs = end - start
   print("{} updates in {:.1f} seconds ({}/s)".format(updates, secs, int(updates/secs)))

   ppm.update_channels([1000, 2000, 1000, 2000, 1000, 2000, 1000, 2000])

   time.sleep(2)

   ppm.cancel()

   pi.stop()
   

skathi
Posts: 12
Joined: Thu Aug 02, 2018 9:08 am

Re: PPM signal creating

Fri Aug 31, 2018 1:56 pm

thank you very much

bem22
Posts: 8
Joined: Tue Oct 15, 2019 3:42 pm

Re: PPM signal creating

Fri Nov 01, 2019 9:24 pm

Is there a way to do this in C?

User avatar
Gavinmc42
Posts: 5453
Joined: Wed Aug 28, 2013 3:31 am

Re: PPM signal creating

Sat Nov 02, 2019 12:57 am

I'm dancing on Rainbows.
Raspberries are not Apples or Oranges

bem22
Posts: 8
Joined: Tue Oct 15, 2019 3:42 pm

Re: PPM signal creating

Sun Nov 10, 2019 4:18 pm

Does anyone have any experience with worked examples for generating PPM on raspberry pi using the pigpio library written by joan, please? I've hit a dead end with this.

User avatar
joan
Posts: 15549
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: PPM signal creating

Sun Nov 10, 2019 8:17 pm

http://abyz.me.uk/rpi/pigpio/examples.h ... ode/PPM.py shows how to use Python to generate PPM on a GPIO.

You will need to translate the Python to C. That should not be too difficult as there is a direct mapping from the Python pigpio calls to the C pigpio calls. I don't expect that the translation will be difficult to anyone with a reasonable level of C experience.

bem22
Posts: 8
Joined: Tue Oct 15, 2019 3:42 pm

Re: PPM signal creating

Thu Nov 14, 2019 12:57 pm

@joan, I tried to reach you in different ways for this problem. I am struggling to understand the code because there are no comments and I can't inherit any functionality from how the library is written.

Some variables are not self-explanatory, such as class X in the example above.

I am willing to create multiple examples for C and C++ and contribute to your repository if you help me a little bit with this.

bem22
Posts: 8
Joined: Tue Oct 15, 2019 3:42 pm

Re: PPM signal creating

Wed Jan 08, 2020 3:33 pm

Hello,

Here's an update about this post. I followed joan's guidance and I almost managed to translate the python to C.

Code: Select all

#include "ppmer.h"
#include <pigpio.h>
#include <sys/time.h>
#include <malloc.h>
#include <asm/errno.h>
#include <errno.h>
#include <zconf.h>
#include <string.h>

int microsleep(long tms)
{
    struct timespec ts;
    int ret;

    if (tms < 0)
    {
        errno = EINVAL;
        return -1;
    }

    ts.tv_sec = tms / 1000;
    ts.tv_nsec = tms * 1000;

    do {
        ret = nanosleep(&ts, &ts);
    } while (ret && errno == EINTR);

    return ret;
}

int init(unsigned int gpio, int channels, int frame_ms) {

    GAP = 300;
    NO_WAVES = 3;

    if (gpioInitialise() < 0) return 0;

    // Initialize all the variables in the PPM encoder structure
    ppm_factory.gpio = gpio;
    ppm_factory.channel_count = channels;
    ppm_factory.frame_ms = frame_ms;
    ppm_factory.frame_us = frame_ms * 1000;
    ppm_factory.frame_s = frame_ms / 1000;

    ppm_factory.widths = malloc(sizeof(int) * 8);

    for(int i=0; i<3; i++) {
        ppm_factory.waves[i] = malloc(6 * (channels + 1) * sizeof(gpioPulse_t));
    }
    // Set the pulse width of each channel to 1000
    for(int i=0; i<channels; i++) {
        ppm_factory.widths[i] = 1000;
    }

    // Set the wave ids to control value
    for(int i=0; i<NO_WAVES; i++) {
        ppm_factory.wave_ids[i] = -1;
    }

    // Set wave id counter
    ppm_factory.next_wave_id = 0;

    // Set GPIO pin to ground
    gpioWrite(gpio, PI_LOW);

    // Set the update time
    gettimeofday(&ppm_factory.update_time, NULL);

    return 0;
}

void update() {
    uint32_t  micros = 0;
    int i, wave_id;
    struct timeval remaining_time;


    // Set the first channel_count * 2 elements in the wave pulse (i.e lows and highs)
    for(i=0; i < ppm_factory.channel_count*2; i+=2) {
        ppm_factory.waves[ppm_factory.next_wave_id][i] = (gpioPulse_t){1u << ppm_factory.gpio, 0, GAP};
        ppm_factory.waves[ppm_factory.next_wave_id][i+1] = (gpioPulse_t){0, 1u << ppm_factory.gpio, ppm_factory.widths[i/2] - GAP};
        micros+=ppm_factory.widths[i];
    }

    // Fill with ground for the remaining time of the frame
    ppm_factory.waves[ppm_factory.next_wave_id][i] = (gpioPulse_t){1u << ppm_factory.gpio, 0, GAP};
    micros+=GAP;
    ppm_factory.waves[ppm_factory.next_wave_id][++i] =
            (gpioPulse_t){0, 1u << ppm_factory.gpio, ppm_factory.frame_us - micros};

    // Create the wave
    gpioWaveAddGeneric(i, ppm_factory.waves[ppm_factory.next_wave_id]);
    wave_id = gpioWaveCreate();

    // Send the wave to the wire
    gpioWaveTxSend(wave_id, PI_WAVE_MODE_REPEAT_SYNC);

    // Save the wave id in the array and increment the counter
    ppm_factory.wave_ids[ppm_factory.next_wave_id++] = wave_id;

    // Reset the counter when it reaches NO_WAVES
    if(ppm_factory.next_wave_id >= NO_WAVES) { ppm_factory.next_wave_id = -1; }

    struct timeval current_time;
    gettimeofday(&current_time, NULL);
    remaining_time.tv_sec = ppm_factory.update_time.tv_sec + ppm_factory.frame_s - current_time.tv_sec;
    remaining_time.tv_usec = ppm_factory.update_time.tv_usec + ppm_factory.frame_us - current_time.tv_usec;

    // Sleep for the remaining time
    if(remaining_time.tv_usec > 0) {
        gpioDelay(remaining_time.tv_usec);
    }

    // Update the timer
    gettimeofday(&ppm_factory.update_time, NULL);

    // Get next wave_id
    wave_id = ppm_factory.wave_ids[ppm_factory.next_wave_id];

    // Delete the next wave (by id) and clear the wave_id in the wave reference array
    if(wave_id != -1) {
        gpioWaveDelete(wave_id);
        ppm_factory.wave_ids[ppm_factory.next_wave_id] = -1;
        memset(ppm_factory.waves[ppm_factory.next_wave_id], 0 , (ppm_factory.channel_count + 1) * 2);
    }
}

void update_channel(unsigned int channel, unsigned int width) {
    ppm_factory.widths[channel] = width;
    update();
}

void update_channels(unsigned int *widths) {
    ppm_factory.widths = widths;
    update();
}

void destroy() {
    // Stop the current wave
    gpioWaveTxStop();

    // Clean all waves and wave_ids
    for(int i=0; i < NO_WAVES; i++) {
        if (ppm_factory.wave_ids[i] != -1) {
            gpioWaveDelete(ppm_factory.wave_ids[i]);
            ppm_factory.wave_ids[i] = -1;
        }
    }

    gpioTerminate();
}

Code: Select all

 
#include <time.h>
#include <bits/types/struct_timeval.h>
#include <pigpio.h>

#ifndef SERVER_PPMER_H
#define SERVER_PPMER_H
short GAP;
short NO_WAVES;

typedef struct encoder_struct {
    int channel_count;
    unsigned int gpio;
    int frame_ms;
    int frame_us;
    int frame_s;
    int next_wave_id;
    struct timeval update_time;
    int wave_ids[3];
    gpioPulse_t *waves[3];
    unsigned int *widths;
} encoderStructy;

struct encoder_struct ppm_factory;

int init(unsigned int gpio, int channels, int frame_ms);
void update();
void update_channel(unsigned int channel, unsigned int width);
void update_channels(unsigned int *widths);
void destroy();
#endif //SERVER_PPMER_H

I say almost because although I can generate the first 8 waves, unfortunately, I can't manage to have the break between the waves.

Joan, could you assist me with this, please?

bem22
Posts: 8
Joined: Tue Oct 15, 2019 3:42 pm

Re: PPM signal creating

Wed Jan 08, 2020 4:44 pm

Here's a function call of the above.

Code: Select all

int main(int argc, char *argv[]) {
    init(4, 8, 20);
    update_channel(0, 1500);
    destroy();
}
This is the output on the oscilloscope.
Image

This is almost what I want, but if you look at the last pulse (highlighted by the cursor), it's only 600ms, whereas it should have been 300 and then ~11000 gap.
I debugged this and every function call seems fine. I don't know what to do next

deepo
Posts: 768
Joined: Sun Dec 30, 2018 8:36 pm
Location: Denmark

Re: PPM signal creating

Wed Jan 08, 2020 8:05 pm

I don't know whether it's relevant, but this line in int microsleep(long tms):

Code: Select all

ts.tv_nsec = tms * 1000;
The n in nsec indicated nano seconds, but it's initialised with micro seconds. You need to multiply with a further 1000 to get nano seconds from the input value in milli seconds.
Right?

/Mogens

deepo
Posts: 768
Joined: Sun Dec 30, 2018 8:36 pm
Location: Denmark

Re: PPM signal creating

Wed Jan 08, 2020 8:16 pm

And these lines in int init(unsigned int gpio, int channels, int frame_ms):

Code: Select all

ppm_factory.frame_ms = frame_ms;
ppm_factory.frame_us = frame_ms * 1000;
ppm_factory.frame_s = frame_ms / 1000;
I'm not sure, but shouldn't they be separate values, and not an expression of the same value in seconds, milli seconds and micro seconds?
I mean if frame_ms was 1500 shouldn't frame_s=1 and frame_ms=500?

This code in void update():

Code: Select all

remaining_time.tv_sec = ppm_factory.update_time.tv_sec + ppm_factory.frame_s - current_time.tv_sec;
remaining_time.tv_usec = ppm_factory.update_time.tv_usec + ppm_factory.frame_us - current_time.tv_usec;
This code seems to require frame_s and frame_us to not be representing the same value.

/Mogens

bem22
Posts: 8
Joined: Tue Oct 15, 2019 3:42 pm

Re: PPM signal creating

Sun Jan 12, 2020 2:15 am

You were right.
https://electronics.stackexchange.com/q ... pberry-pi/
Here's the code, guys

snroeust
Posts: 1
Joined: Wed Feb 10, 2021 7:20 pm

Re: PPM signal creating

Wed Feb 10, 2021 7:26 pm

Even though this post is a little old, it is the first goole entry on ppm output from rpi.
I have tried the python code, it works well but is a little slow. (46 updates/sec) on raspberry pi zero.
does anyone have ways to make this faster?

Return to “Beginners”