Adafruit Playground is a wonderful place to share what you find interesting. 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.
Adafruit Playground is a safe place to share with the wonderful Adafruit community of makers and doers.
-
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.
-
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
-
Using Multiple WiFi Access Points with CircuitPython
Introduction:
If you have the need your WiFi project to operate at various locations with different WiFi SSID/PASSWORD settings at each location, read on. If you are using an MCU with built-in WiFi that CircuitPython 9.0.0 or later supports, there may be a solution to your issue.
Overview of the project:
This article will provide you with two tools to get you started.
- A code.py defined function (def) that will cycle through the WiFi networks defined in settings.toml. It will also show a sorted list of available WiFi access points found locally.
- A sample settings.toml to get you started.
- There are additional functions and features that will be covered as we go along.
- There will be a description of how each function works and interacts.
Why would you need multiple WiFi SSIDs in IoT projects?:
Let’s say you have a project that you must develop at home, show friends how it works at your bridge club, test it under various situations, and demonstrate its features to a customer. Having all the SSIDs and PASSWORDs predefined and having your project cycle through them without your intervention, except the first time you add them, would speed things up.
Prerequisites:
The system requirements are simple. An MCU with built-in WiFi that is supported by Circuit Python 9.0.0 or newer. My tests were run on a Raspberry Pi Pico W. All the libraries are built into CP 9.x.x that the sample code.py needs. They are: import os, wifi, random, binascii. The additional libraries used by the diagnostic code are import time, board, digitalio, ipaddress, supervisor, microcontroller and are also built-in.
If your MCU is listed at the web site below, your board is probably supported.
https://docs.circuitpython.org/en/latest/shared-bindings/wifi/index.html
Click on Available on these boards for a full listing. Considering the length of the boards listed, I have not tested, nor can I guarantee this code will work with any or all of them.
-
Touchscreen TFT & SDCard with the Raspberry Pi Pico W
The goal for this project is to get the display, touch screen, and sd card working on the Pi Pico by calibrating the touchscreen and printing the contents of the sdcard to serial console.
Learning how to share multiple SPI peripherals reduces the amount of pins you need which can increase the amount of available pins for other uses. A SPI bus is very similar to an I2C bus except the SPI peripheral has a unique chip select pin assigned to it. I find it easier to think of the chip select pin as a SPI address pin. You can only have 1 address per device with I2C and the same holds true with SPI devices except the address is a physical pin.
Because SPI peripherals require a physical pin you will be limited to how many you can have based on how many free pins your microcontroller has. What SPI lacks in chain ability it makes up for with speed.
- I2C devices are half-duplex
- SPI devices are full duplex
Because SPI communication is twice as fast as I2C it makes far more sense to use SPI for displays and sd cards! Don't get me wrong, I2C definitely has its place for uses such as:
- temp sensors
- optical sensors
- 7-Segment displays
- 14-Segment displays
- small 16x9 matrix LED modules
- Neopixel strips
- and chaining a ton of devices on 1 bus without the limitation of physical pins per device.
When it comes to needing faster communication for a display that has 480x320 (153,600 total) pixels or sd card reading & writing for a single device that's where the SPI protocol outperforms I2C.
I recently worked on a project with a Raspberry Pi Pico that needed to have a Touchscreen TFT & SD Card. That's actually 3 peripherals because the touchscreen controller chip requires pins too. In the majority of scenarios when you have a SPI touchscreen you actually have 2 SPI devices, the display and the touchscreen.
In the Raspberry Pi Pico pinout diagram there are 2 separate SPI buses. SPI0 and SPI1. SPI0 peripherals cannot communicate with peripherals on the SPI1 bus and vice versa. A peripheral would be anything on the SPI protocol such as a display, sdcard, temp sensor, etc... They are highlighted below with magenta labels. Please notice that each SPI bus is prepended with SPI0 or SPI1.
-
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). -
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).
-
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.
-
Github Desktop CRLF to LF PowerShell Workaround
The Windows Github Desktop program is an easy to use Git GUI for Windows users and it works great in most situations. However, it has a notorious history of converting every file in a Git repository checkout/clone to all CRLF (Windows Style) line endings. Line endings are also known as EOL's (End of Line).
To make line endings visible in Notepad++ go to
View > Show Symbols > Show End of Line
Here's an example of what CRLF's look like when made visible.
-
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).
-
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!
-
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.
-
Chrome Book Shutter-Offer using Neo Trinkey
Have you ever wanted to trick your friends into letting you shut off their Chromebook, Well now you can.
- First you need to install circuit python for the trinkey
- Second you need to add the libraries to the trinkeys
- finally copy the code and save
The libraries you need are neopixel.mpy and adafruit_hid
-
WiFi Power Management for the Raspberry Pi Pico W
With CircuitPython 8.x.x the new cyw43 library was introduced. The CYW43 supports controlling the power and efficiency of the CYW43439A0 WiFi chip on the Raspberry Pi Pico W microcontroller and other microcontrollers. The pins controlling 3 minor functions were transferred to the CYW43439A0 because Raspberry Pi Pico had used all the RP2040 pins. So the CYW43439A0 WiFi module had to be shoehorned between the RP2040 and the green LED, SMPS_MODE, and VBUS_SENSE functions. These three functions and the CYW43439A0 will be discussed here.
There are four sections to the new cyw43 internal library. This is new to CircuitPython starting with version 8.0.0. The features that import cyw43 will give you access to are:
- CYW43439A WiFi power management
- Access to and control of the onboard Green LED
- Access to and control of the SMPS_MODE option (PFM, PWM)
- Access to whether power is being supplied by way of the USB port by reading VBUS_SENSE
Four WiFi power management modes are predefined with the following:
- PM_STANDARD, 0xa11142: Is the standard power management mode. It enables power management and sets the power conservation timer to 200ms, 0x14.
- PM_AGGRESSIVE, 0xa11c82: It enables power management and sets the power conservation timer to 2000ms, 0xc8. This mode provides optimal power usage at the cost of performance.
- PM_PERFORMANCE, 0x111022: It enables power management and sets the power conservation timer to 20ms, 0x02. This mode uses more power to increase performance.
- PM_DISABLED, 0xa11140: Power management is disabled in this mode. The power management timer is set to 200ms, 0x14. Note: CircuitPython sets this mode at power on and reset because it provides the best connectivity reliability.
-
Alternate Power Settings: There does not seem to be a standard name for what each means. These are from the MicroPython documentation.
PM_NONE = 0x000010 # Like PM_DISABLED above
PM_POWERSAVE = 0x000011 # More power savings than PM_PERFORMANCE above
PM_PERFORMANCE = 0xA11142 # Same as PM_STANDARD above
To see what each bit of the 12 bit word does, check the first document referenced at the end of this document.
The following code snippet shows how to set the mode to the STANDARD mode.
try:
import cyw43 # Also tests for Raspberry Pi Pico W
cyw43.set_power_management(cyw43.PM_STANDARD)
except ImportError:
cyw43 = None
print(hex(cyw43.get_power_management())) # Will show the HEX value.
The challenge for the designers of the Raspberry Pi Pico W to add WiFi services they needed to repurpose three feature pins. The green LED, SMPS_MODE, and VBUS_SENSE control lines are used. To access them on the Pico W, the following code snippets may help you.
To drive the green LED we must now go through the WiFi module. Fortunately, board.LED does that for us.
# Green LED, WL_GPIO0, CYW0
GreenLED = digitalio.DigitalInOut(board.LED)
GreenLED.direction = digitalio.Direction.OUTPUT
GreenLED.value = True # Turn green LED ON
GreenLED.value = False # Turn green LED OFF
print('Green LED ', GreenLED.value) # Test for green LED state
SMPS_MODE sets the way the power management chip converts the supplied power to the 3.3 volts the RP2040 MCU and other devices require. SMPS_MODE controls the PS (Power Save) pin on the RT6150 buck/boost power manager.
When PS is low (0) (the default on Pico) the regulator is in Pulse Frequency Modulation (PFM) mode, which, at light and moderate loads, saves considerable power by only turning on the switching MOSFETs occasionally to keep the output capacitor topped up. PFM outputs a constant wave shape but at different frequencies depending on the load. Higher load >> higher frequency. An inductor is used to store power between the HIGHs to smooth out the ripple introduced by turning the power converter MOSFETs ON and OFF.
Setting PS high (1) forces the regulator into Pulse Width Modulation (PWM) mode. PWM mode forces the SMPS to switch continuously, which reduces the output ripple considerably at light loads (which can be good for some use cases) but at the expense of much worse efficiency. PWM outputs a constant frequency with a varying portion of HIGH versus LOW states. With a higher current demand, the output will increase the duration of the HIGH state and reduce the duration of the LOW state. An inductor is used to store power between HIGH states to smooth out the 3.3 volts sent to the circuits. Improved ripple is achieved, but worse efficiency at light loads will occur. It may improve Analog to Digital Conversion and other stability critical operations.
Note: Under heavy load conditions the switcher will be in PWM mode irrespective of the PS pin state.
# SMPS_MODE, WL_GPIO1, CYW1, 0=PFM Mode (Default, Best Efficiency),
# 1=PWM Mode (Improved ripple, worse efficiency at light loads)
SMPS = digitalio.DigitalInOut(board.SMPS_MODE)
SMPS.direction = digitalio.Direction.OUTPUT
SMPS.value = False # PFM Mode, most efficient
print('SMPS_MODE ', SMPS.value)
By reading the value from VBUS_SENSE you can determine if power is being supplied through the USB connector. If the value is True (1) power is being provided from the USB connector. If it is False (0) power is coming from some other source. It may be ~2.0 volts to 5.5 volts applied to the VSYS pin or 3.3 volts applied to the 3V3 pin. Applying 3.3 volts to the 3V3 pin is not advised unless it is exceptionally well regulated. Momentary startup overvoltage spikes can be lethal to your MCU. If power is applied to VSYS an onboard diode connected between VBUS and VSYS prevents back feeding power to the PC USB port.
# VBUS_SENSE, WL_GPIO2, CYW2
# True = we have power from USB
# False = power via VSYS or 3V3
VBUS = digitalio.DigitalInOut(board.VBUS_SENSE)
VBUS.direction = digitalio.Direction.INPUT
print('VBUS_SENSE', VBUS.value)
Source documents:
https://docs.circuitpython.org/en/latest/shared-bindings/cyw43/index.html
https://datasheets.raspberrypi.com/picow/pico-w-product-brief.pdf
-
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.