RPi.GPIO
RPi.GPIO is the classic way to poke the Raspberry Pi's pins: set a numbering mode, configure each pin as an input or output, then read and write levels. Almost every older tutorial and Stack Overflow answer is written against it, and gpiozero is built on top of it. The community documentation lives on SourceForge.
PyRasp ships a RPi.GPIO module with the same API and constants as the real 0.7.1 library. GPIO.VERSION reports "0.7.1", and GPIO.RPI_INFO describes a Raspberry Pi 4 Model B.
Reading a button
Wire a push button between GPIO27 and ground, then enable the internal pull-up:
from wiring import *
pi = Pi()
pi.GPIO27 >> Button() >> pi.GNDPoll it in a loop. With PUD_UP the pin reads LOW while the button is held:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)
while True:
if GPIO.input(27) == GPIO.LOW:
print("button pressed")
time.sleep(0.1)Click the button on the board while the program runs to see the message.
Numbering: BCM and BOARD
setmode() must be called before anything else. GPIO.BCM uses the Broadcom numbers printed on the pinout (GPIO17, GPIO27); GPIO.BOARD uses physical header positions (11, 13). Calling setmode() twice with a different mode raises ValueError.
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(11, GPIO.OUT) # physical pin 11 = BCM GPIO17
GPIO.output(11, GPIO.HIGH)
Outputs
GPIO.setup(17, GPIO.OUT, initial=GPIO.LOW)
GPIO.output(17, GPIO.HIGH)
GPIO.output(17, GPIO.LOW)
GPIO.output([17, 27], [1, 0]) # several channels at once
Writing to a channel that was not set up as an output raises RuntimeError. Always GPIO.cleanup() at the end (or in a finally) to return the pins to inputs.
Inputs and pulls
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) # idle high
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # idle low
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_OFF) # floating
value = GPIO.input(27) # 0 or 1
The pull options and why you need one are covered in the pull-up primer. GPIO.input() on a pin that was never set up raises RuntimeError.
PWM
PWM is software PWM, exactly as in the real library:
pwm = GPIO.PWM(17, 1000) # channel 17, 1000 Hz
pwm.start(0) # duty cycle in percent
pwm.ChangeDutyCycle(25) # 25 % on
pwm.ChangeFrequency(500)
pwm.stop()
start() and ChangeDutyCycle() take 0.0–100.0; anything else raises ValueError. Only one PWM object may exist per channel. There is more on the idea in the PWM primer.
Edges and callbacks
Waiting for an edge without burning CPU:
channel = GPIO.wait_for_edge(27, GPIO.FALLING, timeout=5000) # timeout in ms
if channel is None:
print("nothing happened")
Callbacks fire in a background thread:
def on_press(channel):
print("pressed on", channel)
GPIO.add_event_detect(27, GPIO.FALLING, callback=on_press, bouncetime=200)
# ... the rest of the program ...
GPIO.remove_event_detect(27)
event_detected() is the polling-free version for main loops:
GPIO.add_event_detect(27, GPIO.FALLING, bouncetime=200)
while True:
if GPIO.event_detected(27):
print("pressed")
time.sleep(0.01) # other work
The edge can be GPIO.RISING, GPIO.FALLING or GPIO.BOTH. add_event_callback() attaches extra callbacks after add_event_detect(). Detection requires the channel to be an input, and each channel can have only one detection setup at a time — a second add_event_detect() on the same channel raises RuntimeError.
Going deeper
The module keeps the same constants and error messages as the real one, so numerical comparisons (GPIO.OUT == 0) still work. GPIO.gpio_function(pin) reports GPIO.IN or GPIO.OUT, and GPIO.cleanup(pin) can reset a single channel.
wait_for_edge() returns the channel when the edge arrives and None on timeout, matching the documented behaviour. The PWM class follows the PWM reference: a frequency change is kept on the object and used the next time it runs, and start() takes the duty cycle on every call.
Simulator vs real hardware
- Writes are saved, not latched. After
output()or aPWMchange, the simulator re-solves the circuit before the call returns, so the nextinput()on a wire connected through a component sees the new level immediately. On a real Pi you would see propagation delays measured in nanoseconds, not simulated batches. - Edge detection is not an interrupt. The simulation reports level changes in batches after each step; heavy edge traffic arrives a little later than on hardware, but every edge is delivered.
- Bounce times work differently. With
bouncetime=, the simulator waits until the pin has stayed put for that long and then reports once (trailing debounce). Real RPi.GPIO instead ignores edges for the bounce time after the first edge, which can report a press mid-bounce. Trailing debounce is what current Pi OS gives gpiozero via lgpio. - Inside a callback,
input()is a snapshot. While callbacks process a batch of edges,GPIO.input()answers with the level at the edge's own simulated time, not the latest one — so a rotary encoder callback that reads the other channel sees a consistent pair. The clock is pinned the same way, sotime.monotonic()in a callback reads the edge's timestamp. - Callbacks are cooperative threads.
add_event_detectcallbacks andwait_for_edgewaiting share the interpreter with your main program; a callback that blocks forever stalls the others. A callback that raises prints its traceback without stopping detection, like on a Pi. setwarnings()is accepted but nothing warns. The simulator does not print "channel already in use"-style warnings.- PWM is modelled. The simulation receives a duty cycle and frequency; it does not toggle a pin, so there is no jitter and no CPU cost. Timings are exact in simulated time.
- Only BCM GPIO0–GPIO27 exist.
GPIO28+raisesValueError, and theBOARDmap covers the regular 40-pin header. There is no/dev/gpiomem, no root check and noRuntimeError: Not running on a Raspberry Pi. GPIO.setup(..., GPIO.SPI)style alternate functions are not modelled.SPI,I2C,SERIALandHARD_PWMconstants exist and compare correctly, but a pin is only ever an input or an output here; I²C and SPI are driven through their own libraries (smbus2, spidev).