rpipico-circpy
Tuesday 1 April 2025

CircuitPython with Raspberry-Pi-Pico and NeoPixel Matrices

Why do we use these parts?

Micro-Controllers

Raspberry-Pi-Pico (RP2040)
Raspberry-Pi-Pico-W (RP2040)
Raspberry-Pi-Pico2 (RP2350)
Raspberry-Pi-Pico2-W (RP2350)

  • 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
  • RPi-Pico specs:
    Processor RAM Flash Cores Clock speed
    RP2040 264KB 2MB 2x ARM Cortex-M0+ 133MHz
    RP2350 520KB 4MB 2x ARM Cortex-M33 150MHz

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?

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

  • approximately 1-2 hours

Tools

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

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 definitions 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!

 1# SPDX-FileCopyrightText: 2025 Pagong
 2# SPDX-License-Identifier: MIT
 3
 4import time
 5import board
 6import neopixel
 7
 8####################### 
 9
10# for Rpi-Pico with 16x16 NeoPixel-Matrix
11NUM_COLS = 16
12NUM_CELLS = 16
13NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.
14
15SPEED = 0.1        # Increase to slow down the effect. Decrease to speed it up.
16BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
17
18PIN = board.GP28   # This is the default pin on my RPi-Pico with 16x16 NeoPixel matrix
19pixels = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=False)
20
21#####################
22
23black = 0
24color = ( 0xff, 0xcc, 0xd8 )
25
26while True:
27    pixels.fill(black)
28    pixels.show()
29    time.sleep(5*SPEED)
30
31    pixels.fill(color)
32    pixels.show()
33    time.sleep(SPEED)

Rainbow

 1# SPDX-FileCopyrightText: 2025 Pagong
 2# SPDX-License-Identifier: MIT
 3
 4import time
 5import board
 6import neopixel
 7import rainbowio
 8
 9####################### 
10
11# for Rpi-Pico with 16x16 NeoPixel-Matrix
12NUM_COLS = 16
13NUM_CELLS = 16
14NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.
15
16SPEED = 0.01       # Increase to slow down the effect. Decrease to speed it up.
17BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
18
19PIN = board.GP28   # This is the default pin on my RPi-Pico with 16x16 NeoPixel matrix
20pixels = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=False)
21
22#####################
23
24black = 0
25
26while True:
27    pixels.fill(black)
28    pixels.show()
29    time.sleep(50*SPEED)
30
31    for i in range(NUM_PIXELS):
32        for j in range(NUM_PIXELS):
33            color = rainbowio.colorwheel(i+j)
34            pixels[j] = color
35        pixels.show()
36        time.sleep(SPEED)

Breathe

 1# SPDX-FileCopyrightText: 2025 Pagong
 2# SPDX-License-Identifier: MIT
 3
 4import time
 5import board
 6import neopixel
 7import math
 8
 9####################### 
10
11# for Rpi-Pico with 16x16 NeoPixel-Matrix
12NUM_COLS = 16
13NUM_CELLS = 16
14NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.
15
16SPEED = 0.2        # Increase to slow down the effect. Decrease to speed it up.
17BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
18
19PIN = board.GP28   # This is the default pin on my RPi-Pico with 16x16 NeoPixel matrix
20pixels = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=False)
21
22#####################
23
24def lerp(begin, end, t):
25    return begin + t*(end-begin)
26
27def hsv2rgb(h, s, v):
28    g = h*6.0
29    i = math.floor(g)
30    f = g - i
31
32    p = v * (1.0 - s)
33    q = v * (1.0 - s*f)
34    t = v * (1.0 - s*(1.0-f))
35
36    r, g, b = [
37        (v, t, p),
38        (q, v, p),
39        (p, v, t),
40        (p, q, v),
41        (t, p, v),
42        (v, p, q),
43    ][int(i)%6]
44
45    return int(r*255), int(g*255), int(b*255)
46
47#####################
48
49hue_A = 15 
50sat_A = 230
51val_min = 120.0
52
53hue_B = 95
54sat_B = 255
55val_max = 255.0
56
57palette = []
58def breathe():
59    step = 1.0   # 0.5
60    delta = (val_max - val_min) / 2.35040238
61    max_range = 128
62
63    for i in range(2*max_range):
64        dV = (math.exp(math.sin(step * i/max_range * math.pi)) - 0.36787944) * delta
65        val = val_min + dV
66        t = (val - val_min) / (val_max - val_min)
67        hue = lerp(hue_A, hue_B, t)
68        sat = lerp(sat_A, sat_B, t)
69
70        rgb = hsv2rgb(hue/255.0, sat/255.0, val/255.0)
71        #print(i, dV, val, rgb)
72        palette.append(rgb)
73
74#####################
75
76pulse = 2.5
77breathe()
78      
79while True:
80    for i in range(256):
81        color = palette[int(pulse*i)&255]
82        pixels.fill(color)
83        pixels.show()
84        time.sleep(SPEED)

2D Matrices

Sinus

  1# move through Sinus and Cosinus terrain
  2#
  3# 21 Mar 2025 - @pagong
  4# Uses Raspberry-Pi Pico with a 16x16 NeoPixel LED matrix
  5
  6import time
  7import board
  8import random
  9import neopixel
 10import rainbowio
 11
 12import neomatrix
 13
 14#####################
 15
 16# for RPi-Pico with 16x16 NeoPixel-Matrix
 17NUM_COLS  = 16
 18NUM_CELLS = 16
 19
 20NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.
 21SPEED = 0.01       # Increase to slow down the animation. Decrease to speed it up.
 22BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
 23PIN = board.GP28   # This is the default pin on RPi-Pico with 16x16 NeoPixel matrix
 24
 25leds = neopixel.NeoPixel(PIN, NUM_PIXELS, brightness=BRIGHTNESS,
 26                         pixel_order=neopixel.GRB, auto_write=False)
 27
 28matrixType = (  neomatrix.NEO_MATRIX_BOTTOM + neomatrix.NEO_MATRIX_LEFT +
 29                neomatrix.NEO_MATRIX_ROWS + neomatrix.NEO_MATRIX_ZIGZAG   )
 30
 31matrix = neomatrix.NeoMatrix(
 32    leds,
 33    NUM_COLS, NUM_CELLS,
 34    1, 1,
 35    matrixType,
 36)
 37
 38grid = matrix._grid
 39
 40#####################
 41
 42# prepare rainbow palette
 43palette = []
 44for k in range(256):
 45    palette.append(rainbowio.colorwheel(k))
 46
 47# change direction of movement
 48def change_direction():
 49    xs, ys = 0, 0
 50    while (abs(xs) + abs(ys) == 0):
 51        xs = random.randint(-1, 1)
 52        ys = random.randint(-1, 1)
 53    return float(xs), float(ys)
 54
 55def do_frame():
 56    for i in range(NUM_COLS):           # for each pixel row
 57        sinx = math.sin(start_x + step*i)
 58        pxl = grid[i]
 59        for j in range(NUM_CELLS):      # for each pixel column
 60            cosy = math.cos(start_y + step*j)
 61            val = 1.0 + (sinx * cosy)
 62            col = int(val * 127.5)      # scale it from -1 - +1 -> 0 - 255
 63            pxl[j] = palette[col]       # convert hue to rainbow color
 64
 65#####################
 66
 67import math
 68step = (1.1 * math.pi) / float(NUM_COLS)
 69start_x = 0.0
 70start_y = 0.0
 71
 72incr = 0.1
 73xsign = 0.0
 74ysign = 1.0
 75
 76Debug = True
 77
 78while True:
 79    t1 = time.monotonic_ns()
 80    do_frame()
 81    t2 = time.monotonic_ns()
 82
 83    grid.show()
 84    t3 = time.monotonic_ns()
 85
 86    if Debug:
 87        d1 = (t2 - t1) / 1000000.0
 88        print(f"Compute {d1} ms", end=" +\t")
 89        d2 = (t3 - t2) / 1000000.0
 90        print(f"Display {d2} ms", end=" =\t")
 91        print(f"Total {d1+d2} ms", end=" -->\t")
 92        print(f"{1000.0/(d1+d2)} fps")
 93
 94    # move around in noise space
 95    start_x += incr * xsign
 96    start_y += incr * ysign
 97    if (random.randint(0, 99) == 8):
 98        xsign, ysign = change_direction()
 99
100    time.sleep(SPEED)

Noise

  1# noise_square_code.py -- playing with simplex noise in CircuitPython
  2# 9 Feb 2023 - @todbot / Tod Kurt
  3# https://github.com/todbot/CircuitPython_Noise
  4#
  5# 21 Mar 2025 - @pagong
  6# Uses Raspberry-Pi Pico with a 16x16 NeoPixel matrix
  7
  8import time
  9import board
 10import random
 11import neopixel
 12import rainbowio
 13
 14import matrix16
 15
 16#####################
 17
 18# for RPi-Pico with 16x16 NeoPixel-Matrix
 19BRIGHTNESS = 0.1   # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
 20PIN = board.GP28   # This is the default pin on RPi-Pico with 16x16 NeoPixel matrix
 21matrix = matrix16.MatrixSetup(PIN, "hsquare", 1.0)
 22grid = matrix._grid
 23
 24NUM_COLS  = matrix._width
 25NUM_CELLS = matrix._height
 26
 27NUM_PIXELS = (NUM_COLS * NUM_CELLS)  # Update this to match the number of LEDs.
 28SPEED = 0.01       # Increase to slow down the animation. Decrease to speed it up.
 29
 30#####################
 31
 32# use Todbot's noise module from community bundle
 33import noise
 34noise_scale = 0.07   # noise_scale * max(width, height) should be smaller than 1.0
 35noise_incr = 0.01
 36noise_x = 0.0
 37noise_y = 0.0
 38xsign = -1.0
 39ysign = -1.0
 40
 41#####################
 42
 43# prepare rainbow palette
 44def make_palette(palette, bright):
 45    for hue in range(256):
 46        color = rainbowio.colorwheel(hue)
 47        r = int(bright * float((color >> 16) & 255))
 48        g = int(bright * float((color >> 8) & 255))
 49        b = int(bright * float(color & 255))
 50        col = (r, g, b)
 51        palette.append(col)
 52
 53# change direction of movement
 54def change_direction():
 55    xs, ys = 0, 0
 56    while (abs(xs) + abs(ys) == 0):
 57        xs = random.randint(-1, 1)
 58        ys = random.randint(-1, 1)
 59    return float(xs), float(ys)
 60
 61def do_frame():
 62    for i in range(NUM_COLS):           # for each pixel column
 63        nsx = noise_x + noise_scale*i
 64        pxl = grid[i]
 65        for j in range(NUM_CELLS):      # for each pixel row
 66            # get a noise value in 2D noise space
 67            n = noise.noise(nsx, noise_y + noise_scale*j)
 68            c = int((n+1.0) * 127.5)    # scale it from -1 - +1 -> 0 - 255
 69            pxl[j] = palette[c]         # convert hue to rainbow color
 70
 71#####################
 72
 73palette = []
 74make_palette(palette, BRIGHTNESS)
 75
 76Debug = True
 77
 78while True:
 79    t1 = time.monotonic_ns()
 80    do_frame()
 81    t2 = time.monotonic_ns()
 82
 83    grid.show()
 84    t3 = time.monotonic_ns()
 85
 86    if Debug:
 87        d1 = (t2 - t1) / 1000000.0
 88        print(f"Compute {d1} ms", end=" +\t")
 89        d2 = (t3 - t2) / 1000000.0
 90        print(f"Display {d2} ms", end=" =\t")
 91        print(f"Total {d1+d2} ms", end=" -->\t")
 92        print(f"{1000.0/(d1+d2)} fps")
 93
 94    # move around in noise space
 95    noise_x += noise_incr * xsign
 96    noise_y += noise_incr * ysign
 97    if (random.randint(0, 99) == 8):
 98        xsign, ysign = change_direction()
 99
100    time.sleep(SPEED)

Domino Clock

Further Examples

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


Backlinks