I’ve started exploring FreeRTOS on the ESP32 and decided to try a practical example instead of only reading docs. I set up a simple two-way traffic light using two sets of Red/Green/Yellow LEDs.
For phase 1, the timing is:
Red = 60 seconds STOP
Green = 50 seconds GO
Yellow = 10 seconds Ready to STOP
That makes a 120-second cycle, and phase 2 is the same idea but offset. No pedestrian lights I just want to understand the basics of FreeRTOS scheduling.
I divided the control into two tasks, each handling one 120-second cycle. If I roughly estimate that updating one LED state takes about 1 ms of CPU time, then both tasks together might need around 6 ms total. The whole cycle length is 240 seconds, so the CPU is basically free for most of the time (~234 seconds).
later I will plan to involve two push buttons with UART communication as well mainly to keep practicing FreeRTOS features.
What I’m trying to figure out is the best way to schedule these two tasks: since both phases are equally important, how should I structure the blocking/delays so that each task runs at the right time, without interfering with the other? For example, I thinking to use vTaskDelay() with the exact durations (60s, 50s, 10s), or is there a better pattern for timing in FreeRTOS?
Any suggestions for good practices would also be appreciated.
please query this forum for “traffic light.” It is one of the most widely used metaphors/starting points of concurrency modeling, and many many approaches have been discussed back and forth, so in this case it may indeed be a good idea to get back to reading.
Edit: Of course it is a splendid idea to set yourself hands-on exercises as you go along, but so have all those who posted here in the past.
My first suggestion is to think about the problem a bit, and ask yourself, are the two “phases” independent of each other, or should they depend on each other in some way.
Think about what should happen if something delays one of the phases a bit.
You want to get in the habit of thinking out the problem, before you start to lock things down with code.
Think about your question about “not interfering”. You specification has two things happening at the exact same time, (one phase going Yellow to Red, while another goes Red to Green). Can a CPU do two different things at once. NO. The only way to do that would be to have separate computers for each phase, and that doesn’t scale, and possibly creates other issues. What you want is that each runs and meets its requirements, but that means you need to think a bit and actually create requirements with measurable specifications, not just an idealized sequence of actions.
The two phases should depend on each other to ensure safe and coordinated traffic flow. If one phase changes state, the other must respond accordingly to prevent conflicting signals.
If I were doing this without FreeRTOS, I’d probably set up a 1 ms timer interrupt and handle LED updates in the main loop based on the elapsed time. This way, I could manually track the timing and sequence the phases correctly.
Thanks, that makes sense I see your point about defining the requirements first instead of just coding . In my simple setup, the two phases are dependent:when Phase 1 goes Red /Green/ Yellow, Phase 2 should always stay in the opposite state so they stay synchronized.
As you are pointing out, if you weren’t trying to force yourself to use an RTOS, a simple time base interrupt (and it doesn’t need to be a 1ms timer, for you specifications, it could be once every 10 seconds, maybe once a second or 100ms to allow changing the specifications to other reasonable numbers). A specification that doesn’t really need an RTOS, or even multiple tasks, isn’t a great platform for learning to use an RTOS, as you won’t see the things that need it.
If you rethink the problem into a framework where you do have somewhat independent
”actors” (that will become tasks) each controlling different parts of the system, with limited communication to each other, you can setup a problem which can be a learning exercise.
One start would be to look at your first answer and define what a “conflicting signal” would be. That can help you think about what the specifications need to specify.
I’ve tried to capture the specification in a simple table that shows the exact state of each direction
Time
NS Direction
EW Direction
50 Seconds
Green (GO)
RED ( STOP)
10 Seconds
Yellow ( Prepare to STOP )
RED ( STOP)
50 Seconds
RED ( STOP )
Green ( GO )
10 Seconds
RED ( STOP)
Yellow ( Prepare to STOP )
Repeat cycle
An accident would basically happen if both directions were ever allowed to go at the same time. For example, if NS and EW were Green together, or even both Yellow together, then drivers from both sides could enter the junction and collide.
So the system really needs to guarantee that only one direction is ever Green at a time, that every Green always goes through Yellow before Red, and that the cycle sticks to the specified timings without too much jitter. Basically, the promise has to be that no conflicting signals ever appear, and that the sequence always plays out in a predictable, safe way
I’m not really sure if I’m going in the right direction, but this is just my attempt. I know a real traffic light system can have a lot more complexity, but for now I kept it simple. I also believe the whole thing could be done in a single task, but my idea was to split it into two tasks so I can get some hands-on practice with task synchronization in FreeRTOS.
The point is, you wrote your specification in a way that you don’t need tasks, as it is just a plain sequence of events that gets cycled through. That controller has full control over all the lights, and just sequences through the states. This could well be an optimal way to build this simple light controller (and isn’t far off of may simple traffic light control boxes that doesn’t have anything remotely like a computer in them.)
The issue is. this isn’t a good model to try to design a simple RTOS application to, as it doesn’t really need the RTOS. As I said, start by rethinking the problem with a requirement that doesn’t allow this simple solution. For instance, what if one directions lights need to be controlled by one “agent”, and the other set by another, and you want to minimize the amount of communication between the two agents. Think of a “protocol” you might use to implement the design, and what you actual requirements would be. As you point out, both sides being red isn’t really an issue, but you want to limit/control that duration to keep the intersection running (and some lights actually intentionally have an all red period to give cars a chance to clear the intersection). You do need to avoid neither side being red, as that generates a conflict.
I agree with this claim if your requirements will ONLY ever be simple light timing. Timed interrupts without an RTOS would be simpler.
There is nothing wrong with using an RTOS if you’re expecting your feature set to grow in the future - as in the examples you’ve mentioned. I also think there is value here for learning FreeRTOS. You will still need to go through steps like setup, compilation, and learning to use beginner APIs which will always be needed.
Even then, the issue with it as a learning exercise, is the “optimal solution” those specs will drive you to are a single task in a loop with vTaskDelay()s followed by changing the lights. To make a better learning problem, you need to add something that breaks that simple method. Forcing two agents to control different parts is a basic way to do that, and one that can then be scaled up to more complicated systems.
Do you want me to create two independent tasks that coordinate to avoid conflicts?
Each direction (NS and EW) would be controlled by its own task. Each task would operate independently with its own cycle timing (e.g., Green 50 s, Yellow 10 s, Red 60 s), but before turning Green, it would check that the other task is already Red to prevent situations like both being Green or Yellow at the same time
no, the point is that your implementation needs to model the task at hand well. Take a very simple traffic light, for example, where each cycle is completly deterministic: W-E green, S-N red and vice versa, with no external dependencies such as traffic density of neighboring lights. In this case, you do not need concurrency at all, you can simply model your light as a finite state automaton executing in a single task, and in that case, concurrency would not only be useless but counter productive. In more complex scenarios, things are of course different, and you might benefit from concurrency but then need to address dependencies.
So you need to analyze where you really benefit from concurrency before you design your tasks.
I’m a bit confused now. I thought the idea of @richard-damon was that I should try task synchronization, which is why I proposed creating two independent tasks. If I were doing this same two-way traffic light without FreeRTOS, I’d just implement it as a simple state machine. But when it comes to FreeRTOS, I’m struggling how to shape this into a proper learning model that’s where I’m getting stuck
So if you don’t actually want two independent tasks, does that mean a single task should control the whole traffic system? If that’s the case, then which FreeRTOS features would I really be learning? I already know the timing requirements, and both directions are equal priority, so I thought the idea was to encourage me to explore interprocess communication and synchronization.
The point is that YOU want two tasks, as you want to learn how to communicate between them with FreeRTOS. I have been pointing out that the problem, as you first defined it, doesn’t really want two tasks, and thus isn’t a good version of the problem to try to solve to learn what you want to learn. My point is that if you reframe the problem in a way that it needs two agents (that will become tasks) then you have a vehicle to work on to learn what you want.
To need two agents, one agent can’t be allowed to access the whole system. Putting a goal of limiting communication is to remove the possible solution of making one of the agents just a puppet that does what the other tells it to do, reducing the problem back to essentially the single agent version.
So, my point is that before you try to solve the problem, you need to reframe it in a way that the solution will take you towards what you want to learn, rather than that path being a unneeded complication to a good solution.
So I thought a bit more about the traffic light problem after your feedback.
To make it more of a FreeRTOS learning exercise, I’m now thinking of it like this: instead of one central controller, I split the intersection into two independent tasks. One task is responsible only for Road A’s lights, and the other only for Road B’s lights. Each task runs its own timing (green, yellow, red), but since they’re independent, they somehow need to coordinate with each other to avoid unsafe situations.
They can’t both be green at the same time (that’s a conflict).
And if one task gets slightly delayed, the whole system should still stay safe.
So the whole point of this setup is that the two tasks have to talk to each other in some way. Maybe that means some kind of signal, handshake, or message passing I’m not yet deciding exactly how (semaphore, queue, etc.), but the idea is they need some synchronization mechanism.
This forces me to practice inter-task communication, which is really what I want to learn from FreeRTOS.
That way, the problem isn’t just “blink LEDs on a schedule,” it actually makes me:
Run two tasks in parallel.
Work out how they share control of the intersection.
Learn to use FreeRTOS features for coordination instead of just delays.
Does this sound like the kind of reframing you wanted me to do?
Yes, so now you need to think about what “rules” are needed to be specified to define this. What is needed so the two sides can’t both be Green (or Yellow) at the same time, and still have the system operate. Then you can try to define the tasks to implement those rules and run them.
I tried to rethink this with the semaphore example.
So initially I thought of making it like this:
NS Task takes semaphore:
Green1 for 50s
Yellow1 for 10s
Red1 for 60s
Then give semaphore to EW Task
EW Task takes semaphore:
Green2 for 50s
Yellow2 for 10s
Red2 for 60s
Then give semaphore back to NS
What I realized is that just splitting it into two tasks and using semaphore doesn’t really solve the full problem, because while it prevents them from running at the same time, it doesn’t make sure the actual traffic rules are followed. It doesn’t control timing
So I thought of making it more like this:
NS Task
Takes the semaphore:
Green1 + Red2 for 50s
Yellow1 + Red2 for 10s
Red1 + Green2 for 50s
Red1 + Yellow2 for 10s
Then gives semaphore to EW task
EW Task
Takes the semaphore:
Green2 + Red1 for 50s
Yellow2 + Red1 for 10s
Red2 + Green1 for 50s
Red2 + Yellow1 for 10s
Then gives semaphore back to NS
This way both tasks are still independent, but the logic makes sure they never end up both Green (or both Yellow) at the same time and now the timing plus safe light combinations are controlled correctly.
Does this version make more sense for what you were suggesting?
EW goes Green2, AFTER Red1 has been red for 60 seconds, as that is when you gave the semaphore, so EW waited to then to take it.
Your second method violates the separation rule that NS tasks should be only handling the NS lights, as it shouldn’t be touching the “2” lights, and thus is just going back to one task broken into two pieces that run in sequence, which is just a waste of resources.
Think when NS can tell EW that it can do something, and EW reply back to NS when it can do its action.