Objective and use case
What you’ll build: Build a “pattern-leds-chaser” on the Pico-ICE (Lattice iCE40UP5K), using the RP2040 side of the board to drive 8 LEDs in a clean, repeating chase pattern.
Why it matters / Use cases
- Demonstrates the capabilities of the RP2040 microcontroller in real-time LED control.
- Provides a foundation for understanding microcontroller programming and hardware interfacing.
- Can be extended to include more complex patterns or integrate with other sensors for interactive displays.
- Serves as an introductory project for those interested in FPGA development with the Lattice iCE40UP5K.
Expected outcome
- Successful execution of the LED chase pattern with a clear visual output.
- Validation of the setup through specific commands and expected responses during the flashing process.
- Measurement of response time from input to LED output, aiming for less than 100ms latency.
- Documentation of any errors encountered and their resolutions during the setup process.
Audience: Beginners to intermediate users interested in microcontrollers and FPGAs; Level: Basic.
Architecture/flow: The project involves setting up a Raspberry Pi host, configuring a Python environment, wiring LEDs, and flashing MicroPython to the Pico-ICE.
Pico-ICE (Lattice iCE40UP5K) LED Chaser (Basic Level)
Objective: Build a “pattern-leds-chaser” on the Pico-ICE (Lattice iCE40UP5K), using the RP2040 side of the board (Pico‑compatible) to drive 8 LEDs in a clean, repeating chase pattern. You will prepare a Raspberry Pi host (Raspberry Pi OS Bookworm 64‑bit) with Python 3.11, set up a Python virtual environment, install the necessary tools, wire the LEDs, flash MicroPython, copy the code, run it, and validate the behavior step by step.
This is a hands‑on practical with a focus on correctness and reproducibility. It includes exact commands, versions and file paths. No circuit drawings are used; all connections are explained with text and a table.
Note: Pico‑ICE combines a Raspberry Pi Pico‑compatible RP2040 microcontroller with a Lattice iCE40UP5K FPGA. In this basic exercise we focus on the RP2040 to immediately realize the LED chaser. In “Improvements,” you will find guidance to push the pattern into the FPGA fabric later.
Prerequisites
- A Raspberry Pi 4/400/5 running:
- Raspberry Pi OS Bookworm 64‑bit (fully updated).
- Python 3.11 (default on Bookworm).
- Internet access on the Raspberry Pi to install packages.
- A spare USB port on the Raspberry Pi to connect the Pico‑ICE.
- Basic breadboard and wiring skills (no soldering required if your Pico‑ICE is header‑equipped).
- Familiarity with a terminal and the concept of a Python virtual environment.
Confirm OS and Python:
cat /etc/os-release
python3 --version
Expected: Bookworm and Python 3.11.x.
Materials
- Device: “Pico‑ICE (Lattice iCE40UP5K)” — exact model as specified.
- Host: Raspberry Pi 4/400/5 (running Raspberry Pi OS Bookworm 64‑bit).
- USB data cable appropriate for your Pico‑ICE’s USB connector (ensure it is data‑capable).
- 1 × solderless breadboard.
- 8 × LEDs (5 mm or 3 mm; any color).
- 8 × 330 Ω resistors (1/4 W).
- ~20 × male–male jumper wires.
- Optional (for later improvements): Digilent Pmod 8LD (8‑LED Pmod), but not required here.
We will assume the Pico‑ICE is pin‑compatible with the Raspberry Pi Pico. The RP2040 GPIOs we will use are GP2–GP9.
Setup/Connection
1) Enable interfaces on the Raspberry Pi
We follow the Raspberry Pi family defaults. Even though the LED chaser doesn’t require I2C/SPI/UART on the host, enabling them now saves time for future expansions.
Option A: Using raspi-config (interactive)
- Open the TUI:
sudo raspi-config - Interface Options:
- I2C: Enable.
- SPI: Enable.
- Serial Port: Disable login shell over serial; Enable serial port hardware.
- Finish and reboot when prompted.
Option B: Edit /boot/firmware/config.txt (manual)
- Open the config file:
sudo nano /boot/firmware/config.txt - Add (or ensure these lines exist and are uncommented):
dtparam=i2c_arm=on
dtparam=spi=on
enable_uart=1 - Save, exit, and reboot:
sudo reboot
These steps do not interfere with USB communications to the Pico‑ICE.
2) Create a project directory and Python virtual environment
-
Create a working folder and venv:
mkdir -p ~/pico-ice-led-chaser
cd ~/pico-ice-led-chaser
python3 -m venv .venv
source .venv/bin/activate
python -V
Expected output: Python 3.11.x -
Install host utilities and Python tooling:
sudo apt update
sudo apt install -y screen curl unzip
pip install --upgrade pip
pip install mpremote pyserial gpiozero smbus2 spidev
Notes: - mpremote will be used to copy and run MicroPython files on the RP2040.
- gpiozero, smbus2, spidev are installed per family defaults, to prepare for future projects on the Raspberry Pi itself.
3) Prepare MicroPython firmware for the Pico‑ICE (RP2040 side)
We will run the LED chaser using MicroPython on the RP2040. Download a known stable MicroPython build for the RP2040 (Raspberry Pi Pico). Example shown below uses MicroPython v1.21.0 for rp2-pico; you can substitute a newer stable version if preferred.
-
Create a firmware folder and download:
mkdir -p ~/pico-ice-led-chaser/fw
cd ~/pico-ice-led-chaser/fw
curl -LO https://micropython.org/resources/firmware/rp2-pico-20230426-v1.21.0.uf2
ls -lh -
Put the Pico‑ICE into BOOTSEL mode (RP2040 bootloader):
- Unplug the Pico‑ICE from USB.
- Press and hold the BOOTSEL button on the Pico‑ICE.
- While holding BOOTSEL, plug the USB cable into the Raspberry Pi.
-
Release BOOTSEL after the board enumerates as a mass storage device (RPI-RP2).
-
Copy the UF2 to the mounted volume:
- Identify the mount path (it typically auto‑mounts under /media/pi/RPI-RP2 on Raspberry Pi OS desktop; on Lite you may need to mount it).
- Copy:
cp ~/pico-ice-led-chaser/fw/rp2-pico-20230426-v1.21.0.uf2 /media/pi/RPI-RP2/
sync -
The board will automatically reboot into MicroPython. The mass storage volume disappears; this is expected.
-
Verify the USB serial device appears:
dmesg | tail -n 50 | grep -i tty
ls -l /dev/ttyACM*
Expected: something like /dev/ttyACM0
4) Wire the 8 LEDs to the Pico‑ICE
We’ll use GPIO pins GP2–GP9 to drive the LEDs. Each LED needs a current‑limiting resistor. We choose 330 Ω from GPIO to LED anode, LED cathode to GND.
- Orientation reminder:
- LED anode: longer lead → connect via resistor to the GPIO.
- LED cathode: shorter lead/flat side → connect to GND.
- Ground reference: use any GND pin on the Pico‑ICE header.
Use the standard Raspberry Pi Pico header pinout (Pico‑ICE is Pico‑compatible). The table below maps LEDs to GPIOs and the physical pin numbers on the Pico header.
| LED # | RP2040 GPIO | Pico Header Pin | Connection Instruction |
|---|---|---|---|
| 1 | GP2 | Pin 4 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 2 | GP3 | Pin 5 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 3 | GP4 | Pin 6 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 4 | GP5 | Pin 7 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 5 | GP6 | Pin 9 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 6 | GP7 | Pin 10 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 7 | GP8 | Pin 11 | GPIO → 330 Ω → LED anode; LED cathode → GND |
| 8 | GP9 | Pin 12 | GPIO → 330 Ω → LED anode; LED cathode → GND |
Notes:
– Any GND pins may be used for the cathodes (common ground). Convenient ground pins near these are Pin 3 (GND), Pin 8 (GND), and Pin 13 (GND).
– Keep wiring short and neat to avoid shorts. Double‑check polarity of each LED before powering.
Full Code (MicroPython on RP2040)
Save the following as main.py. It implements a bidirectional “chaser” (Knight Rider‑style), with a configurable speed and clear pin mapping for GP2–GP9. It also performs a brief startup test lighting all LEDs.
# Tested with MicroPython v1.21.0 on RP2040 (Raspberry Pi Pico compatible).
#
# Wiring: See table in the tutorial.
# - Each LED anode goes to one GPIO via a 330 Ω resistor.
# - Each LED cathode goes to GND.
from machine import Pin
import time
# Ordered list of RP2040 GPIO numbers used for the chaser
LED_PINS = [2, 3, 4, 5, 6, 7, 8, 9]
# Convert into Pin objects set to OUTPUT, initially LOW
leds = [Pin(gp, Pin.OUT, value=0) for gp in LED_PINS]
# Chaser timing (seconds between steps); adjust as needed
STEP_DELAY_S = 0.08 # 80 ms per step ~ 12.5 steps/sec
def all_off():
for led in leds:
led.value(0)
def all_on():
for led in leds:
led.value(1)
def startup_test():
# Light all briefly, then a walking 0 test, then off
all_on()
time.sleep(0.3)
for i in range(len(leds)):
for j, led in enumerate(leds):
led.value(0 if i == j else 1)
time.sleep(0.05)
all_off()
def chase_loop():
# Ping-pong pattern: 0..7 forward, then 6..1 backward
n = len(leds)
index = 0
direction = 1 # 1 forward, -1 backward
while True:
# Light exactly one LED at position 'index'
for i, led in enumerate(leds):
led.value(1 if i == index else 0)
time.sleep(STEP_DELAY_S)
# Compute next index and direction
if direction == 1 and index == n - 1:
direction = -1
elif direction == -1 and index == 0:
direction = 1
index += direction
def main():
print("Pico-ICE LED chaser starting; pins:", LED_PINS)
startup_test()
chase_loop()
if __name__ == "__main__":
main()
Behavior:
– On boot: all LEDs on briefly, then a quick walking‑zero test, then the chaser begins.
– The pattern moves from LED1 to LED8 and back continuously.
– Adjust STEP_DELAY_S to speed up or slow down.
Optional: You can create variants with different patterns (e.g., bounce with two LEDs, fading via PWM, etc.) in the “Improvements” section later.
Build/Flash/Run Commands
This section gives exact, copy‑pasteable commands to put the code onto the Pico‑ICE and run it.
1) Ensure MicroPython is flashed
If you followed the earlier “Prepare MicroPython firmware” step, the device is already running MicroPython. If not, return to that step.
2) Identify the USB serial path
List connected ACM devices:
ls -l /dev/ttyACM*
Typically /dev/ttyACM0. If multiple devices exist, use dmesg to confirm which one appeared after plugging in the Pico‑ICE:
dmesg | tail -n 50 | grep -i "ttyACM"
3) Copy and run main.py with mpremote
- Go back to your project directory with the main.py you created:
cd ~/pico-ice-led-chaser -
Connect and copy:
mpremote connect /dev/ttyACM0 fs cp main.py :main.py
This copies main.py to the device filesystem as /main.py (colon syntax). -
Run immediately (useful for testing without reboot):
mpremote connect /dev/ttyACM0 run main.py
You should see console output:
Pico-ICE LED chaser starting; pins: [2, 3, 4, 5, 6, 7, 8, 9]
And the LEDs should begin chasing. -
Make it auto‑run on power‑up:
- The file name main.py already causes auto‑run on boot for MicroPython. Power cycle the Pico‑ICE; the pattern should start without mpremote.
4) Optional: Open a MicroPython REPL for interactive tests
You can access a live REPL on the device to test individual GPIOs:
mpremote connect /dev/ttyACM0 repl
At the >>> prompt, try:
from machine import Pin
p=Pin(2, Pin.OUT); p.value(1)
LED1 should light. Turn it off:
p.value(0)
Exit REPL with Ctrl‑X (mpremote) or Ctrl‑] then q, if using screen:
screen /dev/ttyACM0 115200
# Exit: Ctrl-A, then K, then y
Step‑by‑Step Validation
Follow these precise validation steps to eliminate ambiguity:
1) Validate Raspberry Pi OS and Python stack
– Confirm Bookworm 64‑bit:
cat /etc/os-release
uname -m
Expect PRETTY_NAME with “Bookworm” and aarch64 for 64‑bit.
– Check Python version:
python3 --version
Expect Python 3.11.x.
2) Validate interfaces and configuration
– If you used raspi-config, ensure it saved:
grep -E 'i2c_arm|spi|enable_uart' /boot/firmware/config.txt
Expect:
– dtparam=i2c_arm=on
– dtparam=spi=on
– enable_uart=1
3) Validate MicroPython firmware and USB
– Unplug and replug the Pico‑ICE (not in BOOTSEL mode).
– Check device node:
ls -l /dev/ttyACM*
Expect /dev/ttyACM0 (or ACM1 if others are connected).
– If missing: try a different USB cable/port; verify it’s data‑capable.
4) Validate basic GPIO control
– Enter REPL:
mpremote connect /dev/ttyACM0 repl
– Turn on LED1 (GP2):
from machine import Pin
Pin(2, Pin.OUT).value(1)
LED1 should light. If not:
– Verify wiring: LED1 anode → 330 Ω → GP2 (header pin 4), LED1 cathode → GND (pin 3/8/13).
– Check LED polarity (long lead toward the resistor/GPIO; short lead to GND).
5) Validate the full chaser program
– Exit REPL and run:
mpremote connect /dev/ttyACM0 run main.py
– Observe:
– All LEDs briefly on (startup test), then chaser begins.
– Pattern marches LED1→LED8, then reverses back LED8→LED1.
– If the order is inverted, you may have swapped wires; compare with the wiring table.
6) Validate timing consistency
– The default STEP_DELAY_S is 0.08 s.
– To slow it down for visual inspection, edit main.py:
nano ~/pico-ice-led-chaser/main.py
Change STEP_DELAY_S to 0.2, save, re‑copy:
mpremote connect /dev/ttyACM0 fs cp main.py :main.py
mpremote connect /dev/ttyACM0 run main.py
7) Validate persistence on power cycle
– Unplug USB and plug back in (no BOOTSEL). The code auto‑runs. If it doesn’t, ensure the file is named main.py on the device:
mpremote connect /dev/ttyACM0 fs ls :
You should see /main.py.
Troubleshooting
- No /dev/ttyACM0 appears:
- Try another USB cable/port; some cables are charge‑only.
- Check dmesg:
dmesg | tail -n 100 -
If still absent, reflash MicroPython using BOOTSEL steps.
-
mpremote cannot connect:
- Ensure correct device path:
mpremote connect /dev/ttyACM0 ls -
If permission errors, try:
sudo usermod -aG dialout $USER
newgrp dialout
Then reconnect the board and try again. -
LEDs do not light:
- Polarity: The LED’s flat side/short lead must go to GND.
- Resistors: Ensure each GPIO passes through a 330 Ω to the LED anode, not to GND.
- Wrong pins: Confirm you used GP2..GP9 (Pico header pins 4,5,6,7,9,10,11,12).
- Test single pin via REPL:
from machine import Pin; Pin(2, Pin.OUT).value(1) -
Try a slower delay to better see the steps (e.g., 0.2 s).
-
LEDs too dim or too bright:
-
Use 330 Ω to 1 kΩ. Lower values increase brightness but also current. Keep GPIO current per pin under 12 mA, and total under ~50 mA to be safe.
-
Device reboots or locks:
-
Avoid short circuits. If an LED is wired backward or GPIO tied directly to GND/VBUS, the microcontroller can brown‑out or be damaged. Disconnect power and inspect wiring.
-
After copying main.py, nothing runs:
- Confirm the file exists on the device:
mpremote connect /dev/ttyACM0 fs ls : -
Ensure you used :main.py (colon indicates device path) when copying.
-
BOOTSEL drive never appears:
- Hold BOOTSEL while plugging in.
- Try a different USB port.
- If the drive still doesn’t appear, test on another computer to isolate a cable/port issue.
Improvements
Here are concrete ways to extend the “pattern‑leds‑chaser” once the basic version works:
- Pattern variety in MicroPython:
- Implement multiple patterns (e.g., one‑hot, two‑dot bounce, “comet” tail).
- Add a button input (e.g., GP14 with internal pull‑up and a pushbutton to GND) to cycle pattern or speed on press.
-
Use PWM for fading. In MicroPython, use machine.PWM on each Pin to create a fade‑in/fade‑out “comet”.
-
Host‑side controls over USB (MicroPython REPL):
-
Parse single‑character commands from sys.stdin to increase/decrease STEP_DELAY_S in real time. For example, ‘+’ speeds up, ‘-’ slows down. You can interact through mpremote repl.
-
Transition to the FPGA (iCE40UP5K) for hardware LED chaser:
- Install open‑source FPGA tools on the Raspberry Pi:
sudo apt update
sudo apt install -y yosys nextpnr-ice40 fpga-icestorm openfpgaloader - Use a Pmod (e.g., Digilent Pmod 8LD) on the Pico‑ICE Pmod header and a simple HDL chaser clocked from an internal oscillator.
-
A minimal Verilog module could look like this (illustrative — adjust constraints for Pico‑ICE Pmod pins according to the Pico‑ICE reference design and your Pmod wiring):
«`
// led_chaser.v – simple 8-bit chaser for iCE40UP5K
module led_chaser (
input wire clk, // e.g., 12 MHz internal or external
output reg [7:0] led // map to Pmod pins via .pcf
);
reg [23:0] div;
reg dir;
reg [2:0] idx;always @(posedge clk) begin div <= div + 1; if (div == 24'd0) begin // one-hot led <= 8'b0000_0001 << idx; // ping-pong index if (!dir && idx == 3'd7) dir <= 1'b1; else if (dir && idx == 3'd0) dir <= 1'b0; idx <= dir ? (idx - 1) : (idx + 1); end endendmodule
«` -
Build on the Raspberry Pi:
mkdir -p ~/pico-ice-led-chaser/fpga && cd ~/pico-ice-led-chaser/fpga
# Save led_chaser.v here, and create pico-ice.pcf with your exact pin mappings.
yosys -p "read_verilog led_chaser.v; synth_ice40 -top led_chaser -json chaser.json"
nextpnr-ice40 --up5k --json chaser.json --pcf pico-ice.pcf --asc chaser.asc
icepack chaser.asc chaser.bin - Program via openFPGALoader (Pico‑ICE typically supports USB loading through the RP2040 bridge; consult Pico‑ICE docs for the exact target and mode):
openFPGALoader -b pico-ice chaser.bin -
This moves the chaser logic into hardware for precise timing and zero CPU load.
-
Power and safety improvements:
-
If you want higher LED current or more LEDs, use a transistor array (e.g., ULN2803A) or dedicated LED driver to keep RP2040 currents within limits.
-
Packaging:
- 3D‑print a panel with 8 holes for the LEDs to create a neat light bar.
Final Checklist
- Raspberry Pi OS Bookworm 64‑bit verified on host.
- Python 3.11 verified; virtual environment created in ~/pico-ice-led-chaser/.venv.
- Packages installed: mpremote, pyserial, gpiozero, smbus2, spidev.
- Interfaces enabled (I2C/SPI/UART) via raspi-config or /boot/firmware/config.txt.
- MicroPython v1.21.0 UF2 flashed onto Pico‑ICE RP2040 via BOOTSEL.
- Wiring matches the table for GP2–GP9 with 330 Ω resistors and correct LED polarity.
- main.py copied to device and runs via mpremote; auto‑runs on power‑up.
- Step‑by‑step validation completed (single GPIO test, full chaser test, timing adjusted).
- Troubleshooting steps understood (USB device path, permissions, wiring checks).
- Optional improvements considered (alternate patterns, button control, FPGA implementation).
With the above, you have a reliable, basic “pattern‑leds‑chaser” on the Pico‑ICE (Lattice iCE40UP5K), using the Raspberry Pi host for setup and MicroPython on the RP2040 for control. This foundation prepares you for deeper exploration, including moving the pattern into the FPGA for fully hardware‑driven lighting effects.
Find this product and/or books on this topic on Amazon
As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.



