Skip to content

Real time provider rpi

Peter Corke edited this page May 25, 2026 · 1 revision

Real-time provider: Raspberry Pi GPIO

This page documents the rpi realtime I/O provider used by BDRealTime.

Current status:

  • Working and currently the primary tested hardware path.
  • Uses gpiozero for GPIO and MCP3008 ADC.
  • Uses pigpio hardware PWM when available, with fallback to gpiozero.PWMOutputDevice.

Implementation module: src/bdsim/blocks/io_rpi_gpio.py.

What this provider supports

  • PWMOUT via provider analog output.
  • DIGITALOUT and DIGITALIN on GPIO pins.
  • ANALOGIN using MCP3008 channels 0-7.

Provider names accepted:

  • rpi
  • rpi_gpio
  • gpio
  • gpiozero

Install on Raspberry Pi OS

Recommended packages:

sudo apt update
sudo apt install -y python3-gpiozero python3-lgpio pigpio python3-pigpio

If you install with pip instead:

pip install gpiozero pigpio

Start pigpio daemon for hardware PWM support:

sudo systemctl enable pigpiod
sudo systemctl start pigpiod
sudo systemctl status pigpiod

If pigpiod is not running, bdsim still runs but PWM falls back to software PWM.

Boot configuration (/boot/firmware/config.txt)

For reliable hardware setup (especially PWM + SPI), check boot-time config.

Edit:

sudo nano /boot/firmware/config.txt

Recommended settings:

# Enable SPI for MCP3008 (or other SPI ADCs)
dtparam=spi=on

# Free PWM channels used by onboard analog audio (important for GPIO18/19 PWM)
dtparam=audio=off

Then reboot:

sudo reboot

Notes:

  • dtparam=audio=off is important when you use hardware PWM on BCM18/19, since onboard analog audio uses the same PWM hardware resources.
  • If you need onboard analog audio, prefer PWM pins BCM12/13 for control output, or disable hardware PWM per block with io_options={"hardware_pwm": False}.

Runtime selection

from bdsim.realtime import BDRealTime

rt = BDRealTime(io_provider="rpi", toolboxes=False)

Channel conventions

For rpi, channel values are physical pin/channel identifiers, not TOML device names.

  • GPIO pins: integer BCM numbers (for example 18) or strings like "GPIO18", "BCM18".
  • ADC channels: integer 0..7 for MCP3008.

Examples:

clock = bd.clock(50, "Hz", name="clock")      # 50 Hz control/sample clock

u = bd.PWMOUT(clock, channel=18, freq=10_000, name="u")
y = bd.ANALOGIN(clock, channel=1, name="y")
yref = bd.ANALOGIN(clock, channel=0, name="yref")

PWM behavior

PWMOUT writes a normalized duty command in [0, 1].

  • Values below 0 clamp to 0.
  • Values above 1 clamp to 1.

Hardware PWM is attempted on BCM pins:

  • 12, 13, 18, 19

Use these pins if you want stable high-frequency PWM.

MCP3008 analog input

ANALOGIN with rpi expects MCP3008 (enabled on CS0) channel index 0..7.

Return value is normalized by gpiozero.MCP3008 in [0, 1].

If you need engineering units, add scaling in your diagram (GAIN, SUM, etc.).

Useful io_options

Per block, pass backend options via io_options={...}.

Examples:

# Force software PWM even on hardware-capable pin
u = bd.PWMOUT(clock, channel=18, freq=5_000,
              io_options={"hardware_pwm": False})

# Override active polarity
dout = bd.DIGITALOUT(clock, channel=23,
                     io_options={"active_high": True})

# Pull-up for digital input
din = bd.DIGITALIN(clock, channel=24,
                   io_options={"pull_up": True})

# Explicit ADC type (currently mcp3008 only)
y = bd.ANALOGIN(clock, channel=0,
                io_options={"adc_type": "mcp3008"})

Minimal test script

#!/usr/bin/env python3
from bdsim.realtime import BDRealTime

rt = BDRealTime(io_provider="rpi", toolboxes=False)
bd = rt.blockdiagram()

clock = bd.clock(0.02, name="clock")
wave = bd.WAVEFORM(wave="square", freq=0.25, unit="Hz", min=0.25, max=0.75, name="u")
pwm = bd.PWMOUT(clock, channel=18, freq=10_000, name="pwm")

bd.connect(wave, pwm)

bd.compile()
rt.run(bd, tf=20)

An LED on GPIO18 will alternate between 25% for 2 seconds and 75% brightness for 2 seconds. The LED is driven by a PWM pulse train at 10kHz.

Wiring notes

  • PWM LED demo: BCM18 (physical pin 12) plus resistor (470ohm) and LED to GND.
  • MCP3008: wire SPI correctly (MOSI, MISO, SCLK, CE0) and 3V3/GND.
  • Ensure SPI is enabled when using MCP3008:
sudo raspi-config
# Interface Options -> SPI -> Enable

Common issues and fixes

  1. RpiGPIOProvider requires gpiozero to be installed

Install gpiozero in the Python environment used to run bdsim.

  1. pigpio connection not available

Start pigpiod service; otherwise software PWM fallback is used.

Detailed daemon checks:

# Service status
sudo systemctl status pigpiod --no-pager

# Start/restart and verify
sudo systemctl restart pigpiod
sudo systemctl is-active pigpiod

# Process check (should show pigpiod)
pgrep -a pigpiod

If pigpiod fails to start, inspect logs:

sudo journalctl -u pigpiod -n 100 --no-pager

Common causes:

  • Missing package (pigpio not installed).
  • Conflicting GPIO service/process already holding resources.
  • Stale daemon instance from manual launch.

Useful recovery sequence:

sudo systemctl stop pigpiod
sudo killall pigpiod 2>/dev/null || true
sudo systemctl start pigpiod
sudo systemctl status pigpiod --no-pager
  1. Permission denied for GPIO access
  • Quick check: run once with sudo to confirm it is permission-related.
  • Then fix user permissions/groups according to your Raspberry Pi OS configuration.
  1. ADC channel error

ANALOGIN channel must be in 0..7 for MCP3008.

  1. How to confirm hardware PWM vs software fallback

The provider tries hardware PWM first (on BCM 12, 13, 18, 19) when pigpiod is available. If that path is unavailable, it falls back to software PWM.

Practical diagnostics:

# 1) Confirm daemon is alive
sudo systemctl is-active pigpiod

# 2) Observe GPIO function while diagram runs
sudo raspi-gpio get 18

Typical interpretation while actively driving PWM on BCM18:

  • Hardware PWM path: pin function usually shows an ALT mode (PWM function).
  • Software fallback: pin typically remains plain OUTPUT.

Additional clues:

  • Hardware PWM at high carrier rates (for example 10 kHz) remains stable with lower CPU load.
  • Software PWM tends to show more jitter and higher CPU usage as frequency increases.

For A/B testing, force software path from the block:

u = bd.PWMOUT(clock, channel=18, freq=10_000,
                            io_options={"hardware_pwm": False})

If behavior is unchanged when forcing software mode, you were likely already on fallback. If stability/CPU usage gets worse, hardware PWM was likely active before.

Telemetry with RPi provider

Use TELEMETRY exactly the same way as any provider.

telemetry = bd.TELEMETRY(clock, nin=3, endpoint="192.168.1.10:5001")

On the desktop/laptop viewer:

telemetry-client --listen 0.0.0.0:5001

External references

Clone this wiki locally