CircuitPython simulator: the official firmware on a Pico, online
Beginner
Or see every lesson.
- 455parts on the bench
- 43boards running now
- 1.00xreal time, on every board
Mokxi runs CircuitPython on the Raspberry Pi Pico, the Pico W, the Arduino Nano RP2040 Connect and the ESP32-S3-DevKitC-1. Put a Pico on the canvas, choose CircuitPython (code.py) in its Firmware panel, write code.py and press Run. What runs is the official Adafruit firmware, CircuitPython 10.3.1 for the Raspberry Pi Pico, byte for byte as circuitpython.org publishes it, on Mokxi’s instruction-level RP2040. Nothing in the page interprets your Python.
The board boots the way a real one does: CircuitPython mounts its CIRCUITPY drive, waits out its one-second safe-mode window while the LED on GP25 blinks, and starts USB. The computer on the end of the board’s simulated USB cable then opens its serial port and saves code.py onto the drive, as your own computer does when you save from your editor, and CircuitPython’s auto-reload runs it.
The circuit above is the first of the starters below: board.LED blinks the Pico’s own LED and a red LED on GP15, and the Serial Monitor says on and off.
How code.py gets onto the board
CircuitPython keeps its files on a drive called CIRCUITPY, in the Pico’s flash. At Run, Mokxi writes that drive before the board powers on: the firmware’s own boot_out.txt, the Adafruit libraries your program imports under lib/, and every .py, .txt, .json, .csv and .toml tab but code.py.
code.py itself is saved over USB, block by block, the way an operating system writes a file to a USB drive. CircuitPython takes the save into its flash and reloads, and code.py runs with the Serial Monitor connected, so every line it prints is shown. It prints about two seconds after Run, most of that CircuitPython’s own safe-mode window and the wait after a save before it reloads.
Blink with digitalio
digitalio.DigitalInOut(board.LED) is the Pico’s own LED on GP25, and board.GP15 drives a red LED through 220 ohms to ground. The two light together, and the Serial Monitor prints on and off after CircuitPython’s code.py output: line.
# CircuitPython on a Raspberry Pi Pico: blink an LED.
# board.LED is the Pico's own LED (GP25); the red LED on GP15 through
# 220 ohms blinks with it.
import board
import digitalio
import time
onboard = digitalio.DigitalInOut(board.LED)
onboard.direction = digitalio.Direction.OUTPUT
led = digitalio.DigitalInOut(board.GP15)
led.direction = digitalio.Direction.OUTPUT
while True:
onboard.value = led.value = True
print("on")
time.sleep(0.5)
onboard.value = led.value = False
print("off")
time.sleep(0.5)
A button with the pin’s pull-up
A button from GP14 to ground, with switch_to_input(pull=digitalio.Pull.UP): the pin reads True until the button is pressed, and False while it is held. The program prints Press the button, lights the board’s LED while you hold it, and counts each press once.
# CircuitPython on a Raspberry Pi Pico: a button lights the LED.
# The button joins GP14 to ground. The pin's own pull-up holds it True, so
# a press reads False.
import board
import digitalio
import time
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
button = digitalio.DigitalInOut(board.GP14)
button.switch_to_input(pull=digitalio.Pull.UP)
print("Press the button")
presses = 0
was_pressed = False
while True:
pressed = not button.value
led.value = pressed
if pressed and not was_pressed:
presses += 1
print("pressed", presses)
was_pressed = pressed
time.sleep(0.01)
A NeoPixel rainbow with the neopixel library
Eight WS2812 LEDs on GP16. Adafruit’s neopixel library goes into lib/ by itself, and it sends the strip’s bits from one of the Pico’s PIO state machines, as on a real Pico. The colors turn around the strip, and every 64 steps the program prints the step and the first pixel’s color, starting step 64 first pixel.
# CircuitPython on a Raspberry Pi Pico: a NeoPixel rainbow.
# Eight WS2812 LEDs on GP16, driven by Adafruit's neopixel library (which
# the editor puts in lib/ for you) through the Pico's PIO.
import board
import neopixel
import time
pixels = neopixel.NeoPixel(board.GP16, 8, brightness=0.3, auto_write=False)
def wheel(pos):
# 0 to 255 around the color wheel: red, green, blue and back to red.
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)
print("Rainbow on 8 NeoPixels")
step = 0
while True:
for i in range(8):
pixels[i] = wheel((i * 32 + step) % 256)
pixels.show()
if step % 64 == 0:
print("step", step, "first pixel", pixels[0])
step = (step + 4) % 256
time.sleep(0.02)
A BME280 with adafruit_bme280
busio.I2C(board.GP5, board.GP4) is the Pico’s I2C0, SCL first and then SDA. Adafruit’s adafruit_bme280 library reads the sensor; you do not add it anywhere, as the editor puts it in the drive’s lib/ folder when code.py imports it. With the part set to 22.5 C and 45% it prints 22.5 C 45.0 % humidity and the pressure every two seconds, and when you change the temperature on the part the next reading follows it.
# CircuitPython on a Raspberry Pi Pico: a BME280 to the Serial Monitor.
# I2C0 on GP5 (SCL) and GP4 (SDA); Adafruit's adafruit_bme280 library goes
# into lib/ for you.
import board
import busio
import time
from adafruit_bme280 import basic as adafruit_bme280
i2c = busio.I2C(board.GP5, board.GP4)
bme = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)
while True:
print("%.1f C %.1f %% humidity %.1f hPa" % (bme.temperature, bme.relative_humidity, bme.pressure))
time.sleep(2)
Text on an SSD1306 OLED with displayio
displayio draws on the OLED through adafruit_displayio_ssd1306, and adafruit_display_text makes the labels, in the firmware’s own terminalio font. Both libraries, and the adafruit_ticks that adafruit_display_text needs, go into lib/ by themselves. The program writes a title and a counter that goes up once a second, and prints count 0, count 1 and on.
# CircuitPython on a Raspberry Pi Pico: text on an SSD1306 OLED.
# I2C0 on GP5 (SCL) and GP4 (SDA). displayio draws a label with the built-in
# terminalio font; adafruit_displayio_ssd1306 and adafruit_display_text go
# into lib/ for you.
import board
import busio
import displayio
import i2cdisplaybus
import terminalio
import time
import adafruit_displayio_ssd1306
from adafruit_display_text import label
displayio.release_displays()
i2c = busio.I2C(board.GP5, board.GP4)
bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(bus, width=128, height=64)
group = displayio.Group()
title = label.Label(terminalio.FONT, text="Hello, Pico!", x=4, y=8)
counter = label.Label(terminalio.FONT, text="", x=4, y=32, scale=2)
group.append(title)
group.append(counter)
display.root_group = group
n = 0
while True:
counter.text = str(n)
print("count", n)
n += 1
time.sleep(1)
WiFi on the Pico W
The Pico W runs CircuitPython’s own build for it, with its CYW43439 radio chip modeled from its pins: board.LED is the LED on the radio chip, and wifi.radio.connect() joins Mokxi’s simulated access point. Its name and password are the board’s WiFi properties, which start as Mokxi-Lab and breadboard. socketpool and Adafruit’s adafruit_requests, with the adafruit_connection_manager it needs, both put in lib/ for you, then fetch the made-up .mokxi sites of the simulated network, such as http://weather.mokxi/.
The network is simulated: names outside .mokxi are not found, and https:// has nowhere to go. Bluetooth, the access point mode’s clients and power save are not simulated, and wifi.radio.ap_info raises NotImplementedError, as CircuitPython’s Pico W port does not implement it.
The libraries that go into lib/ by themselves
Mokxi reads each import line and puts the Adafruit libraries it finds into the drive’s lib/ folder, with what they import in turn, as circup would. They are Adafruit’s own compiled .mpy files from their CircuitPython 10.x bundle, unchanged: adafruit_bme280, adafruit_display_text (with adafruit_ticks), adafruit_displayio_ssd1306, adafruit_framebuf (with its font5x8.bin at the top of the drive), adafruit_hcsr04, adafruit_requests (with adafruit_connection_manager), adafruit_ssd1306 and neopixel. A tab named after a library, such as neopixel.py, is your own and is used instead.
Modules built into the firmware need nothing: board, digitalio, analogio, pwmio, busio, time, displayio, terminalio, i2cdisplaybus and the rest.
The REPL
The Serial Monitor is the Pico’s USB serial port, both ways: what code.py prints, its tracebacks, and Code done running. when it ends. Ctrl-C stops code.py with a KeyboardInterrupt, any key then enters the REPL, where >>> answers, and Ctrl-D reloads and runs code.py again.
When a program stops with an error, Mokxi marks the line in your file and says what the error usually means in CircuitPython’s words: a MicroPython module such as machine in a CircuitPython program, a missing pull-up, a pin in use, or board.I2C(), which the Pico does not have.
What works, and what does not
Working: digitalio, analogio, pwmio, busio.I2C and busio.UART, with the parts on the canvas; adafruit_bme280, adafruit_ssd1306 with adafruit_framebuf, and displayio with adafruit_display_text. The PIO runs too, so neopixel_write and the neopixel library light a WS2812 strip, and pulseio.PulseIn reads the echo that adafruit_hcsr04 times an HC-SR04 with. A reset from inside, with microcontroller.reset() or the watchdog, keeps the drive, so code.py runs again.
Not simulated yet: the second core; audiobusio, audiopwmio and picodvi; and reading the drive from the computer, which only saves to it. The ESP32-S3-DevKitC-1 runs CircuitPython too, with its own build and its REPL and drive on the S3’s native USB, but WiFi is not simulated under CircuitPython there.
Importing from Wokwi
A Wokwi project for the Pico, the Pico W, the Nano RP2040 Connect or the ESP32-S3-DevKitC-1 with a code.py imports with its board set to CircuitPython, and runs. On another board its code.py is kept as text, and the import says why. Wokwi may have used another CircuitPython version; the import report says which one runs here.
The same code on a real Pico
A CircuitPython board needs no build. Put CircuitPython on the Pico (hold BOOTSEL while you plug it in, then drag the .uf2 from circuitpython.org/board/raspberry_pi_pico onto the RPI-RP2 drive), then save code.py onto the CIRCUITPY drive that appears, and copy any libraries it imports into CIRCUITPY/lib (circup install does it for you).
Questions
Is this real CircuitPython?
Yes. The board runs the official CircuitPython 10.3.1 firmware for the Raspberry Pi Pico, the same .uf2 circuitpython.org publishes, byte for byte, on a simulated RP2040. Nothing in the web page interprets your Python.
Which boards run CircuitPython?
The Raspberry Pi Pico; the Pico W, with WiFi on the simulated network; the Arduino Nano RP2040 Connect with CircuitPython’s own build for it, where `board.LED` is the LED on D13 and `board.I2C()` is on A4 and A5; and the ESP32-S3-DevKitC-1. Choose CircuitPython (code.py) in the board’s Firmware panel. The same Pico can run MicroPython (main.py) or an Arduino sketch instead.
Do I need to install libraries?
No. The Adafruit libraries a program imports, from the ones Mokxi has, go into the drive’s lib/ folder at Run by themselves, with what they need in turn, as circup would put them there.
Does it download anything?
The first Run of a CircuitPython project downloads the firmware, and the browser keeps it for every later Run. Each library is downloaded only when a program imports it. An Arduino sketch never downloads either.
Keep going
Where the facts on this page come from
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.