Arduino traffic signal LED demonstration, Arduino traffic light demonstration, red yellow green LEDs, timed state machine

Traffic Signal Control using Arduino

889.5K Views
40
700
60
25
60
PCBWay

Hello friends! Today we will build a traffic-signal demonstration with Arduino using red, yellow and green LEDs. We will connect the three indicators, define their sequence and write a program that changes states using elapsed time. The same idea can be explored on a breadboard or in a suitable Proteus Arduino simulation.

This is an educational model of one signal head. It teaches output control and timed state transitions; it is not a controller for a public road or a complete intersection. Real installations require coordinated phases, fault handling and jurisdiction-specific timing that cannot be established by a three-LED sketch.

The original tutorial used long delays and changed directly from green to red before showing yellow. In this revision, we will use a clearly defined red, green, yellow sequence and keep the older flowchart as a historical illustration. We will also include an individual current-limiting resistor for every physical LED and explain why simulation alone cannot replace that electrical requirement.

Arduino traffic signal LED demonstration, Arduino traffic light demonstration, red yellow green LEDs, timed state machine
Figure: Arduino traffic signal LED demonstration.

What We Are Going to Build

The red LED connects to digital pin 2, yellow to pin 3 and green to pin 4, preserving the original pin assignment. Only one LED is deliberately on at a time. The demonstration starts red, remains red for ten seconds, changes to green for ten seconds, then shows yellow for three seconds before returning to red.

These durations are chosen to make the classroom sequence easy to observe. They are not recommended road-clearance times. We could choose different demonstration values without changing the program's structure, provided every state has a positive duration and the order remains intentional.

StateRedYellowGreenDuration
RedOnOffOff10 seconds
GreenOffOffOn10 seconds
YellowOffOnOff3 seconds

Writing this table before the code gives us something concrete to check. If the program displays green and yellow together or skips the yellow interval, we can identify a mismatch rather than relying on a vague impression that the LEDs are blinking.

Components and Wiring

Use an Uno R3, three ordinary indicator LEDs, three 680-ohm resistors, a breadboard and jumper wires. The LED colors should match their intended roles. A USB cable can power and program this low-current demonstration.

Historical three-color Arduino traffic signal wiring, Arduino traffic light demonstration, red yellow green LEDs, timed state machine
Figure: Historical three-color Arduino traffic signal wiring.

Arduino pinConnectionReturn path
D2680-ohm resistor followed by red LED anodeRed LED cathode to ground
D3680-ohm resistor followed by yellow LED anodeYellow LED cathode to ground
D4680-ohm resistor followed by green LED anodeGreen LED cathode to ground
GNDBreadboard ground railShared by all three LED branches

The resistor may be on either side of its LED as long as it is in series. Do not replace the three resistors with one shared resistor if you later permit combinations of lights: their current would then depend on which branches are active. Individual resistors make each branch's behavior easier to predict.

Check anode and cathode orientation before applying power. A breadboard's long power rail may be split halfway along its length, so verify that every cathode actually reaches Arduino ground. A color-correct LED connected backward will not become correct through a software change.

Calculate the Indicator Current

For an approximate 5 V output and a red LED forward voltage of 2 V, the current is I = (5 − 2) / 680 = 4.4 mA. If another LED has a 3 V forward voltage, the same resistor gives approximately 2.9 mA. Actual values depend on the LED and the output voltage under load.

The red-branch resistor dissipation is approximately I²R = 0.0044² × 680 = 0.013 W. An ordinary resistor rated comfortably above that value is sufficient for this calculation, but always check the actual parts and supply. These modest currents are appropriate for an indicator experiment and do not justify attaching a high-power lamp directly to the pin.

The Uno R3 documentation provides the board's electrical information. A GPIO absolute maximum is not a normal design target. External lamps, relays and LED strips require suitable driver circuitry rather than merely a different pin number in the sketch.

Why Use a State Machine?

A state machine stores the current operating state and applies a defined transition rule. Here, the state determines which LED is active. Elapsed time determines when the next state begins. Separating those two decisions makes the program easier to extend and inspect.

The original code used two successive delay(5000) calls to create a ten-second interval. That does produce a long pause, but the sketch cannot use ordinary loop-based polling to react to other work during that pause. Adding buttons, diagnostics or communication becomes awkward when the whole sequence is built from blocking delays.

Using millis lets the program check whether an interval has passed and return immediately to loop when it has not. This is cooperative scheduling, not automatic parallel execution. A later blocking function can still delay the state machine, so additions must be reviewed for their own timing behavior.

The retained flowchart documents the earlier order:

Historical traffic signal flowchart before the revised state sequence, Arduino traffic light demonstration, red yellow green LEDs, timed state machine
Figure: Historical traffic signal flowchart before the revised state sequence.

Use the revised state table above as the specification for the new program. In particular, yellow now follows green. The old diagram should not be interpreted as a second conflicting set of instructions for the updated sketch.

Complete Arduino Traffic-Signal Code

Create a new sketch and enter the following program. It uses the built-in Arduino functions and does not require an external library. Serial output reports state changes, which helps compare observed timing with the table.

constexpr byte RED_PIN = 2;
constexpr byte YELLOW_PIN = 3;
constexpr byte GREEN_PIN = 4;

enum SignalState { RED_STATE, GREEN_STATE, YELLOW_STATE };
SignalState state = RED_STATE;
unsigned long enteredAt = 0;

unsigned long durationFor(SignalState value) {
  switch (value) {
    case RED_STATE: return 10000UL;
    case GREEN_STATE: return 10000UL;
    case YELLOW_STATE: return 3000UL;
  }
  return 10000UL;
}

void showState(SignalState value) {
  // Clear previous indicators before enabling the new one.
  digitalWrite(RED_PIN, LOW);
  digitalWrite(YELLOW_PIN, LOW);
  digitalWrite(GREEN_PIN, LOW);
  switch (value) {
    case GREEN_STATE:
      digitalWrite(GREEN_PIN, HIGH);
      Serial.println(F("GREEN"));
      break;
    case YELLOW_STATE:
      digitalWrite(YELLOW_PIN, HIGH);
      Serial.println(F("YELLOW"));
      break;
    default:
      digitalWrite(RED_PIN, HIGH);
      Serial.println(F("RED"));
      break;
  }
}

void setup() {
  digitalWrite(RED_PIN, LOW);
  digitalWrite(YELLOW_PIN, LOW);
  digitalWrite(GREEN_PIN, LOW);
  pinMode(RED_PIN, OUTPUT);
  pinMode(YELLOW_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  Serial.begin(9600);
  state = RED_STATE;
  showState(state);
  enteredAt = millis();
}

void loop() {
  const unsigned long now = millis();
  if (now - enteredAt < durationFor(state)) return;

  switch (state) {
    case RED_STATE: state = GREEN_STATE; break;
    case GREEN_STATE: state = YELLOW_STATE; break;
    default: state = RED_STATE; break;
  }
  showState(state);
  enteredAt = millis();
}

Understand the Timing

enteredAt stores when the current state was applied. On each pass through loop, the program subtracts that timestamp from the current millis value. It changes state only after the required interval has elapsed. The comparison uses unsigned arithmetic, which is suitable for these short intervals across the timer's wrap when the code continues running normally.

The new timestamp is taken after the new outputs are applied. Therefore, an unexpectedly long loop iteration extends the old state rather than causing several states to be skipped in a rapid catch-up sequence. That is an intentional choice for this demonstration. It does not make the software fault tolerant, but it keeps the timing rule understandable.

showState first clears the previous LEDs, then enables the new one. This introduces a very brief all-off interval while GPIO writes execute. It avoids deliberately leaving the previous color on while enabling the next color. The sketch does not claim atomic simultaneous updates or a monitored hardware interlock.

The default branches provide a defined fallback for unexpected state values within these functions. They do not detect every possible software or electrical failure. A failed GPIO, damaged LED or frozen processor requires mechanisms beyond this simple program.

Calculate the Complete Cycle

The nominal demonstration cycle is 10 + 10 + 3 = 23 seconds. Ignoring the small instruction and reporting overhead, the repetition frequency is 1 / 23 = 0.0435 Hz. This is the frequency of a complete three-color sequence, not the switching frequency of a PWM output.

The red and green indicators each occupy 10 / 23 = 43.5 percent of the nominal cycle. Yellow occupies 3 / 23 = 13.0 percent. Those percentages describe how long each state is selected; the LEDs are driven continuously on during their selected state, not dimmed with those PWM duty values.

Two complete nominal cycles take 46 seconds. When testing, observe at least two cycles so that the transition from yellow back to red is checked as well as the initial startup. A test that stops after the first green state misses part of the behavior.

Run the Circuit on an Arduino

  1. Inspect the wiring with power disconnected.
  2. Select the Uno R3 and the correct port in Arduino IDE.
  3. Compile the sketch, then upload it to the board.
  4. Open Serial Monitor at 9600 baud. Opening the port may reset the Uno and restart the red interval.
  5. Confirm red at startup, then green, then yellow, then red again.
  6. Compare the approximate intervals with the state table.
  7. Press Reset during green and check that the application restarts in red.

Reset behavior is a useful deliberate test. It shows that the sequence does not resume from the previous state because that state exists only in volatile memory. During reset and bootloader activity, the sketch has not yet configured the pins, so its software startup choice should not be described as a guaranteed hardware red indication throughout every power condition.

These instructions describe tests for your assembly. No new physical run is claimed for the revised code. Use your observations to identify whether a problem belongs to wiring, program selection, timing or the simulator configuration.

Simulate the Project in Proteus

You can reproduce the three indicator branches with the appropriate Arduino model in Proteus. Our Arduino Library for Proteus tutorial explains the model setup. Compile for the processor represented by that model and assign the resulting program file to it.

Match the simulated clock to the target's expected clock. The classic Uno normally uses 16 MHz. A mismatched clock can affect delay and serial timing even if the source code is unchanged. Rebuild the program after editing rather than assuming that the model automatically reads the unsaved sketch text.

Historical Proteus traffic signal simulation display, Arduino traffic light demonstration, red yellow green LEDs, timed state machine
Figure: Historical Proteus traffic signal simulation display.

The retained screenshot shows the historical simulation. For the revised program, compare the actual simulated state sequence with the table and code in this article. Include series resistors in a physically meaningful schematic, even if an idealized LED model appears to work without them.

A simulated virtual terminal can display the state messages when its receive input is connected to the model's UART transmit output and its settings match 9600 baud. Do not confuse that connection with the three LED pins. The UART display is optional and does not control the sequence.

Inspect More Than the Visible Colors

Use logic probes or waveforms to inspect D2, D3 and D4 over a full cycle. Check that their high intervals do not deliberately overlap and that the transitions occur in the intended order. A graphical LED may update at a display rate that hides a very short transition, whereas the signal trace reveals it.

If timing looks wrong, first verify the loaded program path and clock settings. Then shorten the demonstration intervals temporarily to speed up debugging, keeping a separate copy of the intended values. Restore the final timings before comparing results with the article.

Record the Expected Transition Times

Before extending the circuit, write down what a complete cycle should do. Use the instant that setup enables red as your reference, rather than the instant you connect the USB cable. Bootloader activity and initialization happen before the program establishes that reference.

Elapsed time from initial redExpected stable outputNext transition
0 to approximately 10 secondsRed onlyGreen
Approximately 10 to 20 secondsGreen onlyYellow
Approximately 20 to 23 secondsYellow onlyRed
Approximately 23 secondsNext red interval beginsRepeat the cycle

The word approximately allows for the time between the timer becoming due and loop processing that transition. It does not excuse a several-second error in this small sketch. If a ten-second phase takes twenty seconds, investigate the program, clock or added blocking work instead of accepting that as normal scheduling overhead.

For example, suppose you later add an operation that prevents loop from running between elapsed seconds nine and twelve. The red interval will extend to twelve seconds. The program then starts a fresh ten-second green interval, because it records the transition time after changing the outputs. It does not compress green to recover the missed time. This deliberate behavior keeps every displayed phase at least as long as its configured interval during ordinary operation.

Also check the output combination, not just the elapsed time. In each stable phase exactly one of the three pins should be high. The transition function briefly clears all outputs before selecting the next color, so a short all-off boundary is expected. Two stable high outputs indicate an error or a later modification that needs review. Keep these observations with your wiring notes; they form a useful acceptance checklist when adding a button, display or second model direction.

Common Problems and Fixes

ProblemLikely causeUseful check
Wrong color appearsLED branches connected to different pinsCompare each branch with D2 red, D3 yellow and D4 green
One LED never lightsReversed LED, open connection or wrong breadboard rowTest that branch independently with a resistor
All LEDs remain offNo running sketch, missing ground or wrong program fileCheck startup message and basic board operation
Serial text is unreadableBaud or clock mismatchConfirm 9600 baud and the expected CPU clock
Sequence repeatedly returns to redBoard resets or supply interruptionsInvestigate startup messages and supply stability
New code has no effect in ProteusOld compiled file remains selectedRebuild and check the model's program-file path

Extend the Demonstration Without Losing Control

A useful next step is adding a button that requests a change. Do not let the button immediately jump to any color from any state. Record the request, debounce the switch and define which state transitions are allowed to consume that request. This keeps the timing logic explicit.

For two opposing model directions, use one shared phase definition rather than two independent three-color loops. Otherwise both directions may reach green at the same time. Write a table of permitted lamp combinations and include a deliberately defined clearance state for the model before writing additional GPIO code.

That extension remains an educational exercise. Actual traffic-control systems require requirements, independent safeguards and validation beyond the Arduino state machine shown here. The useful transferable lesson is to express allowed states and transitions clearly, not to copy classroom timing into a real installation.

Project Review and Related Tutorial

The Arduino version makes timing and sequencing easy to change without replacing timing components. It also gives you a natural place to add diagnostics and input handling. Its behavior still depends on correct firmware, a stable supply and the electrical output stage.

Compare it with our traffic-signal demonstration using a 555 timer. That design teaches oscillator and counter behavior, while this project teaches program states. Neither approach becomes a complete road controller merely because three colors appear in sequence.

Frequently Asked Questions

Why does the revised program start with red?

It provides a clear, repeatable startup state for the demonstration. Resetting the board restarts that same sequence rather than continuing from an unknown point.

Can I change the times?

Yes. Change the values returned by durationFor, expressed in milliseconds. Keep the state order and expected test table consistent with your changes.

Why avoid delay for the main sequence?

Elapsed-time checks let loop continue running between transitions. That makes later button handling and diagnostics easier, provided those additions also avoid long blocking operations.

Is one resistor enough for all three LEDs?

Use one resistor per LED branch. This preserves predictable current if the circuit is later changed to permit more than one illuminated indicator.

Can the Arduino drive full-size signal lamps?

No, not directly. Those loads require appropriately designed drivers and power circuitry, and a real installation requires a much broader control and protection design.


Comments

3

Join the conversation

Reply1

hola buenas noches espero te encuentres bien, me eh dedicado a leer cada una de tus publicaciones, a decir verdad me han sido muy útiles para mi proyecto de tesis que trata sobre semáforos inteligentes, estoy documentandome y decidi trabajar con estas plataformas..... muchas gracias ..... pos data ese programa no me corrió le cambie el valor a las resistencias a 330 y fue que funciono ......casi pierdo la cabeza ..... de igual modo sos un genio..... saludos desde VENEZUELA

1 reply