OpenPLC

OpenPLC

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

02 - Block reference

02 - Block reference

Sixteen blocks. UIO is the one every project needs; the rest are there when
you want what they do.

Nothing takes a bitmask or a bare channel number: where a block picks something,
it picks it by name.

UIO

Brings the board up and services it. First in the program, once per scan.

Pin
READY out, BOOL the ADC and the DAC both answered
ERROR out, BOOL they did not
STATUS out, UIO_RESULT why

DIGITAL_IN

Pin
DI01..DI08 out, BOOL the input, true while driven

Sampled by UIO, so the value does not change during a scan. An input that
cannot be read reads false. No debounce and no inversion — R_TRIG, F_TRIG
and TON do that in your own program.

DIGITAL_OUT

Pin
DO01..DO08 in, BOOL drive the output

Driven on the next UIO call. No fault feedback exists: the board has no
readback, so a shorted output looks exactly like a working one.

ANALOG_IN

Pin
AI01_MODE..AI04_MODE in, UIO_MODE what the channel is wired for
AI01..AI04 out, REAL the reading: volts on UIO_VOLTAGE, milliamps on UIO_CURRENT. A saturated input reads 11 V / 22 mA (see 03 Analog)
AI01_RAW..AI04_RAW out, INT the converter count behind it

ANALOG_OUT

Pin
AO01_MODE, AO02_MODE in, UIO_MODE what the channel is wired for
AO01, AO02 in, REAL the value to output: volts or milliamps
AO01_RAW, AO02_RAW out, INT the converter count it became

ANALOG_CAL

One channel at a time, named rather than numbered.

Pin
CHANNEL in, UIO_CHANNEL which channel
OFFSET in, REAL added to the reading after scaling, in volts or milliamps
LOW_COUNT in, INT converter count at the bottom of the range
HIGH_COUNT in, INT converter count at the top
APPLY in, BOOL one shot, use these three now
SAVE in, BOOL one shot, write every channel to flash
RESET in, BOOL one shot, defaults everywhere, and save
OFFSET_NOW out, REAL the offset CHANNEL currently holds
LOW_NOW, HIGH_NOW out, INT the counts CHANNEL currently holds
SAVED out, BOOL one scan, a write succeeded
ERROR out, BOOL the last request failed
STATUS out, UIO_RESULT why

HAT_POWER

Pin
ENABLE in, BOOL power the expansion headers
POWERED out, BOOL what the line was last set to

This drives the HAT power enable, which each HAT uses to switch its own supply
through its own MOSFET — the base board has no switch of its own. Takes effect
immediately, not at the next UIO call. There is no sense line, so POWERED
reports the request, not what the HAT did with it.

NETWORK

Pin
ENABLE in, BOOL ask for the network; clear it to power the radio down
SSID in, STRING(32) the network to join
PASSWORD in, STRING(64) its key, empty for an open network
CONNECTED out, BOOL a network is up, whoever brought it up
IP out, DWORD the address, 0 while down
RSSI out, INT signal strength in dBm, 0 while down
ERROR out, BOOL ENABLE is set and there is still no network
STATUS out, UIO_RESULT why

The radio is shared. If something else brought the network up first — Modbus
TCP in the firmware, or a protocol library — this block reports it and changes
nothing, so the credentials here are only applied to a radio that is down, or
to one this block brought up itself. By the same rule, clearing ENABLE will
not switch off a radio this block did not start.

SSID and PASSWORD may change while connected (a settings page): on a radio
this block brought up, it leaves the network and joins again with the new ones.
CONNECTED drops while it joins; MQTT and Sparkplug come back by themselves.

Clearing ENABLE powers the radio down rather than merely disconnecting it,
which is the saving worth having on a battery. WIFI_SLEEP drives this pin to
duty-cycle it.

BUILTIN_BUTTON

The button built into the board, beside the antenna, also on the HAT header.

Pin
DEBOUNCE in, TIME ignore anything shorter, default T#30ms
LONG in, TIME hold this long and it is a long press, default T#2s
PRESSED out, BOOL held down right now
SHORT_PRESS out, BOOL one scan, on release before LONG
LONG_PRESS out, BOOL one scan, as soon as the hold passes LONG
HELD out, TIME how long it has been held, T#0s when released

Use one instance. SHORT_PRESS and LONG_PRESS are consumed as they are read,
so a second instance would take presses the first never saw.

WIFI_SLEEP

Duty-cycles the radio, for a board on a battery.

Pin
ENABLE in, BOOL keep the radio off except when woken
WAKE in, BOOL rising edge, bring it back now
AWAKE in, TIME how long it stays up once woken, default T#5m
WAKE_EVERY in, TIME also bring it back this often, T#0s for only on WAKE
RADIO_ON out, BOOL the radio should be powered — wire to NETWORK.ENABLE
AWAKE_LEFT out, TIME time before it powers down again
btn();
wifiSleep(ENABLE := TRUE, WAKE := btn.SHORT_PRESS, AWAKE := T#5m);
net(ENABLE := wifiSleep.RADIO_ON, SSID := 'plant', PASSWORD := 'secret');

WAKE takes any signal, not only the button — a shift starting, a fault, a
level switch. Clearing ENABLE hands the radio back and stops duty-cycling it.

SLEEP

Pin
TRIGGER in, BOOL one shot, sleep now
MODE in, UIO_SLEEP light or deep
SECONDS in, UDINT wake after this long, 0 for no timer
EN_WAKE_DI01..EN_WAKE_DI08 in, BOOL use this input as a wake source
DO01_IN_SLEEP..DO04_IN_SLEEP in, BOOL the level DO01..DO04 hold while asleep
HAT_IN_SLEEP in, BOOL keep the HAT powered while asleep
LED_IN_SLEEP in, BOOL keep the status LED lit (dim blue) while asleep
WAKE_CAUSE out, UIO_WAKE what ended the last sleep
ERROR out, BOOL the request was refused
STATUS out, UIO_RESULT why

Deep sleep saves the retained values first, so the board goes to sleep about
10 to 50 ms after TRIGGER; light sleep does not need to. See
04 Sleep, particularly about DO05 to DO08.

RESTART

Pin
REQ in, BOOL rising edge, restart the controller now
REASON out, USINT why this run started (ESP-IDF reset reason): 1 power-on, 3 a software restart, 4 a crash, 5 to 7 a watchdog, 8 deep sleep wake, 9 brownout
RESTARTED out, BOOL this run started from a software restart

For a site that has to recover by itself: a link supervisor that has tried the
modem and the HAT power asks for one controlled restart, and this does it. It
saves the retained values first, so nothing the store was still holding is lost;
the restart waits for that save, about 10 to 50 ms. A REQ already TRUE at
start-up is not an edge.

sup( LinkOk := sp.BORN, RebootAllowed := NOT pumpRunning, Data := supCfg );
reboot( REQ := sup.RebootReq );

PULSE_COUNT

A flow meter, or any pulse output, on a digital input — counted in hardware.

Pin
TOTAL in-out, LREAL the running total in your units: your own variable, RETAIN it
CHANNEL in, USINT the input, 1..8
K_FACTOR in, REAL pulses per unit (100 pulses a litre: 100.0); 0 counts pulses
RATE_PER in, TIME the rate's time base: T#1s, T#1m, T#1h; T#1s if T#0s
FILTER in, TIME shorter pulses are noise (up to about 12 µs)
WINDOW in, TIME the rate is measured over at least this, T#1s if T#0s
ZERO_AFTER in, TIME no pulse this long and the rate is 0, T#30s if T#0s
ENABLE in, BOOL count while TRUE, default TRUE
RATE out, REAL units per RATE_PER
PULSES out, UDINT pulses since counting started
COUNTING out, BOOL the counter is running
ERROR out, BOOL the input could not be counted
STATUS out, UIO_RESULT UIO_TOO_LARGE when all four counters are taken
VAR RETAIN flowTotal : LREAL; END_VAR
flow( TOTAL := flowTotal, CHANNEL := 3, K_FACTOR := 100.0, RATE_PER := T#1h );

See 07 Pulses and diagnostics.

SYS_INFO

How the controller itself is doing. No inputs; refreshed once a second.

Pin
HEAP_FREE out, UDINT internal RAM free now, bytes
HEAP_MIN out, UDINT the least it has been since start
HEAP_LARGEST out, UDINT the largest block that could be allocated
PSRAM_FREE, PSRAM_SIZE out, UDINT external RAM free, and fitted
STACK_MIN out, UDINT the smallest stack margin among the PLC's tasks, bytes
STACK_TASK out, STRING(16) which task
UPTIME out, TIME since the board started
RESET_REASON out, UIO_RESET why it last started
PROGRAM_ID out, STRING(32) the program's identity (its MD5)
FIRMWARE out, STRING(30) firmware and ESP-IDF version
STATUS out, UIO_RESULT UIO_UNSUPPORTED on the Runtime

UIO_EXPANSION

I2C expansion modules on the board's I2C bus: which are fitted, which answer,
and their channels in two images.

Pin
MODULES in-out, ARRAY OF UIO_XMOD the module table, up to 8 rows (below)
XD in-out, ARRAY OF BOOL the digital image: DI modules write, DO modules read
XA in-out, ARRAY OF REAL the analog image: AI, AO and temperature modules
ALL_OK out, BOOL every fitted module is present and working
FITTED_COUNT out, USINT rows fitted
MISSING_COUNT out, USINT fitted rows that are not ok
FIRST_BAD out, INT the first such row, 0 when none
ERROR out, BOOL a fitted module is not ok
STATUS out, UIO_RESULT UIO_UNSUPPORTED on the Runtime
STATUS_TEXT out, STRING(30) the first problem: mod 2 0x21 missing

UIO_XMOD: kind (UIO_XKIND), address (BYTE), fitted (BOOL, default
TRUE) and first (INT, the image row of channel 1) are settings; count,
present, ok, state (UIO_XSTATE) and errors are status.

No chip drivers are built in yet: a fitted module that answers reads
UIO_XS_NO_DRIVER and is not ok. See 08 I2C expansion.

UIO_I2C_SCAN

The I2C addresses that answer, for commissioning.

Pin
FOUND in-out, ARRAY OF BYTE the addresses found, lowest first, the rest 0
START in, BOOL rising edge: scan 16#08..16#77
BUSY out, BOOL scanning (about 30 scans)
DONE out, BOOL the last scan finished
COUNT out, USINT addresses found, the board's 16#49 and 16#5F included
ERROR out, BOOL the bus is not up, or FOUND was too short
STATUS out, UIO_RESULT why

The enumerations

UIO_MODE — UIO_VOLTAGE, UIO_CURRENT.

UIO_CHANNEL — UIO_AI01..UIO_AI04, UIO_AO01, UIO_AO02.

UIO_SLEEP — UIO_LIGHT_SLEEP, UIO_DEEP_SLEEP.

UIO_WAKE — UIO_WAKE_NONE, UIO_WAKE_TIMER, UIO_WAKE_INPUT, UIO_WAKE_RESET.

UIO_RESULT — UIO_OK, UIO_NOT_READY, UIO_INVALID_ARGUMENT, UIO_IO_ERROR,
UIO_NO_DATA, UIO_TOO_LARGE, UIO_UNSUPPORTED.

UIO_RESET — UIO_RST_UNKNOWN, UIO_RST_POWER_ON, UIO_RST_EXTERNAL,
UIO_RST_SOFTWARE, UIO_RST_PANIC, UIO_RST_INT_WDT, UIO_RST_TASK_WDT,
UIO_RST_OTHER_WDT, UIO_RST_DEEP_SLEEP, UIO_RST_BROWNOUT, UIO_RST_SDIO,
UIO_RST_USB, UIO_RST_JTAG, UIO_RST_EFUSE, UIO_RST_PWR_GLITCH,
UIO_RST_CPU_LOCKUP (the ESP-IDF reset reasons, in order).

UIO_XKIND — UIO_XK_NONE, UIO_XK_MCP23017_DI, UIO_XK_MCP23017_DO,
UIO_XK_PCF8574_DI, UIO_XK_PCF8574_DO, UIO_XK_PCA9555_DI,
UIO_XK_PCA9555_DO, UIO_XK_ADS1115_AI, UIO_XK_MCP4728_AO,
UIO_XK_GP8413_AO, UIO_XK_TMP117_T, UIO_XK_MCP9808_T, UIO_XK_SHT4X_TH.

UIO_XSTATE — UIO_XS_UNUSED, UIO_XS_OK, UIO_XS_NOT_FITTED,
UIO_XS_MISSING, UIO_XS_NO_DRIVER, UIO_XS_FAILING, UIO_XS_BAD_ADDRESS,
UIO_XS_IN_USE, UIO_XS_NO_ROOM.

Last updated 2 hours ago
Was this helpful?
Thanks!