C programming question for Arduino

I'm wanting to make timers that work like PLC timers, a condition comes true, a preset period of time later code is executed.

It would be something like:

void mytimer(bool enable, long preset)

void mytimer( (input 1 is on), 1000) // If a condition is true (input 1 ==

1) for 1000ms then code is executed. { // after preset time delay, as long as enable stays true, code here would execute }

This timer would be multi - instance, I'm thinking about a struct mytimerdata[] that would hold the data for one instance of the timer.

The timer data structure would have a long for time started and preset, plus some boolean for timer enabled, timer timing, timer done, last_state. So in this timer function if the last state was disabled and the current state is enabled (condition became true) you record time_started=millis();. If the timer is enabled then you compare millis(); with time_started + preset. If enable condition is not true then clear bits for enable, timing, and done.

The structure array would be how I would use multiple instances, in this case multiple timers.

Now the question How do you control the execution of the code after the function? Does returning a value control this?

In case I'm not clear:

if(condition) { // how does if function control the execution of this code? }

Any better ideas? I'm not wanting to wait for a condition to come true, just control the execution of code after a preset time delay, that way other things get processed in the loop.

RogerN

Reply to
RogerN
Loading thread data ...

PLC firmware uses 'hidden' looping (and interrupts) to give the illusion of simultaneous execution of multiple ladder logic instructions. Almost like an interpreted HDL.

Your if (..) {}; example just continues to the next statement if the condition is false, and unless you check it again the code between the curly brackets will never be executed.

In C you can pass pointers to functions as parameters, so you could set up a timer routine which would be called periodically that executed a function upon timeout, but you need to write code to do it.

General rule is to do only what is absolutely necessary within interrupt service routines and get the heck out of there, setting flags or stuffing queues to have more complex things done later. IOW, I would not suggest calling the functions from within an ISR.

Best regards,

Best regards, Spehro Pefhany

--
"it's the network..."                          "The Journey is the reward"
speff@interlog.com             Info for manufacturers: http://www.trexon.com
Embedded software/hardware/analog  Info for designers:  http://www.speff.com
Reply to
Spehro Pefhany

I'm not sure exactly what you want. I feel like I should, but I haven't had breakfast yet!

But I can see the core of what you want: event A happens, then at some time T later you want code B to execute. Usually the way that I do this in a non-interrupt-driven system is to set up one of the processor clocks to free run, then on the event set

extern int time_now; // set by hardware int timeout;

timeout = time_now + T; enabled = true;

Then in my task loop I'll have a check

if (enabled == true && time_now - timeout > 0) { // put your code here }

Note that the order of operations in the test is important: time_now _will_ roll over; if you test on time_now > timeout you will get different, and quite possibly undesired, behavior from your code. Note also that whatever size your 'int' is (probably 16 bits), this method will only support a timeout of MAX_INT (32767, if 16 bits) ticks.

Alternately, you can have a routine that just runs at 1ms intervals, and keep track of all your timers there.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

I.e., in ladder logic, you have a single rung with the conditional enabling a timer. The completion of the timer enabling the "code here would execute" portion of that *other* rung.

In a ladder logic analogy, you would do:

if (timer_expired) { do_something(); } try_again();

If you further want to qualify this execution with the initial "arming" condition remaining true:

if ((input_1_is_on) && (timer_expired)) { do_something(); } try_again();

Here, "try_again()" is some mechanism that causes other "rungs" to be evaluated.

And these "rung-equivalents" would be continuously re-evaluated. A more intuitive control structure would then be:

while ((input_1_is_on) && (timer_expired)) { do_something(); try_again(); }

This ensures everything in the while-loop is done before the input is re-examined and/or timer is re-verified as having expired (ask yourself how can an expired timer NOT remain expired?)

But, there is nothing in this model that precludes something like:

while ((input_1_is_on) && (timer_expired)) { while (FOREVER) { twiddle_thumbs(); } /* NOT REACHED */ try_again(); }

In this case, this one "rung" effectively monopolizes the processor (since try_again() is never invoked!)

Similarly, even if you don't (carelessly) twiddle_thumbs FOREVER, if do_something() takes a REALLY LONG TIME, then the other rungs will see their execution deferred. If do_something() takes a varying amount of time, then this deferral becomes unpredictable (the order in which the rungs are evaluated determines your actual system response; changes to one rung affect the performance of all others. This is usually not A Good Thing)

PLC's give the illusion of parallel operation (parallel rung evaluation) by running a multitasking interpreter behind the scenes. In essence, the interpreter evaluates part of each rung before advancing to the next rung (to resume evaluation from wherever it left off "last time" it looked at this rung).

[you can also evaluate an entire rung before advancing to the next but then the complexity of each rung affects its neighbors]

PLC's optimize this "rung switching" ability by carefully considering the complexity of each operation that can be invoked within a rung as well as each condition that can be examined. They publish a metric that effectively indicates how many of these evaluations can be performed per second.

But, you have a very limited set of conditions/operators available to you with ladder logic. Each is well defined in terms of the effort required to implement it. This doesn't translate well to a conventional programming environment.

I've already shown how you wouldn't want to treat the entire if/while block as a single operation (because it can take FOREVER to execute). Perhaps treat individual C statements as schedulable operations? What happens when someone writes the single line of code: count_to(1000000); while someone else has: count++; count++; count++; ... The former has gimmicked the system to get more resources (his *one* line of code gets executed once for each time one of the other guy's "count++" gets executed).

Instead, you want to build a mechanism that sits *under* the code (like the interpreter that sits under the ladder rungs) and parcels out resources dynamically for each of the various "programs" co-existing on the machine. A preemptive multitasking OS (MTOS) gives you this capability (which need not be an RTOS).

Then, each rung becomes:

while(FOREVER) { if (enabling_conditions) perform_actions(); }

Noting that perform_actions can affect enabling conditions for other "rungs".

You can then build a task that implements any number of timers which can be enabled/disabled individually, set to expire at different times, etc. If the number of timers is large, you can use delta queues to reduce the cost of updating them to something closer to constant time. Or, you can cheat and run:

timer_t timers[NUMBER_TIMERS];

update_timers() { now = get_system_time(); elapsed_time = now - last_time; last_time = now; if (elapsed_time > 0) { for (index=0; index < NUMBER_TIMERS; index++) if (timer[index] > elapsed_time) { timer[index] -= elapsed_time; } else { timer[index] = 0; } } } }

each time you process the "rungs".

Here, you can set a timer to expire at some time in the future by simply loading it with a value: timer[MY_TIMER] = 37; and then wait for the timer to "expire" by: while (timer[MY_TIMER] > 0) /* SIT IDLY */ ; or, test the state of the timer: if (timer[MY_TIMER] > 0) { do_something_while_timer_is_still_running() } or, *retrigger* an unexpired timer: if (timer[MY_TIMER] > 0) { timer[MY_TIMER] = 37; } else { timer_expired_before_I_managed_to_retrigger_it(); } or, abort a running (or non-running!) timer: timer[MY_TIMER] = 0; etc.

Personally, I frown on doing any "timer arithmetic" to alter a timer's "remaining time" to anything other than a full reload (retrigger) or "forced expiration".

No idea what's inside an arduino but surprised if it doesn't have *some* hooks to make this possible.

If you only have a *single* thread of execution (i.e., one big "loop"), then you have to take on the task of updating the timers at some point in that loop so that their values are available at other places in the loop.

E.g.,

big_loop() { update_timers(); do_other_stuff(); }

You also have to ensure that the all of the "other stuff's" are "fair" (using whatever definition of "fair" is appropriate for you) in how they share the set of resources in that *single* thread.

For example:

do_other_stuff() { do_one_thing(); do_other_thing(); }

do_one_thing() { while (timer[MY_TIMER] > 0) /* SIT IDLY */ ; // do what you REALLY wanted to do }

will block do_other_thing() from executing until timer[MY_TIMER] reaches '0'. But, it will also block update_timers() from executing -- so, timer[MY_TIMER] never gets updated! Furthermore, this sort of thing might not be obvious during testing:

do_one_thing() { ... if (today == "April 15 2012") { while (timer[MY_TIMER] > 0) /* SIT IDLY */ ; // do what you REALLY wanted to do } else { // do whatever you normally do } }

Will hide the bug until April 15th...

Reply to
Don Y

If you had something like a ThreadX OS, it would be simple.

TimerISR() {..}

Main() { ... StartTimer( timeout, &TimerISR);

...go about business

} You can have a bunch of these system timers...

regards, Bill ps: no syntax checking!!!

Reply to
Bill Martin

I usually use an interrupt that runs at 1ms and place timer code in there, like this:

unsigned int delay, delay1; // 65535 ms

TimerISR(){

if(delay) --delay; if(delay1) --delay1;

}

Then use them like so:

delay=1000; // 1 sec

while(delay){

// your code here

}; // blocking function

You could also just run a counter in a ISR and create a function GetTickCount (aka Microsoft) And use safe subtraction (handles the rollover) see

Either one will work, just watch for getting hung up in a loop (Use a timer to get out!)

Cheers

Reply to
Martin Riddle

Oops, something I didn't make clear.. millis() is part of the Arduino functions. From Arduio Reference:

millis() Description Returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days.

So what I want to do is make my loop run as fast as it can and control by setting flags and conditions, sort of parallel processing like in a PLC, not sequential style programming.

A little background. My first computer was a sinclare zx 80 (IIRC) for $199, later I bought a Commodore 64, did some Basic and 6502 assembly language. Later I played with PIC microcontrollers, 16C84, 16f84 came out later. So about everything I learned was pretty much sequential style programming. When I got a 286/12 I bought Turbo C++ and a "Teach yourself C" book, got where I could write programs but not enough practice to get good at it. After some time I ended up with an Engineering company that did industrial automation type work. It was difficult at first for me to get PLC's to work like I wanted, I was used to writing sequential type of programs and the PLC was parallel, all rungs execute at the same time, well, not really but I learned that I should write programs as if they did.

Here's a project I designed, built, and programmed the controls for.

formatting link

You can see there are a lot of things happening at once.

Anyway, over the years I have pretty much figured out how I can write the same type of programming in other languages beside ladder logic. The straight runs just AND and OR conditions to control outputs when they should be according to the logic. I think I could write the bulb machine program in 'C' very much like it was written in ladder logic.

One of the problem areas is getting timers to work like PLC timers, they can have dozens timing multiple events at once, none interfering with the others. So I'm trying to come up with a good function that will compare the preset time with the system time and execute additional code 1 pass per pass through the loop (won't hang up the loop).

Now I know I can write something to hog the processor with a loop and halt the main loop, but I will have to be careful not to, if it happens I will have to correct the error.

The system timer will overflow once every 50 days so I need to set an "will_overflow_bit" when the destination time is less than the current time ( a positive number plus a positive number is greater unless there is an overflow). So once the timer condition is true, and the timer preset is timed out, the code will be executed once per loop, like an if() function, not cycled inside continuously like a while() function.

RogerN

Reply to
RogerN

In that case, then Arduino's millis() is my time_now, your ints should be

32-bits, and I'm pretty darned sure that millis() just rolls over from 0xffffffff to 0. That would be 49 days, 17 hours, 2 minutes 47.296 seconds, which is "about 50 days" for anyone but Spock.

You avoid the rollover by doing what I showed you. For example, if the time is 0xffffff00 and you want an event to pop off 1 second later, you would calculate a timeout of 0x000002e8. _If_ you did a test on

millis() > timeout

then it would be popping off all the time; but if you test on

timeout - millis() > 0

then you would find that timeout - millis() would have the value 1000, then 999, then 998, etc., down to 0 and then to -1 (if you are using signed arithmetic), or UINT32_MAX (if you are using unsigned arithmetic). Either one of these conditions can be tested for, and your test will be unambiguous as long as your test often enough and don't have really long times -- basically, as long as your desired timeout plus the amount of time you wait to do the check is shorter than 25 days, you should be OK.

--
My liberal friends think I'm a conservative kook.
My conservative friends think I'm a liberal kook.
Why am I not happy that they have found common ground?

Tim Wescott, Communications, Control, Circuits & Software
http://www.wescottdesign.com
Reply to
Tim Wescott

I was aware of the rollover problem and was going to handle it something like:

boolean mytimer(boolean currentState, unsigned long Preset) { unsigned long currentTime = millis(); if(previousState = 0 && currentState=1 // first pass that timer condition became true { TimeUp = currentTime + Preset; if{currentTime>TimeUp) overflow = 1; }

if(overflow==1 && currentTime=TimeUp) return(1); return(0); }

Then the above might be used in a program something like:

if(mytimer(a>b(some condition to test for), 100) // If Evaluates my timer, returns zero until after 100 milliseconds of true condition (a>b example here) { // Code to be executed after timed condition is true for preset time (turn light on/off, read sensor, position servo, alarm, or whatever }

Your method works without using the overflow bit so that simplifies it some.

Now what I would like to do is define a data structure that I can use so I can have multiple instances of the timer.

These are new to me but maybe get the idea; struct mytimerdata { unsigned long Preset, currentTime; boolean previousState, currentState; } struct mytimerdata analogUpdate, faultDelay, UpdateDisplay;

Now I'm wondering how I can point my different timers to their data structure.

RogerN

Reply to
RogerN

Just make your "mytimer" function take a pointer to a "mytimerdata" struct, and do the obvious inside.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

Can the application tolerate the startup time from a reset every so often (much sooner than 50 days)? I think ATMega328P starts in less than 10ms and so it seems like pulling the reset pin to high from time to time would alleviate the need for the extra variable. Atmel does not recommend using another pin for resetting AVR but it does work. You can also setup a watchdog timer to reset it in the software (and free an I/O pin).

Besides, I don't know if I would trust an Arduino board to run for 50 days without a reset anyhow. I've had it hang on me for various reasons after several days of continuous operation and I think it might just make the whole system a bit more reliable if you reset it at a predictable time.

------------------------------------- /\_/\ ((@v@)) ():::() VV-VV

Reply to
DA

That's what I want to do big_loop() { read_inputs(); read_operator_interface(); process_add_ons(); control_logic(); check_faults(); update_display(); update_outputs(); }

Everything will need to pass right through. if(condition) turn on output or set logic bit; else turn off output or reset logic bit;

The only allowed exceptions would be if I need to read or write precision timed pulses where a loop scan time would cause too large of an error. For example if I were going to read a pulse from a radio control receiver ranging between 1 to 2 milliseconds every 20 milliseconds, the precision time would be more important to be accurate than general scan time varying a millisecond or so.

This sort of thing won't be allowed, it would destroy scan time. The control of execution won't be by holding up scan time but rather by setting up the condition. example: will not use while(not ready); // waste time until ready;

instead use: if(ready)execute code it's ready to execute;

That's the thing I'm trying to avoid, never sit idly except for short time for precision timing. If this doesn't work well enough then I'll set up interrupts for time critical functions. The timer I'm describing checks to see if it's time to execute the code, if not it doesn't wait, just goes on to the next instruction.

RogerN

Reply to
RogerN

So, throw the "update_timers()" in there, somewhere.

If the only places you examine/alter timer values are in update_timers() and, thereAFTER, in the various other functions in your loop, you don't have to worry about atomic operations (i.e., no one will alter timer[foo] while is reading it -- regardless of the implementation data type).

You also have to decide if you are implementing

*timers* or timeOUTs. A timer can be used to *measure* time; a timeout is used to delimit a time *period* (i.e., 20 seconds from now).

If you want to blink a light, you would typically do:

light_on(foo); delay(ON_INTERVAL); light_off(foo); delay(OFF_INTERVAL);

Note that this only guarantees that the light stays on for AT LEAST the ON_INTERVAL (and off for at least the OFF_INTERVAL). I.e., if your "scan time" has too much variation, then the lamp might be on for ON_INTERVAL+epsilon1 and, similarly, off for OFF_INTERVAL+epsilon2.

This is important if, for example, you want to maintain synchronism between periodic actions (imagine having two different lights blinking from two different INSTANCES of this bit of code. It is possible (likely!) that one will creep wrt the other (and this creep can vary).

OTOH, if you are aware of this, you can define a "task" that does:

raise_flag(foo); delay(ON_INTERVAL); lower_flag(foo); delay(OFF_INTERVAL);

and have *all* tasks that need to be synchronized do:

if (test_flag(foo)) { // this is the on portion of the synchronized cycle } else { // this is the off portion }

One of the big advantages of using timers for delays/timeouts (instead of to measure elapsed time) is that you already implicitly have acknowledged that some things might take a bit longer than planned (because you don't know what the other activities in the big_loop() might be doing, right now, to defer some specific response).

It also means your code doesn't need to know what the units of time actually are! milliseconds, quarter seconds, etc. (which they would otherwise need to be aware of if they have to convert "elapsed time units" into "elapsed *time*")

If you have a good macro processor, (or, want to manually maintain your code), you can implement pseudo multitasking *within* a function. This allows an expensive "task" (sorry, hard to avoid that term) to deliberately be staged over multiple passes "around the loop".

function_within_big_loop() { ...

do_a_little_work();

LET_SOMEONE_ELSE_GET_STUFF_DONE();

do_a_little_more_work();

LET_SOMEONE_ELSE_GET_STUFF_DONE();

do_still_more_work();

LET_SOMEONE_ELSE_GET_STUFF_DONE();

do_whatever_is_left();

/* all done! */ }

I.e., you *manually* simulate the task switch that an MTOS would provide for you. But, you do it *where* it is convenient for your algorithm to "take a break". (There are also constraints on where you *can* do this!)

Instead of thinking about "if it is time", consider thinking about "have I waited long enough" (subtle difference -- the first checks the actual time, the second checks to see if an interval has expired. IME, the second is a lot easier to "get right" within a multitasking-ish framework.

Reply to
Don Y

I don't think you can easily transition from ladder logic "style" to treating each rung as if a "task". You (probably) don't want to constrain your (C) implementation to "run each task to completion" before advancing to the next task (function). It's just too easy to "do too much" in a function and effectively slow down all

*other* functions/tasks.

Alternatively, to *naively* try to slice a function into little pieces and still maintain a coherent model of the task being implemented ("I pointed the ship towards the other vessel -- but it had moved before I got there!")

Instead (*I* think), you want to be able to proceed through a "job" (resorting to an older lexicon) more methodically -- distributing it's computational load over multiple "scans" (iterations) of the big_loop(). Otherwise, the effort to slice the activities into the "multiple, sequentially-enabled rung analogy starts to interfere with the goal of the "job" at hand.

But, this has consequences to the designer since it means *this* task (rung) didn't "start" at the same *virtual* time (i.e., in the same big_loop() iteration) as each of the other tasks. I.e., the inputs *can* have changed -- because this might be iteration #6 and the task (rung) still hasn't been completely processed.

A task (rung) should capture the state of its inputs when *it* starts -- if this is important to the task's integrity. Otherwise, you risk races.

Imagine: if (switch == ON) { do_something(); } ... if (switch == OFF) { do_something_completely_different(); } If "switch" can change between the evaluations of these two conditionals, then you get some bizarre (probably unintended) behavior!

If "switch" reflects the instantaneous value of a hardware input, then a switch that is "in transition" (ON->OFF or OFF->ON) between these two statements screws you.

if "switch" reflects a *variable* into which the hardware state of the physical switch was *copied* "at the start of a scan" (loop), then you are similarly vulnerable if that variable can be updated between those two conditionals. I.e., if the conditionals can be evaluated in DIFFERENT SCANS!

This is true of *every* shared variable/resource! I.e., for timers, you only want to look at "current_time()" in one place and share that value among all "time consumers". Otherwise:

if (current_time() == X) { do_X_stuff(); } ... if (current_time() == Y) { do_Y_stuff(); }

could result in X and Y "stuff" happening in a *single* "scan" of big_loop().

Imagine if do_X_stuff() turns on a light. And, do_Y_stuff() turns it off. Furthermore, imagine you only copy the actual "light" variable to physical outputs at the end of the scan (or, at some explicit point in this "task"). Then, the light may NEVER turn on -- since the Y stuff turns it off before it is ever "manifested" to the outside world.

Ladder logic is designed to support a different sort of programming and application environment. Trying to come up with direct parallels to coding in a sequential programming language... in a single-threaded, sequential environment... means you can easily come up with solutions that don't work well.

(rungs tend to be *short*, predictable; C functions/tasks not quite so!)

Reply to
Don Y

PLC's capture all of the inputs and output states just prior to a scan of your program. When OS of the PLC scans ladder rungs, it is simply viewing and modifying the captured values, not the actual values sitting at the hardware outputs/inputs. This allows for a ladder program to see the same values captured through out, however, changing the state of any outputs with the ladder code, only changes the captured values but does not actually change the hardware output, yet. So this means that any remaining ladder code there after will now see this change. Care must be taken when doing this because it can cause debugging problems when more then one point in the ladder code is setting the value of an output.

At the end of all scans, these values that were captured to reflect output states, which also could have been altered ofcourse, get written back out to the outputs.

You can in effect do the same exact process and that is, to simply capture all of the needed values and hardware states at the start of the loop and store them in variables. You then address those values instead of directly accessing the IO on the fly in your program. This would solve problems where you may have an unpredictable event due to noise or what ever changing the state of an input while you're scanning your code and thus if your code is referencing this input several times, would end up causing your code to fail on making a proper determination with external events. Also, you should do this with the outputs, too. That would prevent an unwanted pulse to appear if your code is changing the output hardware value before it actually decides where it will stay.

P.S.

I could be wrong? But, that PLC shown in the video looks like a PLC5 family from AB ?

Jamie

Reply to
Jamie

That's not ladder logic that you're arguing against -- it's non- preemptive multitasking.

Note that non-preemtime multitasking can work really well, as long as you observe the "don't do too much at once" rule. But if it were so thoroughly hot, there'd be a lot fewer RTOSs in the world.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

Also, I don't think he has a clear understanding on how ladder works. At least it does not appear so. I get the impression he believes each rung is a separate task on its own that can hang there until something is satisfied.

And if the processor unit isn't able to process a complete ladder scan with out the danger of missing some critical input triggers, then it is not suited for the job and the job requires a faster unit.

Jamie

Reply to
Jamie

The ladder logic model *is* nonpreemtive multitasking, though some controllers impose a strict, predictable order on the evaluation of *rungs* (I've never seen any that imposed any order on the evaluation of terms *within* a rung).

Rungs tend to be inherently short. The equivalent of

*expressions* in most traditional programming languages. "Programs" (tasks) written in procedural languages tend to be *long*.

Some controllers give a true parallel illusion to the evaluation of ladder rungs -- as if implementing "relay logic". Others expose the sequential/"interpretive" nature of the implementation which makes rungs look like "(assignment) statements": )

Reply to
Don Y

It's an *analogy*. How would you try to relate ladder logic and rungs to "programming in C"? C has variables, operators, expressions, statements, functions, etc. What are you going to map the "rung" concept onto?

You can model rungs as "assignment statements". Or, as "execution units" -- *tasks*.

If you choose "assignment statements", then this begs the question: "what order are they executed in?" (this varies between implementations).

OTOH, if you treat them as independent execution units, they can operate in "true" parallel or in any sequential order you care to define.

Look at the market that PLC's were created to address. They were NOT designed to do scientific computations. Nor business applications (accounting, etc.). Nor graphical user interfaces. Nor...

They were designed to replace *relays* and switches (and other functional black boxes -- time delay relays with elastic stop nuts, electromechanical counters, etc.).

When you look at a "wiring diagram" for a control system, there is no "line 1" that says, "This start button is pushed which pulls in this relay." "Line 1" (if there were such a thing) could just as easily say, "This relay closure causes the start+run windings of the motor to be energized." "Line 934" might say "The opening of the centrifugal clutch causes the start winding to drop out of the circuit." "Line 28" might say "The start button pulls in this contactor".

This sort of cognitive model is not intended for implicitly representing sequences -- like procedural languages. Electricity doesn't observe "line numbers" on a wiring diagram. AS SOON AS a contact closes, power (signal) is available to everything that it feeds (assuming signal is already present upstream of the contact).

The multi*TASK*ing model stresses the illusion of parallelism. I.e., if you want tasks to execute in SERIES, you have to explicitly add mechanisms to ensure that happens. Otherwise, the model implies that everything *could* operate concurrently (if you assume otherwise, you end up with a race/bug).

So, IMHO, treating rungs as *tasks* is a more sensible model than as "statements" (or any other C construct). I.e.,

big_loop() { update_timers(); ... update_controllers();

read_inputs();

rung1(); rung2(); ... rungN();

write_outputs(); }

The differences between PLC models can then be seen as:

- are inputs and/or outputs buffered?

- are rung()'s executed in a predictable manner?

Or, a cleverer implementation! You can write ladder logic to offload a "heavy job" to a lower priority so that it doesn't impact the scan time of the rest of the system. But, this requires more effort (self-discipline) and knowledge of the PLC's actual implementation to best exploit this.

(Or, the approach favored by PLC vendors: selling you another "processor-in-a-module" to offload some can-able functionality)

Reply to
Don Y

That sounds similar to what I have heard, that the inputs are read and written to an input image table. The logic is processes, outputs written to output image table, then the output image is written to the actual outputs.

The one in the video was a Mitsubishi A series IIRC. It's about the same size as the PLC 5. One of the things I would have preferred on the PLC5 is that I could have written each station in a separate ladder file. With the Mitsubishi I had to write everything in a single ladder file.

I made sequence bits that I called things like STN1 STP1 Home(station 1 step

1), STN1 STP2 Open Gripper, STN1 STP3 Gripper Down, STN1 STP4 Gripper Close, STN1 STP5 Gripper Up, STN1 STP6 Gripper to Table .... Gripper Open, ... Gripper Up ... go back to step 1 to rehome gripper for next socket. This way, the first -||- in the rungs told me the Station Number, Step Number, and a brief description of what it was trying to do. So even though everything was in one file, it was still easy to see what was going on. The bits for the steps were all in order so you could just look online at the memory and see what station was hung up and at what step.

Most of the processors I have worked with have an option to look at the listing that looks kind of like an assembly language listing. I know you can make FOR loops inside the ladder programs I have used, but I've never needed to use them.

So I'm thinking I could take a rung like this: A C |--+--||---+--||----( )--| | B | +--|/|_+

and write it something like this:

if((conditionA==1 || conditionB!=1) && ConditionC==1) turn on output; else turn off output;

Ladder Logic one shot rising (OSR) would be if (oldState==0 && currentState==1)...

One thing missing is the way PLC's do timers, they don't halt execution while the timer is timing, such as delay(1000), the timer runs when enabled, when the time is elapsed the Done bit is turned on. So the accuracy of the timer could be a scan time longer. I use timers often for dwell, extend a cylinder, wait a half second go to next step. This is because often cylinders have prox switches to detect their position, sometimes they are not fully stroked out when the input first makes.

If you notice in the video when the bulb is being seated, the bulb gripper opens a little sooner than desired, I put a short dwell timer in it to make sure the bulbs were fully seated. The video was taken when I barely got the machine running. I made manual controls for each station so the people assembling the machine could operate it to get it all aligned. I also made a manually started automatic sequence for each station, so they could hit a single button and that station would run through all the steps like it would in automatic mode. So the morning before I shot the video, I made an auto mode that triggered the stations to do their thing just like the button on the touch screen did.

RogerN

Reply to
RogerN

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.