In this guide I will assume you are familiar with Ladder Logic diagram symbols. If these are new to you, I suggest you first familiarize with the symbology. One good starting point is this Wikipedia article.
I will also assume you have assembled and tested the board following the instructions in the READ.me file.
Before heading into the actual programming of the board, let's familiarize with the hardware. On the top side of
the board you have two switches labelled HI/LO
and RUN/STEP
respectively. These control the clock generator. In run
mode the clock runs freely and can be configured between two speeds. HI
is the "normal" running speed, which is
suitable for running programs and see inputs and outputs being processed timely. LO
is a 1Hz clock which is mostly
suitable for a majestic display of blinking lights. If you run your program at this clock speed remember you might need
to wait seconds between applying inputs and seeing the effects on the output so, generally, you won't use it for testing
purposes.
In STEP
mode the clock generator is stopped, so HI/LO
has no effect. You will be providing clock pulses by
pressing the STEP
button. This mode is very useful for following your program step by step and seeing exactly how the
various registers change as instructions are executed.
On the board you will find the RST
button. This resets the Program Counter to zero and also clears all outputs and
the SPR
registers. It's always a good idea to press RST
after uploading a new program, this will ensure the program
starts from a clean state and is not affected by the settings left by the previous one.
Note the Program Counter is reset to zero when you press RST
but, due to the nature of the counter, you won't see
this relected in the ADDR
bus until you STEP
once.
The board provides 7 "inputs". They are not actual external inputs but a combination of switch and button that can be
used to simulate an external "Normal Open" or "Normal Closed" switch or button. By moving the switch you can change the
input "normal" state to either on or off, as shown by the related LED on the board (IN0-IN6
). The button always
negates the current status. So, for instance, if you set the switch so that the input in on, by pressing the button it
will go off.
Similarly, the board provides 7 outputs which are LEDs (OUT0-OUT6
) that can be set on or off under software control.
There is a timer onboard that can be set from a short, about 0.5s, duration to about 10s through RV1
. This timer is
triggered by an additional output, not available as a general output (OUT7
). The output of the timer is wired to an
input which also cannot be set like the others with the switch and button (IN7
).
There is no shortage of LEDs on the board. The ADDR
and DATA
section show the status of the RAM Address and Data
bus. Here you can see the location in the program being executed and the actual instruction and its argument.
The J
, RR
and W
LEDs show the internal status of registers in the MC14500. They are respectively Jump, which goes
high when a JMP
instruction is executed; RR
which contains the result of the last operation; W
which goes high
when a STO
instruction writes to the outputs.
The rest of the LEDs should be self-explanatory as they give a visual feedback on the status of the switches (such as)
LO
, HI
, R
for RUN
and ST
for step.
In this first example we will be simply reading the status of one of the inputs and mirroring it to an output. A ladder diagram for such application would look something like this:
This can be translated to MC14500 assembly (with some extra code, we will get to that in a moment).
.board=PLC14500-Nano
IEN IN6
OEN IN6
LD IN0
STO OUT0
JMP 0
The .board
directive is metadata that serves the purpose to tell the assembler on which type of board we want to run
our code. This will have an effect on the generated bytecode as well as will provide relevant errors should some part
of the program make use of features not available in a certain board type.
The next two instructions IEN
and OEN
are the two intruders that don't appear in our ladder diagram. These
instructions
stand for Input ENable
and Output ENable
. They are a powerful construct that allows to build conditionals that, in
practice, surf over (skip) a block of code. But, for the time being, let's not digress into this. At this stage it's
enough to know that, after reset, the MC14500 "disables" its input and output or, in other words, ignores any actual
value read from the inputs and any write operation to the outputs. This is a bit unfortunate as it means we must have
such instructions in every program or the program would do nothing at all. For simplicity, we tell the MC14500 to
enable input/output operations on the status of input IN6, so we will need to make sure this is flipped to ON. This
is not how usually programs are written, as this wastes one input. However, for simplicity, the examples in this guide
will use this construct until logical operations are explained later.
The next two lines are the actual representation of our ladder diagram. The LD
instruction will load the value of IN0
into the only register RR
. After this, STO
will store the value in RR
into OUT0
so, in practice, OUT0 will
reflect the status of IN0
.
The last instruction JMP 0
, jumps at the start of the program. Note! The PLC14500-Nano doesn't have a pre-settable
Program Counter, for this reason it allows jumping only to 0. The assembler will error if you try to do otherwise.
However, worry not, with the IEN
/OEN
instructions and the SKZ
conditional there are plenty of options to have more
complex flows than just a single loop.
Let's go ahead and assemble the program. I'm assuming here you downloaded the release zip and unzipped it in a folder.
asm14500.exe examples\example1.asm
If you now look into the examples
folder you should have a 256-bytes file named example1.bin
. The file is 256 bytes
because that's what the bootloader of a PLC14500-Nano expects, your program is just 5 bytes long, the rest has been
padded with 0x0F
. You can see this with a binary editor:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 EA EB 81 88 0C 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F êë.ˆ............
000000E0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F ................
.....
000000F0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F ................
Before we can test the program on the board we will need to identify which COM port gets assigned to our board. Also,
ensure you followed the board assembly instructions and burned the bootloader into the Arduino Nano. The easiest way
to find out the right COM port is to open the Arduino IDE, open the Tools\Port
menu and take a note of what ports
are available (possibly none), then plug in the PLC14500-Nano and check again Tools\Port
to determine the new one
that appeared. In this guide I will assume COM3
, adjust examples as needed.
Let's now load the assembled program to the board:
flash14500.cmd examples\example1.bin COM3
You should see the ADDR
and DATA
LEDs on the board flashing for a few seconds, wait the command to complete and then
press the RST
button on the board to ensure it's running. Also make sure the two switches on top are set to HI
and
RUN
respectively. This ensures the clock is running at normal speed.
To test the program make sure IN6
is switched to on (remember, this reflects the input/output enable of the MC14500),
if you now press momentarily the IN0
button you will notice the OUT0
LED will follow.
Go ahead and turn off IN6
, now regardless of the state of IN0
the OUT0
will not change.
Congratulations! You wrote your first PLC14500 assembly program! In truth, it could be the second if you did the smoke test in the assembly instructions, but this is the first one you, hopefully, understand.
At this stage you might be thinking: surely that was a lot of soldering and typing to just get a LED controlled by a button, I know how to do that with a button and a LED! And you are right, it gets worse though. The next example won't change things much, it will show what you could do with two buttons and a LED. However, this will provide the foundation for more advanced setups not achievable by wiring alone.
The MC14500 provides instructions to perform logical operations. All logical operations are performed between the RR
register value and the input value provided as argument. Below is an example program that will switch on OUT0
only if
both IN0
and IN1
are on.
.board=PLC14500-Nano
IEN IN6
OEN IN6
LD IN0
AND IN1
STO OUT0
JMP 0
This is actually very similar to the previous example, just with the addition of the AND IN1
line. This command will
take the value currently in the RR
register (IN0
in this case) and do a logical AND
with the value in IN1
, the
result will be stored in RR
and, on the next line, stored in OUT0
.
If you assemble and load the program above you will notice you need to have both IN0
and IN1
on for OUT0
to go on.
There's not much point in providing examples for other logical operations, there will be many occasions later on to use them directly in various applications. I will just list below all logical operations provided by the MC14500 for reference.
Instruction | |
---|---|
AND | RR = D AND RR |
NAND | RR = !Data AND RR |
OR | RR = Data OR RR |
ORC | RR = !Data OR RR |
XNOR | RR = !(Data XOR RR) |
We've seen, out of necessity, IEN
and OEN
instructions above. In the examples so far both commands were just run at the
start of the program and the argument was always IN6
. This is perfectly fine for many programs you might want to write
on your trainer board. However, not only this wastes one input, it also fails to make use of the instructions for their
intended purpose. To enable unconditionally inputs and outputs, as we do in these simple examples, you can do the following:
ORC RR
IEN RR
OEN RR
RR
refers to the result register of the MC14500 which is, conveniently, mapped as an input, so it can be addressed
by instructions as if it was an input/output. To understand this block of code, refer above to the ORC
operation in the table above. This is
an OR
operation between the value in RR
and the complement of whatever is currently on the RR
input which, of course,
is RR
itself. This means that, regardless of the value of RR
we will always be making an OR
operation between the
value and its negation, ie always between a 0
and a 1
, hence the result will always be 1
.
If you really want to dive deeper, you might think this is faulty because, on reset, inputs are disabled so the ORC
wouldn't be able to read RR
. This is true, however, at reset RR
is initialized to 0
, and reading an input when
inputs are disabled reads always a zero. For this reason, at reset, we will still be making an OR
between 0
(RR
)
and the complement of the read input, which will result in a 1
.
NOTE Boards up to REV.B (included) didn't have RR mapped to the I/O space and, for this reason, were not able to
use IEN
/OEN
instructions driven by the application status, as described here. If you have a REV.B
board you can easily enable this
functionality with a simple mod. See instructions in the /board folder. If you don't wish to modify your
board, please keep using the IEN IN6
/OEN IN6
construct also in the code in the next sections. You will not be able,
however, to follow more advanced techniques to create IF
/ELSE
/ENDIF
blocks.
With the next example things will start to get more interesting and will move a step further from something that can be achieved by just wiring buttons and LEDs. From here I will start also to introduce the examples in terms of real life applications instead of abstract "the output will be on when this and that happens".
For this example we will consider a common scenario in industrial machines where we have a motor controlled by two buttons, one to start it and one to stop it.
I will take the occasion of our first real-life scenario, to digress and introduce a feature of the assembler that will make your programs easier to understand and maintain. The assembler metadata allows to provide friendly names for the inputs and outputs of the board, these names can then be used throughout the code instead of the input/output names. This will not only make the code much easier to read and understand, it will also allow to change the inputs/outputs assigned to each function a breeze with no risk to forget to change them somewhere in the code.
Friendly names are declared in the assembly file like this:
.io_SWITCH_A=IN0
.io_SWITCH_B=IN1
.io_MOTOR=OUT0
This will then allow us to use them throughout the code like:
LD SWITCH_A
AND SWITCH_B
STO MOTOR
We will see a practical usage soon, but before we can implement our machine control buttons there's one more notion I
need
to introduce which is the Scratchpad RAM (SPR
in short). The SPR
is composed of 8 bits mapped as inputs and outputs
where the outputs are always looped back to their respective inputs. In other words, if you write something to SPR0
you
can read it back later on in the program. This is not true for IN
and OUT
that are not looped back (writing a value
in OUT0
doesn't affect IN0
, as one would expect.). The SPR
is useful to store values we want to persist and use
in different parts of our program.
The ladder diagram for an industrial motor start/stop control would look like this:
START
is our start button and STOP
the stop button. MOTOR
is, you guessed it, the motor. Things get interesting
with RUN
. This is not an actual button or actuator, this represents what you would achieve with a relay. When the coil
of the hypothetical RUN
relay is energized the RUN
contactor would be closed. This, in practice, will keep energized
the RUN
relay coil even when START
is released. This allows us to achieve one of the goals: latch RUN
when START
is pressed. The STOP
button (normally closed), will de-energize RUN
when pressed and, as a consequence, reset the
latch
completing the needed functionality. The bottom rung in the ladder simply applies the RUN
contactor to the motor.
As you might have guessed by now, RUN
is neither an input nor an output, it's just a value we need to store and, as
we just learned, the SPR
is there exactly for this purpose. The code below implements the described start/stop logic.
.board=PLC14500-Nano
.io_START=IN0
.io_STOP=IN1
.io_RUN=SPR0
.io_MOTOR=OUT0
ORC RR
IEN RR
OEN RR
LD START
OR RUN
ANDC STOP
STO RUN
STO MOTOR
JMP 0
I won't go into too much detail of the code as the operations are like those described previously. What is to be noted
is the usage of RUN
(which is a label for SPR0
) to store the status of the motor. If you assemble and load this
program you will notice that when you press IN0
the output OUT0
will go high. Take a moment to look at the SPR0
LED as well and notice how it also goes on. Pressing again IN0
(START
) doesn't to anything, however
pressing IN1
(STOP
) will switch off OUT0
(MOTOR
) as well as SPR0
.
Now that you have a bit more complex program take a moment to familiarize with the step of moving from a ladder diagram
to the assembly code. You might have jumped on the chair when, above, I said "and this ladder diagram can be written as
this program", I won't blame you. However, if you look closely at the first rung START
and RUN
are in parallel, this
means they are passing current if one OR the other is on (LD START
OR RUN
). Moving towards the right of that same
rung we see that current flowing though START
or RUN
can't reach the RUN
coil if STOP
is pressed (it's a Normal
Close contact), so we need ANDC STOP
(if stop is on, ie pressed, we will do an AND with the complement which is off,
this will always result in off, regardless of the status of START
and RUN
).
With a bit of exercise you should be able to read the ladder diagram from top to bottom and type accordingly the assembly code.
PLC14500-Nano comes with one analog onboard timer. The timer (TMR-0
) can be triggered with a high pulse on OUT7
. The
output of the timer output can be read on IN7
. Once the timer is triggered, IN7
will go high for a time that can be
set with RV1
, after this time has elapsed IN7
will go back to low until the timer is triggered again.
We can use the timer to implement, for instance, the controller for a set of lights on the staircase of a block of flats. We will assume 4 buttons, one per floor. The desired behaviour is that which ever button is pressed will cause the lights to turn on and, after a preset time the lights will go off.
.board=PLC14500-Nano
.io_BUTTON_A=IN0
.io_BUTTON_B=IN1
.io_BUTTON_C=IN2
.io_BUTTON_D=IN3
.io_LIGHT=OUT0
ORC RR
IEN RR
OEN RR
LD BUTTON_A
OR BUTTON_B
OR BUTTON_C
OR BUTTON_D
STO OUT7
LD IN7
STO LIGHT
JMP 0
The buttons handling should be clear by now, we just load each button status and OR it into a final status that we use
to trigger TMR-0
(by storing it in OUT7
). We then store into LIGHT
the status of IN7
, which is the timer output.
If you load this program you will notice that by pressing any buttons IN0
-IN3
will cause OUT0
to become high.
After a time, dependent on the setting of RV1
, OUT0
will go off.
Below is the PLC14500-Nano I/O map.
NOTE: Before REV.C, $7 was a general Scratchpad RAM location and the board provided no means to read RR. Having RR mapped to an input allows to unleash the full power of IEN/OEN operations. If you have a pre REV.C board fear not! You can enable the same feature with a very simple mod, see instructions in the /board folder.
Given the nature of the board I won't include more examples in this guide as, I believe, most will build this for the fun of writing new programs and figure out things on their own. The above should have provided enough info to familiarize with elements specific to the board and toolchain.
I suggest you get familiar with the Motorola "MC14500B Industrial Control Unit Handbook" that provides a plethora of
information on the processor itself and on its programming model. In particular, you can find there sections on how
to implement conditionals and how to unleash all the power of the IEN
/OEN
instructions for conditional loops.
Some ideas of programs you might want to try to write, in no particular order:
- Motorized gate with limit switches on open/closed and an obstruction sensor that will cause the gate to re-open.
- A two floors goods lift, with UP/DOWN buttons, sensors to ensure the doors are closed and floor switches
- A motor START/STOP controller with an over-current protection switch that will prevent restarting the motor for a certain time if triggered
- A delayed on timer for a bathroom fan, so that the fan only starts after some delay after the lights are on and keeps running for some time after the lights have been switched off