ESP32 Bench
Boards: ESP32, S2, S3, C3, C6
Flash and monitor: Web Serial
← All projects

Automatic fan

A small DC motor fan that speeds up as the room gets brighter, or warmer with a DS18B20. The ESP32 drives it through a transistor with smooth, silent PWM.

Intermediate30 min115200 baud
Board
Fig. 1. Pins this project uses on the ESP32-DevKitC V4, 38 pin. USB connector at the bottom.

Wiring

FromTo boardNote
Motor +5VFrom USB or a separate 5 V supply, never from a GPIO pin
Motor -Transistor collector
Transistor emitterGNDCheck your transistor's pin order before wiring: a PN2222A is emitter, base, collector with the flat side facing you, but many parts sold as 2N2222 (such as P2N2222A) are collector, base, emitter
Transistor base, through 470 ΩGPIO 25About 5 mA of base current, enough to switch the motor fully on
1N4001 across the motorStripe to motor +, other end to motor -Catches the voltage spike when the motor switches off. Backwards it shorts the supply
Photoresistor3V3 to GPIO 34One leg to 3V3, the other to the pin
10 kΩ resistorGPIO 34 to GNDWith the photoresistor, brighter light gives a higher reading
Optional DS18B20 dataGPIO 4VCC to 3V3, GND to GND, 4.7 kΩ from data to 3V3. Then set SOURCE to USE_TEMPERATURE

Tested on this board

Not run on this board yet. Treat the wiring and code as a first draft.

Firmware, Arduino sketch for ESP32 Dev Module

// Automatic fan: a small DC motor spins faster as the light gets brighter (a
// sunny window warms a room), or as a DS18B20 reads warmer if you wire one and
// set SOURCE to USE_TEMPERATURE. The motor runs through an NPN transistor with
// a flyback diode; the ESP32 pin only sends the PWM signal.
#include <OneWire.h>
#include <DallasTemperature.h>

const int MOTOR_PIN = 25;  // to the transistor's base, through 470 Ω
const int LDR_PIN = 34;      // ADC1 pin: photoresistor and 10 kΩ divider
const int TEMP_PIN = 4;      // optional DS18B20 data line (4.7 kΩ pull-up to 3V3)

enum Source { USE_LIGHT, USE_TEMPERATURE };
const Source SOURCE = USE_LIGHT;

const int PWM_HZ = 20000;  // above hearing, so the motor does not whine
const int PWM_BITS = 8;    // duty from 0 to 255
const int MIN_DUTY = 90;   // below this the motor hums but does not turn; tune for yours

// Light mode: fan off in the dark, full speed in bright light (percent of the ADC range).
const int LIGHT_OFF = 20, LIGHT_FULL = 80;
// Temperature mode: fan off below TEMP_OFF, full speed at TEMP_FULL (°C).
const float TEMP_OFF = 24.0, TEMP_FULL = 30.0;

OneWire oneWire(TEMP_PIN);
DallasTemperature probe(&oneWire);
int lastDuty = 0;

// Duty from 0 (off) to 1 (full) for a reading between the off and full points.
float fraction(float value, float off, float full) {
  return constrain((value - off) / (full - off), 0.0f, 1.0f);
}

void setDuty(float f) {
  int duty = f <= 0 ? 0 : MIN_DUTY + (int)roundf(f * (255 - MIN_DUTY));
  if (duty > 0 && lastDuty == 0) {
    ledcWrite(MOTOR_PIN, 255);  // a short kick gets a stopped motor turning
    delay(150);
  }
  ledcWrite(MOTOR_PIN, duty);
  lastDuty = duty;
}

void setup() {
  Serial.begin(115200);
  ledcAttach(MOTOR_PIN, PWM_HZ, PWM_BITS);  // Arduino-ESP32 core 3.x LEDC API
  ledcWrite(MOTOR_PIN, 0);
  analogSetPinAttenuation(LDR_PIN, ADC_11db);
  if (SOURCE == USE_TEMPERATURE) {
    probe.begin();
    if (probe.getDeviceCount() == 0) Serial.println("No DS18B20 found: check the data wire and the 4.7 kΩ pull-up");
  }
  Serial.println(SOURCE == USE_LIGHT ? "Auto fan: following the light" : "Auto fan: following the temperature");
}

void loop() {
  float f;
  if (SOURCE == USE_LIGHT) {
    long sum = 0;
    for (int i = 0; i < 16; i++) sum += analogRead(LDR_PIN);
    const int light = (int)roundf(sum / 16.0f * 100.0f / 4095.0f);
    f = fraction(light, LIGHT_OFF, LIGHT_FULL);
    setDuty(f);
    Serial.printf("light = %d%%, duty = %d%%\n", light, lastDuty * 100 / 255);
  } else {
    probe.requestTemperatures();
    const float c = probe.getTempCByIndex(0);
    if (c == DEVICE_DISCONNECTED_C) {
      setDuty(1);  // fail safe: run the fan if the sensor is lost
      Serial.println("sensor lost: fan on full");
    } else {
      f = fraction(c, TEMP_OFF, TEMP_FULL);
      setDuty(f);
      Serial.printf("temp_c = %.1f, duty = %d%%\n", c, lastDuty * 100 / 255);
    }
  }
  delay(500);
}

Parts

QuantityPartWhere to buy
1×ESP32 dev boardBuy at Amazon
1×Small DC motor (130 size) with a propeller or fan blade
1×NPN transistor, PN2222 or 2N2222Buy at Adafruit
1×1N4001 diodeBuy at Adafruit
1×470 Ω resistor, for the transistor's base
1×Photoresistor (light sensor)Buy at Adafruit
1×10 kΩ resistor, for the light sensor
1×Optional: DS18B20 temperature probeBuy at Adafruit
1×Optional: 4.7 kΩ resistor for the DS18B20Buy at Amazon
8×Jumper wires

Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.

Libraries

OneWire, DallasTemperature

Load it

  1. In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
  2. Install the libraries listed above from the Library Manager.
  3. Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
  4. Close the IDE's serial monitor, then open the bench here to watch the output.

Field notes

  • Never run a motor straight from a GPIO pin. The transistor switches the motor's current from the 5 V supply; the pin only drives the transistor's base through 470 Ω. With 1 kΩ the transistor may not switch fully on and runs warm.
  • The diode matters: a motor is a coil, and when the transistor switches it off the coil kicks back a voltage spike. The diode, stripe towards 5 V, gives that spike somewhere harmless to go.
  • A PN2222 handles a small 130 motor. A bigger motor, or one that stalls, can draw more than its 600 mA limit and get hot: use a logic-level MOSFET instead.
  • PWM runs at 20 kHz so the motor does not whine. Below MIN_DUTY a motor hums without turning; tune it until the slowest setting still spins, and the sketch gives a stopped motor a short full-power kick.
  • A 9 V battery is a poor supply for this: it sags quickly under a motor's load. A USB power bank or a 5 V adapter lasts far longer.
  • Light mode suits a sunny window that warms a room. For a real temperature fan, wire the DS18B20 and set SOURCE to USE_TEMPERATURE; if the sensor is lost the fan runs at full speed to be safe.

Not tested yet

  • Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.

Community builds

Loading build reports from people who made this.

Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.