Traffic light toy

Children love playing with car and vehicle toys, and this simple toy-sized traffic light adds some extra fun.


Green traffic light Red traffic light light Traffic light seen from the back


The initial version of this traffic light used three different LEDs: green, orange and red. However, finding LEDs with sufficiently discernible color difference between orange and red, and achieving uniform brightness across these LEDs by adjusting the serial resistors, proved to be very difficult. This first version, while functional, was not that great. The version presented here, uses 8mm Neopixel LEDs, allowing for easy adjustment of color and brightness through software, even if in practice the three LEDs consistently display the same color: red for the bottom one, orange for the middle one, and red for the top one. This change also simplifies the bill of material, as there are just 3 identical LEDs. This also simplifies the schematic, as they are daisy-chained, allowing the three of them to be controlled with a single MCU pin (well, in practice two as explained below) instead of three.

The board was designed using KiCad 6.0. The schematic is simple with very few components, as anyway space is quite constrained on the PCB. For this reason, and due to the 5 V power supply required by the Neopixels LEDs, I opted for an ATtiny9 MCU, which operates directly at 5 V. It has limited resources both in terms of RAM, Flash and pin counts, but suits the design well. It only has 6 pins, with two are allocated for the power supply, leaving 4 GPIO pins available. One is reserved for the reset signal of the programming interface, another is used by the push button, and one more controls the Neopixel LEDs. The last pin is used to provide power to the Neopixels LEDs, as unfortunately their idle current of 1 mA would quickly drain the battery if left powered. Indeed there is no ON/OFF switch on this device (as kids will forget to switch it off), instead the MCU enters a power-down after some time, typically consuming less than a micro-amp. The push button is then used to wake-up the MCU when the traffic light needs to be switched on. To avoid exceeding the maximum current per GPIO pin of 40mA, the brightness of the LEDs and the number of simultaneously lit LEDs must be limited, but this is not an issue for a traffic light usage. Given the low pin count of the MCU, the Neopixel and the push button pins are multiplexed with the programming interface. In practice, it means the push button should not be used during programming, although it should only result in programming failure, as programmers should be protected against short circuits. The five pins required to program the MCU are accessible as pads on the PCB, allowing them to be temporarily soldered for programming purposes. Finally, to provide the 5 V required by the MCU and the LEDs, a TPS61222 boost converter is used. It operates down to 0.7V and has a quiescent current of only 5.5 ยต, providing a few months of usage on a pair of NiMH batteries.

All the components from the bill of material should be easily available from many distributors. Soldering the components onto the PCB should be relatively straightforward for someone used to SMD components. As seen on the photos, the battery holder is used as a stand for the traffic light, ensuring its stability, and the PCB is attached through an angle bracket and a pair of M3 screws.


The software for the MCU is quite simple, mostly written in C (with the exception of the timing-critical routine for controlling Neopixel LEDs) and organized around a single source file. The entire code occupies a bit less than two-thirds of the 1-kilobyte flash memory. The RAM is not used as everything fits in the CPU registers, thanks to the 32 registers of the AVR CPU.

The main part of the code consists mostly in a state machine, defined by the states_desc table. It describes all possible states of a traffic light. For each state, the brightness of each of the 3 RGB LEDs inside each of the 3 Neopixel LEDs is defined. Additionally, for each state, the following are defined:

  • Duration of the state
  • Next state at the end of the duration
  • Next state in case of a short button press
  • Next state in case of a medium button press
  • Next state in case of a long button press

Button presses shorter than 10 ms are ignored for debouncing purposes, and longer ones are categorized as follows:

  • Short: >= 10 ms and < 500 ms
  • Medium: >= 500 ms and < 1500 ms
  • Long: > 1500 ms

By default, the state machine is configured to execute the standard traffic light sequence observed in France and many other countries: green, orange, red, and back to green. The code includes comments to support the use of orange and red together between the red and green states, as seen in some countries. A short button push advances to the next state without waiting for the full duration. A medium push switches the traffic light to blinking orange, and another medium push restores the normal sequence. Finally, a long push turns off the traffic light, using the special state state_off, which disables the power supply pin of the LEDs, enables interrupt on the button pin, and puts the MCU in a power-down state.

The signal to control the Neopixel LEDs is generated through bit-banging, using an inline assembly-based routine. As nicely explained by Josh Levine, the timing is not as critical as one might expect from reading the datasheet, only certain aspects of the timing are crucial. This trick enables the use of the internal RC oscillator running at only 8 MHz.

A makefile is provided to compile the code using AVR GCC and AVR Libc:

  $ make
  avr-gcc -MMD -g -Wall -Wextra -Os -fshort-enums -mmcu=attiny9   -c -o main.o main.c
  avr-gcc -MMD -g -Wall -Wextra -Os -fshort-enums -mmcu=attiny9 -Wl,-Map,traffic-light.map,--relax -fwhole-program -o traffic-light.elf main.o
  avr-objdump -h -S traffic-light.elf > traffic-light.lst
  avr-objcopy -j .text -j .data -j .rodata -O ihex traffic-light.elf traffic-light.hex
  AVR Memory Usage
  Device: attiny9

  Program:     594 bytes (58.0% Full)
  (.text + .data + .bootloader)

  Data:          0 bytes (0.0% Full)
  (.data + .bss + .noinit)

To program the device using an AVRISP MKII programmer and the avrdude software, simply execute the make flash command. Admittedly, the makefile has only been tested on Debian GNU/Linux, so it might require some adjustments, especially when using a different programmer.


The contents of this repository, with the exception the software, is released under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0). The software is released under the terms of the GNU GPL, version 2.