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.
-
Look Ma, No Microcontroller! (Night Light Edition) Inspiration
This is a piece of "found art"; a multi-colored circuit board hiding inside some old discarded electronics. It just needed a backlight and a frame to highlight its beauty. From the beginning, I knew this would become a nightlight and held onto the board until I had acquired the perfect combination of discarded LEDs and a few other components needed to make it shine.
Case Design
My 3D printed case had 3 chambers. One for the electronics, one for the LEDs, and a thin slot a the front to hold the circuit board and clear plastic cover. The reason for the separation was to make sure the LEDs were the right distance to illuminate the board and to minimize the light that leaked out that back of the frame while still allowing hot air to escape. The design was almost perfect, but I ended up having to cut a large slot in the back so I could replace the back wall with a slotted version for better ventilation (not pictured).
Before closing up the case, test that it can be dimmed appropriately.
-
Web API's & You Below is a test of the Playground Note code embed directly from Github (must be .md or .py file). If you see the full code below it is working as intended. If the example in the library gets updated this Playground Note might not be kept up to date with future changes. The code below will always be the most up to date as it is pulled directly from the adafruit_requests examples library.
Full Example Code
- An uncomprehensive & comprehensible guide to using Adafruit Requests with web API's.
The amount of online API examples for the Adafruit_Requests library is growing. If you're interested in using an API but an example does not exist and you're unsure how to start I will help walk you through the process.
I wrote the majority of the web API examples currently in the examples directory. I'm a pretty good source on how to approach a new unknown web API and wrangle out some basic data.
JSON
Data is malleable, it can be shaped and formed in a variety of ways. The most common format currently used with REST API's is JSON. The Adafruit_Requests library is particularly good with JSON data. If you don't know how to read or write in JSON don't worry, you won't need to. The library handles all the format conversions for you. All you need to know is how to get at the data you want and I will walk you through how to do that.
Before we begin there are some glossary terms that you'll need to know in order to make sense of JSON for the web.
REST
Representational State Transfer (REST) is a software architecture that imposes conditions on how an API should work. Most web API's use the REST architecture. All a beginner needs to know about REST is it's the way a website allows you to retrieve data from an API.
Endpoint
An endpoint refers to the data to retrieve from an API. This can be part of the JSON heirarchy path or a key:value pair. For web API's typically this refers to the JSON Key:Value pair.
Key:Value
- Key:Value (always a pair separated by a colon)
A key is a unique identifier associated with data and the value is the actual data. An example would be if you're working with a weather API and might want to return current:temperature for a geographic area to display freedom units on a TFT. It would return as `current:70.0` (in Fahrenheit) or if you're living in a sensible country using the metric system it would return `current:21.1` (in Celsius).
Key Error
It's useful to know the above terms because an error that a website or Circuit Python might return is `invalid key:value pair` or simply `key error`. That error means you requested a key:value that does not exist, you spelled it incorrectly, or the request was otherwise incorrectly formatted.
-
Stuff My Dog Said (via AdafruitIO) Inspiration
This project is an extension of the original doggy buttons project where I'll be showing how to make a public dashboard that publishes everything my dog says. This project is just one example of how to extend the features of my doggy buttons and perhaps one of the silliest IOT devices ever made. If you want to know about the buttons themselves, see the original project writeup.
With a project like this, it's also important to think about privacy. I don't want my dog making it possible to tell when we're away from home, so I'll also show how to add a delay so that activity is only published once it's sufficiently stale.Setup Challenges
User Space vs Admin Space
The first version of my program was kicked off by
/etc/rc.local
on boot, but this had the side effect of running the program asroot
. That was fine for my original project, but I had followed best practice and not usedsudo
whenpip3 install
ing the Python libraries needed for loading data to AdafruitIO. If I logged in to run the program as my user, it would end when I logged off butroot
no longer had all the Python libraries needed to run.Lennart (Binary Labs) suggested the simple and elegant solution of adding
sudo -u MY_PI_USERNAME
before the command that would launch my code in/etc/rc.local
. This allows the program to be run as my user without having to login and was much simpler and cleaner than the other solutions I was considering.Concurrency
The first version of my code simply added a call to
aio.send_data(...)
in the same loop that processed button presses, but I quickly discovered that the upload process could take 3 - 4 seconds leading to very delayed sounds when more then one button was pressed. Things like "water outside walk later" became "water ... outside ... walk ... later". This was starting to confuse my dog because the sounds weren't immediately connected with the button presses and would sometimes sound when she had gone to push something else entirely because the button "wasn't working".To fix this it was going to be necessary to introduce separate queues, one for playing sounds that could respond quickly, and one for uploading button presses that could slowly upload data to AdafruitIO in the background.
-
NeoPixel Stick and Pi Pico Need to add a simple diagnostic display to a Pico project? Or perhaps you want a quick and easy NeoPixel light show? Let's see what we can do!
Equipment
-
Reading Pixels from the RA8875 Driver Board Intro to the RA8875 Driver Board for Circuit Python
The Adafruit RA8875 driver in Circuit Python does not currently support displayio. You must use read/write registers with a barebones ra8875 graphics library. The current feature set and how it is used is only for very advanced users.
You can draw a bmp image and overlay text but you'll quickly find that's about all you can do with it. There are only 2 examples provided and the driver board is unlike any other display device for Circuit Python. Any knowledge you have of displayio does not transfer over to this board; the RA8875 is unique.
The interest of using an 800x480 bare display with Circuit Python is typically due to the sheer size of it but it should come with fair warning: You must be capable of programming with circuit python from scratch without displayio.
The bits and bytes of binary color
Since the RA8875 can only display a maximum of 16-bit color; the 24-bit image must be converted to 16-bit (color565).
The RA8875 stores color information in its memory with 2 bytes (2 pairs of 8 bits). Here is a binary representation of how it stores the color. 11111111 00000000 There are 8 bits in 1 byte.
However the RA8875 actually stores them in what is known as swapped color565. Each first byte must be swapped with the 2nd. This is not some type of color conversion error. These are the direct reads from the memory addresses for the stored colors.
-
The Necrochasm: Pushing the Prop-Maker RP2040 to its limit! This is a project that was years in the making, I went through many iterations that failed one way or the other. However, the final project was only started about a few months ago. It's great to see this project realized at last! The trigger really works, it has the appropriate sound effects, it has two firing speeds, and a "virtual ammo" system that you replenish by physically removing and reinserting the cartridge. So, where did it all begin?
The initial step of the process was to design the 3D printable case, which I did in blender. I found someone who extracted a model of the Necrochasm from the game itself and went to work sculpting out the finer details, hollowing out the interior, and adding LED areas. I also took this time to log into Destiny, and use my in-game Necrochasm to record the proper sound effects.
I exported the resulting pieces from blender to fusion 360 where I added the hardware mounting features. My vision for this prop was to have one area where most of the electronics were stored. This trapezoidal area at the bottom-front of the prop looked like it had the most storage capacity.
-
CG-35: A Retro RPN Calculator The CG-35 is a CircuitPython emulation of the Hewlett Packard HP-35 Scientific Reverse-Polish Notation (RPN) calculator designed for the Adafruit ESP32-S3 Feather and 3.5-inch TFT FeatherWing capacitive touch display. The calculator consists of a 10-digit LED-like display backed-up with 20-digit internal calculation precision.
This emulation reproduces the HP-35 calculator's v2.0 firmware where the change sign (CHS) key is active only after digit entry has begun. And because of the
udecimal
andutrig
classes, calculation accuracy of monadic, dyadic, and trigonometric functions was improved. As an added bonus not present on the original calculator, a status message will appear just below the primary display when a calculation error is encountered.The calculator's graphical layout was designed to mimic the aspect ratio of the original calculator -- that's why the left and right sides of the display screen were left empty. However, to provide a more reliable touch screen experience, the keys are somewhat proportionally larger than the original.
This project was inspired by Jeff Epler's DIY Desktop Calculator with CircuitPython project and Jeff's work to create CircuitPython versions of
udecimal
andutrig
. Thank you Jeff!GitHub Repository: https://github.com/CedarGroveStudios/CG-35_Calculator
Primary Code Module
The primary code module
cg_35_calculator.py
, imported bycode.py
, instantiates the display, plots the calculator case and buttons, and implements all calculator operational processes. This module uses a state machine design with the following named states:- IDLE -- Display results or wait for input
-
C_ENTRY -- Coefficient entry (keys:
0
-9
,.
,CHS
,EEX
) -
E_ENTRY -- Exponent entry (keys:
0
-9
,.
,CHS
) -
STACK -- Stack management (keys:
ENTER
,CLR
,CLX
,STO
,RCL
,R
,x<>y
,π
) -
MONADIC -- Monadic calculator functions (keys:
LOG
,LN,
e^x
,√x
,ARC
,SIN
,COS
,TAN
,1/x
) -
DYADIC -- Dyadic calculator functions (keys:
x^y
,-
,+
,*
,÷
) - ERROR -- Calculation error
The calculator's display precision and internal calculation precision are specified using the variables
DISPLAY_PRECISION
andINTERNAL_PRECISION
. Although the internal precision exceeds that of the original HP-35 calculator, it is recommended to keep the existing default settings of 10 digits and 20 digits, respectively, to avoid rounding errors.The variable
DEBUG
can be use to provide additional internal register status via the REPL. This boolean variable defaults toFalse
(no additional register status). -
IKEA Förnufig Air Purifier V2 - Custom fan speed controller + Blinkenlights Basic premise:
Add lights (speed / noise / air-purity indicators, or needless dotstar+neopixel love), use small board to receive tachometer input and drive 24V fan PWM signal from particle sensor, along with new inputs for noise level. Plus show off Blockly based programming on Adafruit IO, and update as and when the new maths functions become available, but for now write a quick CircuitPython version that illustrates all the desired functionality (to port to Adafruit IO).Semi-finished code: - Functionally capable but no reactive light use, rainbow:
[Reproduced from https://github.com/tyeth/Ikea_ItsyBitsyEsp32_Air_Purifier/ ] -
Wippersnapper - Sensirion SEN55 Particulate/VOC/NOx sensor - Plus an educational saga in the quest for a case... Wippersnapper
For those not in the know, WipperSnapper is Adafruit's plug-and-play firmware, which runs on their IO (think IOT) platform, offering free* data history(feeds) with graphs, dashboards, and automatic actions/triggers (along with integrating with other platforms like IFTTT). There is a paid upgrade for longer history, unlimited devices, SMS alerts, etc.
I love it because it's super quick and easy to test a sensor works, and to just get some data recording quickly.
Goto a web-page, flash over usb, add sensors via control panel (feeds + graphs are automatic), done.
Under the hood it's an arduino sketch, so adding additional sensors via github pull requests is surprisingly easy (I've added a few because it makes my future tasks easier).
For this project I'm testing the Sensirion SEN55, which senses Nitrous Oxides (NOx), Volatile Organic Compounds (VOCs), with temperature and relative humidty as a reference (uses SGP40/41 inside), and measures particle counts at <1.0 micron, <2.5, <4.0, and <10.0 micron. The other models of this sensor have less features (SEN54: no NOx, SEN50: no NOx/VOC/Relative Humidity+Temp - only Particulate Counts).
From the standard drivers(arduino/Pi) created by Sensirion we can retrieve the typical particle size, along with the raw particle counts (or in SI units ug/m3), the temperature and humidity, and then two indexes for NOx and VOC.
The NOx index has a baseline of 1.0, and if you hide the sensor under an upside down saucepan and use a lighter under the saucepan before sealing it back up then you will see a rise in the NOx index and then a return to baseline (1).
The VOC index is instead based at 100. You can detect VOC events from many things, the human breath can even be a source. I tested mine with a jar of clear nail varnish, but anything which you can smell, or smells chemically, is probably going to affect the sensor.Connectivity-wise, it uses I2C, or other methods (UART?) which are as yet unpublished. The connector requires a JST-GHR 1.25mm 6 Pin compatible cable. The development kits include such a cable, but are heavily marked up cost-wise, like an additional 150% (£50 for kit, £20 for bare sensor). I advise getting a bare version with an additional cable from elsewhere (coolcomponents have a cable under £2). There is also a Grove connector version from seeedstudio.
-
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.