Wednesday, February 15, 2017

Raspberry Pi and "better" PWM for Servo Control?

RPIO and DMA PWM


Our current method of using software PWM to control servos is ok, but it does use CPU cycles and there is some jitter when the servos are in one position for any length of time. It turns out that there is an alternative (although it is currently in beta) in the RPIO module. From the documentation:

RPIO.PWM provides PWM via DMA for the Raspberry Pi, using the onboard PWM module for semi-hardware pulse width modulation, with a precision of up to 1µs.

With RPIO.PWM you can use any of the 15 DMA channels and any number of GPIOs per channel. Since the PWM is done via DMA, RPIO.PWM uses almost zero CPU resources and can generate stable pulses with a very high resolution. RPIO.PWM is implemented in C (source); you can use it in Python via the provided wrapper, as well as directly from your C source.

An important point to note is that you can use any number of GPIO's per channel, so you don't instantiate a new channel for each servo but use the one channel for as many servos as you have connected. An example of using RPIO.PWM is shown below.

from RPIO import PWM

servo = PWM.Servo()

# Set servo on GPIO17 to 1200µs (1.2ms)
servo.set_servo(17, 1200)

# Set servo on GPIO17 to 2000µs (2.0ms)
servo.set_servo(17, 2000)

# Clear servo on GPIO17
servo.stop_servo(17)

This example shows the high level application of RPIO.PWM. There are also a number of low level methods which you can review via the documentation link above. Two that we would like to briefly cover are:

  1. Subcycles - Each DMA channel is setup with a specific subcycle, within which pulses are added, and which will be repeated endlessly. Servos, for instance, typically use a subcycle of 20ms, which will be repeated 50 times a second. You can add pulses for multiple GPIOs, as well as multiple pulses for one GPIO. Subcycles cannot be lower than 2ms. This default works for us since the SG90 likes a frequency of 50 Hz (i.e. a period of 20 ms).
  2. Pulse-width increment granularity - (10µs by default) is used for all DMA channels (since its passed to the PWM timing hardware). Pulses are added to a subcycle by specifying a start and a width parameter, both in multiples of the granularity. For instance to set 500µs pulses with a granularity setting of 10µs, you’ll need to set the pulse-width as 50 (50 * 10µs = 500µs). The pulse-width granularity is a system-wide setting used by the PWM hardware, therefore you cannot use different granularities at the same time, even in different processes. So unless you change the default, increase your pulse width by 10µs increments.

Installing RPIO


RPIO is an advanced GPIO module for the Raspberry Pi. It includes the following:

  • PWM via DMA (up to 1µs resolution)
  • GPIO input and output (drop-in replacement for RPi.GPIO)
  • GPIO interrupts (callbacks when events occur on input gpios)
  • TCP socket interrupts (callbacks when tcp socket clients send data)
  • Command-line tools rpio and rpio-curses
  • Well documented, fast source code with minimal CPU usage
  • Open source (LGPLv3+)

RPIO is not installed by default on the Raspberry Pi. Depending on what model you are using you may come across some issues. As a first step try:
sudo apt-get update
sudo apt-get install python3-setuptools
sudo easy_install3 -U RPIO
Note that there are different versions for Python 3 and Python 2. This is for Python version 3. If you see this error:
fatal error: Python.h: No such file or directory
compilation terminated.
error: Setup script exited with error: command 'gcc' failed with exit status 1`
The fix is to install the python development headers. Again there are different versions for Python 2 and 3. To install for Python 3 use:
sudo apt-get install python3-dev
Lastly, if when you try and import the module you get the following:
SystemError: This module can only be run on a Raspberry Pi!
Try:
cd ~
git clone https://github.com/metachris/RPIO.git --branch v2 --single-branch
cd RPIO
sudo python setup.py install

Results


We got it working but the results seem a bit flaky. When it works it is good but every couple of runs we get the following error:
Traceback (most recent call last):
  File "/home/pi/python_code/RPIO_test.py", line 11, in <module>
    servo = PWM.Servo()
  File "/usr/local/lib/python3.4/dist-packages/RPIO-2.0.0_beta1-py3.4-linux-armv7l.egg/RPIO/PWM/__init__.py", line 188, in __init__
    setup(pulse_incr_us=pulse_incr_us)
  File "/usr/local/lib/python3.4/dist-packages/RPIO-2.0.0_beta1-py3.4-linux-armv7l.egg/RPIO/PWM/__init__.py", line 87, in setup
    return _PWM.setup(pulse_incr_us, delay_hw)
RuntimeError: Failed to create mailbox device

Started trying to track this error down but then decided life was too short! We will work with the RPi.GPIO PWM and see if we can't work around the jitter. At least it works consistently.

4 comments:

  1. Did you ever try to track down the Failed to create mailbox device error? I'm just starting a project and getting this error somewhat consistently!

    ReplyDelete
    Replies
    1. Hi Craig, no I'm afraid I never did. I've moved on to home automation at the moment but will come back to this after that. I think an Arduino is probably a better solution if you need PWM.

      Delete
    2. Thanks! I started wondering if it was a timing issue (for me, at least). I was loading the servo almost immediately after starting a Flask server. When I moved the servo load to be inside of the web service call, it hasn't errored for me since.

      Delete
    3. That's interesting, I'm also using a web server to stream video and control the robot. I'll give your suggestion a whirl.

      Delete