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

Re: New pigpio Python module

Tue Feb 16, 2016 9:30 am

That should be simple.

What is the gap (in microseconds) between one channel and the next?

Currently there will be glitches from one frame to the next when the channels are changed but I know how to stop them.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Feb 16, 2016 12:06 pm

pickledgator wrote:Yea, I'm basically spoofing a controller signal from the rpi. To generate the 27ms frame, it'll have to run at ~37 Hz. Are the waveform generations expensive?
well it's not expensive, but i fear with python you'll have some "lag" between the input and the output. But you have to try to understand how much :)
second question: what is the input of your spoofed controller?
you have to send the frame 37 times a second, but if you want to create a simil "real time" controller you also have to sample the input 37 times a second..
again, it's a matter of how much delay you can accept :)

but i'm quite curious of the result..

pickledgator
Posts: 7
Joined: Fri Feb 12, 2016 2:41 am

Re: New pigpio Python module

Tue Feb 16, 2016 7:22 pm

@joan: In implementation, we will want the total signal pulse + pause pulse to equal the desired channel width. We are actually only interested in the combined time; the signal to pause ratio lengths don't actually matter as the receiver is just looking for time between leading edges. So for example, a min command would be a total of 1000 microseconds, perhaps 500 pulse + 500 pause. A max command would be 2000 microseconds, perhaps 1500 pulse, 500 pause. But the ppm signal (from a receiver parsing standpoint) would be equivalent to min: 50 signal, 950 pause, max: 50 signal, 1950 pause. This page describes that process: https://sourceforge.net/p/arduinorclib/ ... %20Signal/. For sake of argument, let's pick a pause of 500 microseconds, but I assume it would be some type of parameter that can be adjusted.

@Massi: The input commands are coming from an xbox controller over a USB connection to the rpi using the linux driver. The joystick/button commands are interrupt driven, and populate a persistent input state, which is then run through some algorithms and converted to 8 signals (1000-2000 microseconds). The output thread will be separate from the input thread to ensure that the output commands/pigpio are run as deterministically as possible (may have to hack on the kernel a bit to reduce the OS interrupts, we'll see). I'm planning to release the code on github when it's done. The goal for the project is to utilize an xbox controller that mimics a standard RC controller. The BOM is only about $100 and would make for a controller that is smaller than most available on the market (e.g, taranis, turnigy, etc.).

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

Re: New pigpio Python module

Tue Feb 16, 2016 8:38 pm

You need a gap between the channels otherwise you can't tell the channel pulse length.

In this example code I have used 100µs as it is easily visible.

Code: Select all

#!/usr/bin/env python

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

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_us = int(frame_ms * 1000)

      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)

   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_repeat(wid)
      self._wid[self._next_wid] = wid
      print("create", self._next_wid, "with", wid)

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

      wid = self._wid[self._next_wid]
      if wid is not None:
         print("delete", self._next_wid, "with", wid)
         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)

   ppm = PPM.X(pi, 6)

   updates = 0
   start = time.time()
   for chan in range(8):
      for pw in range(1000, 2000, 5):
         ppm.update_channel(chan, pw)
         updates += 1
         time.sleep(0.03)
   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()
On my Pi Zero I can do 120 updates a second.

With the current pigpio there will be glitches when a channel is updated but it should give you the idea.
ppm-a.png
ppm-a.png (32.63 KiB) Viewed 4216 times
ppm-b.png
ppm-b.png (32.34 KiB) Viewed 4216 times

pickledgator
Posts: 7
Joined: Fri Feb 12, 2016 2:41 am

Re: New pigpio Python module

Thu Feb 18, 2016 3:49 am

@Joan, Thanks for this. It's very helpful. If I'm understanding correctly, I would want to generate the waveform from channels as an array of pulses, then call wave_create() which builds the wave form from the previous add functions. Then call wave_send_repeat() until I have a channel update. I suppose the tricky part you mentioned is that if I get a channel update, we need to wait until the current wave form is done sending before calling wave_delete() and building/sending the new modified wave form from the new channels? Looks like waiting for wave_tx_busy() would do the trick. Does that sound correct?

pickledgator
Posts: 7
Joined: Fri Feb 12, 2016 2:41 am

Re: New pigpio Python module

Thu Feb 18, 2016 7:39 am

Spent some time rearchitecting the PPM class for my needs. Mainly, I separated the send and update calls into asynchronous methods. I'm hoping Joan can comment on whether or not it's smart to utilize wave_send_once for every update, by waiting on the wave_tx_busy() to clear. I'm guessing that strategy is not as efficient as wave_send_repeat(), but for my application, the send_once integrates better. Timing shows that I'm losing ~1ms every 130 frames or so. Not sure how that impacts the waveforms. I don't have an oscope handy and wasn't able to get the pre-built binaries of piscope to run (gtk errors), so I don't have any way to visualize the wave forms right now. The PPM code is available here: https://github.com/pickledgator/xboxrc/ ... ter/PPM.py. Open to comments/suggestions.

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

Re: New pigpio Python module

Thu Feb 18, 2016 8:41 am

You can see the waveforms with piscope.

I have made the changes needed to pigpio to allow synchronisation with the current waveform. Depending on what I do with the documentation and other minor changes I may push V46 out today or tomorrow. In either case I'll post with the needed changes to the PPM code.

Using a repeated waveform is almost certainly the correct way to work. Send once and checking for completion will be subject to all sorts of jitter which will possibly confuse the receiver.

pickledgator
Posts: 7
Joined: Fri Feb 12, 2016 2:41 am

Re: New pigpio Python module

Thu Feb 18, 2016 7:21 pm

joan wrote:I have made the changes needed to pigpio to allow synchronisation with the current waveform. Depending on what I do with the documentation and other minor changes I may push V46 out today or tomorrow. In either case I'll post with the needed changes to the PPM code.
Great, looking forward to the synchronization mods. Thanks again for the help with this.

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

Re: New pigpio Python module

Thu Feb 18, 2016 11:27 pm

Here is the code altered for V46.

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()
There is one new function - wave_send_using_mode.

I have moved the update delay into the class and based it on the frame time. This means you can't update more often than the frame time (pointless) or delete a waveform before it has been used (bad).

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Fri Feb 26, 2016 3:28 pm

Hello joan,
as always, this is not strictly a question regarding the python module, but the pigpio library itself :)

i was looking at the bb_i2c_open function (i've 3 ssd1306 displays, so one is spare :)) and i haven't understood if this function creates a "fake" i2c bus (to be used with standard i2c functions) or it is only intended to be used with the bb_i2c_zip function :)
i suspect the case is the second, since the common i2c_open takes only 0 and 1 as bus..
is this cpu intensive?

thanks :)

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

Re: New pigpio Python module

Fri Feb 26, 2016 3:58 pm

Massi wrote:Hello joan,
as always, this is not strictly a question regarding the python module, but the pigpio library itself :)

i was looking at the bb_i2c_open function (i've 3 ssd1306 displays, so one is spare :)) and i haven't understood if this function creates a "fake" i2c bus (to be used with standard i2c functions) or it is only intended to be used with the bb_i2c_zip function :)
i suspect the case is the second, since the common i2c_open takes only 0 and 1 as bus..
is this cpu intensive?

thanks :)
The bb_i2c_open/close/zip functions must be used as a unit and allows arbitrary GPIO to be used for I2C. The i2c_* functions all use the standard Linux I2C driver which only allows the "proper" GPIO.

I haven't done any timings. They will be CPU intensive if you run at anything over 10 kbps (at least I think so, as I said I haven't actually done any timings). Of course they are only intensive while actually talking to a device.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Fri Feb 26, 2016 4:04 pm

joan wrote:The bb_i2c_open/close/zip functions must be used as a unit and allows arbitrary GPIO to be used for I2C. The i2c_* functions all use the standard Linux I2C driver which only allows the "proper" GPIO.

I haven't done any timings. They will be CPU intensive if you run at anything over 10 kbps (at least I think so, as I said I haven't actually done any timings). Of course they are only intensive while actually talking to a device.
Ok, very clear.
So the way is the i2c-0 bus :)

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Apr 12, 2016 6:19 pm

Joan,
i'm doing some testing with dt overlay and i2c muxes, so that now my sistem is full of i2c buses.

BUT

i've now discovered that pigpio i2c functions accept only 0 and 1 for i2c bus
is this something can be corrected easily?

thanks

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

Re: New pigpio Python module

Tue Apr 12, 2016 6:55 pm

You probably need to change #define PI_NUM_I2C_BUS 2 (pigpio.h) to some larger value depending on your multiplexor. You will need to re-make/install.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Apr 12, 2016 7:07 pm

Perfect!
thanks :)

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

Re: New pigpio Python module

Tue May 31, 2016 8:09 pm

Massi wrote:Perfect!
thanks :)
From V53 I've removed the bus number check so you no longer need to make that #define change (in fact the #define will no longer exist).

An error will be returned if you try to open a non-existent bus.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Wed Jun 01, 2016 7:44 am

joan wrote:
Massi wrote:Perfect!
thanks :)
From V53 I've removed the bus number check so you no longer need to make that #define change (in fact the #define will no longer exist).

An error will be returned if you try to open a non-existent bus.
That's good, thanks :)

in my "to do list when i do not know what to do" there is a modification to pigpio to be able to show the list of open handles :) it should not be hard knowing how to code it

and i do not know how to code it :D

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

Re: New pigpio Python module

Wed Jun 01, 2016 8:13 am

Massi wrote: ...
in my "to do list when i do not know what to do" there is a modification to pigpio to be able to show the list of open handles :) it should not be hard knowing how to code it
...
Not so sure about that. What are you going to do when you get the list?

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Wed Jun 01, 2016 8:45 am

joan wrote:
Massi wrote: ...
in my "to do list when i do not know what to do" there is a modification to pigpio to be able to show the list of open handles :) it should not be hard knowing how to code it
...
Not so sure about that. What are you going to do when you get the list?
well, take i2c handles

it's a number (handle id) connected with another number (device address) and another one (bus number)

Since it's a common problem when i'm testing something and i do not want to spend time with the try - except - finally code to use all handles and get the "no handles available" error, my idea was to have the possibility to show the handles list.
Then i can choose to close some unused handles, or to take back one of them and use again it..

For sure:
- this is not changing life using pigpio
- it will be much faster to do a pigpiod restart
- probably it's completely useless on production code (i've the handle's management in all the finally branch of my code)

But you know, that's for fun :)

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

Re: New pigpio Python module

Wed Jun 01, 2016 8:51 am

I just do

for ((i=0;i<32;i++)); do pigs i2cc $i; done

PlanB
Posts: 102
Joined: Tue Oct 28, 2014 6:44 am

Re: New pigpio Python module

Thu Jun 02, 2016 2:27 am

I'm trying to do PWM with hardware_PWM on a Pi3

Code: Select all

import time
import pigpio
pi = pigpio.pi()
import RPi.GPIO as GPIO
GPIO.setwarnings(0)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
pi.hardware_PWM(18, 1000, 250000) # 10kHz 25% dutycycle
time.sleep(5)
pi.stop()                         # Disconnect from Pi
My problem is the PWM remains when the program has finished but I want it to stop. Also if I stop it manually by cleaning up the GPIO the program won't restart it when I run it again, I have to kill pigpio first then rerun it.

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

Re: New pigpio Python module

Thu Jun 02, 2016 8:04 am

PlanB wrote: ...
My problem is the PWM remains when the program has finished but I want it to stop. Also if I stop it manually by cleaning up the GPIO the program won't restart it when I run it again, I have to kill pigpio first then rerun it.
If you want PWM to stop when you exit the script then you will have to stop it before exiting the script. pigpio does not guess your intentions.

In your script the following will switch hardware PWM on GPIO18 off.

pi.hardware_PWM(18, 0, 0)

You can stop it outside the script with the command

pigs hp 18 0 0

Could you give details about "Also if I stop it manually by cleaning up the GPIO the program won't restart it when I run it again, I have to kill pigpio first then rerun it.". What did you do?

PlanB
Posts: 102
Joined: Tue Oct 28, 2014 6:44 am

Re: New pigpio Python module

Sat Jun 04, 2016 2:30 am

thnx Joan, my sin was not using pi.hardware_PWM(18, 0, 0). I was forcing gpio18 low with a GPIO.output to stop the pwm & I couldn't restart pwm by rerunning the program because I didn't use pi.hardware_PWM(18, 0, 0) to end the previous instance.

Hey I like the ultra fine granularity I can potentially get with the 1 million steps for dutycycle with pi.hardware_PWM(gpio, frequency, dutycycle), just wondering why set_PWM_dutycycle(user_gpio, dutycycle) only has 255 steps? Is that an older version?

Lastly I've had a few crashes where pigpiod hasn't started for whatever reason, I get an error msg asking me if I've started the pigpio daemon but my program won't exit with ctrl c & I have to reboot. Is there a get-out-of-jail equivalent of hp 18 0 0 for this situation too?

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

Re: New pigpio Python module

Sat Jun 04, 2016 8:18 am

PlanB wrote:thnx Joan, my sin was not using pi.hardware_PWM(18, 0, 0). I was forcing gpio18 low with a GPIO.output to stop the pwm & I couldn't restart pwm by rerunning the program because I didn't use pi.hardware_PWM(18, 0, 0) to end the previous instance.

Hey I like the ultra fine granularity I can potentially get with the 1 million steps for dutycycle with pi.hardware_PWM(gpio, frequency, dutycycle), just wondering why set_PWM_dutycycle(user_gpio, dutycycle) only has 255 steps? Is that an older version?

Lastly I've had a few crashes where pigpiod hasn't started for whatever reason, I get an error msg asking me if I've started the pigpio daemon but my program won't exit with ctrl c & I have to reboot. Is there a get-out-of-jail equivalent of hp 18 0 0 for this situation too?
There are two hardware PWM channels (on GPIO 12/18, 13/19). They provide the most flexible PWM. You will get precisely one million steps if the frequency is 250 Hz. More steps for lower frequencies, less steps for higher frequencies (steps is actually 250 million / frequency).

The PWM on the other GPIO is timed with the DMA hardware and is restricted in its resolution (the details are specific to the implementation) but defaults to 800Hz and 250 steps (scaled to 255).

Only one copy of the pigpio daemon may be running at a time. Errors are invariably down to trying to restart the daemon (lock file exists) or trying to restart it before the previous network connection has timed out.

Do you have a repeatable failure example?

Perhaps try ctrl Z, then ps to find the script process id, kill -9 process id, fg (to go back), as an alternative to reboot.

PlanB
Posts: 102
Joined: Tue Oct 28, 2014 6:44 am

Re: New pigpio Python module

Sun Jun 05, 2016 1:10 pm

So 25000 steps at 10kHz. Does that mean my PID loop should increment the duty cycle in blocks of 40 between 0 & 1000000?

Code: Select all

dc=dc+40
pi.hardware_PWM(18, 10000, dc)

Return to “Python”