How an automatic car wash PLC program works
An automatic car wash PLC program is a fixed-order, timed sequence — a state machine that walks through one wash stage at a time. The bay does nothing until two conditions are both true: a car has driven in and broken the CAR_PRESENT photo-eye, and the operator has pressed START. Only then does the cycle begin.
The wash runs four stages in strict order, three seconds each: SOAP, then BRUSHES, then RINSE, then DRYER. Exactly one stage may be energised at any moment — there is no overlap. When SOAP's timer finishes, SOAP must turn off the instant BRUSHES turns on, and so on down the chain. After the DRYER stage completes, a DONE_LAMP latches on to tell the driver the wash is finished. Pressing STOP at any point is an emergency abort: every output drops immediately.
The I/O is pre-wired for you. Three inputs: START (%I0.0, momentary push-button), STOP (%I0.1, abort push-button), and CAR_PRESENT (%I0.2, the car-in-bay photo-eye whose beam is broken by a car). Five outputs: SOAP (%Q0.0, the pre-soak spray valve), BRUSHES (%Q0.1, the scrub-brush motor), RINSE (%Q0.2, the rinse spray valve), DRYER (%Q0.3, the blower), and DONE_LAMP (%Q0.4, the wash-complete indicator).
Because the whole program is driven by on-delay timers handing off to one another, the car wash PLC program is a textbook way to learn TON timers and sequence control before moving on to counters, interlocks and analog work.
Sequencing the stages with TON timers — the car wash PLC program structure
The cleanest way to build this car wash control using a PLC is a phase-latch state machine: one latched bit per stage (PH_SOAP, PH_BRUSH, PH_RINSE, PH_DRYER) and one TON timer per stage. The phase bit says which stage is active; the timer measures how long the stage has run; the timer's done bit hands off to the next phase.
Start the chain by latching the first phase only when both gate conditions are met — SET PH_SOAP on START AND CAR_PRESENT. This is the rung that makes the bay ignore a START press when no car is in the bay, which is exactly what the discriminating 'no-car-no-cycle' test case checks: a naive rung that just mirrors START → SOAP fails immediately because it skips CAR_PRESENT and the sequencing.
Give each phase its own on-delay timer: T_SOAP(IN := PH_SOAP, PT := 3000ms). When T_SOAP.Q goes true the three seconds are up, so RESET PH_SOAP and SET PH_BRUSH on the same rung. Because PH_SOAP just dropped, the timer's IN input drops with it and T_SOAP resets itself, ready for the next car. Chain BRUSHES → RINSE → DRYER the same way, each phase set off the previous timer's done bit.
Drive each output coil directly from its phase bit — | PH_SOAP | := SOAP — so only one stage energises at a time by construction. Latch DONE_LAMP on T_DRYER.Q, the moment the final stage completes. This single-active-phase structure is why the 'happy-path' test sees SOAP, BRUSHES, RINSE and DRYER fire in order with no overlap.
STOP abort and the DONE lamp — getting the edge cases right
Two details separate a car wash PLC ladder diagram that merely runs from one that passes every test: the abort and the completion lamp.
The STOP abort must clear the entire machine, not just the stage that happens to be running. The simplest reliable pattern is to put a | STOP | R= reset on every phase bit (PH_SOAP, PH_BRUSH, PH_RINSE, PH_DRYER) and on DONE_LAMP. When STOP is pressed, every phase latch resets, every timer's IN drops because its phase is gone, and every output coil — driven directly from its phase bit — de-energises on the same scan. The 'stop-aborts' test advances the cycle to BRUSHES, presses STOP, and asserts SOAP, BRUSHES, RINSE, DRYER and DONE_LAMP are all off. A partial abort that only stops the current stage fails here.
The DONE_LAMP must stay off through every wash stage and light only after the DRYER finishes. If you accidentally drive it from a phase bit, or latch it too early, the 'done-only-after-dryer' test catches it: it samples DONE_LAMP while DRYER is still running (it must be off) and again after DRYER completes (it must be on). Latch DONE_LAMP from T_DRYER.Q — the dryer timer's done bit — not from PH_DRYER, so the lamp lights at the end of the dryer stage rather than at its start. Include DONE_LAMP in the STOP reset so a new car starts with a clean indicator.
The timer-chain state machine pattern
Step back from the car wash for a moment, because the structure you build here — a timer chain, sometimes called cascading timers — is one of the most reusable patterns in PLC programming. The idea: the process is a sequence of states, each state has a fixed duration, and the event that moves the machine forward is always a timer's done bit. State comes from latched phase bits (PH_SOAP, PH_BRUSH, PH_RINSE, PH_DRYER); time comes from one TON per phase; the .Q output of each timer is the handoff signal that resets the current phase and sets the next.
The elegant detail is that the timers clean up after themselves. Because each TON's IN input is its own phase bit — T_SOAP(IN := PH_SOAP, PT := 3000ms) — the moment T_SOAP.Q resets PH_SOAP, the timer's input drops and the timer zeroes its accumulator. No explicit timer-reset rungs, no stale elapsed time waiting to surprise you when the next car drives in. Each stage's timer is guaranteed fresh every cycle, by construction.
Compare that to the tempting shortcut: one master timer with PT := 12000ms and comparison rungs carving it into windows — SOAP when the accumulator is below 3000, BRUSHES between 3000 and 6000, and so on. It works for the happy path, then falls apart at the edges. A STOP abort mid-cycle leaves you managing a partially-elapsed master timer; changing one stage's duration means re-deriving every window boundary; and the comparison rungs say nothing about which stage is active, so the program gets harder to read as it grows. The phase-latch chain keeps each stage's logic in one place.
The pattern also scales without surgery. Want a WAX stage between RINSE and DRYER on a real machine? One new phase bit, one new TON, and re-pointing two handoff rungs — every other stage is untouched. Traffic lights, batch mixers, kiln cycles, sterilisation sequences: any fixed-order timed process is this same chain with different tag names, which is exactly why the car wash is worth mastering properly.
Why the car wash is a classic beginner PLC project
The car wash is one of the most-assigned first projects in PLC training, and for good reason. It teaches the three skills every PLC programmer needs — sequencing, timing and a clean operator interface — without any of the complications of analog signals, PID loops or communications.
First, it makes the one-stage-at-a-time discipline concrete. A learner who tries to fudge the sequence with overlapping coils sees the simulated bay run two stages at once and the auto-grader reject it. That immediate, visible feedback teaches the value of a single-active-state machine far faster than a lecture does.
Second, it is the natural home for the TON on-delay timer. Each stage is just 'run this output for three seconds, then move on', which is the canonical TON use case. Chaining four of them — each timer's done bit launching the next phase — is the pattern you will reuse for traffic lights, batch processes and any other timed sequence.
Third, it introduces fail-safe operator control: a START that is properly gated (it does nothing without a car present) and a STOP that drops everything immediately. Those are habits worth forming early.
You can build and run the whole thing on this page. Write the ladder, press Run, and watch the bay step SOAP → BRUSHES → RINSE → DRYER → DONE. Four automated test cases grade the start gate, the full cycle, the STOP abort and the completion lamp — no physical PLC, sprayers or blowers required.
Troubleshooting your car wash program
When a car wash PLC program fails the auto-grader, the fault is almost always in the start gate, the phase handoff, or the abort logic. Here are the failure modes learners hit most often in this scenario, and what each one looks like.
The wash starts with no car in the bay. The classic naive rung maps START straight to SOAP, ignoring the photo-eye. The 'no-car-no-cycle' test presses START with CAR_PRESENT false and asserts every output stays off — a direct START → SOAP mapping fails on the first assertion. The fix is to SET PH_SOAP only on START AND CAR_PRESENT, never to drive SOAP from START directly.
Two stages run at the same time. If the handoff rung sets PH_BRUSH but forgets to reset PH_SOAP, both phase bits are latched and the bay runs soap spray and brushes together. The 'happy-path' test asserts the off-stages at every sample point (SOAP must be false while BRUSHES is true), so any overlap fails. Reset the outgoing phase and set the incoming phase on the same rung, off the same timer done bit, and drive each output coil only from its own phase bit.
The sequence stalls after SOAP. Usually one of three causes: the timer's IN is wired to the wrong bit (something that drops before 3 seconds elapse, so the timer never finishes); the next phase is set from the wrong timer's done bit (T_SOAP.Q accidentally referenced where T_BRUSH.Q belongs); or the preset is wrong — 3000 means milliseconds here, and writing T#3000s or 30000 makes the stage appear frozen. Watch the timer's accumulated value while the stage is active: if it is not counting toward 3000, the IN side is the problem; if it completes but nothing advances, the handoff rung is.
DONE_LAMP lights as soon as the dryer starts. Latching the lamp from PH_DRYER instead of T_DRYER.Q lights it at the start of the dryer stage, not the end. The 'done-only-after-dryer' test samples DONE_LAMP while DRYER is still true and fails the program if the lamp is already on. Latch from the timer's done bit.
STOP does not kill everything. A STOP rung that only resets the currently-running phase — or forgets DONE_LAMP — leaves outputs latched. The 'stop-aborts' test advances the cycle to BRUSHES, presses STOP, and asserts all five outputs are false. Put a STOP reset on every phase bit and on DONE_LAMP; because each output is driven from its phase bit and each timer's IN is its phase bit, everything else drops on the same scan.
The second car never gets a wash. If any phase bit or the DONE lamp survives the previous cycle, the next START press meets a machine that is not in its idle state. The phase-latch structure handles this for you — each phase resets itself during the handoff and the timers self-reset when their phase drops — but only if DONE_LAMP is also cleared, either by STOP or when a new cycle begins.