Manual LDMicro
Manual LDMicro
INTRODUCTION
============
LDmicro generates native code for certain Microchip PIC16 and Atmel AVR
microcontrollers. Usually software for these microcontrollers is written
in a programming language like assembler, C, or BASIC. A program in one
of these languages comprises a list of statements. These languages are
powerful and well-suited to the architecture of the processor, which
internally executes a list of instructions.
PLCs, on the other hand, are often programmed in `ladder logic.' A simple
program might look like this:
|| ||
|| Xbutton1 Tdon Rchatter Yred ||
1 ||-------]/[---------[TON 1.000 s]-+-------]/[--------------( )-------||
|| | ||
|| Xbutton2 Tdof | ||
||-------]/[---------[TOF 2.000 s]-+ ||
|| ||
|| ||
|| ||
|| Rchatter Ton Tnew Rchatter ||
2 ||-------]/[---------[TON 1.000 s]----[TOF 1.000 s]---------( )-------||
|| ||
|| ||
|| ||
||------[END]---------------------------------------------------------||
|| ||
|| ||
(TON is a turn-on delay; TOF is a turn-off delay. The --] [-- statements
are inputs, which behave sort of like the contacts on a relay. The
--( )-- statements are outputs, which behave sort of like the coil of a
relay. Many good references for ladder logic are available on the Internet
and elsewhere; details specific to this implementation are given below.)
* At the most basic level, programs look like circuit diagrams, with
relay contacts (inputs) and coils (outputs). This is intuitive to
programmers with knowledge of electric circuit theory.
Using LDmicro, you can draw a ladder diagram for your program. You can
simulate the logic in real time on your PC. Then when you are convinced
that it is correct you can assign pins on the microcontroller to the
program inputs and outputs. Once you have assigned the pins, you can
compile PIC or AVR code for your program. The compiler output is a .hex
file that you can program into your microcontroller using any PIC/AVR
programmer.
LDmicro is designed to be somewhat similar to most commercial PLC
programming systems. There are some exceptions, and a lot of things
aren't standard in industry anyways. Carefully read the description
of each instruction, even if it looks familiar. This document assumes
basic knowledge of ladder logic and of the structure of PLC software
(the execution cycle: read inputs, compute, write outputs).
ADDITIONAL TARGETS
==================
It is also possible to generate ANSI C code. You could use this with any
processor for which you have a C compiler, but you are responsible for
supplying the runtime. That means that LDmicro just generates source
for a function PlcCycle(). You are responsible for calling PlcCycle
every cycle time, and you are responsible for implementing all the I/O
(read/write digital input, etc.) functions that the PlcCycle() calls. See
the comments in the generated source for more details.
If you did not load an existing program then you will be given a program
with one empty rung. You could add an instruction to it; for example
you could add a set of contacts (Instruction -> Insert Contacts) named
`Xnew'. `X' means that the contacts will be tied to an input pin on the
microcontroller. You could assign a pin to it later, after choosing a
microcontroller and renaming the contacts. The first letter of a name
indicates what kind of object it is. For example:
* Xname -- tied to an input pin on the microcontroller
* Yname -- tied to an output pin on the microcontroller
* Rname -- `internal relay': a bit in memory
* Tname -- a timer; turn-on delay, turn-off delay, or retentive
* Cname -- a counter, either count-up or count-down
* Aname -- an integer read from an A/D converter
* name -- a general-purpose (integer) variable
Choose the rest of the name so that it describes what the object does,
and so that it is unique within the program. The same name always refers
to the same object within the program. For example, it would be an error
to have a turn-on delay (TON) called `Tdelay' and a turn-off delay (TOF)
called `Tdelay' in the same program, since each counter needs its own
memory. On the other hand, it would be correct to have a retentive timer
(RTO) called `Tdelay' and a reset instruction (RES) associated with
`Tdelay', since it that case you want both instructions to work with
the same timer.
Variable names can consist of letters, numbers, and underscores
(_). A variable name must not start with a number. Variable names are
case-sensitive.
The general variable instructions (MOV, ADD, EQU, etc.) can work on
variables with any name. This means that they can access timer and
counter accumulators. This may sometimes be useful; for example, you
could check if the count of a timer is in a particular range.
Variables are always 16 bit integers. This means that they can go
from -32768 to 32767. Variables are always treated as signed. You can
specify literals as normal decimal numbers (0, 1234, -56). You can also
specify ASCII character values ('A', 'z') by putting the character in
single-quotes. You can use an ASCII character code in most places that
you could use a decimal number.
At the bottom of the screen you will see a list of all the objects in
the program. This list is automatically generated from the program;
there is no need to keep it up to date by hand. Most objects do not
need any configuration. `Xname', `Yname', and `Aname' objects must be
assigned to a pin on the microcontroller, however. First choose which
microcontroller you are using (Settings -> Microcontroller). Then assign
your I/O pins by double-clicking them on the list.
Once you have written a program, you can test it in simulation, and then
you can compile it to a HEX file for the target microcontroller.
SIMULATION
==========
You can set the state of the inputs to the program by double-clicking
them in the list at the bottom of the screen, or by double-clicking an
`Xname' contacts instruction in the program. If you change the state of
an input pin then that change will not be reflected in how the program
is displayed until the PLC cycles; this will happen automatically if
you are running a real time simulation, or when you press the space bar.
Then you must choose the cycle time that you will run with, and you must
tell the compiler what clock speed the micro will be running at. These
are set under the Settings -> MCU Parameters... menu. In general you
should not need to change the cycle time; 10 ms is a good value for most
applications. Type in the frequency of the crystal that you will use
with the microcontroller (or the ceramic resonator, etc.) and click okay.
Now you can generate code from your program. Choose Compile -> Compile,
or Compile -> Compile As... if you have previously compiled this program
and you want to specify a different output file name. If there are no
errors then LDmicro will generate an Intel IHEX file ready for
programming into your chip.
Use whatever programming software and hardware you have to load the hex
file into the microcontroller. Remember to set the configuration bits
(fuses)! For PIC16 processors, the configuration bits are included in the
hex file, and most programming software will look there automatically.
For AVR processors you must set the configuration bits by hand.
INSTRUCTIONS REFERENCE
======================
If the signal going into the instruction is false, then the output
signal is false. If the signal going into the instruction is true,
then the output signal is true if and only if the given input pin,
output pin, or internal relay is false, else it is false. This
instruction can examine the state of an input pin, an output pin,
or an internal relay. This is the opposite of a normally open contact.
If the signal going into the instruction is false, then the given
internal relay or output pin is cleared false. If the signal going
into this instruction is true, then the given internal relay or output
pin is set true. It is not meaningful to assign an input variable to a
coil. This instruction must be the rightmost instruction in its rung.
If the signal going into the instruction is true, then the given
internal relay or output pin is cleared false. If the signal going
into this instruction is false, then the given internal relay or
output pin is set true. It is not meaningful to assign an input
variable to a coil. This is the opposite of a normal coil. This
instruction must be the rightmost instruction in its rung.
If the signal going into the instruction is true, then the given
internal relay or output pin is set true. Otherwise the internal
relay or output pin state is not changed. This instruction can only
change the state of a coil from false to true, so it is typically
used in combination with a reset-only coil. This instruction must
be the rightmost instruction in its rung.
If the signal going into the instruction is true, then the given
internal relay or output pin is cleared false. Otherwise the
internal relay or output pin state is not changed. This instruction
instruction can only change the state of a coil from true to false,
so it is typically used in combination with a set-only coil. This
instruction must be the rightmost instruction in its rung.
The `Tname' variable counts up from zero in units of scan times. The
TON instruction outputs true when the counter variable is greater
than or equal to the given delay. It is possible to manipulate the
counter variable elsewhere, for example with a MOV instruction.
When the signal going into the instruction goes from true to false,
the output signal stays true for 1.000 s before going false. When
the signal going into the instruction goes from false to true,
the output signal goes true immediately. The timer is reset every
time the input goes false; the input must stay false for 1000
consecutive milliseconds before the output will go false. The delay
is configurable.
The `Tname' variable counts up from zero in units of scan times. The
TON instruction outputs true when the counter variable is greater
than or equal to the given delay. It is possible to manipulate the
counter variable elsewhere, for example with a MOV instruction.
This instruction keeps track of how long its input has been true. If
its input has been true for at least 1.000 s, then the output is
true. Otherwise the output is false. The input need not have been
true for 1000 consecutive milliseconds; if the input goes true
for 0.6 s, then false for 2.0 s, and then true for 0.4 s, then the
output will go true. After the output goes true it will stay true
even after the input goes false, as long as the input has been true
for longer than 1.000 s. This timer must therefore be reset manually,
using the reset instruction.
The `Tname' variable counts up from zero in units of scan times. The
TON instruction outputs true when the counter variable is greater
than or equal to the given delay. It is possible to manipulate the
counter variable elsewhere, for example with a MOV instruction.
This instruction resets a timer or a counter. TON and TOF timers are
automatically reset when their input goes false or true, so RES is
not required for these timers. RTO timers and CTU/CTD counters are
not reset automatically, so they must be reset by hand using a RES
instruction. When the input is true, the counter or timer is reset;
when the input is false, no action is taken. This instruction must
be the rightmost instruction in its rung.
(x0, y0) = ( 0, 2)
(x1, y1) = ( 5, 10)
(x2, y2) = ( 10, 50)
(x3, y3) = (100, 100)
lie on that curve. You can enter those 4 points into a table
associated with the piecewise linear instruction. The piecewise linear
instruction will look at the value of xvar, and set the value of
yvar. It will set yvar in such a way that the piecewise linear curve
will pass through all of the points that you give it; for example,
if you set xvar = 10, then the instruction will set yvar = 50.
If you give the instruction a value of xvar that lies between two
of the values of x for which you have given it points, then the
instruction will set yvar so that (xvar, yvar) lies on the straight
line connecting those two points in the table. For example, xvar =
55 gives an output of yvar = 75. (The two points in the table are
(10, 50) and (100, 100). 55 is half-way between 10 and 100, and 75
is half-way between 50 and 100, so (55, 75) lies on the line that
connects those two points.)
(x0, y0) = ( 0, 0)
(x1, y1) = (300, 300)
You can fix these errors by making the distance between points in
the table smaller. For example, this table is equivalent to the one
given above, and it does not produce an error:
(x0, y0) = ( 0, 0)
(x1, y1) = (150, 150)
(x2, y2) = (300, 300)
You can specify the target PWM frequency, in Hz. The frequency that
you specify might not be exactly achievable, depending on how it
divides into the microcontroller's clock frequency. LDmicro will
choose the closest achievable frequency; if the error is large then
it will warn you. Faster speeds may sacrifice resolution.
When the rung-in condition for this instruction goes from false to
true, it starts to send an entire string over the serial port. If
the string contains the special sequence `\3', then that sequence
will be replaced with the value of `var', which is automatically
converted into a string. The variable will be formatted to take
exactly 3 characters; for example, if `var' is equal to 35, then
the exact string printed will be `Pressure: 35\r\n' (note the extra
space). If instead `var' were equal to 1432, then the behaviour would
be undefined, because 1432 has more than three digits. In that case
it would be necessary to use `\4' instead.
Remember that LDmicro performs only 16-bit integer math. That means
that the final result of any calculation that you perform must be an
integer between -32768 and 32767. It also mean that the intermediate
results of your calculation must all be within that range.
For example, let us say that you wanted to calculate y = (1/x)*1200,
where x is between 1 and 20. Then y goes between 1200 and 60, which
fits into a 16-bit integer, so it is at least in theory possible to
perform the calculation. There are two ways that you might code this:
you can perform the reciprocal, and then multiply:
|| {DIV y :=} ||
||-----------{ 1200 / x }-----------||
Mathematically, these two are equivalent; but if you try them, then you
will find that the first one gives an incorrect result of y = 0. That
is because the variable `temp' underflows. For example, when x = 3,
(1 / x) = 0.333, but that is not an integer; the division operation
approximates this as temp = 0. Then y = temp * 1200 = 0. In the second
case there is no intermediate result to underflow, so everything works.
If you are seeing problems with your math, then check intermediate
results for underflow (or overflow, which `wraps around'; for example,
32767 + 1 = -32768). When possible, choose units that put values in
a range of -100 to 100.
When you need to scale a variable by some factor, do it using a multiply
and a divide. For example, to scale y = 1.8*x, calculate y = (9/5)*x
(which is the same, since 1.8 = 9/5), and code this as y = (9*x)/5,
performing the multiplication first:
This works for all x < (32767 / 9), or x < 3640. For larger values of x,
the variable `temp' would overflow. There is a similar lower limit on x.
CODING STYLE
============
|| Xa Ya ||
1 ||-------] [--------------( )-------||
|| ||
|| Xb Yb ||
||-------] [------+-------( )-------||
|| | ||
|| | Yc ||
|| +-------( )-------||
|| ||
Instead of this:
|| Xa Ya ||
1 ||-------] [--------------( )-------||
|| ||
|| ||
|| ||
|| ||
|| Xb Yb ||
2 ||-------] [--------------( )-------||
|| ||
|| ||
|| ||
|| ||
|| Xb Yc ||
3 ||-------] [--------------( )-------||
|| ||
This means that in theory you could write any program as one giant rung,
and there is no need to use multiple rungs at all. In practice that
would be a bad idea, because as rungs become more complex they become
more difficult to edit without deleting and redrawing a lot of logic.
* * *
|| Xa {v := } ||
1 ||-------] [--------{ 12 MOV}--||
|| ||
|| Xb {v := } ||
||-------] [--------{ 23 MOV}--||
|| ||
|| ||
|| ||
|| ||
|| [v >] Yc ||
2 ||------[ 15]-------------( )-------||
|| ||
|| {v := } ||
3 ||-----------------------------------{ 0 MOV}--||
|| ||
|| Xb0 {ADD v :=} ||
||-------] [------------------{ v + 1 }-----------||
|| ||
|| Xb1 {ADD v :=} ||
||-------] [------------------{ v + 2 }-----------||
|| ||
|| Xb2 {ADD v :=} ||
||-------] [------------------{ v + 4 }-----------||
|| ||
|| Xb3 {ADD v :=} ||
||-------] [------------------{ v + 8 }-----------||
|| ||
If the MOV statement were moved to the bottom of the rung instead of the
top, then the value of v when it is read elsewhere in the program would
be 0. The output of this code therefore depends on the order in which
the instructions are evaluated. Considering how cumbersome it would be
to code this any other way, I accept that.
BUGS
====
LDmicro does not generate very efficient code; it is slow to execute, and
wasteful of flash and RAM. In spite of this, a mid-sized PIC or AVR can
do everything that a small PLC can, so this does not bother me very much.
If your program is too big for the time, program memory, or data memory
constraints of the device that you have chosen then you probably won't
get an error. It will just screw up somewhere.
Thanks to:
* Marcelo Solano, for reporting a UI bug under Win98
* Serge V. Polubarjev, for not only noticing that RA3:0 on the
PIC16F628 didn't work but also telling me how to fix it
* Maxim Ibragimov, for reporting and diagnosing major problems
with the till-then-untested ATmega16 and ATmega162 targets
* Bill Kishonti, for reporting that the simulator crashed when the
ladder logic program divided by zero
* Mohamed Tayae, for reporting that persistent variables were broken
on the PIC16F628
* David Rothwell, for reporting several user interface bugs and a
problem with the "Export as Text" function
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.