Circuitpython - Tricks
Circuitpython - Tricks
A small list of tips & tricks I find myself needing when working with CircuitPython.
(Note: most all of these assume CircuitPython 7)
Table of Contents
• Inputs
• Read an digital input as a Button
• Read a Potentiometer
• Read a Touch Pin / Capsense
• Read a Rotary Encoder
• Debounce a pin / button
• Set up and debounce a list of pins
• Outputs
• Output HIGH / LOW on a pin (like an LED)
• Output Analog value on a DAC pin
• Output a "Analog" value on a PWM pin
• Control Neopixel / WS2812 LEDs
• Control a servo, with animation list
• Neopixels / Dotstars
• Moving rainbow on built-in board.NEOPIXEL
• Make moving rainbow gradient across LED strip
• Fade all LEDs by amount for chase effects
• Audio
• Audio out using PWM
• Audio out using DAC
• Audio out using I2C
• Making simple tones
• USB
• Rename CIRCUITPY drive to something new
• Detect if USB is connected or not
• Get CIRCUITPY disk size and free space
• Programmatically reset to UF2 bootloader
• USB Serial
• Print to USB Serial
• Read user input from USB Serial, blocking
• Read user input from USB Serial, non-blocking (mostly)
• Read keys from USB Serial
• Read user input from USB serial, non-blocking
• Networking
• Scan for WiFi Networks, sorted by signal strength (ESP32-S2)
• Ping an IP address (ESP32-S2)
• Fetch a JSON file (ESP32-S2)
• What the heck is secrets.py?
• Displays (LCD / OLED / E-Ink) and displayio
• Get default display and change display rotation
• Display an image
• Display background bitmap
• Image slideshow
• Dealing with E-Ink "Refresh Too Soon" error
• I2C
• Scan I2C bus for devices
• Speed up I2C bus
• Timing
• Measure how long something takes
• More accurate timing with ticks_ms(), like Arduino millis()
• Board Info
• Display amount of free RAM
• Show microcontroller.pin to board mappings
• Determine which board you're on
• Support multiple boards with one code.py
• Computery Tasks
• Formatting strings
• Formatting strings with f-strings
• Make and use a config file
• Run different code.py on startup
• More Esoteric Tasks
• Map an input range to an output range
• Constrain an input to a min/max
• Time how long something takes
• Preventing Ctrl-C from stopping the program
• Prevent auto-reload when CIRCUITPY is touched
• Raspberry Pi Pico boot.py Protection
• Hacks
• Using the REPL
• Display built-in modules / libraries
• Use REPL fast with copy-paste multi-one-liners
• Python tricks
• Create list with elements all the same value
• Python info
• Display which (not built-in) libraries have been imported
• List names of all global variables
• Host-side tasks
• Installing CircuitPython libraries
• Installing libraries with circup
• Copying libraries by hand with cp
Inputs
Read an digital input as a Button
import board
from digitalio import DigitalInOut, Pull
button = DigitalInOut(board.D3) # defaults to input
button.pull = Pull.UP # turn on internal pull-up resistor
print(button.value) # False == pressed
Read a Potentiometer
import board
import analogio
potknob = analogio.AnalogIn(board.A1)
position = potknob.value # ranges from 0-65535
pos = potknob.value // 256 # make 0-255 range
Outputs
Output HIGH / LOW on a pin (like an LED)
import board
import digitalio
ledpin = digitalio.DigitalInOut(board.D2)
ledpin.direction = digitalio.Direction.OUTPUT
ledpin.value = True
# your servo will likely have different min_pulse & max_pulse settings
servoA = servo.Servo(PWMOut(board.RX, frequency=50), min_pulse=500,
max_pulse=2250)
while True:
angle, secs = animation[ ani_pos ]
print("servo moving to", angle, secs)
servoA.angle = angle
time.sleep( secs )
ani_pos = (ani_pos + 1) % len(animation) # go to next, loop if at end
Neopixels / Dotstars
Moving rainbow on built-in board.NEOPIXEL
In CircuitPython 7, the rainbowio module has a colorwheel() function. Unfortunately, the
rainbowio module is not available in all builds. In CircuitPython 6, colorwheel() is a built-
in function part of _pixelbuf or adafruit_pypixelbuf.
The colorwheel() function takes a single value 0-255 hue and returns an (R,G,B) tuple given
a single 0-255 hue. It's not a full HSV_to_RGB() function but often all you need is "hue to RGB",
wher you assume saturation=255 and value=255. It can be used with neopixel,
adafruit_dotstar, or any place you need a (R,G,B) 3-byte tuple. Here's one way to use it.
# CircuitPython 7 with or without rainbowio module
import time, board, neopixel
try:
from rainbowio import colorwheel
except:
def colorwheel(pos):
if pos < 0 or pos > 255: return (0, 0, 0)
if pos < 85: return (255 - pos * 3, pos * 3, 0)
if pos < 170: pos -= 85; return (0, 255 - pos * 3, pos * 3)
pos -= 170; return (pos * 3, 0, 255 - pos * 3)
# CircuitPython 6
import time
import board
import neopixel
led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4)
while True:
led.fill( neopixel._pixelbuf.colorwheel((time.monotonic()*50)%255) )
time.sleep(0.05)
Audio
In CircuitPython, there are three different techniques to output audio:
• DAC using audioio
• PWM using audiopwmio - requires RC filter (at least)
• I2S using audiobusio - requires external I2S decoder hardware
CircuitPython's support on particular microcontroller may include support for several of these ways
(e.g. SAMD51 supports DAC & I2S, just one (e.g. ESP32-S2 supports only I2S) or even none (e.g.
Teensy)
Audio out using PWM
This uses the audiopwmio library, only available for Raspberry Pi Pico (or other RP2040-based
boards) and NRF52840-based boards like Adafruit Feather nRF52840 Express. On RP2040-based
boards, any pin can be PWM Audio pin. See the audiopwomio Support Matrix for details.
import time,board
from audiocore import WaveFile
from audiopwmio import PWMAudioOut as AudioOut
wave_file = open("laser2.wav", "rb")
wave = WaveFile(wave_file)
audio = AudioOut(board.TX) # must be PWM-capable pin
while True:
print("audio is playing:",audio.playing)
if not audio.playing:
audio.play(wave)
wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each
time
time.sleep(0.1)
Note: Sometimes the audiopwmio driver gets confused, particularly if there's other USB access,
so you may have to reset the board to get PWM audio to work again.
Note: WAV file whould be "16-bit Unsigned PCM" format. Sample rate can be up to 44.1 kHz, and
is parsed by audiocore.WaveFile.
Note: PWM output must be filtered and converted to line-level to be usable. Use an RC circuit to
accomplish this, see this twitter thread for details.
USB Serial
Print to USB Serial
print("hello there") # prints a newline
print("waiting...", end='') # does not print newline
for i in range(256): print(i, end=', ') # comma-separated numbers
usb_reader = USBSerialReader()
print("type something and press the end_char")
while True:
mystr = usb_reader.read() # read until newline, echo back chars
#mystr = usb_reader.read(end_char='\t', echo=False) # trigger on tab, no
echo
if mystr:
print("got:",mystr)
time.sleep(0.01) # do something time critical
Networking
Scan for WiFi Networks, sorted by signal strength (ESP32-S2)
import wifi
networks = []
for network in wifi.radio.start_scanning_networks():
networks.append(network)
wifi.radio.stop_scanning_networks()
networks = sorted(networks, key=lambda net: net.rssi, reverse=True)
for network in networks:
print("ssid:",network.ssid, "rssi:",network.rssi)
wifi.radio.connect(ssid=secrets['ssid'],password=secrets['password'])
Display an image
The adafruit_imageload library makes it easier to load images and display them. The images
should be in paletized BMP3 format.
import board
import displayio
import adafruit_imageload
img_filename = "bg_jp200.bmp"
display = board.DISPLAY
img, pal = adafruit_imageload.load(img_filename)
group = displayio.Group()
group.append(displayio.TileGrid(img, pixel_shader=pal))
display.show(group)
Image slideshow
import time, board, displayio
import adafruit_imageload
Note: images must be in palettized BMP3 format. If you have ImageMagick installed, you can use
its convert command to take any image format to proper BMP3 format:
convert cat1.jpg -colors 256 -type palette BMP3:cat1.bmp
To make images smaller (and load faster), reduce number of colors from 256. If your image is a
monochrome (or for use with E-Ink displays like MagTag), use 2 colors. The "-dither" options are
really helpful for monochrome:
convert cat.jpg -dither FloydSteinberg -colors 2 -type palette BMP3:cat.bmp
I2C
Scan I2C bus for devices
from: https://learn.adafruit.com/circuitpython-essentials/circuitpython-i2c#find-your-sensor-
2985153-11
import board
i2c = board.I2C() # or busio.I2C(pin_scl,pin_sda)
while not i2c.try_lock(): pass
print("I2C addresses found:", [hex(device_address)
for device_address in i2c.scan()])
i2c.unlock()
Timing
Measure how long something takes
Generally use time.monotonic() to get the current "uptime" of a board in fractional seconds.
So to measure the duration it takes CircuitPython to do something like:
import time
start_time = time.monotonic()
# put thing you want to measure here, like:
import neopixel
stop_time = time.monotonic()
print("elapsed time = ", stop_time - start_time)
Note that on the "small" versions of CircuitPython in the QT Py M0, Trinket M0, etc., the floating
point value of seconds will become less accurate as uptime increases.
More accurate timing with ticks_ms(), like Arduino millis()
If you want something more like Arduino's millis() function, the
supervisor.ticks_ms() function returns an integer, not a floating point value. It is more
useful for sub-second timing tasks and you can still convert it to floating-point seconds for human
consumption.
import supervisor
start_msecs = supervisor.ticks_ms()
import neopixel
stop_msecs = supervisor.ticks_ms()
print("elapsed time = ", (stop_msecs - start_msecs)/1000)
Board Info
Display amount of free RAM
from: https://learn.adafruit.com/welcome-to-circuitpython/frequently-asked-questions
import gc
print( gc.mem_free() )
Computery Tasks
Formatting strings
name = "John"
fav_color = 0x003366
body_temp = 98.65
fav_number = 123
print("name:%s color:%06x temp:%2.1f num:%d" %
(name,fav_color,body_temp,fav_number))
# 'name:John color:ff3366 temp:98.6 num:123'
# code.py
from my_config import config
print("secret:", config['secret_key'])
# 'secret: 3a3d9bfaf05835df69713c470427fe35'
Also useful for graceful shutdown (turning off neopixels, say) on Ctrl-C.
import time, random
import board, neopixel, rainbowio
leds = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4 )
while True:
try:
rgb = rainbowio.colorwheel(int(time.monotonic()*75) % 255)
leds.fill(rgb)
time.sleep(0.05)
except KeyboardInterrupt:
print("shutting down nicely...")
leds.fill(0)
break # gets us out of the while True
With this, to trigger a reload, do a Ctrl-C + Ctrl-D in the REPL or reset your board.
led = DigitalInOut(board.LED)
led.switch_to_output()
def reset_on_pin():
if safe.value is False:
import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE)
microcontroller.reset()
led.value = False
for x in range(16):
reset_on_pin()
led.value = not led.value # toggle LED on/off as notice
time.sleep(0.1)
Hacks
Using the REPL
Display built-in modules / libraries
Adafruit CircuitPython 6.2.0-beta.2 on 2021-02-11; Adafruit Trinket M0 with
samd21e18
>>> help("modules")
__main__ digitalio pulseio supervisor
analogio gc pwmio sys
array math random time
board microcontroller rotaryio touchio
builtins micropython rtc usb_hid
busio neopixel_write storage usb_midi
collections os struct
Plus any modules on the filesystem
# print out board pins and objects (like 'I2C' and 'display')
import board; dir(board)
# print out microcontroller pins (chip pins, not the same as board pins)
import microcontroller; dir(microcontroller.pin)
Python tricks
These are general Python tips that may be useful in CircuitPython.
Python info
How to get information about Python inside of CircuitPython.
Host-side tasks
Things you might need to do on your computer when using CircuitPython.
Freshly update all modules to latest version (e.g. when going from CP 6 -> CP 7) (This is needed
because circup update doesn't actually seem to work reliably)
circup freeze > mymodules.txt
rm -rf /Volumes/CIRCUITPY/lib/*
circup install -r mymodules.txt
Note: on limited-memory boards like Trinkey, Trinket, QTPy, you must use the -X option on
MacOS to save space. You may also need to omit unused parts of some libraries (e.g.
adafruit_midi/system_exclusive is not needed if just sending MIDI notes)