Last night, I received a call from an old friend who works on projects at a small automation company. He had taken on a municipal project to install traffic lights at a newly constructed intersection. He was quite distressed: “Bro, I spent three days drawing the ladder diagram and used a ton of timers. The system works, but it looks like a complete mess to me. If I need to add a night mode or give priority to emergency vehicles later, I’ll have to rewrite everything from scratch!”
I couldn’t help but laugh. This scenario is all too familiar; over a decade ago, I did the same thing. I treated the traffic lights as three independent lights, using a bunch of timers in a relay race: T1 finishes, then T2 starts, T2 finishes, then T3 starts… The code ended up looking like an old lady’s bound feet—long and messy.
About 90% of the beginner tutorials on the market still enthusiastically teach this method. They are not wrong, but they only teach you how to make the lights turn on; they don’t teach you how to run a system “elegantly.” The real core is not how many timers you can use, but a way of thinking—state thinking.
1. We’ve all been fooled by the “timer relay race”
Let’s first look at how most tutorials teach this. Assume a simple north-south and east-west intersection:
North-South green light on for 25 seconds -> North-South yellow light on for 5 seconds -> East-West green light on for 25 seconds -> East-West yellow light on for 5 seconds -> Repeat
The classic “timer relay” method would write it like this:
1. Start, Timer1 (North-South green for 25 seconds) begins timing.
2. When Timer1 times out, trigger Timer2 (North-South yellow for 5 seconds) to start, while turning off the North-South green light.
3. When Timer2 times out, trigger Timer3 (East-West green for 25 seconds) to start, while turning off the North-South yellow light.
4. When Timer3 times out, trigger Timer4 (East-West yellow for 5 seconds) to start, while turning off the East-West green light.
5. When Timer4 times out, jump back to step one, and repeat.
Do you see the problem? The entire system’s state (which light is on at any given time) is implicit in the on/off relationships of a series of timers. This is an “implicit” expression. The program is short, but once the logic becomes complex—like needing to add an all-red clearance time or special modes—you’ll find yourself adding branches in the forest of timers, and a small mistake can lead to logical conflicts, causing the entire system to “deadlock.”
I fell into this trap years ago with a project involving a four-way intersection with pedestrian lights. I spent an entire week debugging, only to find that I had misidentified a timer’s reset point. I felt like smashing the touchscreen.
2. The core weapon: Clearly define the “state”
So, what should we do? The answer is: use the concept of a step sequence function diagram, or simply use an integer variable to clearly mark “which stage we are currently in”.
Don’t be intimidated by the term “function diagram”; its concept is simple: at any moment, the entire system is in one, and only one, clearly defined “state.” We use a variable (like `Current_Step`) to record this state.
Let’s redesign the logic for the traffic light:
1. State 1 (Step1): North-South green light on, East-West red light on. Lasts for 25 seconds.
2. State 2 (Step2): North-South yellow light on, East-West red light on. Lasts for 5 seconds.
3. State 3 (Step3): East-West green light on, North-South red light on. Lasts for 25 seconds.
4. State 4 (Step4): East-West yellow light on, North-South red light on. Lasts for 5 seconds.
See? This is much clearer. In each state, which light is on and which is off is clearly defined. The `Current_Step` variable is the system’s “identity card”; you can instantly know what is happening.
The program’s structure now consists of two parts:
First part: State output. Use a selection branch (Case/IF-ELSE) to assign values to each light based on the value of `Current_Step`.
If `Current_Step = 1`, then North-South green=ON, North-South yellow=OFF, North-South red=OFF; East-West green=OFF, East-West yellow=OFF, East-West red=ON.
If `Current_Step = 2`, then…
…
This part of the code is very intuitive and almost impossible to get wrong.
Second part: State transition. Similarly, based on the value of `Current_Step`, start the corresponding timer. When the time is up, smoothly transition to the next state.
If `Current_Step = 1`, start Timer1 (25 seconds); when the time is up, set `Current_Step = 2`.
If `Current_Step = 2`, start Timer2 (5 seconds); when the time is up, set `Current_Step = 3`.
…
This method decouples logic from output. If I want to modify the output of a state, I just need to change the first part without affecting the flow of states. If I want to add a state (like an all-red for 3 seconds), I just need to insert a step in the sequence and adjust the transition relationship, keeping the code structure clear.
3. Practical enhancement: The final touch from “usable” to “user-friendly”
Once you master state thinking, you’re in the door. But to become an expert, you need to add some “personal touches.” These are the real issues you’ll encounter in projects that ordinary tutorials will never tell you.
How to add emergency vehicle detection? If you use the old method, you’d have to make adjustments in each timer, which is extremely awkward. With state thinking? It’s simple. I can add an emergency vehicle trigger signal in the state transition condition. For example, when `Current_Step = 3` (East-West green light), if an emergency vehicle is detected in the North-South direction, I can skip to `Current_Step = 4` (East-West yellow light) immediately, without waiting for the 25 seconds to end. The logic is clear, and the control is very high.
How to switch between manual and automatic seamlessly? This is where the superiority of state thinking shines. In manual mode, you simply force `Current_Step` to a fixed value (like setting all lights to red). When switching back to automatic, the program continues from that state. There’s no hassle of resetting or synchronizing timers. Honestly, this trick saved me nearly 30% of debugging time in an upgrade project last year.
One timer is enough! Yes, you read that right. Under state thinking, we don’t need a bunch of timers. We only need one global timer. When entering a state, start this timer and set the required time for that state. When the time is up, transition to the next state, reset the timer, and start it again in the new state with the new time. This way, memory usage is lower, and management is easier.
Conclusion:
So, the next time you face a sequential control process, whether it’s traffic lights, robotic arms, or automatic filling lines, don’t rush to pile on timers and intermediate relays. Stop and ask yourself a question: “What are the clearly defined states of this system?”
Sharpening the axe does not delay the work of chopping wood. Drawing the state diagram on paper means you’ve succeeded in half of your programming work. This “state-first” thinking is the most critical step from PLC novice to senior engineer. It brings you not only clean code but also the composure and confidence to handle complex logic.
Try this method in your next project, and trust me, you’ll come back to thank me. Or have you already stepped into the trap of the “timer relay”? Feel free to share your experiences in the comments; let’s have a laugh together.