Interfacing to the PLC best practice

I would love some opinions from the community when it comes to how faceplates link up to your control modules/ blocks / AOIs / FBs

I am currently rewriting our company's standard blocks.

When it comes to linking a control module to a faceplate I like to have a bit per button that changes the state in the block, and then the status is mapped back.

For example (thumbsuck) there would be two bits, one called OnRequest, and one called OffRequest. OnRequest would be set in the SCADA, and then the PLC would set the On bit to true and reset OnRequest. The On Status is then mapped back to the SCADA separately. This gives me definite switching, the SCADA isn't changing the internals of the block directly, and the bits can be mapped to each button. You could animate them to show that the button has indeed been pressed and as soon as the PLC handles it, it would return to its non-pressed state and the status indication would then show.

This also allows me to bit pack into two seperate words, one which the scada reads from, one which it writes to, and even if the SCADA engineer messes up nothing can be accidentally switched. Nothing of the internal block is exposed, there is an interface.

This is all fine

But, now I get to things like configuration setpoints. something like a High Alarm threshold. Now in all the code I have seen these setpoints are linked directly to the SCADA, changing it on a faceplate, changes it internally.

Is this really the way to go about it though? Is this good enough?

The other way I can see this happening is if you follow the same convention as with the above bits. you would have tags that are commands, and tags that are statuses. If you change the high limit it would be written to a register, the PLC would scan this register, if it is not zero it writes that to the internal variable, and this is then mapped back to a feedback variable which is then displayed on the SCADA

This however doubles the amount of tags used, and brings in more complexity. Hence the question, do I go down this route, is it worth it?

What would be the best practice here?
What do you do?

Ignition's native driver doesn't work well with AOIs. You have some reading to do:

Start here, then read everything linked within, and back-linked to it:

(Some of the issues discussed early in that old topic have multiple solutions discussed over the years since.)

Fundamentally, everything in a given Logix top-level tag should be subscribed at a single pace to allow traffic optimization to happen. Every member of UDTs needs to be marked as at least readable in Studio5000's "External Access" settings. Use "InOut" parameters pointing to UDTs in AOIs that you will use with IA's driver, avoiding access AOI members. For AOIs that you will use with my alternate driver, make them entirely readable too, just like for UDTs.

Hey Phil, I know you have explained this before but I can't seem to find it, what is the reasoning that all parameters and all local tags require at least read only? I thought it had something to do with the fact that all tags are read regardless if they are used or not, but any clarification is much appreciated.

The optimization that makes UDTs advantageous is the ability to read the entire UDT as a stream of bytes, where it is broken into members on the Ignition side. This is an absurdly easy thing for the PLC's message processor to deliver, but it will refuse if any member or nested member of the entire UDT is marked "external access: none".

AOI tags are more complicated than UDTs, but are governed by the same security rule.

In PLC memory, UDTs occupy consecutive bytes just like a "C" structure would. The PLC message processor can just grab all the bytes and stuff them into a reply packet. Similar for consecutive array elements.

1 Like

Hi there

So I actually started out this project by reading a lot of your posts, and this post got me going.

So just to clarify, I am not planning on reading straight from the AOI (Thank you for that, that saved me a huge amount of effort on site), and I am breaking it up into different segments. it differs a bit from the _cmd, _fbk, _cfg you described there, but it follows the same principals.

Most of the Control module's data is a Global tag, connected to the AOI with an In/Out parameter. But certain segments/UDTs are accessible from the SCADA and the rest I deem 'internal' to the control module

Now setting a bit, for example AlarmEnable in the Config section/Udt, triggers a set on the inside of the AOI and AlarmEnable is reset. Then that status, AlarmEnabled is given back in another UDT. So I have a write section, and a read section, and nothing is directly making changes to the AOI's internals. AlarmEnabled isn't directly linked, there's a layer in between.

Here's a screenshot for the Auto Command, which changes the mode to Auto:

The mode is then mapped back out to Sts

The only part accessible to the SCADA is the Cmd UDT, and the Sts UDT, Mode is not. That is my 'internal' data

The question is, when it comes do Integers and Reals. Stuff like Alarm setpoints, do you follow this same way of doing it? Would you have a AlarmHighSP Command, and a AlarmHighSP Status/Feedback?

How do you handle a Setpoint?

I wouldn't do any of that. When not using Class1 I/O buffers, strict segregation between inputs and outputs is not required. I would consider an enable a configuration action that is perfectly reasonable to write directly into an AOI. (Writes don't optimize for UDTs or AOIs in most cases.) I'd still monitor the config bits with an InOut parameter or copied data, but the write can be direct. (Use system.opc.writeValues() to avoid subscribing to AOI internals.)

But note: I generally would not copy from a setpoint in an InOut parameter to an AOI internal--there's no reason the AOI code cannot use such a value directly. SImilarly, any config bits/values you place in a configuration InOut UDT can be used by the AOI logic directly. In other words, use members of the config InOut parameter wherever you would otherwise use AOI local tags, unless that member would make more sense in the feedback or command InOut UDTs.

I will admit that I don't really do any of this any more, if my Class1 I/O drivers aren't involved. I just use my alternate client driver, ensure the AOI is entirely readable, and subscribe to everything at a fast pace. The cost of my module is trivial when balanced against the engineering effort to work around the limitations of IA's driver.

Make elements you deem "internal" or "feedback" to be read-only.

Side comment:
By latching/unlatching individual bits for indicating Auto and Manual mode, it’s possible for your control module to be in both Auto and Manual at the same time (obviously not normally, but if someone in Studio goes crazy with Control+T in the wrong place).

If you use an integer instead (i.e. 1=Manual, 2=Auto) then there’s no way of ending up in an invalid state, it’s simply unrepresentable.

2 Likes

Ladder contacts are way more efficient than comparisons. Simply define one bit for Auto, and Manual is simply Not Auto. If necessary, define another bit for On or Enable.

1 Like

Depends on if Maintenance mode can coexist with Auto and Manual, or if it’s a separate state entirely.

True. For commonly used things, I’ll break it out into bits driven off the backing DINT.

EQU(Sts_Mode,1) OTE(Sts_InManual);

DINT for 0, 1, and 2 ?? Oy!

I prefer:

XIC(On) XIC(Auto) OTE(InAuto)
XIC(On) XIO(Auto) OTE(InManual)

Then you can pack a bunch of named mode bits into just a few DWORDs. Poorly designed data types hurt you all over the place: memory usage and excess message traffic in particular.

You can also use the following to robustify the original Auto & Manual bits:

XIC(Auto) XIO(Manual) OTE(InAuto)
XIC(Manual) XIO(Auto) OTE(InManual)

As seen with real Hand-Off-Auto switches feeding PLC inputs.

2 Likes

I’d use 0 as an additional unknown state (when communicating between controllers, having an uninitialized structure default to unknown is nice), but let to my own devices I’d default to using a DINT :man_shrugging:

But you’re right, SINT would be more than enough and would indeed pack much better with its status bits.

In the spirit of sharing, this is how I’d do it:

MOV(0, Wrk_Mode)
XIC(Inp_Manual) MOV(1, Wrk_Mode)
XIC(Inp_Auto) MOV(2, Wrk_Mode)
MOV(Wrk_Mode, Sts_Mode)

EQU(1, Sts_Mode) OTE(Sts_InManual)
EQU(2, Sts_Mode) OTE(Sts_InAuto)

Verbose? Yeah. But when someone asks for a new third mode:

  1. There’s a nice place to slide the logic in.
  2. The data model doesn’t have to change.

For standardized and static stuff like HOA switches this is overkill, but I think consistency is valuable.

Unless it's a true HOA. Probably not that common though.

See my follow-up in comment #10.

If by "consistency" you mean to use the overkill everywhere, I strongly disagree. Extra complexity in the most common cases is actively harmful for maintenance and troubleshooting.

If there really is only two states, use one bit.

If there can only be three or four states, use two bits, and fan out the cases with boolean operations.

Beyond that, a numeric-based consistent approach makes sense.

3 Likes