Learn
Measuring Light with an LDR (Photoresistor, GL5528) on a Raspberry Pi 4B
Build a voltage divider with a GL5528 LDR and a resistor, read it through an MCP3008 ADC, and watch the number change as you change the light level in the simulator.
Note: the LDR Photoresistor is a VIP-tier component in the simulator — you’ll need a VIP plan to use it there.
Try this directly in the free Raspberry Pi 4B simulator — no hardware or signup required. New to the GPIO header? Start with the interactive pinout guide.
What you’ll need
- Raspberry Pi 4B
- MCP3008 ADC
- LDR Photoresistor (GL5528)
- Resistor (10kΩ)
Step by step
- Drag Raspberry Pi 4B onto the Canvas.
- Drag the MCP3008 ADC onto the Canvas. The Pi can't read analog voltages, so the LDR's signal has to go through this chip.
- Drag the LDR Photoresistor (GL5528) and a Resistor onto the Canvas. Select the Resistor and set it to 10 kΩ in its settings panel.
- Wire the MCP3008 to the Pi's fixed SPI pins (physical/BOARD numbering):
| MCP3008 pin | Pi pin |
|---|---|
| VDD | 3.3V |
| VREF | 3.3V |
| AGND | GND |
| DGND | GND |
| CLK | Physical pin 23 (SCLK) |
| DOUT | Physical pin 21 (MISO) |
| DIN | Physical pin 19 (MOSI) |
| CS/SHDN | Physical pin 24 (CE0) |
- Build the voltage divider. The LDR has two legs and no polarity; the Resistor below it is the pull-down:
| From | To |
|---|---|
| Pi 3.3V pin | one leg of the LDR |
| other leg of the LDR | one end of the 10kΩ Resistor (this shared point is the divider's middle) |
| other end of the Resistor | Pi GND pin |
| the shared middle point | MCP3008 CH0 |
Why the pull-down: an LDR's resistance changes with light, but the Pi and the MCP3008 measure voltage, not resistance. Two resistors in series split the 3.3V between them, so the voltage at their middle point depends on the ratio. In bright light the LDR's resistance drops, most of the 3.3V lands across the fixed resistor, and the reading rises; in the dark the LDR's resistance is huge, so the middle point is pulled down toward 0V. Swap the LDR and the resistor and the behavior inverts (bright = lower reading).
- Go to the Code tab and write a script that reads the channel:
import spidevimport asyncio spi = spidev.SpiDev()spi.open(0, 0)spi.max_speed_hz = 1350000 def read_channel(channel): adc = spi.xfer2([1, (8 + channel) << 4, 0]) return ((adc[1] & 3) << 8) + adc[2] # returns 0-1023 while True: value = read_channel(0) print(f"Light reading: {value} / 1023") await asyncio.sleep(0.5)- Select the LDR and set its Light level (lux) in the settings panel — type a value from 0.1 to 100000, or click a preset such as Dark, Dim room, Office, Overcast or Sunlight. The panel also shows the resistance that light level gives. Click Start.
Light level can only be changed while the simulation is stopped. Stop, pick a different lux value, and Start again to see the reading move. With a 10kΩ resistor you should see roughly 100 at 0.5 lux (dark), 409 at 10 lux, 856 at 300 lux (a normal room) and close to 1000 in bright sunlight.
What “working correctly” looks like
- The Console prints "Light reading" lines every half second.
- The higher the lux value you set, the higher the reading: it stays low in the dark, sits mid-to-high in a normal room and approaches the top of the 0-1023 range in bright light.
- The LDR's face on the canvas gets slightly lighter as you raise the light level.
If something’s wrong
- The reading is always 0 → check the MCP3008's VDD, VREF, AGND and DGND are all wired, that CS goes to physical pin 24 (CE0), and that the shared middle point of the divider really goes to CH0.
- The reading doesn't change when you change the light level → make sure the LDR is actually in the circuit (3.3V → LDR → middle point) and that you changed the level while stopped and then restarted.
- The reading is always near 1023, or always near 0 → the divider is missing a half: a missing pull-down resistor to GND leaves the middle point at 3.3V, and a missing LDR leaves it at 0V.
- The values look inverted compared with a tutorial you're following → that tutorial probably puts the LDR on the GND side of the divider; either order works, they just give opposite readings.
- Nothing at all in the Console → confirm the script opens spi.open(0, 0), the same CE0 chip-select pin you wired.