Technical White Paper

Raspberry Pi + Relay DDL: PLC Input Simulation & Fault Injection via I²C

Author: Fred Fisher — President & Principal Engineer, Validus Group Inc. | Industrial Automation & Embedded Systems
Abstract
PLC logic is often validated under normal operating sequences, yet many real-world failures occur when inputs behave abnormally: contact bounce, stuck sensors, intermittent wiring, out-of-order events, or unexpected concurrent signals. This paper documents a low-cost, repeatable method to simulate PLC inputs using a Raspberry Pi 4 with a 4-channel Relay DDL HAT controlled over I²C. The approach supports deterministic (seeded) random stimulus patterns to stress-test ladder logic, state machines, alarms, and recovery behavior in ways that are difficult to reproduce on a live machine.

Included are step-by-step setup instructions for enabling I²C, installing required libraries, creating a Python virtual environment, validating the Relay DDL address on the bus, and running scripts that toggle all four relay channels on/off or generate constrained random patterns for fault-injection testing.

Why Input Simulation Matters in PLC Validation

In production environments, PLC programs rarely fail because an input turns on exactly when expected. Failures are more commonly triggered by edge cases: contacts chatter, sensors intermittently drop, inputs remain stuck, or events occur in a non-ideal order during startup, e-stop recovery, or manual intervention.

A relay-based input simulator provides a safe, bench-friendly way to inject controlled disturbances into PLC input circuits without requiring the full machine to be present. When paired with deterministic pseudo-random patterns, the same “ugly” scenario can be reproduced after program changes for regression testing.

Key concept: “Random” is only useful in controls testing when it is repeatable. A seeded pattern allows engineering teams to reproduce a failure and confirm that a fix actually resolves it.

Raspberry Pi 4 + Relay DDL 4-Channel HAT

The Relay DDL HAT is controlled over I²C (not direct GPIO per channel). On Raspberry Pi platforms, the HAT communicates through the standard I²C bus exposed on the 40-pin header (SDA/SCL).

Practical note: Because the HAT presents relay contacts, it can simulate “dry contact” devices (limit switches, prox outputs through interposing relays, permissives) into PLC input modules when wired correctly. Always follow safe wiring practices and do not use this method for safety-rated circuits.

Configuration and Verification

Enable I²C using the Raspberry Pi configuration utility:

sudo raspi-config
# Interface Options → I2C → Enable
sudo reboot

Install I²C utilities and scan the bus:

sudo apt update
sudo apt install -y i2c-tools
i2cdetect -y 1

Example successful detection for the Relay DDL board:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: 10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
...

The 10 entry indicates an I²C address of 0x10. This document assumes the Relay DDL address is 0x10. If your scan shows a different address, update scripts accordingly.

Virtual Environment + Required Libraries

Install Python tooling and create a dedicated virtual environment:

sudo apt update
sudo apt install -y python3 python3-venv python3-pip

python3 -m venv ~/venvs/plc_sim
source ~/venvs/plc_sim/bin/activate
python -m pip install --upgrade pip

Install the I²C Python library used in this implementation:

pip install smbus2
Remote development: When using PyCharm over SSH, set the interpreter to ~/venvs/plc_sim/bin/python so your run configuration executes on the Pi where the I²C hardware exists.

Validating Relay Switching with i2cset

Before writing application logic, validate that relays respond at the command line. The Relay DDL convention used here is register 0x01..0x04 for relay channels and values 0xFF (ON) / 0x00 (OFF).

# Relay 1 ON / OFF
sudo i2cset -y 1 0x10 0x01 0xff
sudo i2cset -y 1 0x10 0x01 0x00

# Relay 2 ON / OFF
sudo i2cset -y 1 0x10 0x02 0xff
sudo i2cset -y 1 0x10 0x02 0x00

If relays switch correctly via i2cset, higher-level Python control is typically straightforward.

Toggle All Four Channels (Deterministic, Simple)

The script below toggles all four relay channels ON and OFF for a defined number of cycles. This is useful for proving wiring and confirming basic switching behavior prior to PLC integration.

#!/usr/bin/env python3
import time
from smbus2 import SMBus

BUS = 1
ADDR = 0x10
RELAYS = (1, 2, 3, 4)

ON_VALUE = 0xFF
OFF_VALUE = 0x00

def set_relay(bus: SMBus, relay_num: int, on: bool) -> None:
    reg = relay_num  # 0x01..0x04
    val = ON_VALUE if on else OFF_VALUE
    bus.write_byte_data(ADDR, reg, val)

def set_all(on: bool) -> None:
    with SMBus(BUS) as bus:
        for r in RELAYS:
            set_relay(bus, r, on)

def main() -> None:
    on_time_s = 1.0
    off_time_s = 1.0
    cycles = 5

    for _ in range(cycles):
        set_all(True)
        time.sleep(on_time_s)
        set_all(False)
        time.sleep(off_time_s)

    set_all(False)  # safe end state

if __name__ == "__main__":
    main()

Seeded Random Patterns (Repeatable Fault Injection)

For PLC stress testing, use a deterministic seed so the same pattern can be replayed after a logic change. The example below generates short bursts of toggling on random channels with variable dwell times.

#!/usr/bin/env python3
import time
import random
from smbus2 import SMBus

BUS = 1
ADDR = 0x10
RELAYS = (1, 2, 3, 4)

def write_relay(bus: SMBus, relay: int, on: bool) -> None:
    bus.write_byte_data(ADDR, relay, 0xFF if on else 0x00)

def main() -> None:
    seed = 1337
    duration_s = 30
    min_dwell = 0.05
    max_dwell = 0.40

    rng = random.Random(seed)
    t_end = time.time() + duration_s

    with SMBus(BUS) as bus:
        # Start known-safe
        for r in RELAYS:
            write_relay(bus, r, False)

        while time.time() < t_end:
            r = rng.choice(RELAYS)
            on = rng.choice([True, False])
            write_relay(bus, r, on)
            time.sleep(rng.uniform(min_dwell, max_dwell))

        # End known-safe
        for r in RELAYS:
            write_relay(bus, r, False)

if __name__ == "__main__":
    main()
Best practice: For real validation work, log timestamps, relay states, and the random seed to a file so a PLC event trace can be correlated to the exact stimulus sequence.

Using Relay Contacts to Simulate Inputs

Most PLC input modules are designed for structured electrical conventions (sourcing/sinking, commons, and defined current thresholds). A relay simulator works best when it emulates a real field device:

Safety note: Do not use this method to validate safety-rated circuits (e-stop, guard door safety relays, safety PLC inputs). Safety systems require certified components, verified architectures, and documented validation methods.

Practical Takeaways

  • A Raspberry Pi + I²C relay HAT provides a low-cost, repeatable PLC input simulation tool.
  • Validate the hardware first with i2cdetect -y 1 and i2cset before writing scripts.
  • Use a Python venv to keep dependencies stable and PyCharm SSH interpreters clean.
  • Seeded random patterns enable repeatable fault injection and regression testing.
  • Relay contacts simulate field closures safely when the PLC wiring conventions are respected.

For commissioning teams and maintenance engineering, this approach can materially reduce debug time by exposing logic weaknesses early—before a machine is fully assembled or before production schedules make experimentation risky.