Create Your Own Animated Ornament with QTPy!

Options
This discussion has a more recent version.
MicroCenterOfficial
edited January 2022 in Maker

Written by Nick Biederman

With the holiday season in full swing, two things are sure to happen: 1) you'll want to do more holiday DIY projects, and 2) you won’t have time to do holiday DIY projects. Thankfully, the Neopixel Christmas ornament is a fantastic DIY project that won’t take long to put together. This is a simple project that requires minimal materials. We’ll be using a QTPy for its small size and will be powering the ornament via the USB port, but you can use almost any microcontroller and can power it with a battery if you’d prefer. It’s a great way to dip your feet into the CircuitPython ecosystem if you haven’t tried it out yet.

 What You’ll Need

Materials:

  •  CircuitPython compatible Microcontroller (We chose Adafruit’s QTPy2040)
  •  8x8 Neopixel Matrix
  •  Power Supply (We’ll be using a USB cable, but a battery would work well)
  •  Computer with a text editor (We used Notepad++ on Windows, but Mu is a more versatile option)
  •  Programming cable (This varies depending on the microcontroller you use. For the QTPy, a USB-C cable was required.)
  •  Hookup Wire

 Tools:

  •  Soldering Iron and Solder
  • Wirecutter and stripper
  • 3D printer, laser cutter, or other method to manufacture a case
  • Hot glue gun

 

How to Build Your Own Animated Ornament

CircuitPython Preparation

Start by downloading the most recent version of CircuitPython for your board. You can find it on the CircuitPython website. Next, hold the “boot” button on your microcontroller while plugging the microcontroller into your computer. You should see a new drive appear on your computer. Copy the CircuitPython file to the new drive and wait for it to reboot. After the microcontroller reboots you should see a handful of new files and directories.

Once CircuitPython has been installed you should see this file structure on your microcontroller.

 Next, download the appropriate set of libraries for the version of CircuitPython you are using. You can find them here. They’re listed as “bundles” for 6.x.x or 7.x.x. Copy and paste the file “neopixel.mpy” and the directory “adafruit_bus_device” into the root of the lib directory on your microcontroller.

We need two libraries, which should be placed in the “lib” directory of the microcontroller.

Wiring

Now that we have our IDE (Or Integrated Development Environment - basically, where the programming will be created) set up, we can wire up our ornament. We’ll need 3 wires to the matrix: Power (5v), GND, and data. We’ll be connecting power and ground directly to our QTPy. This usually isn’t the best practice, but it works for our project, as we won’t be driving the LEDs at full brightness and are not working with many LEDs. For projects with more LEDs or LEDs operating at full brightness, you may need an external power supply. The data wire can be connected to any digital output pin on the microcontroller. I chose D2, which is labeled “A2” on the QTPy. All the code samples provided below assume you are also using D2.

With only 3 wires, there’s no need to use protoboard for this project.

Programming

Plug your microcontroller into your computer and navigate to the CIRCUITPY drive. Open the “code.py” file in your text editor, and copy and paste the following code into the file. Save the file. Your microcontroller will reboot, and if everything has been done properly you should see a nice gradient from red to green on your LED matrix.

import board
import neopixel
 
pixels = neopixel.NeoPixel(board.D2, 64)
pixels[0] = (255, 255, 0)
for i in range(64):
         pixels[i] = (i/4, (64-i)/4, 0)

If everything is set up properly you should see a nice gradient like this

If your matrix doesn’t light up, check your connections. If you’re using a pin other than 2 on your board you will need to change “Board.D2” in line 4 to the pin you’ve chosen.

 Once we’re sure everything is working properly we can start drawing pictures. While it’s possible to draw an image in a program like Paint and display it on the matrix, I find it easier to create a matrix of RGB values by hand when working with small displays like this. The code below draws a simple Christmas tree with the image stored in an array named “image”:

import board
import neopixel
import time
image =[( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),(255,255, 0),(255,255, 0),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),(255,255, 0),(255,255, 0),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0,255, 0),( 0,255,  0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),
( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),(177, 76, 36),(177, 76, 36),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0)]
 
pixels = neopixel.NeoPixel(board.D2, 64, brightness=0.05, auto_write=True)
 
for i in range(64):
         pixels[i] = image[i]

The array “image” in the above code defines this Christmas tree image

You can change the image that’s displayed by replacing or editing the “image” array. Here’s one that draws a snowflake:

 image =[( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),
( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),
( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),
( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0)]

The “image” array can easily be modified to define other figures, like this snowflake

The code is easy to understand, and can easily be expanded. For example, it can easily be modified to switch between two images as seen below:

import board
import neopixel
import time
tree =[( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),(255,255, 0),(255,255, 0),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),(255,255, 0),(255,255, 0),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),
( 0, 0, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0,255, 0),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),(177, 76, 36),(177, 76, 36),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0)]
 
snowflake = [( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),
( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),
( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),
( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),
( 0, 0, 0),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 64, 64,255),( 0, 0, 0),
( 0, 0, 0),( 0, 0, 0),( 0, 0, 0),( 64, 64,255),( 64, 64,255),( 0, 0, 0),( 0, 0, 0),( 0, 0, 0)]
 
pixels = neopixel.NeoPixel(board.D2, 64, brightness=0.05, auto_write=True)
 
while True:
         for i in range(64):
       pixels[i] = tree[i]
         time.sleep(10)
         for i in range(64):
       pixels[i] = snowflake[i]
         time.sleep(10)

With some small changes, we can make an animated ornament

Closing it up

Now that we’ve assembled and programmed our ornament it’s time to put it in a case. We 3D printed a case using these files posted on Thingiverse. You could also use a laser cutter to build a box or make one from thick paper. Hot glue was used to hold everything in place, and the back pressed on without the need for glue. A bit of printer paper over the front can help diffuse the light and hide the circuit board, but we chose to leave ours exposed.

The case we used is a simple 2-piece design that offers plenty of room for a battery or larger microcontroller.

This quick project is a great way to add some extra light to your Christmas tree. With CircuitPython it’s easy to create dynamic lighting, and assembly is about as simple as it gets. The most time-consuming part is printing the enclosure, which took about 2.5 hours on an Ender 3 v2.

We’d love to see your ornaments and the images you come up with down below!

Looking for more Maker guides and ideas? We’ve got a full section dedicated to Maker as well as guides on How to Choose a 3D Printer, Understanding 3D Printer Filament Typesand New Projects for Old Raspberry Pis. And if you can’t find what you’re looking for, don’t hesitate to post a new discussion and the Community will be happy to help!

Comments

We love seeing what our customers build

Submit photos and a description of your PC to our build showcase

Submit Now
Looking for a little inspiration?

See other custom PC builds and get some ideas for what can be done

View Build Showcase

SAME DAY CUSTOM BUILD SERVICE

If You Can Dream it, We Can Build it.

Services starting at $149.99