circpy-rpipico
Monday 21 July 2025 7 versions

CircuitPython with Raspberry-Pi-Pico and NeoPixel Matrices

https://agmc25-ukw.dortoka.ipv64.de/

Why do we use these parts?

Micro-Controllers

Raspberry-Pi-Pico:

  • boards are cheap and processors are fast
  • supported by many languages (C, Arduino, µPython, Rust, …)
  • Picos have a large eco system:
    • good documentation
    • used by many hobbyist
Model Processor RAM Flash Cores Clock speed WLAN
Pico RP2040 264KB 2MB 2x ARM Cortex-M0+ 133MHz no
Pico-W RP2040 264KB 2MB 2x ARM Cortex-M0+ 133MHz yes
Pico2 RP2350 520KB 4MB 2x ARM Cortex-M33 150MHz no
Pico2-W RP2350 520KB 4MB 2x ARM Cortex-M33 150MHz yes

CircuitPython vs. MicroPython

MicroPython (MPy) and CircuitPython (CPy) are closely related.
The Adafruit company is sponsoring CPy, which is a derivative of MPy.

  • The CPy interpreter uses only a small part of the on-board Flash memory
    • the other part is used as a FAT filesystem, which is
    • visible as an USB flash drive, named CIRCUITPY
  • CPy has a very large eco system of libraries (with documentation and examples)
  • Interpreter:
    • “fast enough”: usually no need for compiled programs
    • fast turn-around-times during development
    • CPy is “FUN” to use

16x16 NeoPixel-Matrix

“NeoPixel” is a term created by Adafruit for “smart” RGB-LEDs.
They are usually based on WS2812B chips.
See the “NeoPixel ÜberGuide” for a detailed explanation.

  • all LEDs are pre-soldered to a flexible PCB: size is only 16cm by 16cm
  • all 256 “NeoPixels” are chained together (in a zigzag style)
  • NeoPixels can be driven with only 3 pins: +5V, GND and Data
  • NeoMatrices have an “affordable” price tag

All resources can be be downloaded from the assets directory, as well.

Optional Enhancements

  • IKEA picture frames
  • Hornbach
    • Diffusors made from “Grey Acrylic Glas”

How do you start?

Select a micro-controller Board

  • without WiFi and Bluetooth
    • RPi-Pico, RPi-Pico2
    • VCC-GND YD-RP2040
    • several boards by WaveShare
  • with WiFi and BLE
    • RPi-Pico-W, RPi-Pico2-W
    • several boards by Pimoroni

Download the matching Firmware

… from Adafruit’s CircuitPython “store” at https://circuitpython.org/down…
… or from the assets directory.

  1. select your board
  2. download the “latest” Firmware: “adafruit-circuitpython-manufacturer-boardname-version.UF2”
  3. and save it to your PC

Install this Firmware to your Board

Read the installation notes in the Adafruit learn guide.

  • Flash procedure:
  1. insert USB cable into Board (but leave PC side unconnected!)
  2. press BOOT button on Board
  3. next, insert USB cable into your PC (and still keep the BOOT button pressed)
  4. now, release the BOOT button
  5. a new USB drive, called “RPI-RP2”, appears
  6. copy the appropriate “.UF2” file to the USB drive “RPI-RP2
  7. the Board reboots and USB drive “RPI-RP2” disappears
  8. a new USB drive, called “CIRCUITPY”, appears
  9. Finished!

Install additional Tools on your PC

  1. install an Editor or IDE:
  2. install Adafruit’s CIRCUP tool to manage the Adafruit and Community libraries on your Board
  3. use your PC’s Filemanager to explore and manage the files on the CIRCUITPY drive

What do you need?

Connect a NeoPixel cable to the Rapsberry-Pi-Pico:

  • Solder a 3-wire cable to pins 34, 38, and 40 of your Pico board.
    The other end of this cable has a plug, which fits to the NeoPixel matrix.
    (Detailed view of “Pico-W” boards with NeoPixel cables.)

Hardware

What Price
Pico board 5-10 €
USB cable 5 €
16x16 NeoPixel matrix 25-30 €
3 pin Data cable included with matrix
Sub total approx. 40 €
IKEA frame 10 €
acrylic diffusor 10 €
Grand total < 60€

Time & Tools

  • soldering iron + some solder
  • ruler + cutter knife
  • cordless drill + some drill inserts (3-12 mm)
  • approximately 1-2 hours

Short Introduction to Programming with Python

If you are new to programming with Python then you might have a look at the “Welcome To CircuitPython” guide by Adafruit. The chapter “How do I learn Python?” has links to guides for every level of experience.

Another guide “Getting Started with Raspberry Pi Pico and CircuitPython” is dedicated to programming Pico boards with CPy. The chapter “NeoPixel LEDs” is very helpful for this workshop.

These guides can also be downloaded from the assets directory.

Structure of a CircuitPython program

  1. import needed libraries (and methods)
  2. define your global variables (and constants)
  3. define (aka. develop) your own functions
  4. initialize all needed objects
  5. start the main loop

Control structures

Python does not use any explicit syntactic brackets ( like begin and end, or { and } ) to delimit compound statements.
That’s why indentation (usually by 4 spaces) is used to group statements.

Python does have most of the usual control structures.
But you need to terminate the ’test’ with a colon :.

 1# IF statement
 2if (a == b):
 3    x = y
 4elif (b == c):
 5    x = z
 6else:
 7    z = x +y
 8
 9# WHILE statement
10i = 0
11while (i < 10):
12    i += 1
13
14# FOR statement
15for j in range(10):
16    print(j)

Function definitions

The same is true for function definitions.
Put the name and all parameters onto the first line and terminate the line with a colon :.
Then you need to indent all statements of the function body.
The function definition ends, when you outdent to the level of the def keyword.

1# Function defintion
2def fname(x, y, z):
3    return x * y * z
4
5print( fname(2, 3, 5) )

Data types

Python does have all the usual data types, like integers, floats and strings.
Some conversions are done implicitly. Others need to be carried out explicitly by calling a function.

1a, b = 5, 7
2x, y = 9.7, 6.5
3z = float( a + b )
4c = int( x / y )
5s = "my "
6t = " know"
7r = "ledge"
8print(z, c, s+t+r)

Data structures

The basic data structures of Python are lists, dictionaries, and tuples:

1# LISTS have indices that are integers and can be used as ARRAYS
2# delimiters are [ and ]
3a = [ 1, 2, 3, ]
4rgb = [ 0xff, 0xcc, 0xd8 ]
5b = [ 'a', 12, 3.14, [], rgb ]
6print( a[1], b[4] )
1# DICTIONARIES are similar to LISTS, 
2# except the indices are strings (or any other type)
3# delimiters are { and }
4word = {}
5word['en'] = 'book'
6word['de'] = 'buch'
7word['fr'] = 'livre'
8words = { 'one': 'eins', 'two': 'due', 'three': 'trois', }
1# TUPLES are an immutable sequence of values
2# delimiters are ( and )
3t = ( 1, 2, 3, 4, 5 )
4print( t[2] )

A few Examples

All Examples can also be downloaded from the assets directory.

Hello world!

# SPDX-FileCopyrightText: 2025 Pagong
# SPDX-License-Identifier: MIT

import time
import board
import neopixel

####################### 

# for Rpi-Pico with 16x16 NeoPixel-Matrix
NUM_COLS = 16
NUM_CELLS = 16
NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.

SPEED = 0.1        # Increase to slow down the effect. Decrease to speed it up.
BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.

PIN = board.GP28   # This is the default pin on my RPi-Pico with 16x16 NeoPixel matrix
pixels = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=False)

#####################

black = 0
color = ( 0xff, 0xcc, 0xd8 )

while True:
    pixels.fill(black)
    pixels.show()
    time.sleep(5*SPEED)

    pixels.fill(color)
    pixels.show()
    time.sleep(SPEED)

Rainbow

# SPDX-FileCopyrightText: 2025 Pagong
# SPDX-License-Identifier: MIT

import time
import board
import neopixel
import rainbowio

####################### 

# for Rpi-Pico with 16x16 NeoPixel-Matrix
NUM_COLS = 16
NUM_CELLS = 16
NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.

SPEED = 0.01       # Increase to slow down the effect. Decrease to speed it up.
BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.

PIN = board.GP28   # This is the default pin on my RPi-Pico with 16x16 NeoPixel matrix
pixels = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=False)

#####################

black = 0

while True:
    pixels.fill(black)
    pixels.show()
    time.sleep(50*SPEED)

    for i in range(NUM_PIXELS):
        for j in range(NUM_PIXELS):
            color = rainbowio.colorwheel(i+j)
            pixels[j] = color
        pixels.show()
        time.sleep(SPEED)

2D Matrices

Sinus

# move through Sinus and Cosinus terrain
#
# 21 Mar 2025 - @pagong
# Uses Raspberry-Pi Pico with a 16x16 NeoPixel LED matrix

import time
import board
import random
import neopixel
import rainbowio

import neomatrix

#####################

# for RPi-Pico with 16x16 NeoPixel-Matrix
NUM_COLS  = 16
NUM_CELLS = 16

NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.
SPEED = 0.01       # Increase to slow down the animation. Decrease to speed it up.
BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
PIN = board.GP28   # This is the default pin on RPi-Pico with 16x16 NeoPixel matrix

leds = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS,
                         pixel_order=neopixel.GRB, auto_write=False)

matrixType = (  neomatrix.NEO_MATRIX_BOTTOM + neomatrix.NEO_MATRIX_LEFT +
                neomatrix.NEO_MATRIX_ROWS + neomatrix.NEO_MATRIX_ZIGZAG   )

matrix = neomatrix.NeoMatrix(
    leds,
    NUM_COLS, NUM_CELLS,
    1, 1,
    matrixType,
)

grid = matrix._grid

#####################

# prepare rainbow palette
palette = []
for k in range(256):
    palette.append(rainbowio.colorwheel(k))

# change direction of movement
def change_direction():
    xs, ys = 0, 0
    while (abs(xs) + abs(ys) == 0):
        xs = random.randint(-1, 1)
        ys = random.randint(-1, 1)
    return float(xs), float(ys)

def do_frame():
    for i in range(NUM_COLS):           # for each pixel row
        sinx = math.sin(start_x + step*i)
        pxl = grid[i]
        for j in range(NUM_CELLS):      # for each pixel column
            cosy = math.cos(start_y + step*j)
            val = 1.0 + (sinx * cosy)
            col = int(val * 127.5)      # scale it from -1 - +1 -> 0 - 255
            pxl[j] = palette[col]       # convert hue to rainbow color

#####################

import math
step = (1.1 * math.pi) / float(NUM_COLS)
start_x = 0.0
start_y = 0.0

incr = 0.1
xsign = 0.0
ysign = 1.0

Debug = True

while True:
    t1 = time.monotonic_ns()
    do_frame()
    t2 = time.monotonic_ns()

    grid.show()
    t3 = time.monotonic_ns()

    if Debug:
        d1 = (t2 - t1) / 1000000.0
        print(f"Compute {d1} ms", end=" +\t")
        d2 = (t3 - t2) / 1000000.0
        print(f"Display {d2} ms", end=" =\t")
        print(f"Total {d1+d2} ms", end=" -->\t")
        print(f"{1000.0/(d1+d2)} fps")

    # move around in 2D space
    start_x += incr * xsign
    start_y += incr * ysign
    if (random.randint(0, 99) == 8):
        xsign, ysign = change_direction()

    time.sleep(SPEED)

Further Examples

see my Github account for more: https://github.com/pagong

Domino Clock

Backlinks