Project Overview & Difficulty Rating
When searching for simple electronics projects that bridge the gap between abstract circuit theory and practical embedded programming, a temperature-controlled cooling fan is the gold standard. This build uses an analog NTC (Negative Temperature Coefficient) thermistor to read ambient temperature, applies the Steinhart-Hart equation to calculate the exact Celsius value, and drives a 5V DC fan via a logic-level MOSFET when a thermal threshold is crossed.
Target Board: ESP32-WROOM-32 DevKit V1 (30-pin variant)
Difficulty: 2/5 (Beginner-Intermediate)
Time to Build: 45 minutes
Core Concepts: Voltage dividers, ADC non-linearity, Steinhart-Hart equation, PWM motor control.
Theory: NTC Thermistors and the Voltage Divider
An NTC thermistor decreases its electrical resistance as temperature rises. However, this relationship is highly non-linear. You cannot simply map resistance to temperature with a straight line. To read this resistance with a microcontroller, we must convert the changing resistance into a changing voltage using a voltage divider circuit.
By placing a fixed 10kΩ resistor in series with our 10kΩ NTC thermistor between the 3.3V supply and Ground, the midpoint voltage ($V_{out}$) shifts as the thermistor's resistance changes. The ESP32's Analog-to-Digital Converter (ADC) reads this midpoint voltage.
The ESP32 ADC Sweet Spot: The ESP32's internal ADC is notoriously non-linear at the extreme ends of its range (near 0V and near 3.3V). By matching the fixed resistor to the thermistor's nominal resistance (10kΩ at 25°C), the voltage divider outputs exactly 1.65V at room temperature. This places our baseline reading dead-center in the ESP32's most linear ADC region, drastically reducing measurement error without needing external op-amp conditioning.
To convert the calculated resistance back into temperature, we use the Steinhart-Hart equation, specifically the simplified Beta (B) parameter equation:
$1/T = (1/T_0) + (1/B) * ln(R/R_0)$
Where $T$ is temperature in Kelvin, $T_0$ is 298.15K (25°C), $B$ is the thermistor's coefficient (3950 for our part), and $R_0$ is 10,000 ohms.
Hardware Spec Sheet & Pin Mapping
Sourcing the exact right components prevents the most common beginner headaches. Do not substitute a standard NPN BJT (like a 2N2222) for the MOSFET here; a BJT will drop ~0.7V and dissipate heat, whereas a logic-level MOSFET acts as a near-perfect switch.
| Component | Exact Variant / Model | Qty | Est. Cost (2026) |
|---|---|---|---|
| Microcontroller | ESP32-WROOM-32 DevKit V1 (30-pin) | 1 | $6.50 |
| Temperature Sensor | 10kΩ NTC Thermistor (B=3950, 1% tolerance) | 1 | $0.50 |
| Fixed Resistor | 10kΩ 1/4W Metal Film (1% tolerance) | 1 | $0.10 |
| Switching Transistor | IRLZ44N Logic-Level N-Channel MOSFET (TO-220) | 1 | $1.20 |
| Cooling Fan | 5V DC Brushless PC Fan (80mm, 150mA draw) | 1 | $4.00 |
| Flyback Diode | 1N4007 Rectifier Diode | 1 | $0.10 |
| Wiring | 22 AWG Solid Core Hookup Wire | - | $5.00/spool |
Pin Mapping Table
| ESP32 Pin | Function | Connects To | Notes |
|---|---|---|---|
| 3V3 | Power Out | Voltage Divider Top | Provides stable 3.3V reference |
| GND | Ground | Common Ground Rail | Must share ground with 5V fan supply |
| GPIO 34 | ADC Input | Voltage Divider Midpoint | Input only pin, no internal pull-up needed |
| GPIO 25 | PWM Output | IRLZ44N MOSFET Gate | DAC1 capable, excellent for PWM |
| VIN (5V) | Power In/Out | Fan Positive (Red) | Only use if powering ESP32 via USB |
Step-by-Step Wiring & Assembly
- Disconnect Power: Ensure the ESP32 is unplugged from USB and the 5V fan supply is disconnected.
- Build the Voltage Divider: Insert the 10kΩ fixed resistor and the 10kΩ NTC thermistor into the breadboard so they share one common leg. Connect the free leg of the fixed resistor to the ESP32's 3V3 pin. Connect the free leg of the thermistor to GND.
- Wire the ADC: Run a jumper wire from the common leg of the voltage divider (where the resistor and thermistor meet) to GPIO 34 on the ESP32.
- Place the MOSFET: Insert the IRLZ44N into the breadboard. Facing the text on the MOSFET, the pins from left to right are Gate, Drain, Source. Connect the Gate to GPIO 25. Connect the Source to GND.
- Wire the Fan and Flyback Diode: Connect the fan's black (ground) wire to the MOSFET's Drain. Connect the fan's red (positive) wire to the 5V supply (or ESP32 VIN if drawing < 300mA total). Critical: Place the 1N4007 diode in parallel with the fan, with the diode's silver stripe (cathode) pointing toward the 5V positive side. This clamps inductive voltage spikes when the fan spins down.
- Establish Common Ground: If using an external 5V supply for the fan, you must connect the external supply's GND to the ESP32's GND. Without an equipotential bond, the MOSFET gate signal will float and fail to switch.
Complete ESP32 Code with Error Handling
This code targets the ESP32 Arduino Core v3.x. Note the use of the modern ledcAttach() API, which replaces the deprecated ledcSetup() functions found in older tutorials. It also includes explicit ADC error handling to catch floating pins and saturation.
#include <Arduino.h>
#include <math.h>
// --- PIN DEFINITIONS ---
#define THERMISTOR_PIN 34 // ADC1_CH6 (GPIO 34)
#define FAN_PIN 25 // PWM Output to MOSFET Gate
// --- THERMISTOR CONSTANTS (10k NTC 3950) ---
#define SERIES_RESISTOR 10000.0
#define NOMINAL_RESISTANCE 10000.0
#define NOMINAL_TEMPERATURE 25.0 // Celsius
#define B_COEFFICIENT 3950.0
#define ADC_MAX 4095.0
#define VCC 3.3
// --- SYSTEM THRESHOLDS ---
#define TEMP_THRESHOLD_ON 28.0 // Turn fan on at 28C
#define TEMP_THRESHOLD_OFF 25.0 // Turn fan off at 25C (hysteresis)
// --- PWM CONFIGURATION ---
const int pwmFreq = 1000;
const int pwmResolution = 8; // 0-255 duty cycle
bool fanState = false;
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to connect
// Configure ADC for 12-bit resolution and 11dB attenuation (0-3.3V range)
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
// Modern ESP32 Core v3.x PWM attachment
ledcAttach(FAN_PIN, pwmFreq, pwmResolution);
ledcWrite(FAN_PIN, 0); // Ensure fan is off at boot
Serial.println("System Initialized. Monitoring temperature...");
}
void loop() {
// 1. Read ADC
int adcRaw = analogRead(THERMISTOR_PIN);
// 2. Error Handling: Check for ADC saturation or floating pin
if (adcRaw >= 4090) {
Serial.println("Error: ADC read saturation (4095). Check if thermistor is shorted to 3V3 or pin is floating.");
delay(2000);
return;
}
if (adcRaw <= 5) {
Serial.println("Error: ADC read near zero. Check if thermistor is shorted to GND.");
delay(2000);
return;
}
// 3. Calculate Resistance
float voltage = (adcRaw / ADC_MAX) * VCC;
float resistance = SERIES_RESISTOR * ((VCC / voltage) - 1.0);
// 4. Steinhart-Hart Calculation
float steinhart;
steinhart = resistance / NOMINAL_RESISTANCE; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= B_COEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (NOMINAL_TEMPERATURE + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15; // Convert to Celsius
// 5. Error Handling: Math domain errors
if (isnan(steinhart)) {
Serial.println("Error: Temperature calculation resulted in NaN. Check resistance math.");
delay(2000);
return;
}
// 6. Hysteresis Control Logic
if (steinhart >= TEMP_THRESHOLD_ON && !fanState) {
fanState = true;
ledcWrite(FAN_PIN, 255); // 100% duty cycle
Serial.printf("Fan ON. Temp: %.2f C\n", steinhart);
}
else if (steinhart <= TEMP_THRESHOLD_OFF && fanState) {
fanState = false;
ledcWrite(FAN_PIN, 0); // 0% duty cycle
Serial.printf("Fan OFF. Temp: %.2f C\n", steinhart);
}
delay(1000); // 1Hz sampling rate
}
Debugging: Common Failures and Error Strings
When the circuit fails to operate, do not start rewriting code. Hardware and wiring account for 90% of embedded failures. Here are the first three things to check when the system misbehaves:
- Verify the Common Ground: Use your multimeter in continuity mode. Check resistance between the ESP32 GND pin and the MOSFET Source pin. It must read < 1 ohm. If it's higher, your gate signal lacks a return path.
- Measure the Voltage Divider Midpoint: With the ESP32 powered, use a multimeter to measure DC voltage at GPIO 34. At room temperature (approx 22°C), it should read between 1.5V and 1.8V. If it reads 3.3V or 0V, your breadboard contacts are failing or a wire is broken.
- Check the Flyback Diode Orientation: If the ESP32 randomly resets when the fan turns off, the inductive kickback from the fan motor is brown-out resetting the 3.3V regulator. Ensure the 1N4007 silver stripe faces the 5V positive rail.
Exact Error Strings and Ranked Causes
If the serial monitor outputs specific errors, follow this decision path:
"Error: ADC read saturation (4095). Check if thermistor is shorted to 3V3 or pin is floating."Ranked Causes:
1. The thermistor leg is not making contact with the breadboard GND rail (floating pin pulls high via internal leakage).
2. The 10k fixed resistor is accidentally shorted to the midpoint wire.
3. GPIO 34 is configured incorrectly (it is an input-only pin and cannot be driven high/low by software, but a stray
pinMode(34, OUTPUT) in old code can corrupt the ADC matrix).
"Error: Temperature calculation resulted in NaN. Check resistance math."Ranked Causes:
1. The ADC returned exactly 0, causing a divide-by-zero in the voltage calculation (
VCC / voltage).2. The thermistor is completely disconnected, resulting in an infinite resistance calculation that breaks the
log() function.3. You are using a PTC thermistor instead of an NTC, causing the math model to collapse outside expected bounds.
Extending and Simplifying the Build
Depending on your end goal, you can modify this simple electronics project to fit your exact needs.
How to Simplify the Build
If the Steinhart-Hart math and ADC non-linearity are causing too much friction, swap the analog NTC thermistor for a digital DS18B20 waterproof temperature probe. The DS18B20 uses the 1-Wire protocol, handles all internal analog-to-digital conversion, and outputs a calibrated Celsius value directly to the ESP32 via a single GPIO pin. You will lose the analog theory experience, but gain plug-and-play reliability.
How to Extend the Build
- Add PID Control: Instead of simple hysteresis (bang-bang control), use the Arduino PID Library to map the temperature error to a proportional PWM duty cycle. The fan will spin faster as the temperature rises, rather than just snapping between 0% and 100%.
- Network Integration: Leverage the ESP32's native WiFi. Add the
PubSubClientlibrary to publish the temperature and fan state to an MQTT broker (like Mosquitto) for integration into Home Assistant. - Add an OLED Display: Wire an SSD1306 128x64 I2C OLED to GPIO 21 (SDA) and GPIO 22 (SCL) to display real-time thermal graphs locally without needing a serial monitor.
FAQ: Simple Electronics Projects
What are the best simple electronics projects for learning analog-to-digital conversion?
Beyond this thermistor fan controller, the best projects for mastering ADC theory include building a digital multimeter (using a precision voltage divider and op-amp buffer), an audio VU meter (using AC coupling capacitors and DC bias to read audio waveforms), and a light-tracking robot (using LDR photoresistors in a voltage divider to steer DC motors). These projects force you to deal with real-world analog noise, sampling rates, and reference voltage stability.
How do I power simple electronics projects without relying on a USB cable?
To untether your ESP32 from a PC, use a 5V 2A USB power bank for temporary bench testing. For permanent installations, step down a 12V wall adapter using a buck converter module (like the LM2596 or MP1584EN) set to exactly 5.0V, and feed that into the ESP32's VIN pin. Never feed raw 12V into the VIN pin; the onboard AMS1117 linear regulator will overheat and trigger thermal shutdown at currents above 50mA.
Why do my simple electronics projects give fluctuating sensor readings?
Fluctuating analog readings are almost always caused by a noisy power supply or high-impedance voltage dividers. The ESP32's ADC requires a low-impedance source (ideally < 10kΩ) to charge its internal sample-and-hold capacitor before the conversion completes. If your voltage divider uses massive resistors (e.g., 1MΩ), the capacitor won't charge in time, resulting in jitter. Additionally, keep analog signal wires away from the PWM motor wires to prevent electromagnetic interference (EMI) from coupling into your sensor lines.






