• Stars
    star
    534
  • Rank 83,095 (Top 2 %)
  • Language
    Python
  • License
    MIT License
  • Created over 3 years ago
  • Updated 12 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Some CircuitPython tricks, mostly reminders to myself

circuitpython-tricks

A small list of tips & tricks I find myself needing when working with CircuitPython. I find these examples useful when picking up a new project and I just want some boilerplate to get started. Also see the circuitpython-tricks/larger-tricks directory for additional ideas.

An older version of this page is a Learn Guide on Adafruit too!

If you're new to CircuitPython overall, there's no single reference, but:

Table of Contents

But it's probably easiest to do a Cmd-F/Ctrl-F find on keyword of idea you want.

Inputs

Read a 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

Can also do:

import time, board, digitalio
button = digitalio.DigitalInOut(board.D3)
button.switch_to_input(digitalio.Pull.UP)
while True:
    print("button pressed:", button.value == False) # False == pressed
    time.sleep(0.1)

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

Note: While AnalogIn.value is 16-bit (0-65535) corresponding to 0 V to 3.3V, the MCU ADCs can have limitations in resolution and voltage range. This reduces what CircuitPython sees. For example, the ESP32 ADCs are 12-bit w/ approx 0.1 V to 2.5 V range (e.g. value goes from around 200 to 50,000, in steps of 16)

Read a Touch Pin / Capsense

import touchio
import board
touch_pin = touchio.TouchIn(board.GP6)
# on Pico / RP2040, need 1M pull-down on each input
if touch_pin.value:
print("touched!")

Read a Rotary Encoder

import board
import rotaryio
encoder = rotaryio.IncrementalEncoder(board.GP0, board.GP1) # must be consecutive on Pico
print(encoder.position)  # starts at zero, goes neg or pos

Debounce a pin / button

import board
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
button_in = DigitalInOut(board.D3) # defaults to input
button_in.pull = Pull.UP # turn on internal pull-up resistor
button = Debouncer(button_in)
while True:
    button.update()
    if button.fell:
        print("press!")
    if button.rose:
      print("release!")

Set up and debounce a list of pins

import board
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
button_pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)
buttons = []   # will hold list of Debouncer objects
for pin in button_pins:   # set up each pin
    tmp_pin = DigitalInOut(pin) # defaults to input
    tmp_pin.pull = Pull.UP      # turn on internal pull-up resistor
    buttons.append( Debouncer(tmp_pin) )
while True:
    for i in range(len(buttons)):
        buttons[i].update()
        if buttons[i].fell:
            print("button",i,"pressed!")
        if buttons[i].rose:
            print("button",i,"released!")

If your board's CircuitPython has the keypad library (most do), then I recommend using it. It's not just for key matrixes! And it's more efficient and, since it's built-in, reduces a library dependency.

import board
import keypad
button_pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)
buttons = keypad.Keys(button_pins, value_when_pressed=False, pull=True)

while True:
    button = buttons.events.get()  # see if there are any key events
    if button:                      # there are events!
      if button.pressed:
        print("button", button.key_number, "pressed!")
      if button.released:
        print("button", button.key_number, "released!")

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

Can also do:

ledpin = digitalio.DigitalInOut(board.D2)
ledpin.switch_to_output(value=True)

Output Analog value on a DAC pin

Different boards have DAC on different pins

import board
import analogio
dac = analogio.AnalogOut(board.A0)  # on Trinket M0 & QT Py
dac.value = 32768   # mid-point of 0-65535

Output a "Analog" value on a PWM pin

import board
import pwmio
out1 = pwmio.PWMOut(board.MOSI, frequency=25000, duty_cycle=0)
out1.duty_cycle = 32768  # mid-point 0-65535 = 50 % duty-cycle

Control Neopixel / WS2812 LEDs

import neopixel
leds = neopixel.NeoPixel(board.NEOPIXEL, 16, brightness=0.2)
leds[0] = 0xff00ff  # first LED of 16 defined
leds[0] = (255,0,255)  # equivalent
leds.fill( 0x00ff00 )  # set all to green

Control a servo, with animation list

# servo_animation_code.py -- show simple servo animation list
import time, random, board
from pwmio import PWMOut
from adafruit_motor import servo

# 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)

# the animation to play
animation = (
    # (angle, time to stay at that angle)
    (0, 2.0),
    (90, 2.0),
    (120, 2.0),
    (180, 2.0)
)
ani_pos = 0 # where in list to start our animation

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)

led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4)
while True:
    led.fill( colorwheel((time.monotonic()*50)%255) )
    time.sleep(0.05)

Make moving rainbow gradient across LED strip

See demo of it in this tweet.

import time, board, neopixel, rainbowio
num_leds = 16
leds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )
delta_hue = 256//num_leds
speed = 10  # higher numbers = faster rainbow spinning
i=0
while True:
  for l in range(len(leds)):
    leds[l] = rainbowio.colorwheel( int(i*speed + l * delta_hue) % 255  )
  leds.show()  # only write to LEDs after updating them all
  i = (i+1) % 255
  time.sleep(0.05)

A shorter version using a Python list comprehension. The leds[:] trick is a way to assign a new list of colors to all the LEDs at once.

import supervisor, board, neopixel, rainbowio
num_leds = 16
speed = 10  # lower is faster, higher is slower
leds = neopixel.NeoPixel(board.D2, 16, brightness=0.4)
while True:
  t = supervisor.ticks_ms() / speed
  leds[:] = [rainbowio.colorwheel( t + i*(255/len(leds)) ) for i in range(len(leds))]

Fade all LEDs by amount for chase effects

import time
import board, neopixel
num_leds = 16
leds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )
my_color = (55,200,230)
dim_by = 20  # dim amount, higher = shorter tails
pos = 0
while True:
  leds[pos] = my_color
  leds[:] = [[max(i-dim_by,0) for i in l] for l in leds] # dim all by (dim_by,dim_by,dim_by)
  pos = (pos+1) % num_leds  # move to next position
  leds.show()  # only write to LEDs after updating them all
  time.sleep(0.05)

Audio

In CircuitPython, there are three different techniques to output audio:

  • audioio -- uses built-in DAC
  • audiopwmio -- uses PWM like arduino analogWrite(), requires RC filter to convert to analog
  • audiobusio -- output I2S data stream, requires external I2S decoder hardware

CircuitPython's support on particular microcontroller may include support for more than one of the above:

  • e.g. SAMD51 (Feather M4) supports DAC and I2S, RP2040 (Pico) supports PWM and I2S

CircuitPython can play WAV and MP3 files, but they must be formatted correctly.

See Preparing Audio Files for CircuitPython

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 which boards support audiopwmio.

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: 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.

Audio out using DAC

Some CircuitPython boards (SAMD51 "M4" & SAMD21 "M0") have built-in DACs that are supported. The code is the the same as above, with just the import line changing. See the audioio Support Matrix for which boards support audioio.

import time, board
import audiocore, audioio # DAC
wave_file = open("laser2.wav", "rb")
wave = audiocore.WaveFile(wave_file)
audio = audioio.AudioOut(board.A0)  # must be DAC-capable pin, A0 on QTPy Haxpress
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)

Audio out using I2S

Unlike PWM or DAC, most CircuitPython boards support driving an external I2S audio board. This will also give you higher-quality sound output than DAC or PWM. See the audioio Support Matrix for which boards support audiobusio.

# for e.g. Pico RP2040 pins bit_clock & word_select pins must be adjacent
import board, audiobusio, audiocore
audio = audiobusio.I2SOut(bit_clock=board.GP0, word_select=board.GP1, data=board.GP2)
audio.play( audiocore.WaveFile("laser2.wav","rb") )

Play multiple sounds with audiomixer

This example assumes WAVs that are mono 22050 Hz sample rate, w/ signed 16-bit samples.

import time, board, audiocore, audiomixer
from audiopwmio import PWMAudioOut as AudioOut

wav_files = ("loop1.wav", "loop2.wav", "loop3.wav")
wavs = [None] * len(wav_files)  # holds the loaded WAVs

audio = AudioOut(board.GP1)  # RP2040 example
mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
                         bits_per_sample=16, samples_signed=True)
audio.play(mixer)  # attach mixer to audio playback

for i in range(len(wav_files)):
    print("i:",i)
    wavs[i] = audiocore.WaveFile(open(wav_files[i], "rb"))
    mixer.voice[i].play( wavs[i], loop=True) # start each one playing

while True:
    print("doing something else while all loops play")
    time.sleep(1)

Also see the many examples in larger-tricks.

Playing MP3 files

Once you have set up audio output (either directly or via AudioMixer), you can play WAVs or MP3s through it, or play both simultaneously.

For instance, here's an example that uses an I2SOut to a PCM5102 on a Raspberry Pi Pico RP2040 to simultaneously play both a WAV and an MP3:

import board, audiobusio, audiocore, audiomp3
num_voices = 2

i2s_bclk, i2s_wsel, i2s_data = board.GP9, board.GP10, board.GP11 # BCLK, LCLK, DIN on PCM5102

audio = audiobusio.I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
mixer = audiomixer.Mixer(voice_count=num_voices, sample_rate=22050, channel_count=1,
                         bits_per_sample=16, samples_signed=True)
audio.play(mixer) # attach mixer to audio playback

wav_file = "/amen1_22k_s16.wav" # in 'circuitpython-tricks/larger-tricks/breakbeat_wavs'
mp3_file = "/vocalchops476663_22k_128k.mp3" # in 'circuitpython-tricks/larger-tricks/wav'
# https://freesound.org/people/f-r-a-g-i-l-e/sounds/476663/

wave = audiocore.WaveFile(open(wav_file, "rb"))
mp3 = audiomp3.MP3Decoder(open(mp3_file, "rb"))
mixer.voice[0].play( wave )
mixer.voice[1].play( mp3 )

while True:
    pass   # both audio files play

Note: For MP3 files and setting loop=True when playing, there is a small delay when looping. WAV files loop seemlessly

Making simple tones

[tbd]

On ESP32-S2-based boards like FunHouse, you cannot yet play WAV files, but you can make beeps. An example is this gist: https://gist.github.com/todbot/f35bb5ceed013a277688b2ca333244d5

USB

Rename CIRCUITPY drive to something new

For instance, if you have multiple of the same device. The label can be up to 11 characters. This goes in boot.py not code.py and you must powercycle board.

# this goes in boot.py not code.py!
new_name = "TRINKEYPY0"
import storage
storage.remount("/", readonly=False)
m = storage.getmount("/")
m.label = new_name
storage.remount("/", readonly=True)

Detect if USB is connected or not

import supervisor
if supervisor.runtime.usb_connected:
  led.value = True   # USB
else:
  led.value = False  # no USB

An older way that tries to mount CIRCUITPY read-write and if it fails, USB connected:

def is_usb_connected():
    import storage
    try:
        storage.remount('/', readonly=False)  # attempt to mount readwrite
        storage.remount('/', readonly=True)  # attempt to mount readonly
    except RuntimeError as e:
        return True
    return False
is_usb = "USB" if is_usb_connected() else "NO USB"
print("USB:", is_usb)

Get CIRCUITPY disk size and free space

import os
fs_stat = os.statvfs('/')
print("Disk size in MB", fs_stat[0] * fs_stat[2] / 1024 / 1024)
print("Free space in MB", fs_stat[0] * fs_stat[3] / 1024 / 1024)

Programmatically reset to UF2 bootloader

import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.UF2)
microcontroller.reset()

Note: in older CircuitPython use RunMode.BOOTLOADER and for boards with multiple bootloaders (like ESP32-S2):

import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
microcontroller.reset()

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

Read user input from USB Serial, blocking

while True:
    print("Type something: ", end='')
    my_str = input()  # type and press ENTER or RETURN
    print("You entered: ", my_str)

Read user input from USB Serial, non-blocking (mostly)

import time
import supervisor
print("Type something when you're ready")
last_time = time.monotonic()
while True:
    if supervisor.runtime.serial_bytes_available:
        my_str = input()
        print("You entered:", my_str)
    if time.monotonic() - last_time > 1:  # every second, print
        last_time = time.monotonic()
        print(int(last_time),"waiting...")

Read keys from USB Serial

import time, sys, supervisor
print("type charactcers")
while True:
    n = supervisor.runtime.serial_bytes_available
    if n > 0:  # we read something!
        s = sys.stdin.read(n)  # actually read it in
        # print both text & hex version of recv'd chars (see control chars!)
        print("got:", " ".join("{:s} {:02x}".format(c,ord(c)) for c in s))
    time.sleep(0.01) # do something else

Read user input from USB serial, non-blocking

class USBSerialReader:
    """ Read a line from USB Serial (up to end_char), non-blocking, with optional echo """
    def __init__(self):
        self.s = ''
    def read(self,end_char='\n', echo=True):
        import sys, supervisor
        n = supervisor.runtime.serial_bytes_available
        if n > 0:                    # we got bytes!
            s = sys.stdin.read(n)    # actually read it in
            if echo: sys.stdout.write(s)  # echo back to human
            self.s = self.s + s      # keep building the string up
            if s.endswith(end_char): # got our end_char!
                rstr = self.s        # save for return
                self.s = ''          # reset str to beginning
                return rstr
        return None                  # no end_char yet

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

USB MIDI

CircuitPython can be a MIDI controller, or respond to MIDI! Adafruit provides an adafruit_midi class to make things easier, but it's rather complex for how simple MIDI actually is. So for outputting MIDI, you can opt to just do raw bytearrays. For reading MIDI, I recommend Winterbloom's SmolMIDI.

Sending MIDI with adafruit_midi

import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
midi_out_channel = 3 # human version of MIDI out channel (1-16)
midi = adafruit_midi.MIDI( midi_out=usb_midi.ports[1], out_channel=midi_out_channel-1)

def play_note(note,velocity=127):
    midi.send(NoteOn(note, velocity))  # 127 = highest velocity
    time.sleep(0.1)
    midi.send(NoteOff(note, 0))  # 0 = lowest velocity

Sending MIDI with bytearray

Since we're often battling timing issues in CircuitPython and humans can detect musical timing differences very easily, we can gain back some overhead by using usb_midi directly.

This code is equivalent to the above, without adafruit_midi

import usb_midi
midi_out = usb_midi.ports[1]
midi_out_channel = 3
note_on_status = (0x90 | (midi_out_channel-1))
note_off_status = (0x80 | (midi_out_channel-1))

def play_note(note,velocity=127):
    midi_out.write( bytearray([note_on_status, note, velocity]) )
    time.sleep(0.1)
    midi_out.write( bytearray([note_off_status, note, 0]) )

MIDI over Serial UART

Not exactly USB, but it is MIDI! Both adafruit_midi and the bytearray technique works for Serial MIDI too. With a simple MIDI out circuit you can control old hardware synths.

import busio
midi_out_channel = 3 # human version of MIDI out channel (1-16)
note_on_status = (0x90 | (midi_out_channel-1))
note_off_status = (0x80 | (midi_out_channel-1))
midi_uart = busio.UART(tx=board.GP16, rx=board.GP17, baudrate=31250)

def play_note(note,velocity=127):
    midi_uart.write( bytearray([note_on_status, note, velocity]) )
    time.sleep(0.1)
    midi_uart.write( bytearray([note_off_status, note, 0]) )

Receving MIDI USB and MIDI Serial UART together

MIDI is MIDI, so you can use either the midi_uart or the usb_midi.ports[] created above with adafruit_midi. Here's an example receiving MIDI from both USB and Serial on a QTPy RP2040. Note for receiving serial MIDI, you need an appropriate optoisolator input circuit, like this one for QTPys or this one for MacroPad RP2040.

import board, busio
import usb_midi        # built-in library
import adafruit_midi   # install with 'circup install adafruit_midi'
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff

uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=31250, timeout=0.001)
midi_usb = adafruit_midi.MIDI( midi_in=usb_midi.ports[0],  midi_out=usb_midi.ports[1] )
midi_serial = adafruit_midi.MIDI( midi_in=uart, midi_out=uart )

while True:
    msg = midi_usb.receive()
    if msg is not None:
        if isinstance(msg, NoteOn):
            print("usb noteOn:",msg.note, msg.velocity)
        elif isinstance(msg, NoteOff):
            print("usb noteOff:",msg.note, msg.velocity)
    msg = midi_serial.receive()
    if msg is not None:
        if isinstance(msg, NoteOn):
            print("serial noteOn:",msg.note, msg.velocity)
        elif isinstance(msg, NoteOff):
            print("serial noteOff:",msg.note, msg.velocity)

Enable USB MIDI in boot.py (for ESP32S2 and STM32F4)

Some CircuitPython devices like ESP32-S2 based ones, do not have enough USB endpoints to enable all USB functions, so USB MIDI is disabled by default. To enable it, the easiest is to disable USB HID (keyboard/mouse) support. This must be done in boot.py and the the board power cycled.

# boot.py
import usb_hid
import usb_midi
usb_hid.disable()
usb_midi.enable()
print("enabled USB MIDI, disabled USB HID")

WiFi / Networking

Scan for WiFi Networks, sorted by signal strength

Note: this is for boards with native WiFi (ESP32)

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)

Ping an IP address

Note: this is for boards with native WiFi (ESP32)

import time
import wifi
import ipaddress
from secrets import secrets
ip_to_ping = "1.1.1.1"

wifi.radio.connect(ssid=secrets['ssid'],password=secrets['password'])

print("my IP addr:", wifi.radio.ipv4_address)
print("pinging ",ip_to_ping)
ip1 = ipaddress.ip_address(ip_to_ping)
while True:
    print("ping:", wifi.radio.ping(ip1))
    time.sleep(1)

Get IP address of remote host

import wifi, socketpool
from secrets import secrets
wifi.radio.connect(ssid=secrets['ssid'],password=secrets['password'])
print("my IP addr:", wifi.radio.ipv4_address)

hostname = "todbot.com"

pool = socketpool.SocketPool(wifi.radio)
addrinfo = pool.getaddrinfo(host=hostname, port=443) # port is required
print("addrinfo", addrinfo)

ipaddress = addrinfo[0][4][0]

print(f"'{hostname}' ip address is '{ipaddress}'")

Fetch a JSON file

Note: this is for boards with native WiFi (ESP32)

import time
import wifi
import socketpool
import ssl
import adafruit_requests
from secrets import secrets
wifi.radio.connect(ssid=secrets['ssid'],password=secrets['password'])
print("my IP addr:", wifi.radio.ipv4_address)
pool = socketpool.SocketPool(wifi.radio)
session = adafruit_requests.Session(pool, ssl.create_default_context())
while True:
    response = session.get("https://todbot.com/tst/randcolor.php")
    data = response.json()
    print("data:",data)
    time.sleep(5)

Serve a webpage via HTTP

Note: this is for boards with native WiFi (ESP32)

The adafruit_httpserver library makes this pretty easy, and has good examples. You can tell it to either server.serve_forver() and do all your computation in your @server.route() functions, or use server.poll() inside a while-loop. There is also the Ampule library.

import time, wifi, socketpool
from adafruit_httpserver.server import HTTPServer
from adafruit_httpserver.response import HTTPResponse
from secrets import secrets  # contains your WiFi credentials

my_port = 1234  # set this to your liking
wifi.radio.connect(secrets["ssid"], secrets["password"]) # connect to your wifi
server = HTTPServer(socketpool.SocketPool(wifi.radio))

@server.route("/")  # magic that attaches this function to "server" object
def base(request):
    my_str = f"<html><body><h1> Hello! Current time.monotonic is {time.monotonic()}</h1></body></html>"
    return HTTPResponse(body=my_str, content_type="text/html")
    # or for static content: return HTTPResponse(filename="/index.html")

print(f"Listening on http://{wifi.radio.ipv4_address}:{my_port}")
server.serve_forever(str(wifi.radio.ipv4_address), port=my_port) # never returns

Set RTC time from NTP

Note: this is for boards with native WiFi (ESP32)

Note: You need to set my_tz_offset to match your region

# copied from:
# https://docs.circuitpython.org/projects/ntp/en/latest/examples.html
import time, rtc
import socketpool, wifi
import adafruit_ntp

from secrets import secrets

my_tz_offset = -7 # PDT

wifi.radio.connect(secrets["ssid"], secrets["password"])
print("Connected, getting NTP time")
pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=my_tz_offset)

rtc.RTC().datetime = ntp.datetime

while True:
    print("current datetime:", time.localtime())
    time.sleep(5)

Set RTC time from time service

Note: this is for boards with native WiFi (ESP32)

This uses the awesome and free WorldTimeAPI.org site, and this example will fetch the current local time (including timezone and UTC offset) based on the geolocated IP address of your device.

import time, rtc
import wifi, ssl, socketpool
import adafruit_requests
from secrets import secrets

wifi.radio.connect(secrets["ssid"], secrets["password"])
print("Connected, getting WorldTimeAPI time")
pool = socketpool.SocketPool(wifi.radio)
request = adafruit_requests.Session(pool, ssl.create_default_context())

print("Getting current time:")
response = request.get("http://worldtimeapi.org/api/ip")
time_data = response.json()
unixtime = int(time_data['unixtime']) + int(time_data['raw_offset'])

print("URL time: ", response.headers['date'])

rtc.RTC().datetime = time.localtime( unixtime ) # create time struct and set RTC with it

while True:
  print("current datetime: ", time.localtime()) # time.* now reflects current local time
  time.sleep(5)

What the heck is secrets.py?

It's a config file that lives next to your code.py and is used (invisibly) by many Adafruit WiFi libraries. You can use it too (as in the examples above) without those libraries

It looks like this for basic WiFi connectivity:

# secrets.py
secrets = {
  "ssid": "Pretty Fly for a WiFi",
  "password": "donthackme123"
}
# code.py
from secrets import secrets
print("your WiFi password is:", secrets['password'])

Displays (LCD / OLED / E-Ink) and displayio

displayio is the native system-level driver for displays in CircuitPython. Several CircuitPython boards (FunHouse, MagTag, PyGamer, CLUE) have displayio-based displays and a built-in board.DISPLAY object that is preconfigured for that display. Or, you can add your own I2C or SPI display.

Get default display and change display rotation

Boards like FunHouse, MagTag, PyGamer, CLUE have built-in displays. display.rotation works with all displays, not just built-in ones.

import board
display = board.DISPLAY
print(display.rotation) # print current rotation
display.rotation = 0    # valid values 0,90,180,270

Display an image

Using displayio.OnDiskBitmap

CircuitPython has a built-in BMP parser called displayio.OnDiskBitmap: The images should be in non-compressed, paletized BMP3 format.

import board, displayio
display = board.DISPLAY

maingroup = displayio.Group() # everything goes in maingroup
display.show(maingroup) # show main group (clears the screen)

bitmap = displayio.OnDiskBitmap(open("my_image.bmp", "rb"))
image = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
maingroup.append(image) # shows the image

Using adafruit_imageload

You can also use the adafruit_imageload library that supports slightly more kinds of BMP files The images should be in paletized BMP3 format.

import board, displayio
import adafruit_imageload
display = board.DISPLAY
maingroup = displayio.Group() # everything goes in maingroup
display.show(maingroup) # show main group (clears the screen)
bitmap, palette = adafruit_imageload.load("my_image.bmp")
image = displayio.TileGrid(img, pixel_shader=palette))
maingroup.append(image) # shows the image

How displayio is structured

CircuitPython's displayio library works like:

  • an image Bitmap (and its Palette) goes inside a TileGrid
  • a TileGrid goes inside a Group
  • a Group is shown on a Display.

Display background bitmap

Useful for display a solid background color that can be quickly changed.

import time, board, displayio
display = board.DISPLAY       # get default display (FunHouse,Pygamer,etc)
maingroup = displayio.Group() # Create a main group to hold everything
display.show(maingroup)       # put it on the display

# make bitmap that spans entire display, with 3 colors
background = displayio.Bitmap(display.width, display.height, 3)

# make a 3 color palette to match
mypal = displayio.Palette(3)
mypal[0] = 0x000000 # set colors (black)
mypal[1] = 0x999900 # dark yellow
mypal[2] = 0x009999 # dark cyan

# Put background into main group, using palette to map palette ids to colors
maingroup.append(displayio.TileGrid(background, pixel_shader=mypal))

time.sleep(2)
background.fill(2)  # change background to dark cyan (mypal[2])
time.sleep(2)
background.fill(1)  # change background to dark yellow (mypal[1])

Another way is to use vectorio:

import board, displayio, vectorio

display = board.DISPLAY  # built-in display
maingroup = displayio.Group()  # a main group that holds everything
display.show(maingroup)

mypal = displayio.Palette(1)
mypal[0] = 0x999900
background = vectorio.Rectangle(pixel_shader=mypal, width=display.width, height=display.height, x=0, y=0)
maingroup.append(background)

Or can also use adafruit_display_shapes:

import board, displayio
from adafruit_display_shapes.rect import Rect

display = board.DISPLAY
maingroup = displayio.Group()  # a main group that holds everything
display.show(maingroup)        # add main group to display

background = Rect(0,0, display.width, display.height, fill=0x000000 ) # background color
maingroup.append(background)

Image slideshow

import time, board, displayio
import adafruit_imageload

display = board.DISPLAY # get display object (built-in on some boards)
screen = displayio.Group() # main group that holds all on-screen content
display.show(screen)       # add it to display

file_names = [ '/images/cat1.bmp', '/images/cat2.bmp' ]  # list of filenames

screen.append(displayio.Group())  # placeholder, will be replaced w/ screen[0] below
while True:
    for fname in file_names:
        image, palette = adafruit_imageload.load(fname)
        screen[0] = displayio.TileGrid(image, pixel_shader=palette)
        time.sleep(1)

Note: Images must be in palettized BMP3 format. For more details, see Preparing images for CircuitPython

Dealing with E-Ink "Refresh Too Soon" error

E-Ink displays are damaged if refreshed too frequently. CircuitPython enforces this, but also provides display.time_to_refresh, the number of seconds you need to wait before the display can be refreshed. One solution is to sleep a little longer than that and you'll never get the error. Another would be to wait for time_to_refresh to go to zero, as show below.

import time, board, displayio, terminalio
from adafruit_display_text import label
mylabel = label.Label(terminalio.FONT, text="demo", x=20,y=20,
                      background_color=0x000000, color=0xffffff )
display = board.DISPLAY  # e.g. for MagTag
display.show(mylabel)
while True:
    if display.time_to_refresh == 0:
        display.refresh()
    mylabel.text = str(time.monotonic())
    time.sleep(0.1)

I2C

Scan I2C bus for devices

from: CircuitPython I2C Guide: Find Your Sensor

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()

One liner to copy-n-paste into REPL for quicky I2C scan:

import board; i2c=board.I2C(); i2c.try_lock(); [hex(a) for a in i2c.scan()]; i2c.unlock()

Speed up I2C bus

CircuitPython defaults to 100 kHz I2C bus speed. This will work for all devices, but some devices can go faster. Common faster speeds are 200 kHz and 400 kHz.

import board
import busio
# instead of doing
# i2c = board.I2C()
i2c = busio.I2C( board.SCL, board.SDA, frequency=200_000)
# then do something with 'i2c' object as before, like:
oled = adafruit_ssd1306.SSD1306_I2C(width=128, height=32, i2c=i2c)

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() )

Show microcontroller.pin to board mappings

from https://gist.github.com/anecdata/1c345cb2d137776d76b97a5d5678dc97

import microcontroller
import board

for pin in dir(microcontroller.pin):
    if isinstance(getattr(microcontroller.pin, pin), microcontroller.Pin):
        print("".join(("microcontroller.pin.", pin, "\t")), end=" ")
        for alias in dir(board):
            if getattr(board, alias) is getattr(microcontroller.pin, pin):
                print("".join(("", "board.", alias)), end=" ")
    print()

Determine which board you're on

import os
print(os.uname().machine)
'Adafruit ItsyBitsy M4 Express with samd51g19'

To get the chip family

import os
print(os.uname().sysname)
'ESP32S2'

Support multiple boards with one code.py

import os
board_type = os.uname().machine
if 'QT Py M0' in board_type:
  tft_clk  = board.SCK
  tft_mosi = board.MOSI
  spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
elif 'ItsyBitsy M4' in board_type:
  tft_clk  = board.SCK
  tft_mosi = board.MOSI
  spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
elif 'Pico' in board_type:
  tft_clk = board.GP10 # must be a SPI CLK
  tft_mosi= board.GP11 # must be a SPI TX
  spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
else:
  print("supported board", board_type)

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'

Formatting strings with f-strings

(doesn't work on 'small' CircuitPythons like QTPy M0)

name = "John"
fav_color = 0x003366
body_temp = 98.65
print(f"name:{name} color:{color:06x} temp:{body_temp:2.1f} num:{fav_number:%d}")
# 'name:John color:ff3366 temp:98.6 num:123'

Using regular expressions to "findall" strings

Regular expressions are a really powerful way to match information in and parse data from strings. While CircuitPython has a version of the re regex module you may know from desktop Python, it is very limited. Specifcally it doesn't have the very useful re.findall() function. Below is a semi-replacement for findall().

import re
def find_all(regex, some_str):
    matches = []
    while m := regex.search(some_str):
        matches.append( m.groups() )
        some_str = some_str[ m.end(): ] # get past match
    return matches

my_str = "<thing>thing1 I want</thing> <thing>thing2 I want</thing>  <thing>thing3 I want</thing>"
regex1 = re.compile('<thing.*?>(.*?)<\/thing>')
my_matches = find_all( regex1, my_str )
print("matches:", my_matches)

Make and use a config file

# my_config.py
config = {
    "username": "Grogu Djarin",
    "password": "ig88rules",
    "secret_key": "3a3d9bfaf05835df69713c470427fe35"
}

# code.py
from my_config import config
print("secret:", config['secret_key'])
# 'secret: 3a3d9bfaf05835df69713c470427fe35'

Run different code.py on startup

Use microcontroller.nvm to store persistent state across resets or between boot.py and code.py, and declare that the first byte of nvm will be the startup_mode. Now if you create multiple code.py files (say) code1.py, code2.py, etc. you can switch between them based on startup_mode.

import time
import microcontroller
startup_mode = microcontroller.nvm[0]
if startup_mode == 1:
    import code1      # runs code in `code1.py`
if startup_mode == 2:
    import code2      # runs code in `code2.py`
# otherwise runs 'code.py`
while True:
    print("main code.py")
    time.sleep(1)

Note: in CircuitPyton 7+ you can use supervisor.set_next_code_file() to change which .py file is run on startup. This changes only what happens on reload, not hardware reset or powerup. Using it would look like:

import supervisor
supervisor.set_next_code_file('code_awesome.py')
# and then if you want to run it now, trigger a reload
supervisor.reload()

Coding Techniques

Map an input range to an output range

# simple range mapper, like Arduino map()
def map_range(s, a1, a2, b1, b2):
    return  b1 + ((s - a1) * (b2 - b1) / (a2 - a1))

# example: map 0-0123 value to 0.0-1.0 value
val = 768
outval = map_range( val, 0,1023, 0.0,1.0 )
# outval = 0.75

Constrain an input to a min/max

The Python built-in min() and max() functions can be used together to make something like Arduino's constrain() to clamp an input between two values.

# constrain a value to be 0-255
outval = min(max(val, 0), 255)
# constrain a value to be 0-255 integer
outval = int(min(max(val, 0), 255))
# constrain a value to be -1 to +1
outval = min(max(val, -1), 1)

Turn a momentary value into a toggle

import touchio
import board

touch_pin = touchio.TouchIn(board.GP6)
last_touch_val = False  # holds last measurement
toggle_value = False  # holds state of toggle switch

while True:
  touch_val = touch_pin.value
  if touch_val != last_touch_val:
    if touch_val:
      toggle_value = not toggle_value   # flip toggle
      print("toggle!", toggle_value)
  last_touch_val = touch_val

Do something every N seconds without sleep()

Also known as "blink-without-delay"

import time
last_time1 = time.monotonic()  # holds when we did something #1
last_time2 = time.monotonic()  # holds when we did something #2
while True:
  if time.monotonic() - last_time1 > 2.0:  # every 2 seconds
    last_time1 = time.monotonic() # save when we do the thing
    print("hello!")  # do thing #1
  if time.monotonic() - last_time2 > 5.0:  # every 5 seconds
    last_time2 = time.monotonic() # save when we do the thing
    print("world!")  # do thing #2

System error handling

Preventing Ctrl-C from stopping the program

Put a try/except KeyboardInterrupt to catch the Ctrl-C on the inside of your main loop.

while True:
  try:
    print("Doing something important...")
    time.sleep(0.1)
  except KeyboardInterrupt:
    print("Nice try, human! Not quitting.")

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

Prevent auto-reload when CIRCUITPY is touched

Normally, CircuitPython restarts anytime the CIRCUITPY drive is written to. This is great normally, but is frustrating if you want your code to keep running, and you want to control exactly when a restart happens.

import supervisor
supervisor.runtime.autoreload = False  # CirPy 8 and above
#supervisor.disable_autoreload()  # CirPy 7 and below

To trigger a reload, do a Ctrl-C + Ctrl-D in the REPL or reset your board.

Raspberry Pi Pico boot.py Protection

Also works on other RP2040-based boards like QTPy RP2040. From https://gist.github.com/Neradoc/8056725be1c209475fd09ffc37c9fad4

# Copy this as 'boot.py' in your Pico's CIRCUITPY drive
# Useful in case Pico locks up (which it's done a few times on me)
import board
import time
from digitalio import DigitalInOut,Pull

led = DigitalInOut(board.LED)
led.switch_to_output()

safe = DigitalInOut(board.GP14)  # <-- choose your button pin
safe.switch_to_input(Pull.UP)

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

Use REPL fast with copy-paste multi-one-liners

(yes, semicolons are legal in Python)

# load common libraries (for later REPL experiments)
import time, board, analogio, touchio; from digitalio import DigitalInOut,Pull

# create a pin and set a pin LOW (if you've done the above)
pin = DigitalInOut(board.GP0); pin.switch_to_output(value=False)

# print out board pins and objects (like `I2C`, `STEMMA_I2C`, `DISPLAY`, if present)
import board; dir(board)

# print out microcontroller pins (chip pins, not the same as board pins)
import microcontroller; dir(microcontroller.pin)

# release configured / built-in display
import displayio; displayio.release_displays()

# turn off auto-reload when CIRCUITPY drive is touched
import supervisor; supervisor.runtime.autoreload = False

# test neopixel strip, make them all purple
import board, neopixel; leds = neopixel.NeoPixel(board.GP3, 8, brightness=0.2); leds.fill(0xff00ff)
leds.deinit()  # releases pin

# scan I2C bus
import board; i2c=board.I2C(); i2c.try_lock(); [hex(a) for a in i2c.scan()]; i2c.unlock()

Turn off built-in display to speed up REPL printing

By default CircuitPython will echo the REPL to the display of those boards with built-in displays. This can slow down the REPL. So one way to speed the REPL up is to hide the displayio.Group that contains all the REPL output. (From user @argonblue in the CircuitPython Discord chat)

import board
display = board.DISPLAY
display.root_group.hidden = True
# and to turn it back on
display.root_group.hidden = False

You can also turn back on the REPL after using the display for your own graphics with:

display.show(None)

Python tricks

These are general Python tips that may be useful in CircuitPython.

Create list with elements all the same value

blank_array = [0] * 50   # creats 50-element list of zeros

Convert RGB tuples to int and back again

Thanks to @Neradoc for this tip:

rgb_tuple = (255, 0, 128)
rgb_int = int.from_bytes(rgb_tuple, byteorder='big')

rgb_int = 0xFF0080
rgb_tuple2 = tuple((rgb_int).to_bytes(3,"big"))

rgb_tuple2 == rgb_tuple

Storing multiple values per list entry

Create simple data structures as config to control your program. Unlike Arduino, you can store multiple values per list/array entry.

mycolors = (
    # color val, name
    (0x0000FF, "blue"),
    (0x00FFFF, "cyan"),
    (0xFF00FF, "purple"),
)
for i in range(len(mycolors)):
    (val, name) = mycolors[i]
    print("my color ", name, "has the value", val)

Python info

How to get information about Python inside of CircuitPython.

Display which (not built-in) libraries have been imported

import sys
print(sys.modules.keys())
# 'dict_keys([])'
import board
import neopixel
import adafruit_dotstar
print(sys.modules.keys())
prints "dict_keys(['neopixel', 'adafruit_dotstar'])"

List names of all global variables

a = 123
b = 'hello there'
my_globals = sorted(dir)
print(my_globals)
# prints "['__name__', 'a', 'b']"
if 'a' in my_globals:
  print("you have a variable named 'a'!")
if 'c' in my_globals:
  print("you have a variable named 'c'!")

Display the running CircuitPython release

With an established serial connection, press Ctrl+c:

Adafruit CircuitPython 7.1.1 on 2022-01-14; S2Pico with ESP32S2-S2FN4R2
>>>

Without connection or code running, check the boot_out.txt file in your CIRCUITPY drive.

import os
print(os.uname().release)
'7.1.1'
print(os.uname().version)
'7.1.1 on 2022-01-14'

Host-side tasks

Things you might need to do on your computer when using CircuitPython.

Installing CircuitPython libraries

The below examples are for MacOS / Linux. Similar commands are used for Windows

Installing libraries with circup

circup can be used to easily install and update modules

$ pip3 install --user circup
$ circup install adafruit_midi
$ circup update   # updates all modules

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

And updating circup when a new version of CircuitPython comes out:

$ pip3 install --upgrade circup

Copying libraries by hand with cp

To install libraries by hand from the CircuitPython Library Bundle or from the CircuitPython Community Bundle (which circup doesn't support), get the bundle, unzip it and then use cp -rX.

cp -rX bundle_folder/lib/adafruit_midi /Volumes/CIRCUITPY/lib

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)

Preparing images for CircuitPython

CircuitPython requires "indexed" (aka "palette") BMP3 images. If using adafruit_imageload the images can have RLE compression, but if using displayio.OnDiskBitmap(), then make sure no compression is used.

To make images load faster, you can reduce the number of colors in the image. The maximum number of colors is 256, but try reducing colors to 64 or even 2 if it's a black-n-white image.

Some existing Learn Guides:

And here's some ways to do the conversions.

Online

Most online image to BMP converters do not create BMPs in the proper format: BMP3, non-compressed, up to 256 colors in an 8-bit palette.

However @Neradoc pointed out that https://online-converting.com/image/convert2bmp/ will work when set to "Color:" mode is set to "8 (Indexed)". Some initial tests show this works! But since I don't entirely trust this, so I'd recommend also trying out one of the following techniques too.

Command-line: using ImageMagick

ImageMagick is a command-line image manipulation tool. With it, you can convert any image format to BMP3 format. The main ImageMagick CLI command is convert.

convert myimage.jpg -resize 240x240 -colors 64 -type palette -compress None BMP3:myimage_for_cirpy.bmp

Command-line: using GraphicsMagick

GraphicsMagick is a slimmer, lower-requirement clone of ImageMagick. All GraphicsMagick commands are accessed via the gm CLI command.

gm convert myimage.jpg -resize 240x240 -colors 64 -type palette -compress None BMP3:myimage_for_cirpy.bmp

Making images smaller or for E-Ink displays

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

NodeJs: using gm

There is a nice wrapper around GraphicsMagick / Imagemagick with the gm library. A small NodeJs program to convert images could look like this:

var gm = require('gm');
gm('myimage.jpg')
    .resize(240, 240)
    .colors(64)
    .type("palette")
    .compress("None")
    .write('BMP3:myimage_for_cirpy.bmp', function (err) {
        if (!err) console.log('done1');
    });

Python: using PIL / pillow

The Python Image Library (PIL) fork pillow seems to work the best. It's unclear how to toggle compression.

from PIL import Image
size = (240, 240)
num_colors = 64
img = Image.open('myimage.jpg')
img = img.resize(size)
newimg = img.convert(mode='P', colors=num_colors)
newimg.save('myimage_for_cirpy.bmp')

Preparing audio files for CircuitPython

CircuitPython can play WAV files great, but it prefers the WAV files to be in a specific format. I've found the best trade-off in quality / flash-space / compatibility to be:

  • PCM 16-bit signed PCM
  • Mono (but stereo will work if using I2S or SAMD51)
  • 22050 Hz sample rate

And remember that these settings must match how you're setting up the audiomixer object. So for the above settings, you'd create an audiomixer.Mixer like:

mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
                         bits_per_sample=16, samples_signed=True)

To convert WAVs for CircuitPython, I like to use Audacity or the sox command-line tool. Sox can convert just about any audio to the correct WAV format:

sox loop.mp3 -b 16 -c 1 -r 22050 loop.wav

To get sox on various platforms:

Some audio Learn Guide links:

About this guide

  • Many of the code snippets are not considered "well-formatted" by some Python linters. This guide favors brevity over strict style adherence.

  • The precursor to this guide is QTPy Tricks, which has similar but different (and still valid) fun things to do in CircuitPython.

  • This guide is the result of trying to learn Python via CircuitPython and from very enlightening discussions with John Edgar Park. I have a good background in other languages, but mostly C/C++, and have taught classes in Arduino for several years. This informs how I've been thinking about things in this guide.

More Repositories

1

blink1

Official software for blink(1) USB RGB LED by ThingM
C#
944
star
2

arduino-serial

Example C and Java host code to talking to an arduino or other "serial" device
C
232
star
3

hidapitester

Simple command-line program to test HIDAPI
C
203
star
4

picostepseq

MIDI sequencer using Raspberry Pi Pico in Arduino & CircuitPython
C
191
star
5

Blink1Control2

Blink1Control GUI to control blink(1) USB RGB LED devices.
JavaScript
151
star
6

usbSearch

Search for USB devices by VID/PID, gets device serial numbers
C
102
star
7

blink1-tool

Command-line tools and C library for blink(1) USB RGB LED
C
80
star
8

mozzi_experiments

Experiments with Mozzi, mostly on SAMD21 & RP2040 chips
C++
79
star
9

wiichuck_adapter

Very simple adpater for hooking a Wii Nunchuck to your Arduino or other microcontroller
Objective-C
72
star
10

SoftI2CMaster

Software I2C / TWI library for Arduino allows any two pins to be SDA & SCL
C++
71
star
11

qtpy-knob

QT Py Media Knob using rotary encoder & neopixel ring
Python
70
star
12

ServoEaser

Arduino library for servo easing
C++
67
star
13

picotouch

Tiny capsense touch MIDI keyboard controller from a Raspberry Pi Pico
Python
67
star
14

hidpytoy

A GUI app for playing with HID devices, written in Python
Python
64
star
15

arduino-i2c-scanner

Simple Arduino I2C scanner as described at http://todbot.com/blog/2009/11/29/i2cscanner-pde-arduino-as-i2c-bus-scanner/
Arduino
64
star
16

CircuitPython_GC9A01_demos

Demos showing how to use CircuitPython displayio driver for GC9A01 round LCDs
Python
50
star
17

qtpy-tricks

Some tips and tricks for CircuitPython, using a QT Py board
41
star
18

win-hid-dump

Sort of a Windows version of `usbhid-dump` to show HID Report Descriptors
C#
40
star
19

mac-hid-dump

Sort of a MacOS version of `usbhid-dump` to show HID Report Descriptors
C
40
star
20

circuitpython-synthio-tricks

tips, tricks, and examples of using CircuitPython synthio
37
star
21

blink1-python

Official Python library for blink(1) USB RGB LED notification device
Python
35
star
22

pico8enc

Lotsa Rotary Encoders on a Raspberry Pi Pico
Python
33
star
23

PicoDVI_experiments

Experiments using PicoDVI (mostly on Adafruit DVI RP2040 Feather)
C++
31
star
24

NeoJoints

Neopixel Joints, millable on the Othermill
Eagle
27
star
25

macropadsynthplug

Abuse StemmaQT port by turning it into Audio Out + MIDI In
Python
25
star
26

picotouch_synth

Thin captouch-based synth platform for Raspberry Pi Pico
Python
24
star
27

MIDIPedalBox

USB MIDI Pedal, powered by CircuitPython + Trinket M0
Python
24
star
28

samd21-programming-notes

SAMD21 programming notes
23
star
29

electron-hid-toy

Simple example of using node-hid in Electron (w/ React)
JavaScript
19
star
30

crashspace-bigbutton

Eagle
18
star
31

MozziScout

Arduino sketches for Oskitone Scout using Mozzi synthesis library
C
17
star
32

plinkykeeb

Simple MIDI controller using KB2040 and computer key switches
Python
16
star
33

circuitpython_staroids

Something like Asteroids but not really, done in CircuitPython
Python
15
star
34

circuitpython_led_effects

Some fun with Neopixel / WS2812 LEDs in CircuitPython
Python
15
star
35

qtpy_synth

Hardware & software for a tiny QTPy-driven synth
Python
13
star
36

LinkM

BlinkM controller / programmer and general USB-to-I2C adapter
C
12
star
37

node-blink1-server

HTTP API server in Node for blink(1) devices
JavaScript
12
star
38

BlinkM-Examples

BlinkM example code for Arduino, Processing, et al
C
11
star
39

hidraw-dump

Use Linux HIDRAW to print info about HID devices (USB & Bluetooth/BLE)
C
9
star
40

blink1mk3

Official hardware design files for blink(1) mk3 USB RGB LED by ThingM
C
8
star
41

circuitpython_screensaver

Do you need a screensaver for CircuitPython? Of course you do
Python
8
star
42

Mill-a-Week

weekly experiments with othermachine Othermill
Eagle
8
star
43

eurorack_hagiwo_stuff

C++
8
star
44

blink1-android

Android library for blink(1) USB RGB LED
Java
7
star
45

ArduinoOnBeagleBone

Arduino and AVR compilation tools for BeagleBone (currently command-line only)
7
star
46

CircuitPython_PS2Controller

CircuitPython library to read Sony PS2 and PS1 ("PSX") game controllers
Python
7
star
47

blink1-java

blink(1) Java / Processing library
Processing
6
star
48

magtag_dayofweek

Use MagTag to display day of week on your fridge
Python
6
star
49

picostepsynth

Python
6
star
50

NeopixelTester

some testing hardware for Neopixels (WS2812, SK6812)
C++
6
star
51

CircuitPython_MicroOSC

Minimal OSC parser, server, and client for CircuitPython and CPython
Python
5
star
52

node-hid-ds4-test

Testing Playstation DualShock4 controller with node-hid
JavaScript
5
star
53

tal_experiments

Experiments with the Teensy Audio Library
C++
5
star
54

tomu-hardware-eagle

Eagle translation of im-tomu/tomu-hardware
5
star
55

ArduinoCore-EFM32

Arduino core for EFM32HG and Tomu boards (very much work in progress)
C
5
star
56

HackadayVectorscopeHacks

Hacks (hopefully) for the Hackaday Superconference 2023 badge
Python
5
star
57

touchwheels

some touchwheels
Python
5
star
58

CircuitPython_Noise

Simplex noise (like Perlin) for CircuitPython
Python
4
star
59

blink1-webhid

JavaScript
4
star
60

blink1-python-old

Python libraries for blink(1) USB LED
Python
4
star
61

node-ifttt-demoserver

Example of how to implement an IFTTT Channel w/ OAuth2
JavaScript
4
star
62

blinkm2

C++
3
star
63

NoiseShield

A simple audio output shield for Arduino
C
3
star
64

electron-hid-test

extremely simple demo of node-hid with electron
JavaScript
3
star
65

StripGrid

Grid controller for LED strips
C
3
star
66

circuitpython_ledtools

hacks
Python
3
star
67

electron-hid-test-erb

electron-hid-test using electron-react-boilerplate
TypeScript
3
star
68

qtpy-keys

Tiny USB keyboard using QTPy and CircuitPython
Python
3
star
69

MIDIHost2Host

Connect two USB-MIDI host devices together, using two Trinket M0s
C++
3
star
70

GPSWiiRecorder

The full set of source for the Make magazine Wii nunchuck roller coaster data logger
C++
3
star
71

node-arduino-serial

NodeJs version of arduino-serial commandline tool
JavaScript
2
star
72

circuitpython_invaders

Python
2
star
73

CloudFridge

C++
2
star
74

TeamPneumo

Development for CashMachine
C++
2
star
75

eagle-tricks

Various bits n bobs to do with Eagle Schematic & PCB software
2
star
76

samd11toy

playing with SAMD11
Eagle
2
star
77

SDLbeerlight

an overly complicated light announcing a certain time of day or state of mind
Eagle
2
star
78

ILOVELAMP

Sketches and Ideas for I LOVE LAMP project
Eagle
2
star
79

seeknobs

knob board for seesaw, and also a drone synth
C++
2
star
80

TrinketTrigger

The simplest Eurorack module?
Python
2
star
81

QTPyNoisyBoi

Python
2
star
82

cirpycp

Copy files from CircuitPython Bundle to your device, based on a list of requirements
Shell
2
star
83

pico_test_synth

Simple board to help make synths with Raspberry Pi Pico and PCM5102 I2S DAC
Python
2
star
84

WS2812_Arduino_Lib_Compare

Comparison of different Arduino libraries for WS2812 / NeoPixel LEDs
Arduino
1
star
85

ReflashBlinkM

Update or replace firmware in a BlinkM
Processing
1
star
86

WingShield

WingShield Industries makes Arduino shield kits, like the ScrewShield
1
star
87

node-tinynative

Smallest possible Node.js native module
C++
1
star
88

Blink1DotNet

C# library blink(1) .NET library for blink(1) devices connected to computer
C#
1
star
89

qtpy_midibff

QTPy MIDI BFF
1
star
90

qt-fun

qt-fun
C++
1
star
91

TrinketTouringMachine

Eurorack modules based around Trinket M0
C++
1
star
92

PWM-Tests

1
star
93

BlinkMNet

A network of Arduino-connected BlinkMs
C++
1
star
94

electron-tray-test

Testing out Tray API in Electron, showing how click event does not work on MacOSX
JavaScript
1
star
95

mp3thing

mp3 player controller for cheap commercial screenless mp3 players
Arduino
1
star
96

CrashSpaceStatus

The "AtCrashSpace" app written using Apache Cordova
JavaScript
1
star
97

todbot.github.com

1
star
98

BlinkM-Arduino

Official BlinkM Arduino library and examples by ThingM
C++
1
star
99

CircuitPython_TouchPIO

Capacitive touch sensing using Pico / RP2040 PIO, using touchio API
Python
1
star