Skip to content

Your First P1AM Program

Wire up a discrete input and output on a P1AM-200, test the logic locally, generate CircuitPython code, and deploy to hardware.

Configure hardware

from pyrung import Bool, Int, Program, Rung, PLC, out, copy, rise
from pyrung.circuitpy import P1AM, write_circuitpy

hw = P1AM()
inputs  = hw.slot(1, "P1-08SIM")   # 8-ch discrete input
outputs = hw.slot(2, "P1-08TRS")   # 8-ch relay output

Button = inputs[1]
Light  = outputs[1]
PressCount = Int("PressCount", retentive=True)

hw.slot() returns typed blocks matching the physical module. Slot numbers must be contiguous from 1, matching the wiring order on the DIN rail.

PressCount is marked retentive=True — its value persists to SD card across power cycles.

Write logic

with Program() as logic:
    with Rung(Button):
        out(Light)

    with Rung(rise(Button)):
        copy(PressCount + 1, PressCount)

Button held → Light on. Each rising edge of Button increments the press counter. Same DSL as any pyrung program — nothing CircuitPython-specific here.

Test locally

def test_button_press():
    with PLC(logic, dt=0.1) as plc:
        # Press button — light turns on, counter increments
        Button.value = True
        plc.step()
        assert Light.value is True
        assert PressCount.value == 1

        # Hold button — light stays on, counter doesn't increment (no new edge)
        plc.run(cycles=5)
        assert Light.value is True
        assert PressCount.value == 1

        # Release and press again — second count
        Button.value = False
        plc.step()
        Button.value = True
        plc.step()
        assert PressCount.value == 2

        # Release — light turns off (out de-energizes when rung is false)
        Button.value = False
        plc.step()
        assert Light.value is False

Run with pytest. The logic is verified before it touches hardware.

Generate code

write_circuitpy(logic, hw, target_scan_ms=10.0, watchdog_ms=500, output_dir="./build")

This writes two files to ./build/:

  • code.py — your program compiled to a CircuitPython scan loop. Regenerate every time you change logic.
  • pyrung_rt.py — the pyrung runtime library (Modbus helpers, protocol state machines). Same for every project.

For faster boot and lower memory use, replace pyrung_rt.py with the pre-compiled pyrung_rt.mpy from the releases page.

Deploy to hardware

One-time board setup

  1. Install CircuitPython on the P1AM-200
  2. Install the CircuitPython P1AM library and its dependencies into CIRCUITPY/lib/
  3. Download pyrung_rt.mpy from the pyrung releases page and copy it to CIRCUITPY/lib/
  4. Insert a FAT-formatted SD card (required for retentive tags like PressCount)

Iterate

Copy code.py to the P1AM-200's CIRCUITPY drive. It runs automatically on boot.

The board switch works as RUN/STOP out of the box — switch down to stop execution (outputs go off), switch up to resume. Retentive tags are saved automatically on RUN→STOP and every 30 seconds when values change.

What you get for free

  • RUN/STOP via the board switch (debounced, default on)
  • Retentive persistence — tagged values survive power loss via SD card with crash-safe writes
  • Watchdog — hardware reset if the scan loop stalls beyond watchdog_ms
  • Scan pacing — the loop targets target_scan_ms and tracks overruns

Next steps