Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
Stepper Curtain Opener: the finished build
ESP32Home automation

Light-sensing curtain opener with a stepper motor (ESP32)

Opens the curtain at sunrise and closes it at dusk.

Difficulty
Medium
Build time
3 h
Parts cost
₹970 · $11
Verified — core esp32 3.3.12, 3 Nov 2026Compile-only

Community draft — report a mistake

Parts list

PartQtyApprox.Buy
ESP32 DevKit V1 (30-pin)1₹380Buy ESP32 DevKit V1 (30-pin)
28BYJ-48 5 V stepper + ULN2003 driver board1₹150Buy 28BYJ-48 5 V stepper + ULN2003 driver board
LDR light sensor module (VCC, GND, DO, AO)1₹40Buy LDR light sensor module (VCC, GND, DO, AO)
Cord pulley / 3D-printed spool1₹100Buy Cord pulley / 3D-printed spool
Jumper wires1₹30Buy Jumper wires
5 V 2 A adapter for the stepper (DC jack to wires)1₹150Buy 5 V 2 A adapter for the stepper (DC jack to wires)
USB cable + 5 V USB charger for the ESP321₹120Buy USB cable + 5 V USB charger for the ESP32
Total₹970

Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.

Wiring

ESP32 DevKit V1Only the pins used are shown · 3.3 V logic5 V 2 A adapter (stepper)28BYJ-48 stepper motor + ULN2003 driverLDR light sensor module+5VGNDD34D25D26D27D14GND3V3IN1IN2IN3IN4+-VCCGNDDO (not connected)AO
Every connection
ESP32 DevKit V1Part · pinWireNotes
D1428BYJ-48 stepper motor + ULN2003 driver · IN1orange
D2728BYJ-48 stepper motor + ULN2003 driver · IN2yellow
D2628BYJ-48 stepper motor + ULN2003 driver · IN3green
D2528BYJ-48 stepper motor + ULN2003 driver · IN4blue
GND28BYJ-48 stepper motor + ULN2003 driver · -blackCommon ground with the stepper adapter
5 V 2 A adapter (stepper) · +5V28BYJ-48 stepper motor + ULN2003 driver · +red~240 mA while stepping — kept off the ESP32’s USB supply
5 V 2 A adapter (stepper) · GND28BYJ-48 stepper motor + ULN2003 driver · -black
3V3LDR light sensor module · VCCred3.3 V so AO never exceeds the ADC’s range
GNDLDR light sensor module · GNDblack
D34LDR light sensor module · AOpurple

Board quirks: The stepper has its own 5 V adapter; only its ground joins the ESP32, so motor current never flows through the board. On most LDR modules AO goes <em>down</em> as light goes up; the sketch inverts it so “brighter = bigger”. The 28BYJ-48 is geared slowly and has a little backlash — great for curtains, not for precise positioning. The sketch assumes the curtain starts closed. A limit switch is the proper fix (see upgrades).

Code

stepper-curtain-opener.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Stepper curtain opener — ESP32 DevKit V1 + ULN2003 driver + 28BYJ-48 stepper + LDR module.
// Opens the curtain when it gets bright and closes it at dusk, with a gap so it doesn't hunt.
// ULN2003 IN1–IN4 → D14, D27, D26, D25. LDR module AO → D34 (ADC1).
constexpr uint8_t COIL[4] = {14, 27, 26, 25};
constexpr uint8_t LDR_PIN = 34;

// 28BYJ-48 in half-step mode: about 4096 steps per output-shaft turn.
constexpr long CURTAIN_STEPS = 4096L * 3;   // three turns of the shaft — adjust for your curtain
constexpr int OPEN_ABOVE = 2600;            // raw ADC (0–4095); on these modules brighter = LOWER, see quirks
constexpr int CLOSE_BELOW = 1600;

const uint8_t HALF_STEP[8][4] = {
  {1, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0},
  {0, 0, 1, 0}, {0, 0, 1, 1}, {0, 0, 0, 1}, {1, 0, 0, 1},
};

bool isOpen = false;
int phase = 0;

void coilsOff() {
  for (uint8_t p : COIL) digitalWrite(p, LOW);   // no holding current: the motor stays cool
}

void move(long steps) {
  int dir = steps > 0 ? 1 : -1;
  for (long i = 0; i < labs(steps); i++) {
    phase = (phase + dir + 8) % 8;
    for (int c = 0; c < 4; c++) digitalWrite(COIL[c], HALF_STEP[phase][c]);
    delayMicroseconds(1200);                       // ~1.2 ms per half-step is reliable on 5 V
  }
  coilsOff();
}

int lightLevel() {
  long sum = 0;
  for (int i = 0; i < 16; i++) sum += analogRead(LDR_PIN);
  return 4095 - (sum / 16);                        // invert so that brighter = higher
}

void setup() {
  for (uint8_t p : COIL) pinMode(p, OUTPUT);
  coilsOff();
  analogReadResolution(12);
  Serial.begin(115200);
  Serial.println("Curtain starts CLOSED — set it closed by hand before power-up");
}

void loop() {
  int light = lightLevel();
  Serial.printf("Light %d (%s)\n", light, isOpen ? "open" : "closed");
  if (!isOpen && light > OPEN_ABOVE) {
    Serial.println("Morning — opening");
    move(CURTAIN_STEPS);
    isOpen = true;
  } else if (isOpen && light < CLOSE_BELOW) {
    Serial.println("Dusk — closing");
    move(-CURTAIN_STEPS);
    isOpen = false;
  }
  delay(5000);
}

Compiled for esp32 on esp32 3.3.12 · 3 Nov 2026

How it works

The 28BYJ-48 is a small geared stepper motor: it moves in tiny exact steps, so the sketch can open the curtain by the same amount every morning without any position sensor. The ULN2003 board is just four transistor switches that let the ESP32’s 3.3 V pins drive the motor’s 5 V coils.

The sketch drives the coils in half-steps — eight patterns per cycle — which is smoother and quieter than full steps. About 4,096 half-steps turn the output shaft once; three turns is the starting value for a typical curtain cord.

An LDR module measures daylight. Opening and closing use two different light levels (hysteresis), so a passing cloud at dusk doesn’t make the curtain twitch open and shut.

After every move the coils are switched off. The gearbox holds the curtain in place, the motor stays cool and the ESP32 saves power.

Troubleshooting

The motor buzzes but doesn’t turn

The coil order is wrong or the steps are too fast. Check IN1–IN4 go to D14, D27, D26, D25 in that order, and keep the 1.2 ms delay.

It opens and closes repeatedly at dusk

The open and close thresholds are too close together. Widen the gap between OPEN_ABOVE and CLOSE_BELOW.

The motor gets hot

The sketch turns the coils off after each move. If yours is hot anyway, check for a stuck pulley.

Upgrades

  • Add limit switches at both ends so it always knows where the curtain is.
  • Add a web page to open and close it from your phone.
  • Use the time (NTP) as well as light — “open at 7 am or at sunrise, whichever is later”.

↑↓ to move · Enter to open