UT_IO_MAP_IN — Input IO map: terminals to functions
UT_IO_MAP_IN — Input IO map: terminals to functions
Your plant logic reads functions ("well float HH", "pump 1 overload"), never a
terminal. The IO map connects each function to a channel from its settings, so
re-wiring a site, or moving a signal to an I2C module or a remote device, is a
setting, not a program change. It also tells your logic whether each value can be
trusted, gives a fail-safe value when it can't, and lets a commissioning engineer
force an input for a set time.
UT_IO_MAP_IN does the inputs; UT_IO_MAP_OUT does the outputs.
The two share the module table and the force slots. The IO types (UT_IO_…) are
described on this page.
Call order in the scan:
- Your IO code copies the hardware into the input image (
DigImg[n].q,
AnaImg[n].v) and sets each module'sok. - UT_IO_MAP_IN.
- Your logic, which reads
Dig[n].q,Dig[n].ok,Ana[n].v. - UT_IO_MAP_OUT.
- Your IO code copies the output image to the hardware.
Pins
| Pin | Type | Default | What to wire | |
|---|---|---|---|---|
| in | ForceTime |
TIME | T#5s | How long a commissioning force lasts. At most 10 min. T#0s, negative or over 10 min: 5 s or 10 min is used (Setting: force time) |
| in | ReleaseAll |
BOOL | FALSE | TRUE ends every input force now and refuses new ones. Wire the HMI link-lost signal, or a "release all forces" key |
| in | UnusedOff |
BOOL | FALSE | TRUE: a function that is not fitted on this site (src NONE) reads FALSE (analog 0.0) instead of its failSafe. A fitted function that is lost still reads its failSafe |
| out | AllOk |
BOOL | Every function with an address reads a good (or forced) channel, and every fitted input module answers | |
| out | AnyForced |
BOOL | An input force slot is forcing | |
| out | ConfigBad |
BOOL | A function's address is wrong (no such module or channel), or its module is not fitted | |
| inout | Dig |
ARRAY[*] OF UT_IO_DI | The digital input functions, one row each | |
| inout | Ana |
ARRAY[*] OF UT_IO_AI | The analog input functions (raw values), one row each | |
| inout | DigImg |
ARRAY[*] OF UT_IO_DCH | The digital input terminals. Your IO code writes q (and bad) |
|
| inout | AnaImg |
ARRAY[*] OF UT_IO_ACH | The analog input terminals. Your IO code writes v (and bad) |
|
| inout | Mods |
ARRAY[*] OF UT_IO_MOD | The module table, shared with UT_IO_MAP_OUT | |
| inout | Force |
ARRAY[*] OF UT_IO_FORCE | The force slots, shared with UT_IO_MAP_OUT. Never RETAIN |
|
| inout | Data |
UT_IO_MAP_DATA | Status: code, text and counts |
Number every array from 1 (ARRAY[1..n]). The row number of a function is its
number on the IO page, and 0 means "none" in the image's fn.
Using it
VAR
IoIn : UT_IO_MAP_IN;
DiFn : ARRAY[1..16] OF UT_IO_DI; (* 1 = well float HH, 2 = pump 1 overload, ... *)
AiFn : ARRAY[1..4] OF UT_IO_AI; (* 1 = sump level, ... *)
DiImg : ARRAY[1..16] OF UT_IO_DCH; (* board DI 1..8, I2C module DI 9..16 *)
AiImg : ARRAY[1..4] OF UT_IO_ACH;
IoMods : ARRAY[1..6] OF UT_IO_MOD;
IoForce : ARRAY[1..4] OF UT_IO_FORCE;
IoInSt : UT_IO_MAP_DATA;
END_VAR
(* 1. your IO code: terminals into the image *)
DiImg[1].q := %IX0.0;
DiImg[2].q := %IX0.1;
AiImg[1].v := INT_TO_REAL(%IW0);
IoMods[1].ok := TRUE; (* the board always answers *)
IoMods[3].ok := I2cDiModuleOk; (* from the I2C driver *)
(* 2. the IO map *)
IoIn(ReleaseAll := NOT HmiLinkOk, Dig := DiFn, Ana := AiFn, DigImg := DiImg, AnaImg := AiImg,
Mods := IoMods, Force := IoForce, Data := IoInSt);
(* 3. your logic reads functions *)
Pump1(Demand := DiFn[1].q, ...);
Level(In := AiFn[1].v, Data := LevelData);
The settings, for example: IoMods[1] is kind := UT_IOK_DI, src := UT_IOS_BOARD,
module := 1, first := 1, count := 8. DiFn[2] (the pump 1 overload, an NC aux
on board input 2) is src := UT_IOS_BOARD, module := 1, ch := 2, nc := TRUE,
failSafe := TRUE.
Addresses
A channel's address is three numbers:
| Setting | Type | Meaning |
|---|---|---|
src |
UT_IO_SRC | Where it is (below). UT_IOS_NONE = the function is not fitted on this site |
module |
USINT | Which module of that source, from 1. The board's DI, DO, AI and AO are each a module: board DI module 1, board DO module 1 … |
ch |
USINT | The channel on that module, from 1 |
UT_IO_SRC: where a channel is
| No. | Value | Meaning |
|---|---|---|
| 0 | UT_IOS_NONE |
Not fitted: the function reads its failSafe (or FALSE / 0.0 with UnusedOff) |
| 1 | UT_IOS_BOARD |
The controller's own terminals |
| 2 | UT_IOS_I2C |
An I2C expansion module |
| 3 | UT_IOS_FIELD |
A field-bus device: Modbus over RS485, or the LoRa mesh |
| 4 | UT_IOS_REMOTE |
A remote value (MQTT / SCADA) |
UT_IO_KIND: what a module's channels are
| No. | Value | Meaning |
|---|---|---|
| 0 | UT_IOK_NONE |
An empty row of the module table, or a force slot not used |
| 1 | UT_IOK_DI |
Digital inputs |
| 2 | UT_IOK_DO |
Digital outputs |
| 3 | UT_IOK_AI |
Analog inputs |
| 4 | UT_IOK_AO |
Analog outputs |
The module table UT_IO_MOD
One row per module. UT_IO_MAP_IN looks after the DI and AI rows, UT_IO_MAP_OUT the
DO and AO rows.
| Field | Type | Default | Meaning |
|---|---|---|---|
kind |
UT_IO_KIND | UT_IOK_NONE | DI, DO, AI or AO |
src |
UT_IO_SRC | UT_IOS_NONE | Board, I2C, field or remote |
module |
USINT | 0 | Its number within that source, from 1 |
first |
INT | 0 | The image row of its channel 1 (an index of DigImg or AnaImg) |
count |
USINT | 0 | How many channels it has |
fitted |
BOOL | TRUE | Setting: installed on this site |
ok |
BOOL | FALSE | Present and answering. Your IO code or the driver sets it every scan; for the board, set it TRUE. Left FALSE, the module reads as missing |
state |
UT_IO_Q | Status: UT_IOQ_OK, UT_IOQ_NOT_FITTED or UT_IOQ_MISSING |
|
used |
USINT | Status: how many functions are mapped to it |
A function's module is the first row with the same kind, src and module.
Channel ch is image row first + ch - 1.
The image UT_IO_DCH, UT_IO_ACH
One row per terminal. The input image (DigImg, AnaImg here) and the output
image (on UT_IO_MAP_OUT) are separate arrays.
| Field | Type | Meaning |
|---|---|---|
q (digital) |
BOOL | The terminal: input contact made, output energised |
v (analog) |
REAL | The raw value (inputs: before scaling) |
bad |
BOOL | Written by its driver: this channel can't be trusted (a stale point, a failed write) |
forced |
BOOL | Status: a force slot holds it |
fn |
INT | Status: the row of the function on it (inputs: the first one); 0 = spare |
The input image always keeps the real terminal value, also while it is forced: the
IO page can show both.
Digital input function UT_IO_DI
| Field | Type | Default | Meaning |
|---|---|---|---|
src, module, ch |
NONE, 0, 0 | Setting: where it is wired (see Addresses) | |
nc |
BOOL | FALSE | Setting: a normally-closed contact. The block inverts it, so q is TRUE = active |
debounce |
TIME | T#0s | Setting: a new state must hold this long before q follows (both ways). T#0s = none |
failSafe |
BOOL | FALSE | Setting: what q reads when the channel is not fitted, missing, bad or wrongly addressed |
q |
BOOL | Status: the value for your logic, TRUE = active | |
ok |
BOOL | Status: q comes from a good channel, or from a force |
|
why |
UT_IO_Q | Status: the quality, and why it is not good (below) | |
raw, since |
BOOL, TIME | Kept by the block for the debounce. Don't write them |
Analog input function UT_IO_AI
| Field | Type | Default | Meaning |
|---|---|---|---|
src, module, ch |
NONE, 0, 0 | Setting: where it is wired | |
failSafe |
REAL | 0.0 | Setting: what v reads when the channel is not good. 0.0 looks like a broken 4–20 mA loop to UT_ANALOG_IN |
v |
REAL | Status: the raw value for your logic. Feed it to UT_ANALOG_IN's In |
|
ok |
BOOL | Status: v comes from a good channel, or from a force |
|
why |
UT_IO_Q | Status: the quality and why |
Quality UT_IO_Q
The why of a function, the state of a module or of a force slot. A function's
ok is TRUE only for OK and FORCED.
| No. | Value | Function | Module | Force slot |
|---|---|---|---|---|
| 0 | UT_IOQ_UNUSED |
Not fitted on this site (src NONE) |
Empty row | Free, released or timed out |
| 1 | UT_IOQ_OK |
Good | Fitted and answering | |
| 2 | UT_IOQ_FORCED |
Its channel is forced | Forcing | |
| 3 | UT_IOQ_NOT_FITTED |
Its module is not fitted on this site | fitted FALSE |
|
| 4 | UT_IOQ_MISSING |
Its module is fitted but not answering | ok FALSE |
|
| 5 | UT_IOQ_BAD |
The channel's bad is set |
||
| 6 | UT_IOQ_NO_MODULE |
No module of that kind, source and number in the table | Refused: no such module | |
| 7 | UT_IOQ_NO_CHANNEL |
The module has no such channel, or the image is too short | Refused: no such channel | |
| 8 | UT_IOQ_SHARED |
Outputs: another function already drives this channel | ||
| 9 | UT_IOQ_BLOCKED |
Outputs: forced, but held off by its block bit |
||
| 10 | UT_IOQ_NOT_PERMITTED |
Outputs: refused or ended, ForcePermit is FALSE |
Force slot UT_IO_FORCE
One row per force. A slot names a channel, not a function, so a spare terminal can
be forced too. Declare the slots without RETAIN.
| Field | Type | Meaning |
|---|---|---|
kind |
UT_IO_KIND | The channel to force: DI or AI here (DO or AO on UT_IO_MAP_OUT). UT_IOK_NONE = slot not used |
src, module, ch |
The channel's address | |
q |
BOOL | Digital: the state. Inputs: the terminal, as if the contact were made |
v |
REAL | Analog: the raw value (inputs: before scaling) |
cmdForce |
BOOL | HMI: force now, or start the time again |
cmdRelease |
BOOL | HMI: release now |
active |
BOOL | Status: forcing now |
timeLeft |
TIME | Status: time until it releases by itself |
state |
UT_IO_Q | Status: UT_IOQ_FORCED, or why not (UT_IOQ_NO_MODULE, UT_IOQ_NO_CHANNEL, UT_IOQ_NOT_PERMITTED), UT_IOQ_UNUSED when free |
row, forceSeen, releaseSeen |
Kept by the block. Don't write them |
The commands act once, on their rising edge, and the block clears them. A command
held on (a Modbus coil, a debugger force) acts once. How a slot runs is on
UT_IO_FORCE_RUN.
Record UT_IO_MAP_DATA
One per map (one for inputs, one for outputs). Status only.
| Field | Type | Meaning |
|---|---|---|
statusCode |
UT_STATUS | OK, a warning, or a configuration problem (below) |
faultId |
UT_FAULT | Which: IO forced, IO module missing, IO address … |
statusText |
STRING(30) | The two in words |
forcedCount |
INT | Input force slots forcing now |
forceLeft |
TIME | The longest time left of the active forces |
badCount |
INT | Functions with an address (src not NONE) that are not good: wrong address, module not fitted, missing or bad. A forced function counts as good |
missingCount |
INT | Fitted input modules not answering (used by a function or not) |
Status
statusCode, faultId and statusText give the most important problem, in the
order below. The text is rebuilt only when the code or the fault changes. The
numbers are in conventions.
statusCode |
faultId |
statusText |
When |
|---|---|---|---|
UT_ST_CONFIG |
UT_F_IO_ADDRESS |
Config: IO address |
A function's module is not in the table, or its channel is not on the module or outside the image |
UT_ST_CONFIG |
UT_F_IO_NOT_FITTED |
Config: IO not fitted |
A function is on a module with fitted FALSE |
UT_ST_WARNING |
UT_F_IO_FORCED |
Warning: IO forced |
An input is forced (forceLeft: the time left) |
UT_ST_WARNING |
UT_F_IO_MISSING |
Warning: IO module missing |
A fitted input module is not answering |
UT_ST_WARNING |
UT_F_IO_BAD |
Warning: IO channel bad |
A function's channel has bad set |
UT_ST_WARN_SETTING |
UT_F_FORCE_TIME |
Setting: force time |
ForceTime is 0, negative or over 10 min |
UT_ST_OK |
OK |
Everything good |
A Config: status here does not stop the block: the other functions carry on, and
the function with the wrong address reads its failSafe.
How it works
- Each scan, for each function row, the block finds its module in the table and its
row in the image, and checks in this order: not fitted (srcNONE), no module,
no channel, forced, module not fitted, module missing, channel bad, else OK. - Good or forced:
q/vis the terminal (or the forced value),okTRUE.
Digital: inverted whenncis set, then debounced. - Not good:
q/vreads the function'sfailSafe, never a plain "off", and
okis FALSE. Your logic decides what a lost signal means: a high-level float
withfailSafe := TRUEreads "high" when its module drops off the bus. - Not fitted (
srcNONE): readsfailSafe, or FALSE / 0.0 withUnusedOff.
okstays FALSE andwhyUNUSED. UseUnusedOffwhen an option that is not
installed must read "not active", while a fitted signal that is lost still reads
itsfailSafe. A function on a module that is not fitted (fittedFALSE) always
reads itsfailSafe. - A channel that comes back (good again after not fitted, missing, bad or a wrong
address, and on the first scan):qfollows it at once, with no debounce. When a
force ends, the real value comes back through the debounce. - Debounce: a new state must hold
debouncebeforeqchanges, on and off. A
shorter pulse is ignored. - One input channel can feed several functions (no warning). The image's
fnshows
the first one. - The status and counts are worked out last. Modules of kind DO and AO, and force
slots of kind DO and AO, are left to UT_IO_MAP_OUT.
Forcing an input (commissioning)
- Fill a slot with
kind(DI or AI), the address and the value (qorv), then
setcmdForce. Inputs can be forced at any time (no permit). - The force goes on the terminal, before the NC setting and before scaling, as a
test wire would. An NC function sees a forcedqTRUE as "contact made", which is
inactive. A forced analog raw value goes through UT_ANALOG_IN's checks: force
2.0 mA to test its wire-break alarm. - The debounce is skipped while forced. A forced channel on a missing or not-fitted
module reads as good (whyFORCED) while forced; the module still shows its state. - Each force ends by itself after
ForceTime(5 s by default, at most 10 min).
cmdForceagain starts the time again.cmdRelease, orReleaseAll, ends it at
once. A shorterForceTimecuts a running force's time at once. - A slot whose address does not exist is refused (
stateNO_MODULE or NO_CHANNEL).
Setting a slot'skindto NONE ends its force. - Changing the address of a slot that is forcing moves the force to the new channel
(if it exists). Release it first. - These slots are separate from the debugger's force.
Invalid settings
- A wrong address or a module not fitted: that function reads its
failSafe,
ConfigBadand aConfig:status. The rest of the map carries on. ForceTime0 or negative: 5 s is used; over 10 min: 10 min.Setting: force time.
Power cut
Nothing is kept: every force ends at a power cut, a PLC stop or a program download.
The functions start again on the first scan, with no debounce. To keep the settings
(src, module, ch, nc, debounce, failSafe, the module table), write them
from your program or declare those arrays RETAIN. Never declare the force slots
RETAIN.