OpenPLC-Ladder-Editor/ldmicro-rel2.2/ldmicro/INTERNALS.txt

138 lines
6.0 KiB
Plaintext

LDMICRO INTERNALS
=================
This document describes LDmicro's internal structure. I intend it as a
quick reference for my own use.
ADDING A NEW ELEM_XXX LADDER INSTRUCTION
========================================
It is necessary to make changes in the following places:
* ldmicro.h -- add the new ELEM_XXX #define
-- add the new MNU_XXX for the menu to add it
-- add any necessary data structures to ElemLeaf
-- add prototypes for newly created extern functions
-- if it is a leaf (it almost certainly is), to
CASE_LEAF
* maincontrols. -- add the code to create the menu
cpp -- add the code to enable/disable the menu when we
go from `edit' to `simulate' mode
* iolist.cpp -- add to routines that build the I/O list; even if
it does not affect the I/O list, it must be added
to the case statement in ExtractNamesFromCircuit,
so that it explicitly does nothing
* draw.cpp -- routines to draw the element on the ladder diagram
* loadsave.cpp -- routines to load and save the element to the
xxx.ld file
* intcode.cpp -- routines to generate intermediate code from the
instruction
* schematic.cpp -- WhatCanWeDoFromCursorAndTopology, update the
enabled state of the menus (what can be inserted
above/below/left/right, etc.) based on selection
-- EditSelectedElement, self-explanatory
* ldmicro.cpp -- typically menu/keyboard handlers to call the
AddXXX function to insert the element
* circuit.cpp -- the new AddXXX function, to add the element at the
cursor point
* simulate.cpp -- CheckVariableNamesCircuit, the design rules check
to ensure that e.g. same Tname isn't used with
two timers
* xxxdialog.cpp -- an `edit element' dialog if necessary, somewhere
(most likely to be simpledialog.cpp, using that)
REPRESENTATION OF THE PROGRAM
=============================
(adapted from an email message, so the tone's a little odd, sorry)
A ladder program can be thought of as a series-parallel network. Each
rung is a series circuit; this circuit contains either leaf elements
(like contacts, coils, etc.), or parallel circuits. The parallel circuits
contain either leaf elements or series subcircuits. If you look at a
.ld file in a text editor then you can see that I chose to represent
the program in this way.
This representation makes the compiler a relatively simple problem.
Imagine that you wish to write a routine to evaluate a circuit. This
routine will take as an input the circuit's rung-in condition, and
provide as an output its rung-out. For a circuit consisting of a single
leaf element this is straightforward; if you look at the Allen Bradley
documentation then you will see that this is actually how they specify
their instructions. For example, the rung-out condition of a set of
contacts is false if either its rung-in condition is false or the input
corresponding to the set of contacts is false. Let us say that for a
leaf element L, the output
Rout = L(Rin).
If that element is a set of contacts named `Xin,' then
L(Rin) := Rin && (Xin is HIGH).
(I will use && for AND, and || for OR.)
Next we must figure out how to compile a series circuit, for example (left
to right), sub-circuits A, B, and C in series. In that case, the rung-in
condition for A is the rung-in condition for the circuit. Then the rung-in
condition for B is the rung-out condition for B, the rung-in condition for C
is the rung-out condition for B, and the rung-out condition for the whole
circuit is the rung-out condition for C. So if the whole series circuit is
X, then
X(Rin) := C(B(A(Rin))).
Notice that the series circuit is not defined in terms of a boolean AND.
That would work if you just had contacts, but for ops like TOF, whose
rung-out can be true when their rung-ins are false, it breaks down.
For a parallel circuit, for example sub-circuits A, B, and C in parallel,
the rung-in condition for each of the sub-circuits is the rung-in
condition for the whole circuit, and the rung-out condition is true if
at least one of the rung-out conditions of the subcircuits is true. So
if the whole parallel circuit is Y, then
Y(Rin) := A(Rin) || B(Rin) || C(Rin).
For the series or parallel circuits, the sub-circuits A, B, or C need not
be leaf elements; they could be parallel or series circuits themselves. In
that case you would have to apply the appropriate definition, maybe
recursively (you could have a series circuit that contains a parallel
circuit that contains another parallel circuit). Once you have written
the program in that form, as cascaded and OR'd simple functions, any
textbook in compilers can tell you what to do with it, if it is not
obvious already.
As I said, though, there are many implementation details. For example,
I currently support five different targets: PIC16, AVR, ANSI C code,
interpretable byte code, and the simulator. To reduce the amount of work
that I must do, I have a simple intermediate representation, with only
a couple dozen ops. The ladder logic is compiled to intermediate code,
and the intermediate code is then compiled to whatever I need by the
appropriate back end. This intermediate code is designed to be as simple
and as easy to compile as possible, to minimize the time required for me
to maintain five back ends. That is one of the major reasons why LDmicro
generates poor code; a more expressive intcode would make it possible
to write better back ends, but at the cost of implementation time.
If you would like to see how I compile ladder logic to a procedural
language, then I would suggest that you look at the code generated by the
ANSI C back end.
Jonathan Westhues, Mar 2006, Oct 2007