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.
-
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
-
Desktop Multifunction Device: Time and Temperature Let's add temperature to the multifunction device using the ADT7410 on the Temperature and Motion Wing.
See Multifunction Device Part 1 for a parts list and assembly instructions.
Reading the Sensor
First we need to set up the sensor:
-
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
-
Using CIRCUP with Web-Workflow Thanks to some hard work by Vladimir Kotal (@vladak)❤️, an open-source community contributor on GitHub who added this pull request, and thanks to our own CircuitPython wizard Tim (@foamyguy), there is now the ability to use
circup
with the web-workflow.I had a play by pulling from github last month as I was desperate. I had a device that was going to be left plugged into the mains, which was running out-of-date code and libraries (but otherwise functional), and so along the way fixed a couple of issues for Windows users or anyone needing libraries with nested folders.
Install
circup
, at least version 1.6.1:To begin with we'll explore how install and to use it, then I'll take you through my specific example...
Install latest CIRCUP:
pip install circup --force
[ --force forcefully overwrites / upgrades]
-
Failing with CircuitPython How to fail with CircuitPython
All code will have errors. You might find it sooner or later, or the error might be in your code, in a library that you are using or even in CircuitPython itself!
It is also very common to write some code on your lab environment, test it a bunch of times, then take it out into the field and watch it fail in lots of ways you would never though of. Exactly the same happens when you give your device to a user or a kid who will test it in ways that will make your coding skills feel silly.
In my case this is happening for a weather station. This should be a simple project, right? Grab some data from a sensor like a Stemma QT thermometer, then send that data to Adafruit.IO and watch it get graphed in a lovely dashboard. Or at least that was my expectation for this project.
As you can see, I started getting missing data for days, and only when I went to the semi-remote location where I am testing my weather station, and manually rebooted the devices, I would get start getting data again. But now the graph is ruined, it has a large gap in the data and to all whom I showed the graphs, the first thing they mentioned is 'what happened to the missing data?'. Now what do I do?
Look at the last hours of the graph. Even if I am missing only a little bit of data, it looks terrible right? You, the reader have no idea how hot it is during the day, how cold it is during the night, or what is the coldest hour. Your eyes go straight to the missing data at the end, with the graphs looking like a shark tooth and being jumpy. What a disaster.
I thought about blaming Adafruit.IO or maybe CircuitPython or maybe it's the fault of the ESP32 that is reading the data. Then I thought about adding extra hardware to it like a TPL5110 power timer but that would require some soldering and would break the wifi workflow that I love so much from CircuitPython 8.
I cloud also get my code full of
try
andexcept
Python blocks on all of the parts of my code. To be honest, I did tried to do it this way, but things would still keep failing so I kept adding moretry/except
blocks until the code looked terrible. I was this close to having a project that was not fun for me, and that I would not like to share how it works with this amazing community that makes CircuitPython what it is. Not sharing? That doesn't sound like me at all. Time to do things a different way.Loving to fail
There is a bit of a hidden gem in CircuitPython and it's the
supervisor
module. I was only able to find a little bit of technical documentation without a simple example that I could just copy and paste into my project, and I also found another example which was close to what I wanted to do in Tod's CircuitPython tricks.After staring at the code I had available for a couple of minutes, I realized that this is what I had to do. I needed to let go of my impulse of keep the code failure free, and embrace CircuitPython failing. I would let if fail any time it wanted, and then I would ask it to try again.
With two simple lines, my weather station code can now fail because the wifi router is down, because the Stemma QT cables had a problem with talking to the sensor, because it was running for so long that it was finding errors in the CircuitPython base code or the request library. It doesn't matter, the board will accept that something weird happened, and would try again. Try this in your code and love to fail as much as I do now!
import supervisor
supervisor.set_next_code_file(filename='code.py', reload_on_error=True)
It should not be this simple, right? Here's how it works:
In the first line of code we are calling a library called
supervisor
. Don't worry, you don't have to install anything, it comes as a standard CircuitPython library and will probably work on any board that you have CircuitPython running on. What it does, is talk to one of the internal parts of CircuitPython and modify how it will behave.The second like, is the one that changes how it works. It's using a function called
set_next_code_file
. It's normally used so that you can run the well knowncode.py
file and if something happens, you can define a second file with another name and that code would do something like recover things, blink a light or beep to indicate that it will not behave as it would normally do. I am not calling a second file, instead I'm telling it to go tocode.py
so that it can try again.And my favorite part comes with the code that says
reload_on_error=True
. The way CircuitPython works is that when it fails it will take you to the REPL console, and show you why it failed. This is a good thing as that is the way that we learn, we make mistakes, the REPL shows us what those mistakes are, and we go and fix them.But for my weather station, once the code was ready and it was doing what I wanted, if there is a failure like the wifi is down, it would go to the console and wait for me to do something. This weather station is located on a dry forest in Costa Rica and it doesn't know that is not next to my computer where it's easy for me to tell it to try again. So what
reload_on_error=True
tells it to do, is to change what it would have usually do and take me to the console, and instead it will do something similar as if I was close so the board, pushing the RESET button every time something unexpected happens.This way, CircuitPython will keep on trying over and over until wifi comes back, and hopefully the graphs will have smaller gaps in the data, and they will look much better and should be able to work like a weather station should. Read the thermometer. Send it to Adafruit.IO. Go to sleep to save some battery.
I love you CircuitPython! You and me can fail as many times as we need to, as long as we do it together and without feeling bad. <3.
-
Tell me a story, Neo Trinkey! Once upon a time.....
..... I remember reading a story in "Summer Weekly Reader" (I think by Isaac Asimov) about a robot that told stories to the young boy who owned it. Mostly fairy tales, till one of the boy's friends loaded a tape with more modern info like spaceships (and robots!).
Now, I don't have that robot... but I have a Neo Trinkey and CircuitPython - that's more than enough.
My repository TellMeAStory has five files, CircuitPython code: scifi.py and prt.py, and text data: planets.sf, aliens.sf and ships.sf. Copy those to a Neo Trinkey, renaming scifi.py to code.py. When the program runs, touching "1" chooses a planet name, and touching "2" will generate a short story. In code.py, if REPL=True, you'll need to be in an editor like mu to see the text. IF you change REPL to be False, then it will use HID support to send the text as if typed on a keyboard! For example:
(touching 1)
Broria KBO6
(touching 2)
Black Sparrow
lifts off from
Broria KBO6
with ancient artifacts
and is damaged in an explosion
and tumbles out of control till emergency crews reach them
Suddenly a ship appears from Notania warning of imminent Bhisih attack.
Planets, Aliens, and Ship names were generated on fantasynamegenerators.com - you can edit the lists with your own choices. And you can rewrite the story elements to your own liking!
[Note: updated to work with CircuitPython 9.x. I moved the lists of aliens, ships and planets to external files to avoid memory allocation problems.]
-
Doggy Buttons! Inspiration
This project was born from two desires. One, see if our dog could be taught to use buttons without spending piles of money. Two, to make use of an ancient Raspberry Pi.
Lots of people have done a project like this, and it isn’t necessary to use a Raspberry Pi. That just happened to be the only unused piece of electronics I had on hand with audio output. But, I will say that I definitely recommend this approach for a couple reasons. Firstly, it’s very easy to get running in Python with Adafruit’s Blinka library. Second, it leaves lots of room for growth (both in number of buttons and features like button presses sending digital messages).
Be sure to read all the way to the bottom for a cute puppy photo!Materials Used
-
Raspberry Pi (low cost model A)
- Gifted by a coworker who was going to throw it out
-
16GB (full size) SD card
- Came with my 3D printer, but is overkill for that purpose
-
Audio cable
- From a box of junk computer parts
-
Audio amplifier
- Included in a box of junk that got shipped to me
-
Speakers
- From an old CRT TV
-
Jumper wires
- Reused from multiple projects
-
Keyboard breakout
- Hand made from salvaged diodes, jumper headers, perf-board, wires, and solder. Let's call it 50% recycled.
-
3D printed buttons (and some hotglue)
- Technically new, but I don't count 3D prints if they displace buying other plastic
-
Electronic buttons inside the prints (also a boot button shutdown switch)
- All salvaged from ewaste or otherwise discarded
-
Wooden mounting board
- Had to buy it from a hobby store
Salvage percentage ~83%! Hard to give an exact number... do you measure by count? By volume? By mass? By value? Do you count new tools you had to buy? Whatever, I'm going to call it 83%.
-
Raspberry Pi (low cost model A)
-
Teach your NeoTrinkey Morse code! C3P0 can talk. R2D2 expresses what he's thinking with beeps and boops. What can you do with a Neo Trinkey?
How about.....Morse code? It's simple, and four neopixels is more than enough to say anything! Here's how I set about doing it.
The great thing about Circuit Python is you can just plug in a device and use any editor on the code, but I like to use thonny as my IDE. Thonny gives you easy access to the REPL and you can have plenty of print() statements for debugging.
-
Spock on a Chip! I love using the random library - it's a simple way to introduce variety into simple programs and here's one example, what I call "Spock on a Chip." Here's a link to the repository.
Copy the files to a NeoTrinkey, changing "spock.py" to "code.py" - when it runs, it occasionally blinks lights, but when you touch one of the pads, it will blink and then pick a random quote out of the "spock" file and print it. If REPL=True, you'll need to be in an editor like mu to see the text. IF you change REPL to be False, then it will use HID support to send the text as if typed on a keyboard! Just the way to jazz up your email! Edit the "spock" file to add your own favorite quotes from everyone's favorite Vulcan.
What's going on?
There are two functions that are used to deliver the quotes. len_file(filename),
def file_len(filename):
with open(filename) as f:
for i, _ in enumerate(f):
pass
return i + 1
and wisdom(file_name).
def wisdom(filename):
qs = open(filename)
for i in range(random.randrange(file_len(filename))+1):
quote = qs.readline()
quote = quote.rstrip()
cmpthink()
qs.close()
return (quote.rstrip())
Give a file name, wisdom() calls len_file() to determine how big the file is, then uses random.randrange() to select the text - that means you can add or subtract from the "spock" file without needing to update the program.
The fun thing with this code is, it can be reused for other purposes. I've already used it to put together a "story" program that uses multiple source files (aliens, suns, planets, etc.) that can be drawn from to generate story lines. It could be used for a diceware style passphrase generator. And, of course, it doesn't have to be Spock on a chip - you can fill the "spock" file with any quotes you choose.
Fascinating. Is this logical? -
NeoTrinkey Tool Kit! It's no secret I think the NeoTrinkey is wonderful. It's small, it's cheap and it runs CircuitPython! Who could ask for more??
As I played with it, one thing I found handy was making some helper modules to assist in building programs - three that are particularly handy are:
-
morse.py - translate text to Morse code, also defines touch pads for input
- def docode(text): # display given text in Morse code
- def blinknum(num,color): #count out a number in a color
- def compthink(): #blink out all the colors when computer "thinking"
-
prt.py - provides a function to print text to the REPL *OR* using HID to send it as typed text.
- def prt(text, REPL): #prints text to REPL if REPL=True; otherwise uses HID to send as typed text
-
ncount.py - blinks numbers, or displays 0-15 in binary.
- def docolor(color): #briefly set all pixels to color
- def blinknum(num,color): #blinks num times in color
- def binnum(num,color): #display num%16 in binary on pixels with color
Using The Modules
Here's a very short program that blinks out, in Morse code some Bible verse fragments (in Klingon - my "nickname" is mrklingon, after all):
from morse import *
John316 = "vaD joH'a' vaj loved the qo'"
Isaiah263 = "Duvoqchugh, vaj rojna'Daq Da'av, DuHarmo'"
Psalm231 = "DevwI'wI' ghaH joH'a''e' jIneHbe'."
while True:
docode(John316)
docode(Isaiah263)
docode(Psalm231)
Pretty simple - it just blinks the code over and over. You could plug it into a power source and let it run - and you could choose your own text. Plugged into a computer running Mu, you would see the text being printed over and over as well.
Using the prt.py module, I wrote magicquest.py. This uses morse.py for the touch pad initialization, and the "compthink()" blinking. Touching pad 1 creates a fantasy character (race, class and name), and pad 2 generates a short story about the character's adventure. I used fantasynamegenerators.com to come up with place names and more.
If you change the value of REPL in the code you can decide if the text gets printed in the REPL using Mu or Thonny, or if it is sent as if typed using a computer.
REPL = False #"printed" text from prt() goes as if typed
REPL = True #"printed" text goes to REPL
I used the ncount.py module's binnum() function in two progams, cards.py and orbit.py.
For the cards.py program, the neotrinkey shuffles a deck of cards when you touch pad1, and pad2 draws a card and prints it in the REPL but displays it as a binary number (1-13) and color
suits = ["diamonds","clubs","hearts","spades"]
scolor = [gold,blue,red,green]Touching 1 and 2 together shuffles and draws three cards.
orbit.py blinks a purple NeoPixel around and around at a speed (sort-of) appropriate to the planet the ship is at - from Mercury to Pluto. This uses binnum() to light up the number 1,2,4, or 8 to move the position around and around the four NeoPixels. Touching pad 1 makes all NeoPixels flash gold (closer to the sun) and shifts to the planet that is next closer to the sun. Touching pad 2 makes all NeoPixels flash blue (farther from the sun) and shifts to the planet next farther out.
Pads 1&2 together make jump the position either to Mercury or Pluto depending on whether you are closer than Mars or at Mar or farther out.
The name of the target planet is printed to the REPL and the orbit speed speeds up or slows down depending on which direction you have moved. (Note: the "speed" is just a factor based the position (planet 1-9) divided by ten. It is not really correct for the real planets).
Adafruit Neotrinkey -
morse.py - translate text to Morse code, also defines touch pads for input
-
How about.... a 4 Pixel Video Game?? Here's a project I had fun putting together using my NeoTrinkey toolkit to make a tiny little video game.
I call it "Galaga" - and it uses the ncount.py and morse.py helper modules.
Gameplay:
- An enemy moves back and forth at random in the 0 and 3 neopixels.
- Touch 1, or 2 to "shoot" the moving enemy (green pixel).
- If you are lined up with it, you get a hit. If not it's a miss.
- A hit blinks gold for the count of hits. If you miss, you blink the number of misses in pale blue.
- A win is 5 hits, and losing is when you have 5 misses or run out of missiles.
- Winning is followed by multicolored blinking.
- Following loss/win the number of missiles, score and miss count are reset and game restarts.
- NOTE: connected to Thonny or Mu, you can see "boom!" and "miss" printed when you hit or miss. Plus notification when you win (or lose).