ESP32-S3 Lab · Day 17 of 30

Make a lamp
read the room

Every knob and button so far has been something you operated. Today a photoresistor and one fixed resistor turn the room's light into a voltage, the ADC turns that voltage into a number, and a threshold turns that number into a decision — set the light level that counts as dark, and the lamp switches itself.

About 25 minutesArduino firstMicroPython optionalNo electronics assumed
Agent assist code TSK-DAY17-NIGHTLAMP

Hand this to an agent so it can pull the lesson packet and coach you step by step.

01 First, know the pieces

What you need

Seven things, one of them new. Tap Define on any part you haven't met — the answer opens as a field note you can read and dismiss without losing your place.

Official manual photo of the ESP32-S3 development board.
Manual photo

ESP32-S3 board

The brain that runs your uploaded sketch.

Official manual image of the ESP32-S3 GPIO extension board.
Manual photo

GPIO extension board

Spreads the pins into rows you can reach and label.

Official manual photo of a photoresistor with its two legs labelled 1 and 2.
Manual photo

Photoresistor

Today's sensor — its resistance follows the light falling on it.

Official manual photo of a red LED with its longer positive leg and shorter negative leg labelled.
Manual photo

LED

The lamp itself — it only works one way round.

Official manual photo of a resistor with coloured value bands.
Manual photo

220 Ω resistor

Sits in series with the LED to keep the current gentle.

Official manual photo of a resistor with coloured value bands.
Manual photo

10 kΩ resistor

Pairs with the photoresistor to turn light into a readable voltage.

Official manual image of a jumper wire.
Manual photo

4 jumper wires

Temporary, solder-free connections.

02 Make the physical circuit

Chart the circuit

The official Freenove diagram is your chart — schematic on top, the same circuit built on a breadboard below. Click it to enlarge. Two small circuits share the board today: the divider that reads the light, and the LED that answers it.

Official Freenove circuit — C Tutorial, Chapter 11 (Photoresistor & LED), page 118.
10 kΩ resistor 3.3V and the divider midpoint The fixed top half of the voltage divider.
Photoresistor divider midpoint and GND Its light-driven resistance sets the midpoint voltage.
Divider midpoint GPIO 1 The ADC reads the light level here.
LED long leg (+) GPIO 14 via 220 Ω This pin sets the lamp's brightness with PWM.
LED short leg (−) GND Completes the LED's path back to zero volts.

Mind the LED's legs. The LED only lights one way round — long leg toward GPIO 14 through the 220 Ω resistor, short leg to ground. The photoresistor has no polarity, so either leg can face either way. Unplug USB before you move any wire.

03 One action at a time

Build it

This is the main path — you can finish the day without opening a single field note. Tap each step as you go to keep your place.

0 / 9 done
  1. Seat the ESP32-S3 on the GPIO extension board and keep USB unplugged while you wire.

  2. Place the photoresistor on the breadboard — its two legs are interchangeable.

  3. Run the 10 kΩ resistor from the 3.3V rail to one leg of the photoresistor, and take the photoresistor's other leg to GND.

  4. Wire the point where the resistor and the photoresistor meet — the divider's midpoint — to GPIO 1.

  5. Place the LED so its long leg (+) heads toward GPIO 14 and its short leg (−) toward ground.

  6. Put the 220 Ω resistor in series between GPIO 14 and the LED's long leg.

  7. Compare every wire to the chart before you plug in USB.

  8. Open Sketch_11.1_NightLamp.ino in Arduino IDE and upload it.

  9. Cup a hand over the photoresistor, then light it with a phone torch — watch the LED answer each move.

The board is sensing.

The lamp is following the room's light all by itself. Head to Test & debug to confirm the behaviour.

04 Read just enough code

Read the code

The whole sketch is five working lines. One line in setup attaches the LED pin to a PWM channel; the loop reads the light and maps it straight onto the lamp's brightness. Switch to MicroPython if you'd rather see the same idea in Python — the wiring never changes.

Sketch_11.1_NightLamp.ino
#define PIN_ANALOG_IN   1
#define PIN_LED         14
#define CHAN            0
#define LIGHT_MIN       372
#define LIGHT_MAX       2048
void setup() {
  ledcAttachChannel(PIN_LED, 1000, 12, CHAN);
}

void loop() {
  int adcVal = analogRead(PIN_ANALOG_IN); //read adc
  int pwmVal = map(constrain(adcVal, LIGHT_MIN, LIGHT_MAX), LIGHT_MIN, LIGHT_MAX, 0, 4095);  // adcVal re-map to pwmVal
  ledcWrite(PIN_LED, pwmVal);    // set the pulse width.
  delay(10);
}
analogRead(PIN_ANALOG_IN)Reads the divider's midpoint voltage on GPIO 1 as a number from 0 to 4095.
constrain(adcVal, LIGHT_MIN, LIGHT_MAX)Fences the reading inside the 372–2048 window before map stretches that window across the full brightness scale — this is where constrain earns its keep.
ledcWrite(PIN_LED, pwmVal)Sets the lamp's brightness on GPIO 14 — 0 is dark and 4095 is full.

05 Understand, don't memorise

A reading becomes a decision

Days 13 and 15 built the first half — a photoresistor in a voltage divider becomes a number, and that number can drive an output. The sketch you upload today uses it as a dimmer, so the lamp follows the room's light up and down. The move that makes a device feel like it decides for itself comes next: holding that same reading against a threshold and switching on which side of the line it lands. That is what the challenge builds.

1

Light bends a resistance

The photoresistor's resistance follows the light on it — bright light lowers it, darkness raises it.

2

The divider makes a number

Paired with the fixed 10 kΩ resistor it forms a voltage divider, so the midpoint voltage on GPIO 1 rises and falls with the light. The ADC reads that voltage as a value from 0 to 4095 — the reading you met on Day 13.

3

Compare to a threshold

Pick one number as the boundary. On one side the room is bright and the lamp stays off; on the other it is dark and the lamp comes on. One comparison turns a live reading into a clean on-or-off decision. Which reading counts as dark depends on how the divider sits, so you measure it — cover the sensor and note the value.

4

Steady the boundary

Right at the line the reading jitters across the boundary and the lamp chatters on and off. A small gap between the on-point and the off-point, or averaging a few readings before you compare, holds it steady.

The decision dark room → reading crosses the threshold → lamp on

Why one fixed line flickers

A real reading wobbles by a few counts even in steady light. When the room sits right at the threshold, that wobble crosses the line many times a second and the lamp flickers. Give the lamp two lines instead — turn on past a darker number, turn off past a brighter one — and the gap between them is wider than the wobble, so the lamp commits to one state. Averaging several readings before you compare smooths the same jitter.

What the fixed resistor adds

A pin reads voltage, and a photoresistor on its own offers only a changing resistance. Pair it with a fixed resistor and the two split 3.3V at their midpoint in proportion to their resistances, so the midpoint voltage tracks the light — and that midpoint is the value the ADC turns into your reading and the threshold judges.

06 Know it worked

Test & debug

Nothing prints to the screen today — the proof is the lamp answering your hand.

What you should see
LED
  • After upload the LED settles at a brightness set by the room's light.
  • Cup a hand over the photoresistor — the brightness slides one way.
  • Shine a phone torch on the sensor — the brightness slides back the other way.

Which direction is which follows how the divider sits on your board — the proof is the slide itself, steady and repeatable each time you cover and light the sensor.

If it doesn't
  • LED never changes? Check the photoresistor's legs and that the divider's midpoint wires to GPIO 1.
  • LED always at full? The divider is likely on the wrong rail — confirm the 10 kΩ goes to 3.3V and the photoresistor's far leg to GND.
  • Nothing lights at all? Check the LED's direction and the 220 Ω resistor on GPIO 14.
  • Upload fails? Swap in a data-capable USB cable.

07 Make the idea yours

Try this: set the threshold to your own room

Your room's dark and bright readings belong to your room, your board, and where the sensor sits — so you measure them, then pick a threshold between them and let the lamp switch on the boundary you chose. It fits inside today's time.

Read your room's real numbers

Add Serial.begin(115200); to setup and Serial.println(adcVal); to the loop, re-upload, and open Serial Monitor. Cup your hand fully over the photoresistor and note the dark reading, then light it with a phone torch and note the bright reading. Those two numbers are your room's real range, and they tell you which reading means dark on your board.

Switch on a threshold you chose

Pick a number partway between your dark and bright readings. Replace the map line with a comparison — on the dark side of your threshold call ledcWrite(PIN_LED, 4095) to light the lamp, on the bright side call ledcWrite(PIN_LED, 0) to switch it off. If it flickers right at the boundary, move the on-point and off-point a little apart so one small wobble can't cross both.

08 Learn it with a hand on the tiller

Coach me through it

Every lesson ships with a code and a machine-readable packet, so an agent can guide you with full context.

Lesson code

TSK-DAY17-NIGHTLAMP

How the agent should behave: guide one physical connection at a time and wait for confirmation, keep the build primary, then teach the day's real idea — turning the ADC reading into a decision with a threshold — and have the learner measure their own room's dark and bright values before choosing one.

Keep your place

Finished Day 17?

Mark it complete — it shows on your course map, and your place is saved on this device.

Field note

Shortcut

Prompt copied