Program the Night-Light
A Raspberry Pi Pico, a light sensor and two dozen lines of MicroPython: a lamp that decides for itself — with every threshold and fade under your editorial control.
Builds on: 11.2 Reading the Analog World11.1 Hello, Microcontroller8.1 Build the PWM Dimmer6.1 The Operational Amplifier
The mission
Build a lamp that switches itself on at dusk, off at dawn, with a knob to set your idea of “dark” — and, because software makes it a two-line change, a fade mode where brightness grows with darkness. This is the complete embedded-systems loop: sense → decide → act, and every stage is a lesson you own: divider sensing (2.2), ADC (11.2), hysteresis (6.1), PWM dimming (Unit 8).
Shopping list (beyond your kit)
| Part | Spec | Qty | ≈ Cost |
|---|---|---|---|
| Raspberry Pi Pico | Pico or Pico W, with header pins (or solder them — a rite of passage) | 1 | $5–7 |
| Micro-USB cable | data-capable, not charge-only! | 1 | $2 |
| Photoresistor (LDR) | GL5528 or similar | 1 | $0.50 |
| Resistor | 10 kΩ — brown·black·orange | 1 | $0.10 |
| Reused: breadboard, LED, 470 Ω, jumpers, and the 100 kΩ potentiometer from Unit 8. The 9 V battery retires — the Pico runs from USB and its pins speak 3.3 V. | |||
The Pico is a 3.3 V citizen. Power your breadboard rails from its 3V3(OUT) pin — never connect the old 9 V battery to any Pico pin, and never feed a GPIO more than 3.3 V. (Your LED at 3.3 V through the same 470 Ω still lights fine: (3.3 − 1.8)/470 ≈ 3 mA — dimmer than before, and PWM at 100% is its new maximum.)
The wiring
Seat the Pico across the breadboard’s centre gap (USB connector at one end, like the 555’s notch rule at a bigger scale). Pin numbers below are the physical pins counted counter-clockwise from the USB end — the same DIP convention you learned on the 555, forty pins instead of eight:
| # | From | To | With |
|---|---|---|---|
| 1 | Pico 3V3(OUT) — physical pin 36 | + rail | jumper (red) |
| 2 | Pico GND — physical pin 38 | − rail | jumper (black) |
| 3 | + rail | a free column (the sensor node) | LDR |
| 4 | sensor node | − rail | 10 kΩ (divider's bottom leg) |
| 5 | sensor node | GP26 / ADC0 — physical pin 31 | jumper |
| 6 | pot outer legs | + rail and − rail | the pot IS a divider (2.2) |
| 7 | pot wiper (middle leg) | GP27 / ADC1 — physical pin 32 | jumper |
| 8 | GP15 — physical pin 20 | LED anode via 470 Ω, cathode to − rail | your standard LED branch |
| 9 | Pico USB | computer | the data cable |
Notice what the divider does here: bright room → LDR small → sensor node pulled toward 3.3 V → high ADC reading. Dark room → low reading. The code’s comparisons follow from that one sentence.
Breathing life into it
- Download the MicroPython firmware file (a
.uf2) for the Pico from micropython.org. - Hold the Pico’s BOOTSEL button while plugging in the USB — it appears as a tiny flash drive.
- Drag the .uf2 onto it. The drive vanishes; the Pico reboots as a Python machine.
- Install Thonny (free, thonny.org), choose MicroPython (Raspberry Pi Pico) as the interpreter — bottom-right corner.
- Paste the program below and press Run. Cover the LDR with your hand.
- When it behaves, save it to the Pico as
main.py— from then on it runs on power-up, no computer needed. Congratulations: you have shipped firmware.
The firmware
from machine import Pin, ADC, PWM
from time import sleep_ms
ldr = ADC(26) # light sensor divider -> GP26
knob = ADC(27) # threshold potentiometer -> GP27
led = PWM(Pin(15)) # night-light LED -> GP15
led.freq(1000) # 1 kHz: your Unit 8 dimmer, in code
HYST = 3000 # Lesson 6.1: no chatter at dusk
lamp_on = False
while True:
light = ldr.read_u16() # 0 (dark) .. 65535 (bright)
threshold = knob.read_u16() # set by the knob
if light < threshold - HYST: # definitely dark
lamp_on = True
if light > threshold + HYST: # definitely bright
lamp_on = False
if lamp_on:
darkness = max(0.0, min(1.0, (threshold - light) / 20000))
led.duty_u16(int(20000 + 45535 * darkness))
else:
led.duty_u16(0)
sleep_ms(50) # 20 decisions per secondTwo dozen lines, and every one traceable to a lesson: the two ADC reads (11.2), the hysteresis pair of ifs (6.1), the PWM duty computation (Unit 8), the superloop with its polite sleep_ms(50) (11.1).
If it misbehaves
| Symptom | Likely cause | Fix |
|---|---|---|
| No board in Thonny | Charge-only USB cable, or interpreter not selected | Use a data cable; pick the Pico interpreter bottom-right |
| LED never lights | LED direction or wrong pin | Long leg toward the 470 Ω on GP15 (physical pin 20) |
| Lamp logic inverted | LDR and 10 kΩ swapped in the divider | LDR to +, fixed resistor to − (or swap the comparisons) |
| Knob does nothing | Outer leg wired instead of the wiper | Middle leg to GP27 — same mistake, same fix as Unit 8 |
| Flickers at dusk | HYST too small for your room | Raise HYST — you have a lab above to pick a value |
Experiments — now it's software
- Breathe: replace the fade math with a slow sine of duty — a MacBook-style breathing lamp. Three lines.
- Log the dawn:
print(light)each loop and watch sunrise as a column of numbers in Thonny. Your first data logger. - Average 16 ADC reads (11.2's tip) and watch the dusk transition steady itself.
- Pico W owners: the same chip has Wi-Fi — a web-controlled lamp is a weekend, not a career.
Where you stand now
Three builds sit on your desk: a blinker you sized with RC math, a dimmer you steered with diodes, and a lamp that senses and decides because you told it how. You can read schematics, size components, debug with Kirchhoff and a multimeter, reason about signals in time and frequency, follow logic from gate to program counter — and now flash firmware. One summit remains: the master course — sampling and Fourier, PID control, the art of real-world tolerances and debugging — ending with the finest full-circle build this course could offer: your own oscilloscope, pointed back at everything you have made.
⚡ Lab — Digital Twin — the Whole Signal Chain
Every block of the night-light, live: room light → LDR → divider → ADC → decision → PWM → LED. Play with dusk before you wire it.
- Sweep room light down slowly, then back up — the on and off points differ. That’s your HYST.
- Set the threshold knob low: the lamp waits for deep darkness.
- Compare switch mode and fade mode — one variable in the firmware.