Raspberry Pi GPIO 模拟器

你可以在普通电脑上测试 Raspberry Pi GPIO 程序的大部分逻辑,但不能假装软件可以复现真实电子电路。2026 年更实用的方案是采用开源 GPIO Zero 的模拟引脚工厂。这样,同一套应用程序既能在自动化测试中使用虚拟引脚,也能在 Raspberry Pi 上使用真实引脚。

这份维护版指南取代了 2019 年文章里以下载旧模拟器为主的建议。原文完整保存在文末的档案区,因此历史来源不会丢失。

先确定模拟器要证明什么

模拟 GPIO 层适合测试:

  • 应用程序的判断、状态转换和错误处理;
  • 虚拟输入改变后,哪个虚拟输出应该开启;
  • 清理逻辑和重复运行测试;
  • 笔记本、CI 执行器或开发容器中的代码。

它不能验证:

  • 真实引脚编号或接线;
  • 电压、电流、噪声、上拉/下拉电阻或开关抖动;
  • 电气时序和特定硬件后端的行为;
  • LED、继电器、电机驱动器或传感器的连接是否安全、正确。

把桌面测试当作快速的第一道关卡,而不是实机测试的替代品。

安装当前工具链

Raspberry Pi OS 通常已经提供 GPIO Zero。在树莓派上,优先使用发行版软件包:

sudo apt update
sudo apt install python3-gpiozero

在开发电脑上,把依赖放入虚拟环境:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install gpiozero pytest

下面的例子采用 GPIO Zero 的 BCM GPIO 编号:17 表示 GPIO17,而不是排针的物理 17 号引脚。接线前,请在 Raspberry Pi 上运行 pinout

为 GPIO 访问建立一个小边界

尽量让判断逻辑与硬件无关,并为持有 GPIO 设备的对象提供明确的清理方法。把下面代码保存为 indicator.py

from gpiozero import Button, LED


def output_for_button(button_pressed):
    return bool(button_pressed)


class Indicator:
    def __init__(self, led_pin=17, button_pin=23):
        self.led = LED(led_pin)
        self.button = Button(button_pin, pull_up=True)

    def update(self):
        if output_for_button(self.button.is_pressed):
            self.led.on()
        else:
            self.led.off()

    def close(self):
        self.button.close()
        self.led.close()

这里的纯函数 output_for_button() 是有意保持简单的。真实项目可以把滤波、互锁、状态机和告警也放进类似的可测试函数中。

不用 Raspberry Pi 进行测试

把下面代码保存为 test_indicator.py

from gpiozero import Device
from gpiozero.pins.mock import MockFactory

from indicator import Indicator, output_for_button


def test_output_decision():
    assert output_for_button(False) is False
    assert output_for_button(True) is True


def test_button_drives_led():
    factory = MockFactory()
    Device.pin_factory = factory
    indicator = Indicator()

    try:
        indicator.button.pin.drive_high()  # pull-up button released
        indicator.update()
        assert indicator.led.value == 0

        indicator.button.pin.drive_low()  # button pressed to ground
        indicator.update()
        assert indicator.led.value == 1
    finally:
        indicator.close()
        factory.close()

运行测试:

pytest -q

MockPin.drive_high()drive_low() 用来模拟输入端看到的电平。对于上拉按钮,高电平表示松开,低电平表示按钮按下并接地。

通过环境变量选择模拟引脚

如果脚本本身没有注入引脚工厂,可在 Python 导入设备之前选择模拟后端,进行快速冒烟测试:

GPIOZERO_PIN_FACTORY=mock python your_program.py

只在开发或 CI 中明确启用它。不要在生产环境里悄悄回退到模拟引脚:程序表面上正常运行、实际却没有控制任何硬件,可能比直接报错停止更危险。

测试 PWM 和相连的虚拟设备

基础 MockPin 不支持 PWM。当测试会创建 PWMLEDServo 或其他 PWM 设备时,应选择支持 PWM 的模拟引脚类:

GPIOZERO_PIN_FACTORY=mock 
GPIOZERO_MOCK_PIN_CLASS=mockpwmpin 
pytest -q

GPIO Zero 还可以连接模拟引脚,让一个虚拟输出驱动另一个虚拟输入。如果代码使用后台 source 线程,请等待略长于所配置的 source_delay,再断言结果。这些功能能测试软件交互,但仍不会模拟真实电路上的负载、频率精度或波形。

如何处理旧的 RPi.GPIO 程序

原文使用手写的 testRPiGPIO.py 模块,并提到两个第三方模拟器下载。小型本地桩仍然可以证明程序调用了少量函数,但它很容易偏离真实 API,而且通常没有实用的输入、事件、PWM 或清理语义。

对于仍在维护的代码,优先选择以下边界之一:

  1. 把设备层代码迁移到 GPIO Zero,并使用 MockFactory
  2. 把旧的 RPi.GPIO 调用隔离在一个适配器内,再在单元测试中伪造该适配器;
  3. 保持旧程序不变,但桌面端只测试纯判断逻辑,适配器仍在 Pi 上运行。

不要把函数名相同当作硬件兼容性的证明。下方档案中的 SourceForge 和 GPIOSimulator 安装说明仅供了解历史,不是当前建议。

增加实机测试关卡

连接任何元件前,先查看开发板官方引脚图和元件数据手册。Raspberry Pi GPIO 输入和输出采用 3.3 V 逻辑。不要把 5 V 接入 GPIO。LED 应串联限流电阻;绝不能用 GPIO 直接驱动电机,应使用合适的驱动器或 H 桥以及相应的电源。

在 Pi 上重复一次小而可观察的测试:

  • 确认使用 BCM 编号还是物理编号;
  • 改动接线前先断电;
  • 从一个输入或输出开始;
  • 验证非活动状态和上拉/下拉方向;
  • 逐一测试安全互锁和清理路径;
  • 最后才连接完整负载。

故障排查

现象常见原因检查方法
出现 BadPinFactory,或没有默认引脚工厂程序不在 Pi 上运行,也没有选择后端测试时设置 GPIOZERO_PIN_FACTORY=mock,或在 Pi 上使用受支持的真实后端
PWMLED 在模拟引脚下失败默认模拟引脚不支持 PWM设置 GPIOZERO_MOCK_PIN_CLASS=mockpwmpin
上拉按钮的读数与预期相反按下时会把引脚接地松开时驱动高电平,按下时驱动低电平,并测试两个状态
测试通过,但电路不工作模拟不会验证接线和电气特性检查 pinout、共地、电阻值、电压和真实设备数据手册
测试相互影响全局设备或引脚工厂没有关闭finally 块或 pytest fixture 中关闭设备和工厂

2019 年原文档案

以下是 2019 年发布的英文原文。除去行尾空白规范化外,内容保持原样。其下载链接和安装命令可能已经过时;当前项目请使用上方维护版指南。

Method 1

Create a file named testRPiGPIO.py and import this file in your projects

import testRPiGPIO as GPIO

#!/usr/bin/python
BOARD = "board"
BCM = "bcm"
OUT = "out"
IN = "in"
HIGH = 1
LOW = 0

def setwarnings(mode):
print(mode)

def output(pin,value):
print(pin, ":", value)

def setmode(mode):
print(mode)

def setup(pin,value):
print(pin, ":", value)

def cleanup():
print("clean-up")

#End


Method 2

Download [this file](https://sourceforge.net/projects/pi-gpio-emulator/) and extract it in the folder of your project.

from EmulatorGUI import GPIO


If you want to install it using pip,

pip install GPIOSimulator


Import it to your project using

from RPiSim.GPIO import GPIO


Supported methods

- GPIO.setmode()
- GPIO.setwarnings()
- GPIO.setup()
- GPIO.input()
- GPIO.output()

An example

from EmulatorGUI import GPIO
#import RPi.GPIO as GPIO
import time
import traceback

def Main():
try:
GPIO.setmode(GPIO.BCM)

GPIO.setwarnings(False)

GPIO.setup(4, GPIO.OUT)
GPIO.setup(17, GPIO.OUT, initial = GPIO.LOW)
GPIO.setup(18, GPIO.OUT, initial = GPIO.LOW)
GPIO.setup(21, GPIO.OUT, initial = GPIO.LOW)
GPIO.setup(23, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(15, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(26, GPIO.IN)

while(True):
if (GPIO.input(23) == False):
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
time.sleep(1)

if (GPIO.input(15) == True):
GPIO.output(18,GPIO.HIGH)
GPIO.output(21,GPIO.HIGH)
time.sleep(1)

if (GPIO.input(24) == True):
GPIO.output(18,GPIO.LOW)
GPIO.output(21,GPIO.LOW)
time.sleep(1)

if (GPIO.input(26) == True):
GPIO.output(4,GPIO.LOW)
GPIO.output(17,GPIO.LOW)
time.sleep(1)

except Exception as ex:
traceback.print_exc()
finally:
GPIO.cleanup() #this ensures a clean exit

Main()

官方参考资料

Leave a Reply