Geometric LED matrix pendant with fortune-telling talent

 

Sometimes, you want to be subtle, and sometimes you want to be loud. And sometimes, you just want to ask the Fortunes a simple yes or no question. 

Now you can do all that with this modern necklace that looks subtle and simple at first glance, and then turns into a versatile light show with a touch or a tap. It even reaches into the random number universe to answer your categorical questions when you touch it!

This necklace is programmed with CircuitPython and made with the adorable DotStar LED Matrix, powered by the ItsyBitsy nRF52840 so you can easily customize your style over Bluetooth using the Adafruit Bluefruit Connect App!

The front panel is a fascinating black LED acrylic material, which stays opaque until LED lights shine through. It also has a brass piece that acts as a capacitive touch button and a rechargeable battery thanks to the LiPo backpack. 

The ItsyBitsy and LiPo battery are all housed inside a case that can be worn behind your neck, and the necklace is connected via a USB micro-B connector, so you can change the electronics housing if you desire. There is a lot of room for customization here to really make this project your own!

This project requires quite a bit of soldering, so here are some excellent guides if you want to brush up on the basics:

You'll also need to either laser cut or 3D print the both the pendant frame and electronics case. The black LED acrylic can be milled or laser cut to a simple square shape.

Parts for Pendant

Do not eat this LED grid just because it is so colorful and bite-sized! This is the tiniest little LED grid we could make, with 64 full RGB color pixels in a square that is only...
$24.95
In Stock
 nice whoppin' rectangular slab of some lovely black acrylic to add some extra diffusion to your LED Matrix project. This material is 2.6mm (0.1") thick and is made of...
$5.95
In Stock
1 x Brass kite shape
For capacitive touch sensing (choose whatever shape you like!)
1 x Small zip tie
Secure the wires on top of the pendant
1 x Double sided foam tape
Strong enough to stick to acrylic and PCBs
1 x Super glue
Get the gel kind so you have a few more seconds before it dries
1 x Jump ring
Optional. Makes it easy to attach the pendant to some thread
1 x Enameled copper wire
Optional. Use as a thin wire to connect the brass shape to the board.

Parts for Electronics + Power

What's smaller than a Feather but larger than a Trinket? It's an Adafruit ItsyBitsy nRF52840 Express featuring the Nordic nRF52840 Bluetooth LE...
$17.95
In Stock
Lithium ion polymer (also known as 'lipo' or 'lipoly') batteries are thin, light and powerful. The output ranges from 4.2V when completely charged to 3.7V. This battery...
$6.95
In Stock
1 x USB DIY Slim Connector Shell - MicroB Plug
To connect the necklace to the ItsyBitsy and power
1 x LiIon/LiPoly Backpack Add-On
Power the ItsyBitsy via a rechargeable battery
1 x Slide Switch
SPDT switch to control power when battery is connected
1 x Embroidery threads
Gold thread to wrap necklace cord and allow braiding of wires
1 x 1M (or greater) ohm resistor
For the capacitive touch pin
1 x Double sided thin tape
For securing the battery
1 x Brass teardrop shape
Optional. To decorate the electronics case with!

There are two circuits to build: the necklace itself and the ItsyBitsy + power housed inside the case. These are connected via the USB MicroB counterparts. These diagrams will detail which circuit connections need to be made, but we'll go over assembly in more detail later.

ItsyBitsy and Power Circuit

Note that the diagram above doesn't show the exact placement of the components -- it was laid out so that you can easily see the wire connections. Also note that this wiring means that the slide switch will turn off battery power, but it has no effect if the ItsyBitsy is connected to USB.

Here is a table for the connections illustrated above to help make it more clear:

LiPo backpack ItsyBitsy USB micro-B
BAT BAT -
G G G
5V USB -
- VHi 5V
- A1 D-
- A2 D+
- D11 ID

To make the slide switch a power switch, cut the copper trace between the two through-hole connectors on the LiPo backpack next to the 5V hole, and solder two wires through those to the slide switch.

To support capacitive touch on D11, connect a 1MΩ resistor from D11 to Ground. 

Necklace LED Matrix Circuit

The above diagram is a representation of the circuit connections that need to be made between the DotStar 8x8 Matrix and the USB MicroB plug. Note that there is a single MicroB plug pictured, with both of its sides showing. 

DotStar Matrix USB MicroB plug
CIN D-
DIN D+
GND GND
+5v +5V
- ID (to brass kite)

You can choose either laser cut parts or 3d printed parts for this project. Either way though, you'll need to cut the Black LED Acrylic front piece to the exact size needed. The cut dimensions of the acrylic varies depending on what method you choose:

  • If using the laser cut frame, cut the acrylic to 28.73mm x 28.70mm (the PDF and DXF files include this)
  • If using the 3d printed frame, cut the acrylic to 29.02mm x 28.99mm

The button link below includes both the laser cutter files and the 3d printer files:

You can also choose between two different types of cases that will hold the electronics and the battery. The hexagonal 3D printed case is a remix of the case from Neopixel LED Heart Necklace and is quite a bit easier to put together. The laser cut rectangular case will be faster to cut out but requires a bit of glueing. Choose the shape that most pleases you!

The button link below also includes both the laser cutter files and the 3d printer files:

Prepare the DotStar Matrix

 

Cut the mounting tabs off the DotStar matrix and carefully sand down with coarse grit sandpaper to smooth the edges. Test if it press fits into the pendant frame you chose before sanding down any further -- you don't want it too loose.

Solder wires

 

The length of the wires will depend on the diameter of your head since they'll need to be long enough to fit your head through. 20cm should be a good starting point, adjust as needed.

Tin the pads for all 4 contacts first, and then tin the exposed wires as well before soldering together. Make sure to cut any leads sticking out after.

It will help to use a different color wire for each pad, so that you can easily identify which wire is which later.

 

Make sure that the wires are flat

 

Double check that the wires are soldered as flat as possible against the PCB so that it will fit nicely within the pendant frame.

Attach matrix to the frame

 

Using either the 3d printed frame or the laser cut frame, push the wired matrix to the frame, it should just press fit in. 

You want the wires to be oriented as shown -- if the top hole is "north", the wires should be "southeast" of the hole. This orientation will ensure that the back cover will protect the wires AND it is the orientation that the code animations are setup for.

Glue the top cover to the frame

 

Using super glue or any other appropriate glue, secure the black LED front cover so that it's aligned with the frame and covers the LED matrix entirely.

Tip: using the "gel" kind for super glue will allow you to have significantly more seconds to align the cover to the frame before the glue dries.

Secure the back cover

 

For laser cut frame only. Secure the back cover using foam double sided tape.

Solder a wire to the brass shape

 

This brass shape will be used as a capacitive touch button. You can use the same wires as the ones used for the matrix, but that will add a bit of bulk to the necklace, so here I've opted to use some thin enameled copper wire instead.

Make sure that you're using a heat resistant silicone surface when soldering the wire to brass like this since the brass piece will get really hot. Check continuity with a multimeter to make sure that the solder joint is good.

Secure the brass shape onto the cover

 

Use some glue or double sided tape, and adjust to your desired position. Pause to admire the beauty that you have created so far :)

Secure the wires and use embroidery floss for support

 

Optionally attach a jump ring through the hole, then use a small zip tie to secure the wires together at the top of the pendant. 

Then, tie a piece of embroidery floss either directly through the pendant hole or through the jump ring if you attached one. This will help give the necklace some support, and will allow you to braid the wires together.

The length of the resulting 2 halves of the embroidery thread should be slightly longer than the rest of the wires to account for the braiding in the next step.

Braid the wires together to form a necklace chain

 

Separate the wires and the embroidery floss into two groups. Each group should have either 3 or 4 wires + floss each. Braid each group together to form the necklace chain.

After braiding, secure the end with a simple knot of the embroidery floss. This will be a good time to double check that the braided necklace chain will be long enough such that your head fits through.

At this point, you'll have something that looks like the picture below. Pause here to admire your handy work! :)

Solder the necklace chain wires to the USB microB plug

 

Now that you have double checked that the length of the wires is enough to fit your head through (you did, right?), you can proceed to soldering the ends to the microB plug. First, insert the wires through the two connector pieces as shown. Then, refer to the circuit diagram in the previous page to solder the proper wires. Use the different wire colors to guide you through this step. 

Note that the circuit diagram refers to both sides of the microB plug (the 2-pad and 3-pad side)

Optional: Wrap the necklace chain with gold embroidery floss

 

This step is optional because it takes a long time to do, but the result is quite satisfying! It helps if you wrap the embroidery floss around something cylindrical so that you can more easily maneuver the floss around the braided wires.

Now, you're ready to tackle the next part: the ItsyBitsy wiring with the LiPo battery!

Note: The following photos will show a silver slide switch, but the same steps apply to the Adafruit black slide switches.

Prepare the power slide switch

 

Cut one of the side legs, and shorten the remaining two legs. Tin the legs, tin the wires, and solder them together. Cover with heat shrink tubing.

Prepare the LiPo backpack

 

Cut the copper trace between the two holes next to the mounting hole. This will allow us to attach the slide switch that will act as a power switch.

Solder 3 ribbon wires to the through-holes for 5V, BAT and GND. The length of the ribbon wires can be about 5cm.

Prepare the USB MicroB port

 

Solder 5 ribbon wires onto all 5 through-holes. The length of the ribbon wires can be around 6cm.

Combine the ground wires

 

Carefully identify the GND wires for both the USB microB port and the LiPo backpack, twist them together, and dab a bit of solder to secure.

Solder combined GND wires plus resistor to ItsyBitsy

 

Take the combined GND wires and fit them into GND of the ItsyBitsy. Before soldering, fit a 1M resistor or higher into GND as well, and put the other end of the resistor through D11.

Do not solder on D11 yet.

BAT and 5V from the LiPo backpack connects to BAT and USB of the ItsyBitsy respectively. Solder these connections.

Solder USB MicroB's ID to ItsyBitsy's D11

 

Carefully identify which wire from the MircoB port is connected to ID, and solder that into the ItsyBitsy's D11 together with the resistor.

This resistor is important to be able to use the brass shape as a capacitive touch button.

Solder remaining 3 wires from microB port

 

Carefully identify the wires, and connect:

  • microB port's 5V to ItsyBitsy's VHi
  • microB port's D- to ItsyBitsy's A1
  • microB port's D+ to ItsyBitsy's A2

Once all the wires are soldered, connect the battery to the LiPo backpack and test to see that the green light on the ItsyBitsy turns on. 

Now, turn off your soldering iron, find some fresh air away from any lingering fumes, and take a deep breath. You've come quite far in this journey, I'm proud of you. :D

Before we move on with final case assembly, we should setup and load the CircuitPython code into the ItsyBitsy so that we can test and make sure that all our soldered connections work before we stuff everything inside an enclosure!

CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.

Set up CircuitPython Quick Start!

Follow this quick step-by-step for super-fast Python power :)

Further Information

For more detailed info on installing CircuitPython, check out Installing CircuitPython.

Click the link above and download the latest UF2 file.

Download and save it to your desktop (or wherever is handy).

Plug your Itsy nRF52840 into your computer using a known-good USB cable.

A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.

In the image, the Reset button is indicated by the magenta arrow, and the BTLE status LED is indicated by the green arrow.

Double-click the Reset button on your board (magenta arrow), and you will see the BTLE LED (green arrow) will pulse quickly then slowly blue. If the DotStar LED turns red, check the USB cable, try another USB port, etc.

If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!

You will see a new disk drive appear called ITSY840BOOT.

Drag the adafruit_circuitpython_etc.uf2 file to ITSY840BOOT.

The LED will flash. Then, the ITSY840BOOT drive will disappear and a new disk drive called CIRCUITPY will appear.

That's it, you're done! :)

As we continue to develop CircuitPython and create new releases, we will stop supporting older releases. Visit https://circuitpython.org/downloads to download the latest version of CircuitPython for your board. You must download the CircuitPython Library Bundle that matches your version of CircuitPython. Please update CircuitPython and then visit https://circuitpython.org/libraries to download the latest Library Bundle.

Each CircuitPython program you run needs to have a lot of information to work. The reason CircuitPython is so simple to use is that most of that information is stored in other files and works in the background. These files are called libraries. Some of them are built into CircuitPython. Others are stored on your CIRCUITPY drive in a folder called lib. Part of what makes CircuitPython so awesome is its ability to store code separately from the firmware itself. Storing code separately from the firmware makes it easier to update both the code you write and the libraries you depend.

Your board may ship with a lib folder already, it's in the base directory of the drive. If not, simply create the folder yourself. When you first install CircuitPython, an empty lib directory will be created for you.

CircuitPython libraries work in the same way as regular Python modules so the Python docs are a great reference for how it all should work. In Python terms, we can place our library files in the lib directory because its part of the Python path by default.

One downside of this approach of separate libraries is that they are not built in. To use them, one needs to copy them to the CIRCUITPY drive before they can be used. Fortunately, we provide a bundle full of our libraries.

Our bundle and releases also feature optimized versions of the libraries with the .mpy file extension. These files take less space on the drive and have a smaller memory footprint as they are loaded.

Installing the CircuitPython Library Bundle

We're constantly updating and improving our libraries, so we don't (at this time) ship our CircuitPython boards with the full library bundle. Instead, you can find example code in the guides for your board that depends on external libraries. Some of these libraries may be available from us at Adafruit, some may be written by community members!

Either way, as you start to explore CircuitPython, you'll want to know how to get libraries on board.

You can grab the latest Adafruit CircuitPython Bundle release by clicking the button below.

Note: Match up the bundle version with the version of CircuitPython you are running - 3.x library for running any version of CircuitPython 3, 4.x for running any version of CircuitPython 4, etc. If you mix libraries with major CircuitPython versions, you will most likely get errors due to changes in library interfaces possible during major version changes.

If you need another version, you can also visit the bundle release page which will let you select exactly what version you're looking for, as well as information about changes.

Either way, download the version that matches your CircuitPython firmware version. If you don't know the version, look at the initial prompt in the CircuitPython REPL, which reports the version. For example, if you're running v4.0.1, download the 4.x library bundle. There's also a py bundle which contains the uncompressed python files, you probably don't want that unless you are doing advanced work on libraries.

After downloading the zip, extract its contents. This is usually done by double clicking on the zip. On Mac OSX, it places the file in the same directory as the zip.

Open the bundle folder. Inside you'll find two information files, and two folders. One folder is the lib bundle, and the other folder is the examples bundle.

Now open the lib folder. When you open the folder, you'll see a large number of mpy files and folders

Example Files

All example files from each library are now included in the bundles, as well as an examples-only bundle. These are included for two main reasons:

  • Allow for quick testing of devices.
  • Provide an example base of code, that is easily built upon for individualized purposes.

Copying Libraries to Your Board

First you'll want to create a lib folder on your CIRCUITPY drive. Open the drive, right click, choose the option to create a new folder, and call it lib. Then, open the lib folder you extracted from the downloaded zip. Inside you'll find a number of folders and .mpy files. Find the library you'd like to use, and copy it to the lib folder on CIRCUITPY.

This also applies to example files. They are only supplied as raw .py files, so they may need to be converted to .mpy using the mpy-cross utility if you encounter MemoryErrors. This is discussed in the CircuitPython Essentials Guide. Usage is the same as described above in the Express Boards section. Note: If you do not place examples in a separate folder, you would remove the examples from the import statement.

If a library has multiple .mpy files contained in a folder, be sure to copy the entire folder to CIRCUITPY/lib.

Example: ImportError Due to Missing Library

If you choose to load libraries as you need them, you may write up code that tries to use a library you haven't yet loaded.  We're going to demonstrate what happens when you try to utilise a library that you don't have loaded on your board, and cover the steps required to resolve the issue.

This demonstration will only return an error if you do not have the required library loaded into the lib folder on your CIRCUITPY drive.

Let's use a modified version of the blinky example.

Download: file
import board
import time
import simpleio

led = simpleio.DigitalOut(board.D13)

while True:
    led.value = True
    time.sleep(0.5)
    led.value = False
    time.sleep(0.5)

Save this file. Nothing happens to your board. Let's check the serial console to see what's going on.

We have an ImportError. It says there is no module named 'simpleio'. That's the one we just included in our code!

Click the link above to download the correct bundle. Extract the lib folder from the downloaded bundle file. Scroll down to find simpleio.mpy. This is the library file we're looking for! Follow the steps above to load an individual library file.

The LED starts blinking again! Let's check the serial console.

No errors! Excellent. You've successfully resolved an ImportError!

If you run into this error in the future, follow along with the steps above and choose the library that matches the one you're missing.

Library Install on Non-Express Boards

If you have a Trinket M0 or Gemma M0, you'll want to follow the same steps in the example above to install libraries as you need them. You don't always need to wait for an ImportError as you probably know what library you added to your code. Simply open the lib folder you downloaded, find the library you need, and drag it to the lib folder on your CIRCUITPY drive.

You may end up running out of space on your Trinket M0 or Gemma M0 even if you only load libraries as you need them. There are a number of steps you can use to try to resolve this issue. You'll find them in the Troubleshooting page in the Learn guides for your board.

Updating CircuitPython Libraries/Examples

Libraries and examples are updated from time to time, and it's important to update the files you have on your CIRCUITPY drive.

To update a single library or example, follow the same steps above. When you drag the library file to your lib folder, it will ask if you want to replace it. Say yes. That's it!

A new library bundle is released every time there's an update to a library. Updates include things like bug fixes and new features. It's important to check in every so often to see if the libraries you're using have been updated.

Now that we've gone over the basics of CircuitPython and how to download the libraries, let's gather all the ones we need to make sure that this code runs. 

In your CIRCUITPY drive, make sure that you have a lib folder, and copy these libraries into it:

  • adafruit_ble
  • adafruit_bluefruit_connect
  • adafruit_dotstar.mpy
  • adafruit_led_animation
  • adafruit_pixel_framebuf.mpy

And then, download the font5x8.bin file using the button below, and then copy it into the root of your CIRCUITPY drive.

Your CIRCUITPY drive should now have the following files on it:

This necklace code uses the excellent Adafruit Bluefruit app, you can get it free for both Android and iOS. Read more about what this app can do in its official guide: Bluefruit LE Connect for iOS and Android!

Now that you've setup CircuitPython on the ItsyBitsy, loaded the requisite libraries, and downloaded the app from the links above, we can take a look at the code! Skip towards the bottom of this page if you want to see the entire code right away. We'll go through some of the main parts of the code before then.

The main while loop will be structured like this:

Download: file
# Initial empty state
state = ""
 
while True:
    # Advertise when not connected.
    ble.start_advertising(advertisement)
 
    while not ble.connected:
        # do something while ble is not yet connected
 
    while ble.connected:
        # Receive packets from Adafruit Bluefruit app
        # Set the state to prevent redundant hits
        # Act upon the current state
        # Also handle touch interrupt

The behavior when there is no BLE connection is simple: we detect touch, and then randomly choose Y or N to display. If there's no touch, we display a default, very classy, twinkling animation. Now, you can ask a question of the necklace, and the random number gods will give you the answer you seek!

Download: file
if touch.value:
  yes_or_no() # Randomly displays 'Y' for yes and 'N' for no.
else:
  twinkle() # Keep it classy.

When we are connected over BLE, we then watch out for any packets that come in from the app, and we set the value of the state variable accordingly. Setting this state will keep behavior deterministic and will prevent any app double-taps from affecting the necklace. Note that to keep it simple, I've opted to simply use strings and if-statements to track the state here, but you could also use constant variables and/or other data structures to optimize further.

Download: file
# Set state string based on pressed button from Bluefruit app
# and to prevent redundant hits
if isinstance(packet, ButtonPacket) and packet.pressed:
  # UP button pressed
  if packet.button == ButtonPacket.UP and state != "chase":
    state = "chase"
    # DOWN button
    elif packet.button == ButtonPacket.DOWN and state != "comet":
      state = "comet"
      # ...

Finally, we display animations based on the value of the state string, and we choose a different default animation just to indicate to the necklace viewer that Bluetooth is indeed connected. Touch is in a separate if-statement so that it can be triggered in the middle of the other animations.

Download: file
# Touch is handled as a separate state
if touch.value:
    yes_or_no()

# Act upon the state
if state == "chase":
    chase.animate()
elif state == "comet":
    rainbow_comet.animate()
elif state == "rainbowchase":
    rainbow_chase.animate()
elif state == "hello":
    pixels.fill(0)
    scroll_text(packet, SCROLL_TEXT_CUSTOM_WORD)
else:
    chase.animate()

Full code

The rest of the code are organized into utility functions that handle separate animations or responsibilities. You can copy and paste the entire code below into code.py using the mu-editor or any other preferred text editor, and hit save! The code has a few comments spread throughout that will help explain what the code is doing.

import time
import adafruit_dotstar
import board
import random
import touchio
from adafruit_pixel_framebuf import PixelFramebuffer
from adafruit_led_animation.animation.rainbowchase import RainbowChase
from adafruit_led_animation.animation.rainbowcomet import RainbowComet
from adafruit_led_animation.animation.chase import Chase
from adafruit_led_animation.color import PINK

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket

################################################################################
# Customize variables

# Set capacitive touch pin
TOUCH_PIN = board.D11

# These are the pixels covered by the brass cap touch
# We will try to avoid using these pixels in the "twinkle" default animation
COVERED_PIXELS = [40,41,42,48,49,50,56,57,58]

# Adjust this higher if touch is too sensitive
TOUCH_THRESHOLD = 3000

# Adjust SCROLL_TEXT_COLOR_CHANGE_WAIT lower to make the color changes for
# the text scroll animation faster
SCROLL_TEXT_COLOR_CHANGE_WAIT = 5

# Change this text that will be displayed when tapping 2 on the
# Bluefruit app control pad (after connecting on your phone)
SCROLL_TEXT_CUSTOM_WORD = "hello world"

# Increase number to slow down scrolling
SCROLL_TEXT_WAIT = 0.05

# How bright each pixel in the default twinkling animation will be
TWINKLE_BRIGHTNESS = 0.1

###############################################################################
# Initialize hardware

touch_pad = TOUCH_PIN
touch = touchio.TouchIn(touch_pad)
touch.threshold = TOUCH_THRESHOLD

ble = BLERadio()
uart_service = UARTService()
advertisement = ProvideServicesAdvertisement(uart_service)

# Colors
YELLOW = (255, 150, 0)
TEAL = (0, 255, 120)
CYAN = (0, 255, 255)
PURPLE = (180, 0, 255)
TWINKLEY = (255, 255, 255)
OFF = (0, 0, 0)

# Setup Dotstar grid and pixel framebuffer for fancy animations
pixel_width = 8
pixel_height = 8
num_pixels = pixel_width * pixel_height
pixels = adafruit_dotstar.DotStar(board.A1, board.A2, num_pixels, auto_write=False, brightness=0.1)
pixel_framebuf = PixelFramebuffer(
    pixels,
    pixel_width,
    pixel_height,
    rotation=1,
    alternating=False,
    reverse_x=True
)
# Fancy animations from https://learn.adafruit.com/circuitpython-led-animations
rainbow_chase = RainbowChase(pixels, speed=0.1, size=3, spacing=6, step=8)
chase = Chase(pixels, speed=0.1, color=CYAN, size=3, spacing=6)
rainbow_comet = RainbowComet(pixels, speed=0.1, tail_length=5, bounce=True, colorwheel_offset=170)


def scroll_framebuf_neg_x(word, color, shift_x, shift_y):
    pixel_framebuf.fill(0)
    color_int = int('0x%02x%02x%02x' % color, 16)

    # negate x so that the word can be shown from left to right
    pixel_framebuf.text(word, -shift_x, shift_y, color_int)
    pixel_framebuf.display()
    time.sleep(SCROLL_TEXT_WAIT)

def scroll_text(packet, word):
    # scroll through entire length of string.
    # each letter is always 5 pixels wide, plus 1 space per letter
    scroll_len = (len(word) * 5) + len(word)
    color_list = [CYAN, TWINKLEY, PINK, PURPLE, YELLOW]

    color_i = 0
    color_wait_tick = 0
    # start the scroll from off the grid at -pixel_width
    for x_pos in range(-pixel_width, scroll_len):
        # detect touch
        if touch.value:
            pixel_framebuf.fill(0)
            pixel_framebuf.display()
            return;

        # detect new packet
        if isinstance(packet, ButtonPacket) and packet.pressed:
            return;

        color = color_list[color_i]
        scroll_framebuf_neg_x(word, color, x_pos, 0)

        # Only change colors after SCROLL_TEXT_COLOR_CHANGE_WAIT
        color_wait_tick = color_wait_tick + 1
        if color_wait_tick == SCROLL_TEXT_COLOR_CHANGE_WAIT:
            color_i = color_i + 1
            color_wait_tick = 0

        if color_i == len(color_list):
            color_i=0

    # wait a bit before scrolling again
    time.sleep(.5)

# Manually chosen pixels to display "Y"
# in the proper orientation
def yes(color):
    pixels[26] = color
    pixels[27] = color
    pixels[28] = color
    pixels[36] = color
    pixels[44] = color
    pixels[21] = color
    pixels.show()
    time.sleep(0.1)
    pixels.fill(0)

# Manually chosen pixels to display "N"
# in the proper orientation
def no(color):
    pixels[26] = color
    pixels[19] = color
    pixels[12] = color
    pixels[27] = color
    pixels[28] = color
    pixels[29] = color
    pixels[30] = color
    pixels[37] = color
    pixels[44] = color
    pixels.show()
    time.sleep(0.1)
    pixels.fill(0)

def yes_or_no():
    pixels.fill(0)
    print(touch.raw_value)
    value = 0
    pick=0

    pick = random.randint(0,64)
    time.sleep(0.1)

    if pick % 2:
        print('picked yes!');
        yes(PINK)
        time.sleep(1)
    else:
        print('picked no!');
        no(TEAL)
        time.sleep(1)


def twinkle_show():
    pixels.brightness = TWINKLE_BRIGHTNESS
    pixels.show()
    time.sleep(.1)
    if touch.value:
        return;

def twinkle():
    # randomly choose 3 pixels
    spark1 = random.randint(0, num_pixels-1)
    spark2 = random.randint(0, num_pixels-1)
    spark3 = random.randint(0, num_pixels-1)

    # make sure that none of the chosen pixels are covered
    while spark1 in COVERED_PIXELS:
        spark1 = random.randint(0, num_pixels-1)
    while spark2 in COVERED_PIXELS:
        spark2 = random.randint(0, num_pixels-1)
    while spark3 in COVERED_PIXELS:
        spark3 = random.randint(0, num_pixels-1)

    # Control when chosen pixels turn on for dazzling effect
    pixels[spark1] = TWINKLEY
    pixels[spark2] = OFF
    pixels[spark3] = OFF
    twinkle_show()
    pixels[spark1] = TWINKLEY
    pixels[spark2] = TWINKLEY
    pixels[spark3] = OFF
    twinkle_show()
    pixels[spark1] = TWINKLEY
    pixels[spark2] = TWINKLEY
    pixels[spark3] = TWINKLEY
    twinkle_show()
    pixels[spark1] = OFF
    pixels[spark2] = TWINKLEY
    pixels[spark3] = TWINKLEY
    twinkle_show()
    pixels[spark1] = OFF
    pixels[spark2] = OFF
    pixels[spark3] = TWINKLEY
    twinkle_show()

    pixels.fill(OFF)
    pixels.show()
    time.sleep(0.6)

# Initial empty state
state = ""

while True:
    # Advertise when not connected.
    ble.start_advertising(advertisement)

    while not ble.connected:
        if touch.value:
            yes_or_no()
        else:
            twinkle()

    while ble.connected:
        # Set the state
        if uart_service.in_waiting:
            # Packet is arriving.
            packet = Packet.from_stream(uart_service)

            # set state string based on pressed button from Bluefruit app
            # and to prevent redundant hits
            if isinstance(packet, ButtonPacket) and packet.pressed:
                # UP button pressed
                if packet.button == ButtonPacket.UP and state != "chase":
                    state = "chase"
                # DOWN button
                elif packet.button == ButtonPacket.DOWN and state != "comet":
                    state = "comet"
                # 1 button
                elif packet.button == '1' and state != "rainbowchase":
                    state = "rainbowchase"
                # 2 button
                elif packet.button == '2' and state != "hello":
                    state = "hello"

        # Touch is handled as an interrupt state
        if touch.value:
            yes_or_no()

        # Act upon the state
        if state == "chase":
            chase.animate()
        elif state == "comet":
            rainbow_comet.animate()
        elif state == "rainbowchase":
            rainbow_chase.animate()
        elif state == "hello":
            pixels.fill(0)
            scroll_text(packet, SCROLL_TEXT_CUSTOM_WORD)
        else:
            chase.animate()
The Dotstar Matrix can get quite hot when all or most of the LEDs are on, so try to make sure you're testing any new animations on a table and checking how hot it gets first before wearing the necklace. However, the animations included above shouldn't result in too much heat.

Test capacitive touch

Now you can connect the necklace via the microB port to test that the twinkling animation and capacitive touch sensing is working. You may need to adjust the threshold of the capacitive touch if it's too sensitive or not sensitive enough.

Test Bluefruit app interaction

You can now test the interaction with the necklace using the Bluefruit app. Turn on the power switch, and open the Bluefruit app -- you should see your device like so:

 Then, press "Connect" and then "Controller" and then "Control Pad". You can then click on the following buttons in the Control Pad screen and see the animations change in the necklace:

  • Up button
  • Down button
  • "1" button
  • "2" buton

Go ahead and change the corresponding animations in the code to whatever you choose!

We're almost done. Head on over to final assembly to stuff everything into an enclosure!

You can either choose the 3d printed case or the laser cut case. I recommend the 3d printed case since it's a very slight mod of the excellent case from the Neopixel LED Heart Necklace tutorial, and it allows access to the reset button. 

If you want something a bit smaller and compact though, the laser cut case is provided here as well (it's slimmer in width, but thicker in depth).

3D printed hex case

Carefully slot the ItsyBitsy, LiPo backpack and slide switch as shown -- the model will have dedicated structures to support these parts.

Align the microB usb port to the remaining hole to the left of the switch. Secure it using some double sided foam tape, and make sure that it's right up against the edge of the case. Test that the necklace's microB plug will fit into the plug through the case hole and adjust accordingly.

Connect the battery to the LiPo backpack and place on top of the ItsyBitsy as shown. You may want to tuck it under the wires. There should be no tape needed to secure this battery. 

Nudge the wires away from the RESET button before putting on the case cover, making sure to align the RESET button with the button extender.

Optionally, add another brass shape to this case for extra bling.

Laser cut rectangular case

This case is made up of 4 different types of panels, with finger joints that slot into each other:

  1. Top/bottom panels
  2. Side panels
  3. Necklace port panel
  4. Power and charging port panel

Add thin tape to LiPo battery and foam tape to LiPo backpack

 

Then, with the bottom panel and one of the side panels attached perpendicularly, place the battery and the LiPo backpack to the bottom panel as shown in the photo.

The thin tape is needed for the battery so that the ItsyBitsy can fit on top of it and align with Panel #4 (as referenced above).

Fit the switch in the appropriate cutout in Panel #4 and fit that panel with the rest of the case.

Secure the USB MicroB port in place

 

Use foam tape to secure the microB port as shown in the photo. The edge of the port should be as close as possible to the inner edges of the finger joints.

Then, attach Panel #3 (necklace port panel), and make sure that the microB port is aligned with the cutout.

Place the ItsyBitsy on top of the battery

 

Cut some foam tape to fit in the flat areas on the bottom of the board. The foam tape should be thick enough to allow the ItsyBitsy to lay flat. The amount of tape shown in the photo should provide enough resistance when inserting a micro USB plug into the board.

Secure the ItsyBitsy on top of the battery and make sure to align the edge of the ItsyBitsy right up against the panel, centering the microUSB port with the cutout on the panel.

Close up the case

 

Once you're happy with how everything is laid out, you can use super glue or any other appropriate glue on the finger joints to solidify the case.

Secure and glue the last top panel, and if desired, add a bit of flair with another brass shape. The contrast of brass and black here is simply beautiful!

And that's it! You can now light up the night (or day) with your very own modern geometric LED matrix necklace. Ask the necklace a question, touch for the answer, and may the Fortunes smile upon you.

Below is a photo of the combination of the 3D printed hex case and the laser cut pendant frame, which is definitely my fave combo. Since the parts of this project is quite modular, there's lots of room for customization, and with CircuitPython it's quite easy to change the LED animations. Have fun modifying this project so that it suits your own style!

This guide was first published on Dec 22, 2020. It was last updated on Dec 22, 2020.