Evaluation of PWM Performance of RPi.GPIO and Navio2
Within our upcoming demonstrator, we use a Raspberry Pi together with a Navio2 navigation board, where we use PWM signals to control motors and servos. This post evaluates the accuracy, achievable resolution and stability of the PWM outputs of both Raspberry Pi 4 and the Navio2 board. For the RPi, we use the software-controlled PWM via the Python Library RPi.GPIO
. The Navio2 board is controlled via sysfs
devices.
Evaluation of RPi.GPIO
RPi.GPIO
uses software PWM. For each PWM output, an extra thread runs, with using nanosleep()
between on- ond off-periods. See e.g. the source code, function pwm_thread
. This method is inherently inaccurate since there is no guaranteed accuracy for nanosleep (see nanosleep manpage). Here, we test the accuracy on an RPi4 running the Navio2 Raspbian image.
Test code:
For the purpose of testing we created a python test script rpio_pwm.py
# file: rpio_pwm.py
import sys
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
FREQ = 50
def toDC(ms):
return ms/1000.0 * 50 * 100
p = GPIO.PWM(18, 50)
p.start(toDC(float(sys.argv[1])))
input("Press Enter to close")
Results
Accuracy
The oscilloscope was set up to measure the pulse width (see bottom left corner of the oscilloscope screen). In addition to the measured pulse width, the pulse width is quite jitterish, i.e. sometimes it is high for too long or too short (in the range of roughly 50us).
Test with 1ms DC: python rpio_pwm.py 1
Test with 1.5ms DC: python rpio_pwm.py 1.5
Test with 2ms DC: python rpio_pwm.py 2
Resolution
To test resolution of the PWM cycle, the following results were obtained:
requested pulse width | measured pulse width | delta t |
---|---|---|
500us | 559us | 59us |
500.1us | 559us | 58.1us |
501us | 560us | 59us |
505us | 564us | 59us |
510us | 568us | 58us |
pulse width was set by python rpio_pwm.py <pw>
.
Jitter
The jitter was measured qualitatively by looking at the oscilloscope screen. Roughly 50us jitter of the pulse width can be expected. The jitter is more or less independent of the system load (checked with sysbench --test=cpu run
running or not running).
Conclusion
From these results, we can see that RPi PWM duty cycle can be set with 1us resolution, and the constant offset is roughly 59us. The jitter is around 50us, though 80% of the time, the pulse width is correct with +-5us accuracy (minus offset).
Evaluation of Navio2
PWM
Navio2 uses a dedicated microprocessor to generate the PWM signal on the Servo Rail. We used the Navio2 pwm class to set the dutycycle. We need to reset the duty cycle in in regular intervals, as otherwise the Navio2 PWM output stops.
Test code:
# file: navio_pwm.py
import sys
import time
import navio.pwm
import navio.util
navio.util.check_apm()
with navio.pwm.PWM(1) as pwm:
time.sleep(0.1)
pwm.set_period(50)
time.sleep(0.1)
pwm.enable()
time.sleep(0.1)
while True:
pwm.set_duty_cycle(float(sys.argv[1]))
time.sleep(0.1)
Results
Accuracy
The oscilloscope was set up to measure the pulse width (see bottom left corner of the oscilloscope screen).
Test with 1ms DC: python navio_pwm.py 1
Test with 1.5ms DC: python navio_pwm.py 1.5
Test with 2ms DC: python navio_pwm.py 2
Resolution
To test resolution, the following results were obtained:
requested pulse width | measured pulse width | delta t |
---|---|---|
500us | 498.92us | -1.08us |
500.1us | 498.92us | -1.18us |
500.9us | 498.92us | -1.98us |
501us | 499.92us | -1.08us |
501.1us | 499.92us | -1.18us |
505us | 503.92us | -1.08us |
pulse width was set by python navio2_pwm.py <pw>
.
Jitter
The jitter was measured qualitatively. Looking at the oscilloscope screen, no jitter was visible at all.
Conclusion
From these results, we can see that the resolution is in the range of 1us, and the offset is roughly -1us (negligible). The jitter is non-existent.
Navio2 PWM Breakdown
In the docs, this is mentioned:
Kernel driver for Navio2 that generates PWM needs to be fed with data at least every 100 ms. So it’s necessary to update the value in set_duty_cycle every 100 ms or less to make PWM output works.
This behaviour has been confirmed: When setting the PWM signal only once, the signal breaks down after roughly 1s. Hence, we need to update the PWM signal all the time.
Conclusion
RPI.GPIO
is jittery (+-50us), and has a constant offset of +60us (i.e. the pulse width is wider than configured). The PWM signal does not need to be updated all the time to keep it running.- Navio2 PWM is non-jittery and accurate (1us). Needs to be updated all the time to keep it running.
- Resolution is 1us for both systems.
Depending on the application, the jitter and non-accurate PWM setting of the RPi.GPIO
library can become a problem. At the same time, a high PWM frequency can create some load on the RPi CPU. The obtained results encouraged us to discard the GPIO method completely and only rely on the Navio2 PWM.