Build a Pico 2 LED Dimmer
Build a Pico 2 dimmer that turns ambient light readings into LED brightness.
Introduction
30 Second Summary
Light changes throughout the day, but a basic lamp keeps shining at the same level. Matching its brightness to the room requires a way to sense light and respond.
In this project, you will build a light-controlled LED dimmer with a Raspberry Pi Pico 2 running MicroPython. Along the way, you will see why a single threshold loses useful sensor detail.
What You'll Build
Your finished dimmer responds to a covered photoresistor with a gradual LED fade while Thonny displays the changing readings.
By the end of this project, you'll have:
- A visible GPIO blink test that makes an external LED flash at a steady interval.
- Live ADC readings that rise or fall as you change the light over a photoresistor.
- A standalone PWM dimmer with gradual brightness changes after every power-up.
- Secret Mission: Add an integer-based filter that steadies noisy brightness changes under constant light.
Are there any prerequisites?
No previous microcontroller experience is required. You need basic Python familiarity plus the listed hardware before the hands-on steps.
Before We Start
Before you connect a wire or flash firmware, take a moment to define the physical behavior you want to create. This gives every circuit connection and code change a clear purpose.
Set Up the Pico 2 and MicroPython
Your light-controlled dimmer cannot react to voltage until the Raspberry Pi Pico 2 has a verified firmware runtime. A reliable USB connection lets Thonny communicate with that runtime.
You will first confirm that every hardware part is ready. You will then install MicroPython before proving the connection through the Shell.
In this step, get ready to:
- Confirm that your Pico 2 kit contains the required hardware.
- Install Thonny 5.0.0 on your Mac.
- Prepare a verified MicroPython 1.29.0 runtime on the Pico 2.
Check your kit and install Thonny
Each component has a specific job in the dimmer. Checking the kit now prevents a missing part from interrupting the circuit work later.
- Inspect the Pico 2 for two rows of metal headers that are already soldered to the board.
- Place the breadboard beside the Pico 2.
- Locate the CdS photoresistor.
- Locate the 10 kΩ resistor.
- Locate the 220 Ω resistor.
- Locate the single-colour LED.
- Gather the male-to-male jumper wires.
- Confirm that the Micro-USB cable supports data transfer.
Good, your kit now has every physical part needed for the dimmer.
Thonny combines a Python editor with a device Shell. The macOS installer must match the processor inside your Mac.
Your current Thonny state determines the next action.
✔️ I have the required version
Thonny 5.0.0 already satisfies this project.
- Press Cmd+Space to open your Mac search bar.
- Type Thonny into the search bar.
- Press Enter to open Thonny.
You should see the Thonny application window.
ⓧ I have an older version
Installing Thonny 5.0.0 updates the application before you connect the Pico 2.
Intel Macs use thonny-5.0.0-x64.pkg. Apple silicon Macs use thonny-5.0.0-arm64.pkg.
- Visit the official Thonny download page.
- Download the package that matches your Mac processor.
You should see the downloaded .pkg file in your browser's downloads list.
- Open the downloaded package from your browser's downloads list.
The macOS installer should open.
macOS may request administrator approval because the installer adds an application to your Mac.
- Approve the administrator request if it appears.
- Complete the installer using the default installation location.
You should see confirmation that the installation finished.
- Press Cmd+Space to open your Mac search bar.
- Type Thonny into the search bar.
- Press Enter to open Thonny.
You should see the updated Thonny application window.
ⓧ Thonny is not installed
Intel Macs use thonny-5.0.0-x64.pkg. Apple silicon Macs use thonny-5.0.0-arm64.pkg.
- Visit the official Thonny download page.
- Download the package that matches your Mac processor.
You should see the downloaded .pkg file in your browser's downloads list.
- Open the downloaded package from your browser's downloads list.
The macOS installer should open.
macOS may request administrator approval because the installer adds an application to your Mac.
- Approve the administrator request if it appears.
- Complete the installer using the default installation location.
You should see confirmation that the installation finished.
- Press Cmd+Space to open your Mac search bar.
- Type Thonny into the search bar.
- Press Enter to open Thonny.
You should see the Thonny application window.
That's the software side ready. Thonny now opens on your Mac.
Thonny Will Not Open?
- Confirm that the downloaded package matches your Mac processor.
- Close any older Thonny window before running the installer again.
- Check that the installer reports a completed installation.
Ask for help if the installation still fails: help me troubleshoot Thonny installation on my Mac.
Flash MicroPython onto the Pico 2
Flashing copies the MicroPython runtime onto the board. The Pico 2 exposes temporary USB storage while you hold its BOOTSEL button.
Flashing firmware can feel risky. At this stage, there are no project files or circuit settings on the board to lose.
- Visit the official Pico 2 MicroPython download page.
- Download the .uf2 file listed for MicroPython 1.29.0.
You should see the downloaded .uf2 file in your browser's downloads list.
- Disconnect the Micro-USB cable from the Pico 2.
- Hold the BOOTSEL button while connecting the Pico 2 to your Mac with the data-capable Micro-USB cable.
You should see a USB mass-storage device on your Mac.
- Release the BOOTSEL button.
- Drag the downloaded .uf2 file onto the USB mass-storage device in Finder.
- Wait for the Pico 2 to reset automatically.
The temporary USB storage disconnects after the copy. That automatic reset confirms that the Pico 2 has accepted the firmware file.
USB Storage Does Not Appear?
- Disconnect the Pico 2 before trying the BOOTSEL sequence again.
- Keep BOOTSEL pressed until the USB storage appears.
- Try another data-capable Micro-USB cable if the board receives power without appearing on your Mac.
Ask for help with the connection: help me get my Pico 2 into BOOTSEL mode.
Connect Thonny to the MicroPython Shell
The Shell gives you a REPL for sending one Python instruction at a time to the Pico 2. A successful response proves that the editor is talking to the runtime on the board.
- Disconnect any other Pico boards from your Mac.
- Return to Thonny.
Keep the flashed Pico 2 connected through the data-capable Micro-USB cable.
- Click Run in Thonny's top menu.
- Select Select interpreter.
You should see the interpreter selection dialog.
- Choose MicroPython (Raspberry Pi Pico) as the interpreter.
The dialog should now show the Pico interpreter as your selected device type.
- Leave automatic port detection enabled.
- Confirm the interpreter selection using the dialog's confirmation control.
The Shell should show a MicroPython prompt from the connected Pico 2.
Before you test the connection, what do you expect the Shell to print beneath the prompt?
- Send a test instruction to the Pico 2 by entering this in Thonny's Shell:
print("Pico 2 ready")
What Does This Test Prove?
- The Shell sends the print() instruction to the MicroPython runtime on the Pico 2.
- The returned text proves that Thonny can communicate with the board.
You should see Pico 2 ready below the MicroPython prompt. Your Pico 2 is now accepting Python instructions from Thonny.
No MicroPython Prompt?
- Confirm that the Pico 2 remains connected through the data-capable cable.
- Confirm that MicroPython (Raspberry Pi Pico) is still selected as the interpreter.
- Disconnect other Pico boards so automatic port detection finds only this device.
Ask for help if the Shell remains unavailable: help me connect Thonny to my Pico 2.
You have flashed MicroPython onto the Pico 2 before verifying a live connection through Thonny. Next, you will connect an external LED to GP15 before making it blink under firmware control.
Make an External LED Blink
You have already proved that your Raspberry Pi Pico 2 can run MicroPython from Thonny. The next proof leaves the Shell and reaches a real component.
An external LED turns a GPIO signal into visible evidence. You will build a current-limited circuit on a breadboard. Then you will pulse GP15 every 500 milliseconds.
In this step, get ready to:
- Wire an external LED to GP15 through a 220 Ω resistor.
- Create a temporary MicroPython blink script.
- Verify a steady hardware blink from GP15.
Wire the external LED circuit
The LED has polarity, so its leg direction matters. The 220 Ω resistor limits the current flowing through it.
Working without power keeps an accidental breadboard short from reaching the Pico 2. The Micro-USB cable stays disconnected while you build the circuit.
- Click Stop/Restart backend in Thonny to leave the Pico 2 idle.
- Unplug the Micro-USB cable from the Pico 2.
- Seat the Pico 2 across the breadboard's centre channel.
- Insert the LED so its long leg and short leg occupy different breadboard rows.
- Place the 220 Ω resistor between the GP15 row and the LED's long-leg row.
- Connect the LED's short-leg row to a GND row with a jumper wire.
How Does the Circuit Path Work?
When GP15 is high, current passes through the resistor and the LED before returning through GND. The LED lights only when its long leg faces the resistor side of this path.
When GP15 is low, the voltage across the LED disappears. This gives your script direct control over the light.
Create the blink script
MicroPython's Pin class lets your script control GP15 as a digital output. A repeating loop can alternate that output between high and low states.
- Switch back to the Thonny window from earlier.
- Create a blank script with the new-file control in Thonny's top toolbar.
- Add the blink sequence by copying this code into the editor:
from machine import Pin
from time import sleep_ms
# Configure GP15 as a digital output for the external LED
led = Pin(15, Pin.OUT)
# Repeat high and low states to create a visible blink
while True:
led.value(1)
sleep_ms(500)
led.value(0)
sleep_ms(500)
What Does This Code Do?
- The imports provide the digital pin control and millisecond delay used by the script.
- The Pin(15, Pin.OUT) expression configures GP15 as an output. The led variable keeps a reference to that output.
- The value(1) call drives the output high.
- The value(0) call drives the output low.
- Each sleep_ms(500) call holds the current state for half a second.
- The while True loop repeats the sequence until you stop the backend.
- Click File in Thonny's menu bar.
- Click Save as.
- Choose your Mac as the save location.
- Enter led_test.py as the file name.
- Confirm the save.
You should see led_test.py in the editor tab. This confirms that your temporary hardware test is saved on your Mac.
✔️ Awesome, I've got everything!
Great. Your led_test.py script is ready to send to the Pico 2.
ⓧ I'd like to double check the full code
Compare your complete led_test.py file with this reference.
from machine import Pin
from time import sleep_ms
# Configure GP15 as a digital output for the external LED
led = Pin(15, Pin.OUT)
# Repeat high and low states to create a visible blink
while True:
led.value(1)
sleep_ms(500)
led.value(0)
sleep_ms(500)
The comments identify the output setup and the repeating blink sequence. Check the indentation inside while True carefully.
Run the hardware test
The infinite loop keeps control of the Pico 2 until you stop the backend. Thonny's Shell may stay busy while the script runs.
- Reconnect the Pico 2 to your Mac with the data-capable Micro-USB cable.
- Wait for the MicroPython prompt to return in Thonny's Shell.
Before you run the script, which pattern do you expect to see from the external LED?
- Click Run in Thonny's menu bar.
- Click Run current script. You can also press F5.
You should see the external LED switch on and off at a steady half-second interval. That physical blink proves GP15 now controls a real output from your firmware.
LED Staying Dark?
Disconnect the Micro-USB cable before adjusting the circuit. Check that the LED's long leg faces the resistor and its short leg reaches GND.
Confirm that the resistor connects to the breadboard row for GP15. Check that each component leg occupies the intended row.
Compare your script with the full-code tab if Thonny reports a Python error. Pay close attention to the indentation beneath while True.
Help me troubleshoot my Pico 2 LED circuit and led_test.py script.
- Click Stop/Restart backend in Thonny before making another circuit change.
You should see the steady blinking stop. Your first firmware-controlled circuit is now complete.
Your Pico 2 can control a physical output. Next, you will add the photoresistor and turn ambient light into changing ADC readings.
Read Light and Test Threshold Control
Your Raspberry Pi Pico 2 has already driven an external LED through GPIO. You cleared a real hardware milestone by making that LED blink with MicroPython.
A light-controlled dimmer needs a changing measurement from the room around it. In this step, Thonny shows that measurement while a midpoint threshold reduces it to two possible LED states.
In this step, get ready to:
- Build a photoresistor circuit that produces a changing voltage.
- Read the voltage through GP26's analog input.
- Test how a midpoint threshold controls the external LED.
Wire the light sensor divider
A voltage divider converts the photoresistor's changing resistance into a changing voltage. The ADC on GP26 turns that voltage into a number your program can read.
Circuit changes can feel delicate at first. Removing USB power keeps the Pico 2 inactive while you place the new components.
- Unplug the Micro-USB cable from the Pico 2.
- Insert the photoresistor into the breadboard with each leg in a separate row.
You should now see the photoresistor bridging two separate breadboard rows.
- Connect one photoresistor row to 3V3 with a jumper wire.
- Connect the other photoresistor row to GP26 with another jumper wire.
The second photoresistor row is now the measurement junction connected to GP26.
- Insert one lead of the 10 kΩ resistor into the measurement junction.
- Connect the resistor's free lead to GND with a jumper wire.
Your measurement junction should now contain the photoresistor leg, the GP26 jumper, and one lead of the 10 kΩ resistor.
How does the divider respond?
With this wiring, more light produces a higher ADC reading. Covering the photoresistor produces a lower reading.
The external LED circuit on GP15 stays unchanged. Your program can now connect the sensor input to that physical output.
Create the threshold controller
The ADC returns a value from 0 to 65535. A midpoint threshold of 32768 divides that range into a low group and a high group.
- Replace the blink program in Thonny's editor with the threshold controller below:
from machine import ADC, Pin
from time import sleep_ms
# Read the voltage produced by the photoresistor divider
light_sensor = ADC(Pin(26))
# Keep the external LED under digital control for this test
led = Pin(15, Pin.OUT)
while True:
# Capture the current 16-bit light measurement
raw = light_sensor.read_u16()
# Reduce the continuous reading to one of two LED states
if raw < 32768:
led.value(0)
else:
led.value(1)
print("adc:", raw)
sleep_ms(100)
What does this code do?
- The ADC(Pin(26)) object connects the program to the voltage divider.
- The read_u16() method stores each scaled ADC measurement in raw.
- The if raw < 32768 condition turns the LED off for the lower half of the range. Values in the upper half turn it on.
- The print() call sends each reading to the Shell for debugging.
- The sleep_ms(100) delay spaces the readings apart so the stream remains readable.
- Save the script on your Mac as threshold_dimmer.py with Thonny's File, Save as option.
- Reconnect the Micro-USB cable to the Pico 2.
You'll see the MicroPython prompt return in Thonny's Shell once the board reconnects.
Before you run the script, predict whether the LED can show every change in the sensor reading.
- Click Run current script in Thonny or press F5.
You'll see a stream of changing adc: values in the Shell. The LED changes only when the reading crosses 32768.
Readings stuck or LED unchanged?
If the reading stays fixed, check that GP26 connects to the junction shared by the photoresistor and the 10 kΩ resistor. Check that the resistor's free lead reaches GND.
If the readings change but the LED never switches, confirm that the existing LED circuit still connects to GP15 through the 220 Ω resistor.
help me troubleshoot fixed ADC readings or an unchanged LED on my Pico 2 circuit.
✔️ Awesome, I've got everything!
Great. Double check that threshold_dimmer.py is saved before measuring the sensor range.
ⓧ I'd like to double check the full code
Compare your saved threshold_dimmer.py file with this complete threshold controller.
from machine import ADC, Pin
from time import sleep_ms
# Read the voltage produced by the photoresistor divider
light_sensor = ADC(Pin(26))
# Keep the external LED under digital control for this test
led = Pin(15, Pin.OUT)
while True:
# Capture the current 16-bit light measurement
raw = light_sensor.read_u16()
# Reduce the continuous reading to one of two LED states
if raw < 32768:
led.value(0)
else:
led.value(1)
print("adc:", raw)
sleep_ms(100)
Measure the threshold's limit
The changing Shell values show how much detail the sensor captures. Comparing those values with the LED exposes how much detail one threshold keeps.
- Leave the photoresistor uncovered under your room light.
- Wait for the Shell readings to settle around a small range.
- Record a typical bright value here: your bright ADC reading.
- Cover the photoresistor with your hand.
- Wait for the Shell readings to settle around a new range.
- Record a typical covered value here: your covered ADC reading.
Your uncovered reading should be higher than your covered reading. This confirms that the divider is responding to light.
Before the final check, predict whether intermediate ADC values produce intermediate LED brightness.
- Move your hand slowly over the photoresistor while watching the Shell values and the LED.
You'll see many different ADC values while the LED still snaps abruptly between off and on. That shortfall is intentional because one threshold maps the entire sensor range to only two output states.
You have proved that the sensor captures more detail than digital threshold control can display. Next, you'll use PWM to turn that discarded range into gradual LED brightness.
Turn Readings into Smooth Brightness
Your threshold controller proved that the Raspberry Pi Pico 2 can read light through its ADC. It also exposed the cost of reducing thousands of readings to two LED states.
This step uses PWM to preserve that useful range. Each sensor reading becomes a proportional duty cycle. The LED fades as the light level changes.
In this step, get ready to:
- Replace digital LED switching with proportional PWM control.
- Compare printed ADC values with PWM duty values.
- Save main.py to the Pico 2 for automatic startup.
Replace digital output with PWM
MicroPython's ADC.read_u16() returns values from 0 to 65535. The PWM.duty_u16() method accepts the same scale. The sensor reading can therefore become the LED duty value directly.
- Click Stop/Restart backend in the Thonny toolbar.
The running threshold controller stops. You'll see the MicroPython prompt return in the Shell.
- Switch back to the threshold_dimmer.py editor tab.
- Select all the code by pressing Cmd+A.
- Replace the selected code with the proportional dimmer program below:
from machine import ADC, Pin, PWM
from time import sleep_ms
light_sensor = ADC(Pin(26))
led = PWM(Pin(15), freq=5000, duty_u16=0)
while True:
raw = light_sensor.read_u16()
brightness = raw
led.duty_u16(brightness)
print("adc:", raw, "pwm:", brightness)
sleep_ms(100)
What does this code do?
- The first import adds PWM alongside the existing ADC and Pin classes.
- The light_sensor object continues reading the voltage-divider signal through GP26.
- The led object controls GP15 with a PWM frequency of 5000.
- The brightness = raw line keeps the sensor value on the same 16-bit scale used by the PWM duty cycle.
- The print() call places the ADC reading beside the PWM value for comparison.
- The sleep_ms(100) delay keeps the loop responsive while leaving the Shell output readable.
Thonny's save dialog is a little fiddly because it can target either your Mac or the board.
- Click File in the top menu bar.
- Click Save as.
You'll see a destination chooser for the new file.
- Select your Mac as the destination.
- Choose your Desktop as the file location.
- Enter main.py as the filename.
- Press Enter to save the file.
The editor tab now shows main.py. Your Mac has the finished program.
Before you run the script, do you think the LED will still jump between two states or pass through intermediate brightness levels?
- Click Run current script in the toolbar or press F5.
Each Shell line shows adc: beside pwm:. Both labels have the same number.
The LED should pass through intermediate brightness levels as the light changes. You have turned the sensor's changing readings into proportional output.
LED Not Fading?
Confirm that the LED setup line reads led = PWM(Pin(15), freq=5000, duty_u16=0). A digital Pin.OUT object still produces only two output states.
Check that the loop passes brightness into led.duty_u16(). Also confirm that the external LED remains connected to GP15 through the 220 ohm resistor.
Help me debug a Pico 2 PWM LED that does not fade with changing ADC readings.
✔️ Awesome, I've got everything!
Great. Double check that main.py is saved on your Mac before you continue.
ⓧ I'd like to double check the full code
Compare your local main.py with the complete program below.
from machine import ADC, Pin, PWM
from time import sleep_ms
light_sensor = ADC(Pin(26))
led = PWM(Pin(15), freq=5000, duty_u16=0)
while True:
raw = light_sensor.read_u16()
brightness = raw
led.duty_u16(brightness)
print("adc:", raw, "pwm:", brightness)
sleep_ms(100)
Compare ADC and PWM values
The matching ADC and PWM values expose the direct mapping inside the loop. Changing the light should move both values through more than two states.
- Cover the photoresistor slowly with your hand.
- Pause at several partially covered positions.
- Uncover the photoresistor slowly.
At each position, you'll see a different number in the Shell. The LED now passes through intermediate brightness levels instead of snapping at one threshold.
Save main.py to the Pico 2
A file named main.py on the board runs during MicroPython startup. Saving the dimmer there makes the program start whenever the Pico 2 receives power.
- Click Stop/Restart backend in the Thonny toolbar.
The stream of ADC and PWM values stops. You'll see the MicroPython prompt return in the Shell.
- Click File in the top menu bar.
- Click Save as.
The destination chooser opens again.
- Select the Raspberry Pi Pico as the destination.
- Enter main.py as the filename.
- Press Enter to save the device copy.
- Confirm that main.py is listed on the Raspberry Pi Pico.
Power cycling the board is safe after the backend has stopped.
- Disconnect the Micro-USB cable from the Pico 2.
Before you reconnect it, do you expect the dimmer to start without pressing Run?
- Reconnect the Micro-USB cable without holding BOOTSEL.
- Wait for Thonny to reconnect to the Pico 2.
- Shade the photoresistor slowly after the Shell begins printing.
You'll see changing adc: and pwm: values without pressing Run. The LED brightness changes gradually as you shade the sensor.
Your standalone dimmer now starts from power-up. The complete sensor-to-output loop works without an editor command.
Secret mission
Filter Noisy Sensor Readings
Real sensor readings can shift slightly under steady light. Add an integer-based filter that steadies the LED brightness while preserving its response to genuine light changes.
Clean Up Your Resources
Clean Up Your Resources
Your MicroPython dimmer uses hardware you already purchased. It creates no subscriptions or ongoing software charges.
Choose how you want to leave the project. You can keep the dimmer powered, pause it by unplugging the cable, or remove the circuit and project files.
Resources you used:
- The breadboard circuit connected to your Raspberry Pi Pico 2.
- The device copy of main.py stored on the Pico 2.
- The Mac copy of main.py stored in the location where you saved it.
Keep everything running
No action is needed. Choose this if you want to keep demonstrating or experimenting with the light-controlled dimmer.
- Leave the LED and photoresistor circuit assembled on the breadboard.
- Keep both copies of main.py.
- Leave the Micro-USB cable connected whenever you want the dimmer running.
Pause - I'll come back to this later
Pausing removes power from the circuit while preserving the wiring and code. You can resume from the same state later.
- Stop the running script in Thonny.
- Unplug the Micro-USB cable from the Pico 2.
- Leave the breadboard circuit assembled.
- Keep both copies of main.py.
The Pico 2 retains main.py without power. Reconnecting USB power starts the dimmer automatically.
Delete - I don't want to use this again
Remove the project scripts and dismantle the breadboard circuit. The MicroPython runtime remains installed for future Pico 2 projects.
- Stop the running script in Thonny.
- Find main.py in Thonny's Raspberry Pi Pico device file list.
- Select main.py.
- Remove the selected device file with the file list's delete control.
- Confirm that main.py no longer appears in the Raspberry Pi Pico device file list.
The project script is now gone from the board. The firmware remains ready for another program.
Dismantling the circuit is safe once USB power is disconnected.
- Unplug the Micro-USB cable from the Pico 2.
- Remove the jumper wires from the breadboard.
- Remove the LED from the breadboard.
- Remove the 220 Ω resistor from the breadboard.
- Remove the photoresistor from the breadboard.
- Remove the 10 kΩ resistor from the breadboard.
You should now see no electrical connections between the Pico 2 and the breadboard components.
The Mac copy is separate from the device copy. Remove it if you want to clear the project from your computer as well.
- Use Finder to locate the Mac copy of main.py in the location where you saved it.
- Move the selected main.py file into the Trash.
- Empty the Trash to remove the file from your Mac.
- Confirm that Finder no longer lists main.py in its saved location.
Nice Work!
Nice Work!
Fantastic work! Your Raspberry Pi Pico 2 now reads ambient light through a real breadboard circuit. It converts each reading into steady LED brightness through PWM.
You've learned how to:
- Set up a verified firmware environment with MicroPython through Thonny's REPL.
- Build a sensor-to-output circuit that samples a photoresistor voltage divider through GP26's ADC to control an external LED from the GP15 GPIO output.
- Expose the information loss of threshold-only control with an LED that snaps between two states. Convert the same 16-bit readings into proportional brightness through PWM from a startup-ready main.py.
- Secret Mission: Added integer-based exponential smoothing that steadies noisy ADC readings without making the LED feel sluggish.
Ready to quiz yourself?