Skip Navigation

Barkhausen Institut

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 widthmeasured pulse widthdelta t
500us559us59us
500.1us559us58.1us
501us560us59us
505us564us59us
510us568us58us

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 widthmeasured pulse widthdelta t
500us498.92us-1.08us
500.1us498.92us-1.18us
500.9us498.92us-1.98us
501us499.92us-1.08us
501.1us499.92us-1.18us
505us503.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.