2.3 Photoresistor Analog Read
The goal of this example is to show you how to measure light intensity using a photoresistor (LDR) connected to one of the analog input pins on the NULA MINI board.
You will learn how to use the ADC class to read a continuous range of values instead of simple on/off signals.
This is your first step into analog input, essential for sensors like light, temperature, or sound sensors.
In this documentation you will learn:
- How analog inputs differ from digital inputs.
- How to connect a photoresistor to the NULA MINI board.
- How to read analog values with
ADCandread(). - What the
attensetting does and why it matters.
Hardware required:
- 1x Soldered NULA MINI board
- 1x Breadboard
- 1x Photoresistor (LDR)
- 1x 10kΩ resistor
- 4x Jumper wires
- 1x USB-C cable
Putting the components together
Follow the seven steps below. Each photo is taken from the same position, so you can compare it with the previous one and see exactly what changed.
1. Insert the NULA MINI board on the breadboard
Push the board into one end of the breadboard so that its two rows of pins sit on either side of the centre channel, with the chip facing down.

2. Bring out power and ground
Two jumpers first, so the circuit has something to sit between.
- From j30 (
GND) across to the blue − rail. That rail becomes your ground line. - From j29 (
3V3) up to row 14.

3. Add the photoresistor
The LDR bridges row 14, where 3.3 V now arrives, and row 11.

4. Add the 10kΩ resistor
The resistor continues the chain downwards: from row 11 to row 9.

5. Connect the resistor to ground
One jumper from row 9 across to the blue − rail, which is already connected to GND.

The chain is now complete: 3.3 V → LDR → 10kΩ → GND. Current flows through both parts in series.
6. Tap the middle with IO5
Now the measurement itself. Run a jumper from j28 (IO5) to row 11, the row shared by the lower leg of the LDR and the upper lead of the resistor.

IO5 measures.7. Connect the board to your computer

IO5 taps the row where they meet.How a voltage divider works
The board cannot measure resistance. It can only measure voltage. A photoresistor changes its resistance with light, so on its own it gives the board nothing to read.
Pairing it with a fixed resistor solves that. The two of them in series form a voltage divider, and the voltage at the point between them depends on the ratio of the two resistances:
- In bright light the resistance of the LDR drops, so it keeps less of the 3.3 V for itself and the voltage at
IO5rises. - In darkness the resistance of the LDR climbs, it keeps more of the voltage, and the reading at
IO5falls.
That is why the fixed resistor matters as much as the sensor: without it there is no midpoint to measure.
Understanding analog input
Unlike digital pins that only read 1 (high) or 0 (low), analog pins can measure a continuous range of voltages between 0 V and 3.3 V. The ADC (Analog-to-Digital Converter) inside the NULA MINI converts these voltages into numbers between 0 and 4095, because it uses a 12-bit ADC.
Here is what the board in the photos actually measured, in an ordinary office in daylight:
| Lighting condition | Reading |
|---|---|
| A phone torch held against the sensor | 2200–2530 |
| Normal indoor room light | 1000–1070 |
| Covered with a hand | 85–150 |
In MicroPython the converter is its own class, imported alongside Pin:
from machine import Pin, ADC
You then wrap the pin you want to measure in an ADC object, and read it with read():
ldr = ADC(Pin(LDR_PIN), atten=ADC.ATTN_11DB)
light_value = ldr.read()
analogReadResolution(12) in setup() and then analogRead(LDR_PIN), MicroPython does the setting up when the object is created. The 12-bit range is what read() gives you on this chip, so there is nothing to configure for it.The atten setting chooses how large a voltage the converter can measure. ADC.ATTN_11DB is the widest of them, the one that covers the full 0 V to 3.3 V swing, and it is what this example wants, because the photoresistor is expected to move across the whole range.
atten out even though this firmware would have picked it for you. On the MicroPython build these pages were tested with (v1.29.0, ESP32_GENERIC_C6), ADC(Pin(5)) with no atten argument behaves exactly like ADC.ATTN_11DB: in the same room light both read about 1050. Other boards and older builds default to the narrowest setting instead, so spelling it out is what makes the example portable. It is not a line you can rely on being unnecessary.atten is what makes a photoresistor look broken. Swap ADC.ATTN_11DB for ADC.ATTN_0DB and the converter only measures up to about 1 V, so ordinary room light already pushes it near the top: the same sensor that reads 1050 at ATTN_11DB reads 3930 at ATTN_0DB, and it flattens against 4095 the moment the light improves. A reading stuck high that will not move is far more often this than a wiring fault.Code
# ADC stands for Analog to Digital Converter. Where Pin can only tell us high or low, the ADC measures the actual
# voltage on a pin and gives us a number for it, which is what we need to measure a whole range of light levels.
from machine import Pin, ADC
import time
# This is a variable to which we assign the number of the pin that we connected the output of the photoresistor to.
# In this example, we will use IO5, which supports analog input.
#
# This example also needs a 10k resistor. A photoresistor changes its resistance with light, but the board can only
# measure a voltage, so we pair the two in what is called a voltage divider.
LDR_PIN = 5
# Here we create our ADC object, which we named "ldr". We hand it a Pin, and from then on we read the light level
# through this name. The atten setting chooses how large a voltage the converter can measure. ADC.ATTN_11DB is the
# widest setting, which lets us use the full range of the photoresistor from complete darkness to bright light.
ldr = ADC(Pin(LDR_PIN), atten=ADC.ATTN_11DB)
# Print a startup message to confirm that the program is running.
print("Cover or shine light on the sensor to see value changes...")
while True:
# read() reads the voltage at the given analog pin and converts it into a number. Since the NULA board uses a
# 12-bit ADC, the returned value will range from 0 to 4095.
# The higher the light intensity, the lower the resistance of the photoresistor, and the higher the voltage read.
light_value = ldr.read()
# Print the measured value to the console.
print("Light level:", light_value)
# time.sleep() starts a pause in the code, given in seconds. Half a second makes the readings easy to follow
# with your eyes. Feel free to experiment with this value.
time.sleep(0.5)
What you should see
Press Run. The startup message appears, and then a new reading arrives in the Shell twice a second. Leave the sensor alone for a moment and the number barely moves, just a few counts of jitter either side of whatever your room light gives:
Cover or shine light on the sensor to see value changes...
Light level: 1057
Light level: 1058
Light level: 1058
Light level: 1055
Light level: 1057
Light level: 1058
Now put your hand over the sensor and watch the numbers drop.

This is the real Shell output from shading the sensor with a hand, lifting the hand away, and then holding a phone torch against it:
Light level: 426
Light level: 147
Light level: 372
Light level: 106
Light level: 111
Light level: 107
Light level: 101
Light level: 97
Light level: 86
Light level: 266
Light level: 113
Light level: 1036
Light level: 1057
Light level: 1051
Light level: 1063
Light level: 1066
Light level: 1052
Light level: 1182
Light level: 1571
Light level: 2121
Light level: 2502
Light level: 2526
Light level: 2465
Light level: 2325
Light level: 2378
147 followed by 372, or the 86 followed by 266: the sensor is fast enough to see a hand still moving, so every wobble and every gap between your fingers shows up as its own reading. The number only settles once your hand does. Notice too that it takes two or three readings to climb from room light to torch light. That is the half-second time.sleep(), not the sensor being slow.IO5 really is tapping the junction row and not one of the outer rows.atten setting rather than at a faulty sensor. Check that the line reads ADC.ATTN_11DB and not ADC.ATTN_0DB.Full example
Check out the full example code on the link below:
2.3_Photoresistor_Analog_Read.py
Example that shows how to read analog values from a photoresistor and print light levels to the console.