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.
-
Pinned by jepler
About Jeff
I’ve been a software developer ever since I first started typing in program listings on an 80s home computer.
I’ve been a contributor to Free and Open Source software for over 20 years. See my github profile for my contributions and original software.
I'm proud to be working with Adafruit, mainly on CircuitPython, and helping enable people learn and create fun projects with whatever environment works best for them.
-
Trane furnace canbus
Trane® Link UX360 Smart Thermostat Installation Guide page 8 gives some detail on the CAN bus wiring (wire colors)
CAN voltages are referenced to GND but none of the wires is labeled as GND.
Red measures as 27VAC relative to the body of the furnace which is presumably grounded. So: Red is power (but not good for microcontroller power!), while blue is GND. This is within the 18-30VAC range specified by the thermostat.
About 2.6VDC is measured from furnace body to DH/DL lines, within the 2-4V expected.
-
USB, XT, PS2 keycode cross reference in CSV
Recently for the Fruitjam-286 emulator, I needed to create a good mapping from USB HID codes to XT codes.
There are many resources online including Keyboard Scancodes (Andries Brouwer) which is a nice HTML table.
I converted the table into a csv file, and then used a bespoke Python program to convert it to a format for the emulator.
First, my program opens the csv file and checks its structure
-
pico-286 emulator notes
HDMI output only supports a subset of all modes documented in README:
- TEXTMODE_80x25_COLOR (rendered as 320x150 with chunky 4x6 characters)
- CGA 320x200x4 including a "BW" variant
- Composite 160x200x16 including a "force" variant
- TGA 160x200x16
- TGA 320x200x16
- VGA 320x200x256 (including a "x256x4" variant, probably the famous "X mode")
- EGA 320x200x16x4
Booting a floppy:
Grab a compatible floppy, I used the file from MS-DOS 3.3 - Working Copy With Utilities direct link is DOS 3.3 Working.img
Create a directory "XT" on the SD card
Put this in the "XT" directory and rename to "fdd0.img"
It should also be possible to create the following image names: fdd1.img hdd0.img hdd1.img
I made a bootable floppy based on
https://archive.org/download/msdos_VGAFUN_shareware
It's temporarily at
https://media.unpythonic.net/emergent-files/sandbox/mario-joust.img
put this on the device as fdd0 and you can run a bad port of joust or mario (the arcade ones with the pipes)
-
Apple II game notes
Moon Patrol (F2)
Left/right: Move buggy
a: jump
space: shoot
emulator keys
f1-f8: select image (with left OS key: reset and start image)
f12: reset (try left OS key) (Real //e required ctrl, emulator does not)
left OS key: open apple
right OS key: closed apple
Emulator limitations
Floppy images are read-only
BASIC prompt
Press ctrl+open apple right at startup to enter BASIC
pr#3: 80 column mode
-
Mac emulator on Fruit Jam using Pico-Mac
If you're a certain age, this project will take you right back to the 11th grade computer lab, full of black & white Macintosh computers with floppy disks.
We took the excellent pico-mac emulator and fine tuned a few things for the Adafruit Fruit Jam:
- The RP2350's HSTX peripheral is used to create a digital video signal on the board's DVI-D video output (compatible with HDMI monitors)
- The on board USB hub is supported, so you can connect a keyboard & mouse to the two USB ports
- The SD card is supported, so you can load your favorite Macintosh software
- The PSRAM chip is supported and can emulate a 4MB Macintosh (otherwise, up to 400kB of RAM can be emulated)
- The I2S audio chip is supported, so you can hear the basic system beep (as well as digital audio from some games and applications) on the internal speaker or via the external speaker/headphone jack
Install the Emulator
There are multiple emulator images provided, each with different settings:
- "4096k" emulates a Mac with 4MB RAM, using the psram chip, while "400k" emulates a non-standard mac with 400kB RAM without using psram. For applications that do not need much RAM, the "400k" emulator may be faster. "-psram" accompanies "4096k" as a reminder that this uses the PSRAM chip.
- "640x480" emulates a Mac with a standard VGA resolution screen. "512x342" emulates the original Mac desktop resolution precisely.
- "-oc" overclocks the Fruit Jam to 264MHz for faster emulation. This overclocks the RP2350 chip that powers the Fruit Jam. Just like PC overclocking, there’s some risk of reduced component lifespan, though the extent (if any) can’t be precisely quantified and could vary from one chip to another. Proceed at your own discretion. (-oc -psram builds, if added, also overclock the PSRAM chip to 133MHz)
If you're not sure which image you want, choose 4096-512x342-psram.
Plug the Fruit Jam into your computer with a good USB C data cable. While holding down the UF2 button (Button #1), slide the power switch from OFF to ON. Or, if your Fruit Jam was already powered on, hold the UF2 button and press the reset button briefly.
Your Fruit Jam will connect in bootloader mode, adding a drive called "RP2350" to your computer. Simply drag or copy the umac uf2 file to this drive.
After the firmware loads, the device will reboot into the umac emulator.
-
CircuitPython "Avalanche Noise" RNG with TPS65131 & NPN transistors
I've long had an interest in random number generation. In fact, a very early PCB I designed and built was exactly for this purpose: Arduino Random Number Generator.
That project used a property of transistors called "avalanche noise". Inconveniently, it required a supply of +-10V to work properly. Avalanche noise is sometimes explained as being a "quantum effect" and thus is supposed to be a source of true randomness.
When the TPS65131 went into the store, I knew it was time to revisit this project. This little PCB generates positive and negative voltages from a single +5V input.
Parts
-
Sandbox
-
3D printed Bracket for 4 64x64 2mm RGB matrix panels
I wanted to assemble four of those 64x64mm RGB matrix panels. However, my 3D printer is limited to about 250x205mm, and the panels are 256x256mm.
It required a little creativity, but I devised a single print of about 249x136mm such that by printing two and flipping one of them over, it becomes a bracket that can hold the four panels together.
I have a feeling that the injection molded bodies of these panels may change from time to time, potentially moving the screw & post positions. My 4 panels were purchased around February 2025.
I developed the part in OpenSCAD, Free (GPL) 3D modeling software available on Linux, Mac, & Windows. To create a 3D printable STL file, open the SCAD file, press F5 to "render" it to triangles, and then F7 to export to STL.
-
supervisor.runtime.display in CircuitPython 9.2.5+
CircuitPython 9.2.5 adds a new property to the supervisor Runtime object,
display.If your board has a built in display that is automatically configured by the CircuitPython core (e.g., boards like the Feather ESP32-S3 Reverse TFT), then this display is available as
supervisor.runtime.displayin addition toboard.DISPLAY.So what's different about the new
supervisor.runtime.display?- This property is available on all boards that support
displayio, not just boards with built in displays - Unlike
board.DISPLAY, this property is settable, and remembers its value after your code file finishes running. This means you can set this property once in boot.py and then use the display each time yourcode.pyruns, or re-use a display set by a previous run of code.py. - Due to technical limitations in CircuitPython, when a display is released,
board.DISPLAYbecomes a "None-like object": one that prints asNonebut fails the checkboard.DISPLAY is None.supervisor.runtime.display is Noneworks correctly to check whether a default display is configured.
Setting
supervisor.runtime.displayThere are two approaches:
- Do it unconditionally in boot.py and depend on this in code.py
- Do it conditionally in code.py, if
supervisor.runtime.display is None
... in boot.py
Here's a code snippet that shows configuring a 240x240 ST7789 display connected to an EyeSpi BFF on a QT Py board like the QT Py RP2040:
- This property is available on all boards that support
-
CNC Rotary Encoder Internals
I was working with one of Adafruit's CNC Rotary Encoders and made a mistake wiring. Well, long story short, I killed it. I am not sure exactly what I did, but I suspect I accidentally drove its outputs with an improper voltage.
To prevent the experience from being a total loss, I took the time to partially disassemble it.
The plastic cover around the screw terminals can easily be removed, exposing a PCB. 3 pins hold this PCB onto the rest of the assembly. After desoldering them, the component side of the board can be seen.
The board features a "7550" voltage regulator. The incoming supply is regulated down from whatever it is to 5V for the internal circuitry. C2 is the input smoothing capacitor, and C1 is the output smoothing capacitor.
D1 and D2 are light emitting diodes in series (likely IR) which are positioned under matching sensors in the upper part of the assembly. A "331" (330Ω) resistor limits the current through the LEDs.
The 3 soldered positions are a supply and 2 returns from the sensor package in the upper part of the assembly. Resistors R2 & R3 are pull ups, while caps C3 and C4 smooth out any spurious transitions of the signals. It seems most likely that the sensor assembly consists of two phototransistors, which can pull the pins of the "HC14", a schmitt-trigger inverter. This creates the signals A and B at a dependable logic level, and also the inverted A/ and B/ signals.
Diode D3 is a reverse current protection diode; in case VCC and GND are swapped, no current can flow. Interestingly it's on the GND side, while I thought it was more common to see on the VCC side.
If I cared to determine which component(s) I damaged, this is totally a board that could be reworked by hand. However, I don't plan to spend the time and instead will just pick up a fresh encoder from the store—and double check my wiring next time.
-
Make Logic Analyzer Connector Blocks
Do you have a logic analyzer like Saleae, or even one of those rp2040-based ones?
Do you hate plugging up all the signals when you want to use your logic analyzer on a multi-channel problem?
While working on RGB matrix support on the Pi 5, I sure did! But then I remembered that Adafruit has the parts I need to make some cables that are quick to connect & disconnect from my logic analyzer.
In my case, I'm plugging into a Rigol MSO1074, but I might also want to use my Saleae in the future, so I made up two 2x4 blocks. This way, I can use the connectors on either device.
It's quick and easy and best of all you don't have to crimp any connectors because the pre-crimped jumper wires are right there in the Adafruit store.
Start by grabbing the parts you need. Get a packet of housings for the "project end" and the "logic analyzer" end, as well as the correct jumper wires (male/male, male/female, or female/female) for the connectors.
For instance, I was connecting the 2 row male header of my logic analyzer to the 2 row male header on the Adafruit RGB Matrix Bonnet, so I used Female/Female jumper wires, a pair of 2x4 housings from the small dual row wire housing pack, and a single 2x10 housing from the large dual row wire housing pack. (there are actually 2x8 pins, but the 2x10 housing fits the space of the keyed connector better, reducing the chance of plug mistakes. A keyed housing would be better, but then I'd be off to digikey to look for one)
Now, tear off some wires from the set of jumper wires. I worked in groups of 5, so that each 2x4 housing for the logic analyzer end had 4 signal wires & 1 GND wire.
I connected 8 different signals, somewhat arbitarily: 4 RGB data lines, 1 address line, and 3 control lines.
Double check the position of each signal & GND, and make sure you have at least one GND signal (but more ground wires are better for signal integrity! The ideal is to have one GND between each signal)
Once you're satisfied you've got the correct wire at the correct position, insert the wire into the housing until it clicks firmly into place. The orientation of the crimped pin is important, it will only latch one way.
If you make a mistake, it's not the end of the world. Frequently it's possible to free a "locked in" connector. Here's the procedure I follow: First, push the pin as far forward in the housing as possible, opening a gap under the plastic latch. Then, carefully insert a jeweler's screwdriver under the latch. Then, pull back on the pin. When done properly, this frees the wire & leaves the latch intact. However, if you bend the latch out too far it deforms and will no longer retain a wire when one is re-inserted.
-
CircuitPython "Ring Oscillator" RNG with SN74AHCT14
I've long had an interest in random number generation. In fact, a very early PCB I designed and built was exactly for this purpose: Arduino Random Number Generator.
That project used a property of transistors called "avalanche noise". Inconveniently, it required a supply of +-10V to work properly. Avalanche noise is sometimes explained as being a "quantum effect" and thus is supposed to be a source of true randomness.
There are other types of physical randomness. One actually exists inside the RP2040 chip already: It has a Ring Oscillator peripheral built in. However, this project shows how to build a Ring Oscillator from a simple "78*14" chip and process it into an infinite unguessable string of bytes using CircuitPython.
I built this project with a QT Py RP2040. It's very simple; the only other required parts are the 74*14 chip, a breadboard, and some wire.
This is not a truly robust RNG and you shouldn't use it for anything serious. For example, someone could tamper with it and just remove the connection between the RP2040 and the ring oscillator; the code wouldn't notice, but its outputs would be exactly the same each time it was powered on. Real RNG products will have part of the software that verifies that the random source is behaving like a random source and is not fixed at a single value, or otherwise trivially predictable.
Ring Oscillator Theory
A Ring Oscillator is based on a ring of an odd number of Schmitt-trigger XOR gates. This project uses three gates in its ring.
The output of gate 1 is tied to the input of gate 2; the output of gate 2 is tied to the input of gate 3, and the output of gate 3 is tied to the input of gate 1.
Suppose you want to know the value that will appear at the output of each gate. Well, let's suppose the input to gate 1 is HIGH. Then we must have:
- Gate 1 input: HIGH output: LOW
- Gate 2 input: LOW output: HIGH
- Gate 3 input: HIGH output: LOW
Notice how we concluded that if the gate 1 input is HIGH, then the gate 1 input is LOW. In philosophy class, you'd call this a logical contradiction and just decide that such a thing cannot exist. But in a physical system, what happens is: Each gate takes some length of time to "drive" its output from HIGH to LOW or vice versa; and each gate has some specific input voltage to determine whether it wants to drive its output HIGH or LOW. And in fact these properties vary unpredictably from moment to moment.
The exact period of a ring oscillator like this varies from moment to moment, depending on many physical details. A lot of ink can be spilled by electronic engineers & physicists about exactly "how random" this is, but a screenshot from a scope shows clearly that over even a short period of time the crests and troughs of the output of the ring oscillator "smear out" across all possibilities:
-
Halloween Flame Effect Prop
At my choir's halloween performance, we had a prop witches' cauldron. I added a flame effect using a MagTag with CircuitPython & a NeoPixel strip.
It was pretty simple to code up and it worked flawlessly during the performance.
You could pick almost any CircuitPython microcontroller for this project; I chose a magtag because it was handy, and had an on-off switch as well as a JST connector that was easy to connect to my NeoPixel strip.
Just for fun, I made an image for the MagTag's e-ink display with a spooky AI-generated bat scene plus arrows pointing at the charging port and on-off switch. The screen refreshes each time the main code runs.
-
TM1814 LEDs with CircuitPython & RP2040
There are a lot of addressable LEDs out there. The chief variant is NeoPixel .. NeoPixel and Dotstar. Our two types of addressable LEDs are NeoPixel and Dotstar .. and TM1814....
(you get it right? It's like the Spanish Inquisition skit, but about LEDs?)
Long story short: TM1814 addressable LEDs can work with CircuitPython on the RP2040, but you'll have to use some custom code to talk to them. The TM1814PixelBackground class is a PixelBuf, though, so you can use it with the LED animation library no problem.
There's one big difference with TM1814 LEDs: The logic levels are inverted compared to NeoPixels. And, the first 64 bits are an overall brightness value. Two differences. The bits are inverted, and the timing is subtly different, and there's an overall brightness value. And if you don't continuously update the pixels, they enter a test mode. Four. Four main differences. And often the strips are powered from a higher voltage like 12V.
You have one last chance left to show me the source code
But first! You'll want to make sure you're using CircuitPython 9.2, at least 9.2.0-beta.0 or newer. It seems there was a teensy, tiny bug in CircuitPython that you'd encounter if you used this code. So with that out of the way
Two, I'll allow you two last chances to show me the source code
Grab the code from below (or from github) and place it on your CircuitPython device as code.py. Then, hook up your TM1814 strip to CircuitPython's GND and A0 pins. Hook up appropriate power to the TM1814 strip (mine was labeled "12V", so I used a 12V supply. 5V definitely didn't work! And definitely don't cross-connect the LED strip's supply into the RP2040's VCC or +5V!).
When the CircuitPython LED code is not running (or if it malfunctions) you get an obnoxious test pattern generated by the TM1814 LEDs themselves. Sorry, there's nothing we can do about it.
Once the code's running you'll get a comparatively soothing rainbow LED animation instead.
If you won't show us the source code I'm afraid we'll have to put you in the Comfy Chair
-
Installing Python 3.13 using uv
I use Debian Linux; the current stable version of Debian has Python 3.11.2. At the time I write this, Python 3.13 has just been released. So I used my desire to try out the new Python as a chance to explore uv as well. My instructions here are tested on Debian Linux, but may work with modifications on other Linux-based systems like Ubuntu or Raspberry Pi OS.
What's uv, anyway?
uv is "An extremely fast Python package and project manager, written in Rust." It's on github and pypi and is also well-documented.
Among its many features, uv is capable of managing multiple versions of Python, so I decided to use it to test drive Python 3.13.
Installing uv via pipx
First, install the pipx command using the operating system package manager. (debian package name: pipx)
Then, install uv:
pipx install uvOnce you've done this, you should have the
uvcommand available. However, you may see a warning like this: