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.
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:
- 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).
- 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.
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:
Note that there are different versions for Python 3 and Python 2. This is for Python version 3. If you see this error:
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:
Lastly, if when you try and import the module you get the following:
SystemError: This module can only be run on a Raspberry Pi!
cd ~ git clone https://github.com/metachris/RPIO.git --branch v2 --single-branch cd RPIO sudo python setup.py install
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.