Getting Started

Adafruit Playground is a wonderful and safe place to share your interests with Adafruit's vibrant community of makers and doers. Have a cool project you are working on? Have a bit of code that you think others will find useful? Want to show off your electronics workbench? You have come to the right place.
The goal of Adafruit Playground is to make it as simple as possible to share your work. On the Adafruit Playground users can create Notes. A note is a single-page space where you can document your topic using Adafruit's easy-to-use editor. Notes are like Guides on the Adafruit Learning System but guides are high-fidelity content curated and maintained by Adafuit. Notes are whatever you want them to be. Have fun and be kind.
Click here to learn more about Adafruit Playground and how to get started.
-
Hiking Masterpiece Overview
Welcome to the Adafruit guide on creating a stunning Badger Mountain themed art piece! In this guide, we'll show you how to bring your artistic vision to life using the powerful Feather RP2040 microcontroller, along with an array of components including audio jacks, buttons, and LED strips.
Circuit Diagram
-
Running Pi-HATs with a Raspberry Pi Pico Running Pi-HATs with a Raspberry Pi Pico
Being a Pi-user since the very first Pi1, I own many different Pi-HATs. Some of them are in daily use, but many of them are sitting in the shelf. So I wondered if I could give them a second chance in combination with a Pico. And one of my major use-cases are e-ink displays for the Pi. These e-inks don't really match with the Pi, since they are optimized for low-power scenarios. But even the Pi-Zero drains batteries too fast to be a suitable partner for these kind of displays.
Although equipped with a full 2x20 pin socket, most HATs only use a few pins like power, ground, I2C or SPI. So using a bunch of jumper cables should already be sufficient. Although that is true and fine for initial tests, a good and solid connection is always the better alternative.
So I did some research and discovered a few adapter-boards on the market that might be suitable. But on closer inspection it turned out that most of them missed one important point: just mapping some arbitrary pins is not enough. So I decided to create my own adapter boards.
Hardware
One board uses the Pi form-factor, the second uses the Pi-Zero form-factor:
They fit into standard enclosures, but since the USB-connection is on the side they need an additional cutout. Changing available 3D-models should be a simple task. And the bigger adapter PCB has a footprint for a standard JST-2 battery connector exactly where the USB-power cutout is. Anyhow, this was not the major challenge when designing these boards.
The biggest challenge was the correct pin-mapping. The Pi has I2C, UART, two SPI and I2S. I did not take the last one into account so I ended up with two revisions. I2S needs two consecutive pins. On the Pi, that is GPIO18 and GPIO19, but they are not next to each other on the pin-header.
Another contraint was space. I did not want to route traces below the WLAN-chip and antenna of the Pico-W. In the end I had to make a compromise for the Pi-Zero adapter: the first revision maps both SPIs but not I2S, the second revision maps SPI0 and I2S but not SPI1. Which is not a big deal since I haven't found a HAT yet that actually uses SPI1.
I also don't map the ID-pins of the Pi. These are used to automatically configure the correct driver on the Pi. On the Pico, you don't run a generic OS but a specific program, so you have to take care about correct drivers already before when you put them on the CIRCUITPY-drive.
The Pi-adapter has more space. I added a SD-card reader and I broke out a number of pins. One of the drawbacks of many HATs is that they block the complete pin-header although they only use a few pins. Breaking out the pins is not strictly necessary since you can access all pins from the back anyhow.
Software
The second part of the project was porting the HAT-drivers to the Pico. For Adafruit HATs, that was fairly simple. Adafruit has CircuitPython support for almost everything they sell. And since Blinka brings CircuitPython to the Pi-SBCs, "porting" the drivers is a matter of using the correct pins.
On example: the speaker-bonnet. The learning guide (https://learn.adafruit.com/adafruit-speaker-bonnet-for-raspberry-pi) tells you it is using the I2S pins GPIO18, GPIO19 and GPIO21 on the Pi. After looking up the mapping for the adapters, you just plug in those pins into a small example program provided by a second guide: (https://learn.adafruit.com/mp3-playback-rp2040/pico-i2s-mp3) and off you go playing MP3 on the speaker-bonnet. This is actually much simpler on the Pico compared to the Pi, because you don't have to go through all the steps to install the relevant drivers.
For other HATs, you will usually find CircuitPython example code for the Pi using Blinka in the learning guide for the HAT. In this case, you can take the code as is and only replace the pin-numbers.
I also own a number of HATs from Pimoroni. They don't provide CircuitPython drivers, but at least for some of the HATs there are ready to use drivers for the builtin driver-IC. In only a few cases I had some real porting work to do. But once I found out how to translate CPython I2C/SPI-calls to CircuitPython, the porting was straightforward.
Project-Repository
You can find the project repository here: https://github.com/bablokb/pcb-pico-pi-base. The repo has KiCad design files as well as ready to use production files for my preferred PCB manufacturer.
Also in the repo are CircuitPython libraries and example code for all the HATs I tested or ported.
Next Steps
What I might do in the future is to create a similar adapter PCB for the Feather form-factor. While in my current designs the Pico sits inbetween the PCB and the HAT, with the Feather I would probably make the Feather plug in from behind.
The second thing I am working on is to support the new Waveshare ESP32-S3-Pico. This is an ESP32-S3 in the Pico form-factor with identical physical dimensions and identical pin-layout. This breakout is interesting since it gives me a device with far more memory than the Pico provides. And I don't have to create new adapter boards. First results look promising.
CircuitPython Board Definition Files
Since I have two form-factors and two revisions each with their own pin-mapping, looking up the mapping is cumbersome. So I also created my own CircuitPython versions that do the mapping for me. So
board.GPIO18
will always map to the correct pin on the Pico, regardless which PCB I use.With two form-factors, two revisions and now three devices (Pico, Pico-W, EPS32-S3-Pico) I have a total of 16 combinations, thus potentially 16 CircuitPython versions. A lot to maintain, but not all combinations are actually in use (yet).
-
Automating PIP & CircuitPython-Stubs updates for Windows Users This article is for Circuit Python developers that use PIP and CircuitPython-Stubs in a Windows environment.
Stubs are helpers for code completion hints with IDE's such as PyCharm, VSCode, and others.
Unfortunately PIP & CircuitPython-Stubs do not automatically stay updated. These are things you must manually update when a new version of Circuit Python is released or whenever your heart dictates you want to update it. This is a problem because I never remember to keep them updated and recently found out my version of stubs was last updated in Circuit Python 7.3.3 (we're now at Circuit Python 9.0.1).
This is assuming you already have PIP and CircuitPython stubs installed.
The manual way to update them is:
python -m pip install --upgrade pip
pip install circuitpython-stubs --upgrade
These are the types of things I do not want to be required to remember to update. I consider these things minutia that should automatically stay updated.
Windows Task Scheduler can automate the process of keeping both PIP and circuitpython-stubs up to date every time you log into Windows. First we need to create a batch script and put it in a directory/folder that will always be available to Windows.
I chose to put it in the following folder:
\Downloads\CircuitPython-Stubs_Updater
You can name the script whatever you'd like. You can edit a .bat file as easly as a .txt file, you don't need a special IDE to do it, Notepad works fine for it.
I named my batch file:
circuitpython-stubs.bat
It resides inside of the CircuitPython-Stubs_Updater folder and includes the following code in the .bat file.
-
Building an Anti-Dew Heater Controller Introduction
Astronomy is my primary hobby and taking photographs of night-sky objects is my particular interest. A downside to this hobby is that it is very weather dependent. If it’s cloudy nothing can be seen. Weather reports are important to monitor but they just serve the general area. The sky conditions at my specific location are better monitored with an AllSky Camera.
An AllSky Camera is simply a camera with a fisheye lens that’s pointed up into the sky. A program takes pictures of the sky all night long so checking the sky conditions can be done by looking at the latest sky image. Is it too cloudy to take images? Are clouds starting to move in? Just check the AllSky Camera!
The Issue with Dew
Unfortunately, the dome of the AllSky Camera is prone to have dew forming on it when the humidity gets high. Once that happens, the Allsky images are totally unusable. To combat dew, many AllSky Cameras have a dew heater built into them. The heater in my camera is very simple: Apply 12 VDC and the heater is on. Remove the voltage and the heater is off. This applies about 10 W of power to the heater, and it does get hot enough to keep dew from forming on the dome. Sometimes it gets too hot.
If the humidity is moderate the 10 W of power is way more than needed to keep the dome clear. An unwanted side effect of too much heat is that cameras don’t like it. The hotter a camera gets the noisier, or grainer, its picture becomes. This is especially apparent with long exposures and AllSky Cameras can take up to 60-second exposures under a dark sky! The better solution is to vary the amount of power applied to the heater so that only enough is applied to keep dew from forming, and no more.
-
Using Github Codespaces for CircuitPython Development Using Github Codespaces for CircuitPython Development
Introduction
If you wan't to contribute to CircuitPython, one of the hurdles you need to take is the installation of the development environment.
There is a nice guide from Dan Halbert https://learn.adafruit.com/building-circuitpython which walks you through all the necessary steps.
There are a few problems though:
- you will need to download and install a lot of software-packages. Some of them might even need other versions than those that the packet-manager of your distribution provides. Or they conflict with other projects you are working on.
- If you use a different flavor of Linux, you cannot just copy and paste the commands from the guide but also have to change commands and package-names.
- Your software-environment is bloated. Disks are very large these days, so this is not the main problem, but backups take definitely longer (I assume that you do backup your computer).
You could use a dedicated development machine or a virtual machine, but setting this up is again additional work.
Github Codespaces are a solution for all of these problems. A Codespace is a sort of virtual Linux-system. Technology wise it is a Linux container running within docker in the cloud. If you have a Github account, you can create such a system within seconds. You just head to https://github.com/codespaces and create a codespace from one of the templates (the "Blank" template is just fine).
The interface to the codespace is the web-version of "Visual Studio Code" (VSC), so you have a state-of-the-art editor, terminals, git and so on - all from within your browser. As an alternative, you can install VSC on your local machine, add the codespace-extensions from the VSC-marketplace and connect from your local VSC to you codespace. This is higly recommended, since the browser version is sometimes sluggish.
Since codespaces use ressources in the cloud, Github charges for using them. The good news is that the free plan of every account has 120 CPU-hours and 15GB storage per month included. The minimal machine has 2 CPUs, so this boils down to 60 hours per month. This should be enough unless you are a professional developer.
Automatic Setup for CircuitPython
At this point, you could just create an empty codespace from the template and follow the guide from Dan. I actually recommend that you do that once, since you will learn about the different tools you need to install.
For regular use, it is much simpler to let Github do all this work. For this reason the CircuitPython repository has predefined codespace configurations for most of the ports.
So the normal workflow would look like this:
- create a fork of https://github.com/adafruit/circuitpython
- create a new development branch within your fork
- clone this branch into a codespace
- go for a coffee-break: the initial setup will take about 10 minutes
- edit and build your own version of CircuitPython
- add, commit and push any changes back to your branch
- create a pull-request for upstream
You can find detailed instructions for the third step in the Readme: https://github.com/adafruit/circuitpython/blob/main/.devcontainer/Readme.md
Daily Use
Once you have created your codespace, you can keep it and use it whenever you want. Codespaces have two states: "active", i.e. running or "stopped". In the latter state you are only charged for the storage, so don't forget to stop your codespace after you finished your work. Github will automatically stop your codespace after 30 minutes of inactivity. In your accout settings you can change this value to something shorter. Also, Github will delete unused codespaces after 30 days of inactivity. But you will be prompted before this happens.
Storage size is a minor problem, since Github does not charge for the storage that the standard Linux image uses. A fully operational codespace for the espressif-port e.g. has about 2.4GB, so the 15GB limit will be enough for a number of codespaces.
Further Reading
Codespaces are a powerful tool with many features not covered here. To find out more, read the documentation: https://docs.github.com/en/codespaces.
Final Note
The scripts for the automatic setup of codespaces are not maintained by the core CircuitPython developers. As CircuitPython evolves the buildsystem will change and the scripts might stop working. In this case, it is best to create an issue.
-
Building a scientific handheld calculator with double precision math, complex math, uncertainties and fractions The goal of this project is to build a Python based handheld and battery powered scientific calculator the size of a cigarette box (well, pocket calculator). Scientific, as in "reasonable precision". Float32 (single precision) is certainly acceptable for a display precision of, say 6 or 7 digits, but the follow-up rounding errors are not - at least not for me. I experimented with Decimal math before but ended up having to fight memory constraints with jepler-udecimal (https://github.com/jepler/Jepler_CircuitPython_udecimal) and my own extensions even on a Feather RP2040 with 256kB of user RAM. So I eventually decided to make a custom CircuitPython build and try to enable float64 (double precision) math. (Thanks to the Adafruit folks for their help!). Needless to say that float64 is entirely handled in C without the help of a potential floating-point unit (FPU) but then this approach is still much more CPU and memory efficient than implementing everything in Python.
The code for this project is on https://github.com/h-milz/circuitpython-calculator/ and will be discussed in this article.The terminal window displays a couple of features of this tiny machine, from top to bottom: calculating pi using arctan(1), the arsinh of a complex argument, multiplying two numbers with uncertainties, and multiplying two fractions including reducing it to lowest (positive) denominator.
The dongle on the lower right is a PCF8523 RTC hanging off the Keyboard Featherwing's STEMMA Qt connector (which, sadly, is unusable if you want a handheld with a back cover ... What did arturo182 think?)
On the backside you can see the Adafruit Feather M4 Express and the LiPo battery, which is fixed to the Featherwing using a double-sided adhesive foam strip. -
Custom Flight Sim Controllers with CircuitPython and MobiFlight Introduction
The flight simulator industry has spawned dozens of custom controllers for those who are looking for a more realistic and entertaining experience. These controllers range from yokes and throttles to communication and GPS systems and their price can rival the costs of actual aviation equipment.
Thankfully there is a way to create your own controllers using low cost microcontrollers, buttons, encoders and many other input devices. There are even ways to output settings to LEDs, LED segments and displays but this guide does not cover output scenarios.
For this hookup guide I am using a controller I made for myself. The G1000 glass cockpit has a dual-rotary encoder in the lower right corner labeled FMS (Flight Management System) that is a pain to control with a mouse, even more so while the plane is in the air. So I decided to build my own controller to simulate the G1000 corner. My controller includes the FMS encoder knobs and the 6 buttons that tend to be used at the same time.
You can find the source files and STL files I used on GitHub.
The Controller
For my FMS controller I found the PEC11D-4120F-H0015, a dual concentric rotary encoder (meaning it has an inner and outer encoder ring that can turn). This is not the FAA approved version on the actual G1000 but works the same and cheaper. The dual rotary encoder is similar to a single encoder except has two sets of A/B/C pins. The encoder also has a push switch. The push buttons above the encoder are standard push buttons.
The encoder and buttons are wired to a Feather board but almost any board that can run CircuitPython with USB HID enabled will work.
-
An Almost Free Non-Destructive Soldering Jig I just couldn't bring myself to use the standard practice of soldering pins onto breakout boards using a solderless breadboard in spite of the recommendations and endorsements of some famous solder artists. I would cringe thinking that the heat would eventually warp the plastic portion of the board. It is a solderless breadboard after all.
Fortunately, after placing many Adafruit orders over the years, I had build up quite a collection of free half-sized Perma-Proto breadboards. The solution for creating a non-destructive soldering jig became obvious. Stack four Perma-Proto PCBs together, use a couple of M3 nylon screws and nuts, and place some rubber feet on the bottom. Voilà!
Sadly, the Perma-Proto board is no longer offered as a free product promotion. That's okay; I still have enough in the inventory for many future projects. And coasters. I have coasters.
-
A Beginners Guide to writing USB HID Report Descriptors by a Beginner Why Do You Want A HID Report Descriptor?
The USB specification includes a section on Human Interface Devices (HID). These devices range from keyboards, mice, joysticks, audio controls to medical controls, eye tracking and LED lighting. CircuitPython has support for USB HID with built-in defaults for a keyboard, mouse and consumer control and associated libraries. Dan Halbert has an excellent guide to get started with this.
A USB report descriptor tells the host machine the how to talk to your device. But what if you want to communicate to a device that no one else has written a report descriptor for? Perhaps you are building a new joystick, LED indicator or medical ultrasound device. Then you will have to write your own report descriptor.
Simple Descriptor Example
Initially when you look at report descriptors they seem complicated but it is best to think of them as a description of one or more reports that can be sent to or from your device. Each report gives the current state you wish to communicate. For example a joystick report may give the X and Y axis, a throttle value and if any of a dozen buttons are pressed. The joystick may also have a second report defined that allows the host machine to light up one or more indicator lights.
Below is an example report descriptor for a joystick that has 5 buttons. This descriptor only defines one report with each button taking up 1 bit, and 3 padding bits. While this is readable it is hard to create. There is an easier way.
-
Easy Helldivers II Stratagem Macros for RP2040 Macropad If you've been playing the awesome new co-op game Helldivers 2, you'll be familiar with the "stratagem" gameplay mechanic in which you summon various weaponry from airborne and satellite craft. This mechanic requires you to hold down a key/button to activate the stratagem input mode, while entering a sequence of directional key input.
Often during the game I tend to get overstimulated in the middle of a heated battle and my mind goes completely blank when trying to recall the one of the myriad codes I need to use to summon the effect I want. Even though the game UI displays them, it's still pretty hectic to precisely enter the sometimes lengthy codes when a giant scorpion is trying to rip my head off. To help spread liberty and democracy in the most efficient way possible, I thought I'd try to see if I could set up some stratagem macros using the awesome MACROPAD Hotkeys project from Phillip Burgess. NOTE: For simplicity, this particular setup mimics keyboard input, so until I hear otherwise I'm assuming this only works on the PC version of Helldivers II.
Custom Code
It took a few tries to figure out how to set up the macros so that the game would consistently accept them. Thankfully, the MACROPAD Hotkeys project is incredibly versatile and I was able to do this using the built-in features of the project without having to modify any of the core code. The main factors involved in getting this to work seemed to be:
- After pressing the stratagem input button, a significant delay was needed before the game would accept the directional input. I'm assuming this is due to needing to wait for the UI to fully present in-game before allowing further input. With experimentation it seems like the smallest delay possible is about 0.4 seconds.
- Each directional key needed to be pressed for a certain duration. The smallest duration I've tried for this so far is 0.05 seconds.
- A small delay is needed after each direction key is released. The smallest duration I've tried for this so far is 0.05 seconds.
In the /macros folder on my macropad CIRCUITPY drive, I created a new file named helldivers-2.py. Since I anticipate changing this file frequently as I progress through the game, I wanted to make it as simple as I could to add/edit new macros, so that I could even edit them in real-time as I played the game. To make this a little easier, I wrote a bit of custom code at the top of the file.
-
WaveStore: Create a Library of synthio Voices The arbitrary waveform capability along with ADSR envelopes and filters has made it very easy to create custom musical voices with CircuitPython synthio. And if you use an additive synthesis tool like WaveBuilder, you'll begin to quickly amass quite a few custom musical voices as you experiment with the nearly infinite number of oscillator combinations.
Hearing Voices
Up to this point, I've been keeping track of the numeric specifications for WaveBuilder oscillators and synthio.Envelopes in project code or scribbled on a note pad. That means that when building a new project that needs to use a previously created voice, I'll cut and paste the voice definition code into the new project. The process works, but isn't ideal. What if a synthio musical voice could be loaded from a collection in a file folder, kind of like working with a font file?
So a concept for the WaveStore project began to develop. Here are the initial requirements.
- Library File Management -- We'll make it easy at first and just work with a collection stored on an SD card. No need to tax the brain to conjure up a way to write to the CircuitPython root directory just yet. After creating and storing a voice to a file, we'll manually copy the files from the SD card in order to use and share between projects.
- Files -- In the spirit of keeping it simple, only files representing waveforms and envelopes will be created. Those objects form the fundamental elements of a musical voice. Perhaps we can add filters and other effects later.
-
Library File Names -- Waveforms will be stored in a standard wave file with a
.wav
extension, thanks to the awesome Adafruit_CircuitPython_Wave library. Envelopes and filters definitions will be stored in plain text files with.adsr
and.fltr
extensions but will transform to synthio.Envelope and synthio.BiQuad objects when retrieved. - Icons -- Imagine being able to select a musical voice waveform or envelope by touching a screen icon. Functions will be provided to save and retrieve bitmap images of waveforms and envelopes as well as capturing the contents of an entire screen. We'll start with 64x64 pixel bitmaps created by WaveViz. A graphical frequency response representation of a bi-quad filter's coefficients is in the works but is a bit beyond today's skillset. There's a potential workaround, but I'd rather derive it directly from the numbers. Hoping for an epiphany soon.
In the future, more features will likely be added to WaveStore such as support for filters and on-screen icon buttons -- as my experience and Python skill set grows. What would you add?
Enter WaveStore
WaveStore is under development but is available for testing. The current alpha version of the WaveStore class provides the following helpers:
- Wavetables
- read_wavetable -- Read a
.wav
file and return a memory view object. - read_wavetable_ulab -- Read a
.wav
file and return a ulab.numpy array object. The ulab array can be mixed with other wavetable array files. - write_wavetable -- Format a wavetable and write it as a standard
.wav
file.
- read_wavetable -- Read a
- Envelopes
- read_envelope -- Read an
.adsr
file and return a synthio.Envelope object. - write_envelope -- Write a text interpretation of a synthio.Envelope object to an
.adsr
file.
- read_envelope -- Read an
- Bitmaps
- read_bitmap -- Read a
.bmp
file and return the bitmap as a TileGrid object that can be added to a displayio.Group object. - write_bitmap -- Write a bitmap image to a
.bmp
file. - write_screen -- Write the screen contents to a
.bmp
file on the SD card.
- read_bitmap -- Read a
- Utilities
- get_catalog -- Returns a list of files in a specified folder.
-
Output Eurorack Control Voltage (CV) Signals from synthio This note describes a method to output Eurorack CV (control voltage) signals from CircuitPython synthio using the PCM510x I2S DAC.
Rather than employing CV-like object controls such as Envelope and LFO to only adjust the parameters of other synthio objects, it would be useful to also control physically external devices such as the CV inputs of Eurorack modules. To do that we'll need to configure the synthio.Note object to ignore its typical behavior as an oscillator. Oh, and it would be handy to have an I2S DAC on-hand with a DC-coupled output that's capable of positive and negative output voltage that can be connected to a Eurorack module.
Here's the test setup:
- Create a Note.waveform (wave shape table) object containing the maximum wave value (16-bit signed). This is an array filled with a single value that acts like a fixed DC voltage.
- Set the Note wave shape oscillator frequency to an arbitrary value such as 440Hz. The frequency value is unimportant since the oscillator waveform output will simply be a fixed value.
- Define a synthio.LFO object to output the LFO signal. If outputting an ADSR envelope is desired, define a synthio.Envelope object.
- Create a synthio.Note object where the amplitude parameter is controlled by the ADSR envelope or LFO.
- "Press" the note to output the ADSR envelope or LFO signal from the I2S DAC.
Instead of the I2S DAC, CV output signals can be created in this manner using audiopwmio and audioio to use PWM or analog DAC output pins. Boards like the QT PY RP2040 and Grand Central M4 Express could be used for PWM or analog DAC outputs. Keep in mind that, unlike the 0 volt baseline of the I2S DAC, the baseline of a PWM or analog DAC signal is biased to approximately +1.65 volts.
I2S DAC Output
-
Look Ma, No Microcontroller! (Soldering Lamp Edition) Inspiration
I needed a soldering lamp that didn't cast strong shadows and was an opportunity to salvage the LEDs from a broken TV left by the dumpster.
There are lots of ways to make a good soldering lamp from LEDs. I happened to pick a difficult path that required some reverse engineering but I hope you'll find the journey... illuminating. ;-)
-
WaveViz: Plot a synthio Wave Table or Envelope WaveViz is a CircuitPython class to create a positionable
displayio.TileGrid
graphics widget from asynthio.waveform
wave table orsynthio.Envelope
object (or any one-dimensional list for that matter). The class also makes the underlying bitmap and palette objects available for other uses such as saving the widget to an image file.The long-term objective is to be able to save and import sounds and ADSR envelopes from a library of synthio wave table files and envelope objects. The first step in the process is to create a visualizer to help characterize the sounds graphically. We'll worry about saving and retrieving waveform and envelope objects sometime in the future (watch for a new class, WaveStore to appear soon). For now, let's work on getting images of wave tables and envelopes.
-
Custom ANO Fidget Firmware Awhile back Adafruit featured a story on their blog about a really cool fidget toy built using a rotary encoder and a NeoPixel-compatible LED ring. I loved the idea so much that I wanted to make my own version, and spent a few days designing my own with parts I had on-hand. As it turns out, I wasn't the only one: the Ruiz Brothers built an incredibly stylish take on this idea using an ANO rotary encoder and published an excellent learn guide for it!
While there are physical differences between the two versions, the hardware is similar enough that I wanted to write a CircuitPython firmware that would work their version as well as mine. I wanted the firmware to have multiple modes, with the ability to add more in the future as ideas came to me. Ideally it would be something others could build on as well.
Update: You can see a live demo of the firmware running on the Ruiz brothers' fidget toy in this episode of 3D Hangouts