So much for my "roll"... I've actually been working on the project and just skipped blogging so I'll do a little bit of catch up today.
One of the things that bogged the original project down was the requirement to slavishly recreate all of the PLC-5 instructions and architecture. That's a total dead-end. Far better to get something useful implemented first and then add instructions as needed. Well apply the Pareto Principle (aka the "80/20" rule) and look at the 20% or so of the instructions that are used the most. We're helped by this in looking at smaller and simpler PLCs -- what instructions survive the limitations of a smaller system and what instructions are discarded? More experience in coding actual applications helps as well. With a few notable exceptions, the instructions I've chosen to support first are all of the ones that I've used recently.
Before we dive into that entirely, a brief aside about data types. The types that seem to be common to most PLCs are: Input and Output bit maps, Bit, Integer (16-bit) and Float. Timers and Counters as well. After that come ASCII data and Control types. More and more systems seem to have 32-bit integers as well. For the first round, we'll implement everything in that list up to Counters -- we really don't need Float just yet because I left the most useful instructions out, but implementing the type helps get things rolling.
Phase 1 Instructions
Back to the instructions: First, and foremost, are the basic bit instructions. XIO, XIC and OTE. (I'm going to assume that my readers have a basic knowledge of PLC instructions -- if you don't, then there are a lot of reasonably good references out there. I'm going to add the latching output instructions, OTL and OTU although I will likely never use them -- they're dangerous. Hmmm, maybe I need to write my own instruction descriptions just to have the chance to explain that one. One-shots, ONS are very useful and I'll add OSR and OSF as well, just for completeness.
Naturally, we're going to need to support an OR operation, since without it, all we can do is AND and that really won't be useful. In ladder logic terms, an OR operation is a "branch." The instructions for these aren't as explicit in a graphical interface, but when converting a program to some binary form, they need to be there. Internally, we'll call these BRS (BRanch Start), BRN (BRanch Next) and BRE (BRanch End.)
We now move on to the timer instructions. There are three common instructions, TON (Timer ON), TOF (Timer OFf) and RTO (Retentive Timer On.) Although I've only needed to use TON, both TOF and RTO are slight variations so it makes sense to just implement them all at one time.
Counters are very straight-forward, CTU (CounT Up) and CTD (CounT Down.) They're also extremely useful so they get put on the list. To support both timers and counters, we add the RES (RESet) instruction.
Now we'll add a few instructions to round out the first phase. END is necessary, since every ladder file has one at the bottom. TND (Temporary eND) is mildly useful in debugging and not much extra effort, so it gets tossed in the bag. AFI (always false) is also useful in debugging (ladder rungs are true at the start.) Good programming practice often splits a program into multiple files, so we need some subroutine support. We'll implement SBR, JSR and RET, although without parameter support. That forces us into using globals to communicate parameters, which is a bad practice in general, but actual parameter support can be a challenge so we'll defer that and rely on the globals.
One other useful instruction, especially when dealing with initializing and testing timers and counters is MOV (MOVe.) Consistent with the data type support, it'll allow conversion between integer and float as well as supporting literal values.
Finally, like the branches, we need some internal instructions for the binary form so that the interpreter knows where it is. SOR (Start Of Rung) and EOR (End Of Rung) are all that we need.
Phase 2 Instructions
Phase 2 will round out the collection of "most useful" instructions. As I've been implementing, I've regretted not putting a couple of these into Phase 1, but I'm going to stick with my initial plan.
First we have some basic arithmetic. ADD, SUB, MUL, DIV, SQR (SQuare Root), NEG (NEGate), and CLR (CLeaR.) More complex, but still very useful are SCL (SCaLe) and SCP (SCale with Parameters.) These allow us to convert raw sensor input into something more useful in a program; in part, that gives us some independence from the actual sensors used, meaning that if we change a sensor, we don't have to reprogram the entire thing!
Along with arithmetic, some Boolean logic will help (it can be done in the ladder logic but that can get very messy.) AND, OR, XOR, and NOT are in this set.
Comparison is important. How can we tell if our tank is too full if we can't check the level against some parameter? For this phase, we'll do EQU, NEQ, GRT, GEQ, LES, and LEQ.
Finally, a couple of useful instructions for setting/copying values. COP (Copy) and FLL (Fill.) Although these are "file" instructions, they don't need the Control data type. That one becomes useful much later.
Phase 3 and Beyond!
We'll see how things develop beyond this. There's a rich set of possible instructions there, but implementation from here on out is going to depend on need. Assuming I can get this thing up and running, and connected to something useful, we'll see what's required.
Next up: Binary representations of the instruction set.