Skill Level: Intermediate
Table Of Contents
- Introduction
- What Is Needed
- Background Information
- Constructing The Scene And Circuit
- Writing The Program
- Running And Testing The Simulation
- Additional Resources
- Summary
Introduction
This tutorial will show you how to use a CircuitPython compatible board to simulate fireflies so you can enjoy them any time of year.
A basic understanding of electronics and programming is expected along with some familiarity with the CircuitPython platform. If you are new to CircuitPython, or would just like to refresh your knowledge, please see our Getting Started With CircuitPython On Compatible Microcontroller Boards tutorial before proceeding with this one.
This tutorial uses a solderless breadboard to build a circuit from a schematic diagram. The All About Circuit’s Understanding Schematics, SparkFun’s How to Read a Schematic, Core Electronics’ How to Use Breadboards, and Science Buddies’ How to Use a Breadboard guides are good resources for learning how to translate a schematic to a breadboard.
The resources created for this tutorial are available on GitHub for your reference.
What Is Needed
- Linux, macOS, Or Windows Based Computer With A USB Port
- Printer
- Mu Python Editor (recommended)
- CircuitPython Compatible Microcontroller Board (with at least 8 digital outputs) With Compatible USB Cable (available on Adafruit)
- Solderless Breadboard (available on Adafruit and SparkFun)
- 9 x Male/Male Jumper Wires (available on Adafruit and Arrow)
- 9 x Alligator Clip Test Leads (available on Adafruit and SparkFun)
- 8 x Standard 5mm or 3mm Yellow LEDs (5mm available on Adafruit and SparkFun, 3mm available on Adafruit and Sparkfun)
- 8 x 330 Ω Resistors (available on SparkFun and Amazon)
- Conductive Copper Tape (available on Adafruit and Amazon)
- Masking Tape And/Or Electrical Tape (preferred for insulating electrical connections)
- Scissors
- Double Sided Tape Or Glue Stick
- Nail Or Tack
- Cardboard
Background Information
One night while watching fireflies in the backyard, my wife and I came up with the idea to create a firefly project that lit up the same way fireflies do in the summertime. She would create the design and I would create the electronics. For this project, we’ll be printing out her scene design and constructing the circuit on the back of the scene. In order to be as realistic as possible, I reviewed several sources on the internet to determine typical firefly flash timings. I did not realize that there were so many species of fireflies or that they were so different in their flash patterns. I started with some initial flash timings based on that research, but ended up fine tuning them in the program based on my own observations watching them in the backyard.
Depending on how you want your simulated fireflies scene to look and how permanent you want the project to be, there are a variety of options you can use for your scene and circuit. The scene can be physically propped up on its side with cardboard stabilizers or placed in a frame or shadow box for a more polished look. The electronics (LEDs and resistors) can be connected in the scene using solder, wires, conductive tape, alligator clip test leads, or some combination thereof. I chose a simpler and more temporary approach for my project, and this tutorial, using conductive tape and alligator test leads, but I am placing the scene within an 8″x10″ frame. In addition, I am using the computer’s USB port to power the microcontroller board and simulation. If you are interested in a more permanent display, you may want to use a separate power source or battery.
I am using Adafruit’s Feather M4 Express microcontroller board connected to a macOS based computer with the Mu Python editor for this tutorial. If you are using a different CircuitPython compatible board, computer setup, or code editor, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.
If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.
Constructing The Scene And Circuit
Download the printable fireflies scene graphic (shown below) from the GitHub repository for this project. There isn’t anything electronically special about this scene, you can design and use your own scene if you prefer.
Print the scene onto a piece of paper and trim the edges to your desired size. I am using a size of 8″x10″ in order to place the scene within a frame of the same size.
Cut out a piece of cardboard (e.g. corrugated cardboard, cereal box, etc.) of the same size as the paper scene and attach the scene with glue or double sided tape to the cardboard. This makes the scene sturdy enough to attach our electronics.
The circuit for the fireflies will be constructed on the back of the scene and connected to our microcontroller board’s GPIO pins according to the schematic shown below.
Poke holes in the scene with a nail or tack where you want your LEDs to shine through. Poke from the front of the scene so that the excess paper and cardboard are pushed to the back. Use larger holes for fireflies in the foreground and smaller holes for fireflies in the background.
Tape the bodies of all the LEDs (on their sides) with masking or electrical tape over the punched holes on the back of the scene so that the light from the LEDs shines through to the front of the scene. All the terminals of the LEDs should be free of tape (available for connections).
Use long strips of conductive copper tape to connect the cathode (shorter lead) terminals of all the LEDs together to form a common ground.
Attach a 330 Ω resistor to the scene next to the anode (unconnected longer lead) side of one of the LEDs using masking tape across the body of the resistor, leaving both of the resistor’s terminals free. Attach the closest terminal of the resistor to the LED’s anode using a short strip of conductive tape. You may need to use additional masking or electrical tape underneath the connection in order to isolate the resistor connection from the ground connection. Do this for all of the other LEDs as well.
Verify all components are securely attached to the back of the scene and that you do not have any unintended circuit connections.
The back of your scene should look similar to the following once it is complete.
Next, we will connect our scene to the microcontroller board. Before connecting any circuitry to your board, disconnect it from power and your computer. This avoids accidental damage during wiring.
Many of the CircuitPython compatible boards do not have headers pre-attached. If this is the case for your board, you will need to solder the headers onto the board before attaching the board to a solderless breadboard.
Attach one side of 9 male-to-male jumper wires to each of the microcontroller board’s 8 digital GPIO pins (D0-1, D4-6, and D9-11) and a ground pin.
Using the alligator clip test leads, connect the open terminal of each resistor in the scene to the board’s GPIO jumper wires, and then connect the common ground on the scene to the board’s ground (GND) pin.
Once you’re happy with your fireflies scene and circuit, you can add cardboard stabilizers to the back sides of the scene or place the scene in a frame. The completed scene and circuit should look similar to the following once completed.
Once the circuit and scene are built, verify that you do not have any unintended circuit connections. You can use electrical tape around the alligator clips for more protection.
Connect your microcontroller board to your computer with the USB cable.
Writing The Program
Open Mu or your favorite code editor and create a CircuitPython program with the code shown below.
import random import time import board from digitalio import DigitalInOut DEBUG = True LIGHT_TIME = 0.5 MIN_DARK_TIME = 5.0 MAX_DARK_TIME = 10.0 LEDS = [ DigitalInOut(board.D0), DigitalInOut(board.D1), DigitalInOut(board.D4), DigitalInOut(board.D5), DigitalInOut(board.D6), DigitalInOut(board.D9), DigitalInOut(board.D10), DigitalInOut(board.D11) ] class Firefly: def __init__(self, pin, name="Unknown", is_lit=False, trigger_time=0, trigger_delay=0): self.pin = pin self.name = name self.is_lit = is_lit self.trigger_time = trigger_time self.trigger_delay = trigger_delay def __str__(self): return f"trigger_time = {self.trigger_time}, trigger_delay = {self.trigger_delay}, name = {self.name}, is_lit = {self.is_lit}" fireflies = [] def process_firefly(firefly): current_time = time.monotonic() if firefly.is_lit == False and current_time - firefly.trigger_time >= firefly.trigger_delay: if DEBUG: print(f"Firefly: currentTime = {current_time}, {firefly}") print(" Turning on firefly.") firefly.pin.value = True firefly.is_lit = True firefly.trigger_time = current_time elif firefly.is_lit == True and current_time - firefly.trigger_time >= LIGHT_TIME: if DEBUG: print(f"Firefly: currentTime = {current_time}, {firefly}") print(" Turning off firefly.") firefly.pin.value = False firefly.is_lit = False firefly.trigger_delay = LIGHT_TIME + random.uniform(MIN_DARK_TIME, MAX_DARK_TIME) if DEBUG: print("Running in DEBUG mode. Turn off for normal operation.") for index, led in enumerate(LEDS): led.switch_to_output(value=False) fireflies.append(Firefly(pin=led, name=f"LEDS[{index}]", trigger_delay=random.uniform(MIN_DARK_TIME, MAX_DARK_TIME))) while True: for firefly in fireflies: process_firefly(firefly)
Line 6 defines the debugging mode of the program. If enabled (DEBUG
is set to True
), then debugging messages are printed to the serial console. Set DEBUG
to False
for normal operation.
Lines 7-9 define the firefly flash timings. As mentioned previously these numbers are based on research and observation. LIGHT_TIME
specifies how long each firefly will remain lit in seconds; a fixed half a second in this case. The MIN_DARK_TIME
and MAX_DARK_TIME
constants define the minimum and maximum amount of time that each firefly will not be lit; defined here as somewhere between 5 and 10 seconds. These timing constants can be adjusted to suit your own needs or observations.
The LEDS
array specifies all of the GPIO pins that will be used to control the connected LEDs representing our fireflies. The program was written to take into account a variable number of GPIO pins. So if you want to use fewer or more pins to simulate your fireflies, the program can handle that. I am using 8 GPIO pins, the same number as the background scene, to demonstrate sufficient firefly activity. Most CircuitPython boards can easily handle the current needed for 8 LEDs (about 3-5 mA per LED for 3.3 V GPIO). If you increase the number of LEDs, ensure the board you are using can handle the extra current load.
The Firefly
class, defined on lines 22-31, holds the relevant information required for each distinct firefly instance that we want to simulate. The pin
member contains the GPIO pin that will be used to control the LED for that specific firefly instance. name
is used as an identifier when printing debugging messages. is_lit
holds the status of whether the firefly is currently lit or not. The trigger_time
member keeps track of the last time (in seconds) the firefly was turned on. And finally, trigger_delay
holds the delay (also in seconds) between the time the firefly was last lit to the next time that the firefly will be lit. A string representation method, __str__
, is utilized to specify what gets printed when the Firefly
instances are printed within the debugging messages.
Line 33 defines the array of individual Firefly
instances that are created and initialized for use in lines 55-57. As you can see on line 57, I am randomizing the trigger delay value to be somewhere between MIN_DARK_TIME
and MAX_DARK_TIME
. I also chose to use the index of the LEDS
array as the name to identify which firefly was being processed within the debugging messages.
The process_firefly()
function, defined on lines 35-50, is the heart of the program. It compares the current time against the firefly timing constants to determine the appropriate times to turn on or off the firefly instance along with its associated LED. The is_lit
, trigger_time
, and trigger_delay
firefly instance members are then updated as needed in preparation for the next time that the firefly will be processed. If the debugging mode is enabled, the current time and status of the firefly instance are also printed upon each change of status.
Finally, the endless loop at the end of the program processes each firefly in turn by calling the process_firefly()
function on each firefly instance.
If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.
Running And Testing The Simulation
Now that our scene and circuit are built and our software is written, it is time to run and test our simulated fireflies.
If you are using the Mu editor with a general CircuitPython compatible microcontroller board, click the Serial icon within the menu bar at the top to open the serial console. It will appear at the bottom of the Mu editor’s window and will be used to view the program’s output. Save the program as code.py to the top level of the board’s CIRCUITPY drive and it will begin running automatically.
You should see all of the fireflies lighting up within the scene along with each firefly’s status change being printed within the serial console (if the debugging mode is enabled).
The following video shows my simulated scene.
Working Fireflies Simulation
If you are interested, try adjusting some of the firefly timing constants and watching how those changes affect your simulation.
The flash pattern and timings that I used for my simulated fireflies may be different from the firefly species you have in your neck of the woods. If you are feeling particularly ambitious, perhaps you can try to implement some of the more complex firefly flash patterns as described within the Talk Like a Firefly by Science Friday and Firefly Flash Patterns by the United States National Park Service articles.
If you don’t intend to have a more permanent display, now would be a good time to reset all of the board’s GPIO pins back to their default states. This ensures no outputs are being driven that may damage your board or connected electronics when plugging in your board for your next project. Copy the current code.py program to fireflies.py and update code.py to contain only the following endless loop.
while True: pass
Additional Resources
The following is a list of additional resources you may find helpful.
- Firefly on Wikipedia
- Talk Like a Firefly by Science Friday
- Firefly Flash Patterns by the United States National Park Service
Summary
In this tutorial, we
- built a scene with LEDs to display simulated fireflies,
- connected the scene’s LEDs to a CircuitPython compatible microcontroller board,
- wrote a CircuitPython program to simulate the firefly flash timings of the LEDs, and
- ran and tested the program to display our simulated fireflies.
The final source code, schematic, and scene used for this tutorial are available on GitHub. The GitHub version of the code is fully commented to include additional information, such as the program’s description, circuit connections, library references, code clarifications, and other details. The comments are also Sphinx compatible in case you want to generate the code documentation.
Thank you for joining me on this journey and I hope you enjoyed the experience. Please feel free to share your thoughts or questions in the comments section below.
This tutorial is provided as a free service to our valued readers. Please help us continue this endeavor by considering a donation.
Why not just use ws2811 and a nano?
Using WS2811 chips and neopixels is definitely an option, but I believe using standard LEDs is a cheaper alternative and easier to implement.
To what nano are you referring? I am only familiar with Arduino Nano boards. I used the Adafruit Feather M4 Express board because that is what I had available.