Subroutines are great for building re-useable and easy to maintain code because it let’s
you use one chunk of code in multiple places.
And that saves you time and effort.
Let’s do an example so you can see how to use subroutines and learn exactly what you
can and – and more importantly - can’t do with them.
Suppose we have an analog input measuring something like the fluid level of a tank.
And when it gets to a certain level let’s scale the input by some scaling factor in
R10 and put that in D1.
And when we reach a certain calculated result, we want to turn on an output coil and an analog
output that is a scaled version of the input.
Now suppose we need to use this code for several different machines or processes.
The temptation is to copy this code, change the input, the calibration parameters and
the outputs.
Then copy it and do it again and again and again for each machine.
But that’s a lot of work, the code is really hard to read and when it comes time to change
or update the code you have to do it all these different places and hope you didn’t miss
anything.
The easy way to do this is to create a single subroutine using this code which is identical
every time we use it and then just tell the subroutine which inputs, calibration data
and outputs to use.
To do that, you call the subroutine with a call instruction.
We haven’t created that subroutine yet, so we’ll just do that here by giving it
a new name.
The yellow dot is reminding me the subroutine doesn’t exist yet.
That’s ok – we’ll fix that shortly.
Our input to the routine is WX0 so internally we’ll copy that to N100 for the subroutine
to use internally and we only need one of those.
We’re multiply the input by R10 – that’s fine.
And for outputs we need a digital internal register to map to Y0 and an internal analog
register to map to WY0.
You can re-arrange the rows, delete rows, and move rows around.
We’ll use this as a power flow enabled subroutine – you can make it edge triggered if you
want to - and we can add a counter that keeps track of the number of times this routine
gets used.
That can be handy when you are debugging.
We’ll leave it disabled for now.
Hit OK and Do-more designer reminds us we have a new subroutine name – did we want
allocate memory for that?
Yep.
Do we want to create the Subroutine for that?
Yep.
The name is already filled in and since you can’t do time slicing in subroutines all
of this is greyed out.
You can password protect the subroutine – but be careful.
Make sure you don’t forget that password – if you do the only way to recover the
PLC is to clear it and reset it to factory default.
Well look.
Here’s our new empty subroutine.
I’ll use Shift up arrow to select, and control-X to cut.
Go over to the new subroutine and I’ll use control-Z to paste it in.
This block of ladder code still has the original references so let’s change those to the
generic internal references.
WX0 becomes N100 here and here.
Y0 becomes C0.
WY0 becomes N101.
All subroutines require a RETURN instruction at the end, but Do-more designer already put
one there for us so we are good to go, though I am going to delete these extra rungs just
to clean things up a bit.
So whatever gets mapped into N100 will get compared to this and if it exceeds that, then
that result will get multiplied by whatever is in R10.
If that result is greater or equal to than this, C0 will get set and the result will
be copied to N101.
To use the subroutine we just use the CALL Instruction to map the external values we
want to use to the internal registers in the CALL instruction - which we already did - so
we are done.
In this subroutine we are using R10 as a scaling factor.
That could be set anywhere, OR we could set it in the CALL instruction.
It’s nice to do it here in the Call instruction because now everything you need to know is
in one place.
That’s all there is to setting up a subroutine.
You create the subroutine, map external registers to internal registers, and you are ready to
go.
But here is the real power.
When we need that code to run another machine, we just use another CALL instruction.
And look at this.
Do-more Designer asks if we want to use an existing parameter list – of course we do.
We want this guy’s parameter list with the 2 ins and 2 outs.
Look at that.
The parameter list is ready to go – we just fill in the empty boxes.
Suppose this new machine’s analog input is coming from WX1, the calibration number
we need is this, the output coil goes to Y2, the analog output goes to WY1 for this new
machine.
Done.
So this line of code is calling the subroutine using these I/O can parameters.
And this line is calling the exact same routine with but with these parameters.
I can now do this on as many machines as I want and every one of them will use the exact
same code in the subroutine.
So if I ever need to change that code I only have to change it in one place.
And that makes maintain and debugging your code so much easier.
There are a couple things to keep in mind when using subroutines.
You CAN use loops in a subroutine, just beware that since subroutines don’t have time slicing
the routine won’t exit until that loop is done which could have a big impact on your
scan time.
If an OUT instruction in a subroutine turns on a coil inside a subroutine that coil will
stay on when you exit the routine because the subroutine is really just a continuation
of the calling logic so the power flow is always present.
When you exit a program the power flow is disrupted so the active OUT instruction turns
off.
So make sure you understand that difference – it’s really important.
You can’t use edge triggered inputs inside a Subroutine because they require two scans
to see one state of the signal on one scan and then the changed state on the second scan.
And there is no way to guarantee the subroutine will be run for more than one scan.
So that’s why Do-more designer won’t allow you to use edge triggered instructions in
a subroutine.
Same thing for asynchronous instructions.
They require more than one scan and we can’t guarantee the subroutine will be run for more
than one scan so Do-more Designer warns us when we attempt to do that.
Other than those couple special cases, for all practical purposes subroutines are no
different than your main code block.
By the way, Subroutines CAN call Subroutines.
You can nest those calls up t 84 calls deep.
Just beware that will have an impact on your scan time.
Subroutines can also call themselves.
We call that recursion.
That’s real handy if you are searching for files in directory structures.
You have a routine that goes down into a folder and looks for a file.
If it finds a folder, it calls another copy of itself to drop down into that folder and
search for the file.
If it finds another folder, it calls another copy of itself to drop down into that folder
and search for the file.
Etc.
We call that recursive programming.
Will you need this very often in your day to day PLC programming?
Probably not.
But when you do – it’s an awesome feature to have in your back pocket.
Just be careful – as you can image this can have a huge impact on your scan time.
Finally, subroutines don’t have a structure associated with the like the other code blocks
do.
Which explains why this counter is an extra thing off to the side.
The other code block have that built into their structures – right?
We’ll that ought to be enough to get you going with subroutines.
If you have any questions, please contact AutomationDirect’s free award winning tech
support during regular business hours.
They will be happy to help.
And don’t forget the forums – there are lots of automation professionals there that
love to share their years of experience.
Just don’t post anything directed at AutomationDirect’s support team there, they don’t monitor the
forums on a regular basis.