Download PDF Parts List View on YouTube Download Code

Today, we will be examining those 4-pin computer fans, the ones that cool down our power supplies and heatsinks. We’ll learn how they work and how we can both control and monitor them using an ESP32. Then we’ll build a temperature-controlled fan controller with an OLED display and stall detection.

Introduction

You might not think that a computer fan is a complicated device. It’s just a motor attached to some blades, right?

Well, there is a lot more to computer fans than that! These useful devices utilize brushless DC motors and feature built-in electronic speed control. Some of them provide a tachometer output, and the 4-pin variety also allows for external speed control using Pulse Width Modulation(PWM).

Today, we will learn how these fans operate and how to use a microcontroller to interact with them. We’ll see how to change their speed and how to use their internal tachometers. 

We then have a project, an ESP32-based temperature-sensitive fan control. This unit can operate in either manual or automatic mode and also protects against a stalled or obstructed fan.

So let’s get spinning!

PWM Fan Control

Being able to control the speed of a cooling fan is quite helpful. It can save energy, a crucial factor in battery-powered equipment, and also allows the fan to run quieter.

The fans used in computers and power supplies utilize “brushless motors”, a type of motor that employs electronic control instead of a commutator to switch current through its coils.  On cheaper (2-pin or 3-pin) units, this electronic controller is self-contained and can’t be controlled externally. However, 4-pin fans can be externally controlled using PWM.

Pulse Width Modulation (PWM)

Pulse Width Modulation (PWM) is a widely used method for controlling various DC motors. Servo motors are a good example of PWM-controlled motors; the width of a pulse determines their shaft position.  Standard DC motors can also be controlled using PWM.

As its name would imply, PWM works by modulating the width of a fixed-frequency pulse. In the case of a computer fan, the wider the pulse is, the faster the motor will spin.

To go at full speed, the pulse has a 100% width, or in other words, it is held HIGH. To stop the motor, the pulse is removed and the line is held LOW.

The frequency used by DC fan motors is significantly higher than that used with servo motors. DC fan motors typically use a PWM frequency of 25KHz.

Common DC Fans

DC Fans are available in a variety of sizes, ranging from tiny 25 x 25 mm babies to jumbo 200 x 200 mm beasts. A prevalent size used in computer power supplies is 120 x 120 mm.

They have airflow rates ranging from 30 CFM to 150 CFM. Typically, a larger motor will have a greater airflow.

As for operating voltage, the two most common voltages are 12 volts and 5 volts. However, you can also find 24 and 48-volt models, which are often used in large server cabinets.

However, not every DC fan motor can be controlled using PWM, a fact that you should be aware of when shopping for one. Fortunately, there is a straightforward method for determining if your motor is suitable for PWM control: simply count the number of pins on the connector.  If it doesn’t have four wires, you can’t control it!

2-Wire Fans

A 2-wire fan is as basic as it gets. These fans simply have a positive and negative voltage connection, which runs to the internal electronic speed control (ESC). 

Remember, you must observe the correct polarity when wiring these, or any, DC PWM computer fan. Reversing the polarity will NOT reverse the direction of rotation, unlike a brushed DC motor.

3-Wire Fans

On these fans, a third wire has been added. This wire is a tachometer output.

The tachometer sensor in a DC PWM computer fan usually outputs two pulses per rotation.  So, if we count 1000 pulses in a minute, then the motor is spinning at 500 RPM.

The tachometer output is open-collector with one side connected to ground. You will need to use a pull-up resistor to pull the output up to your desired logic level. Many microcontrollers, such as the ESP32 that we will use later, have internal pull-ups that you can enable in code, saving the cost and space of a physical resistor.

4-Wire Fans

This is the type of fan we want. It features all the benefits of the 3-wire fin, plus a PWM speed control input.

Regardless of the operating voltage, all DC PWM fans use 5-volt logic levels. They are also compatible with 3.3-volt logic. Applying a 25KHz PWM signal within this range will allow you to control the motor’s speed.

You can also use the PWM input as a binary motor control, if that is what you want. Sending this line HIGH (or just leaving it disconnected, as it is internally pulled HIGH) will run the motor at full speed. Grounding (setting LOW) the PWM input will stop the motor.

Ensure that the DC computer fan you are using has four pins, and follow along to learn how to control and monitor it.

Simple Fan Control

We will begin our experiments by simply controlling the fans’ speed using PWM. In some cases, as when you need to run the fan constantly at a lower speed, this might be all you need.

So grab a fan and an ESP32 and let’s get started!

ESP32 Fan Hookup

We will be using an ESP32 as our fan controller. It features an efficient PWM generation system used for LED and motor control, with virtually every logic pin being PWM-capable. It can also generate PWM at 25KHz, something an ATMega328 (used in the Arduino Uno and original Nano) struggles to accomplish.

Virtually any ESP32 microcontroller board will work for these experiments, and for the project we will build at the end. 

I’m using a Seeeduino XIAO ESP32-S3 board, as it meets many of the design requirements. It’s tiny, inexpensive, and widely available. And it has enough pins to do the job. 

Here is how we will hook up the Seeduino XIAO (or any ESP32 board) to the PWM DC Motor:

Note that we are connecting both the PWM and tachometer leads to the ESP32. In our first experiment, we’ll ignore the tachometer; it will be used in later exercises.

Fan Speed Control Code

Here is the code we will use to control the speed of our DC PWM computer fan:

Here is a breakdown of the code:

Step 1: Define the Pins

First, we define the pins used for PWM output and potentiometer input. In this case, we’re using pin 3 (D2 on the XIAO) for PWM output and pin A0 (GPIO 26) for potentiometer input.

Step 2: Configure PWM

In Setup, we configure the PWM signal with a frequency of 25 kHz and an 8-bit resolution (0-255). This is suitable for most computer fans.

Step 3: Read the Potentiometer Value

In the Loop, we read the potentiometer value using the analogRead() function. The value ranges from 0 to 4095 (12-bit ADC).

Step 4: Convert to PWM Duty Cycle

We convert the potentiometer value to a PWM duty cycle value (0-255) using the map() function. This value determines the fan speed.

Step 5: Apply PWM to the Fan

We apply the PWM signal to the fan using the ledcWrite() function.

Step 6: Calculate and Display Fan Speed

We calculate the fan speed as a percentage (0-100%) and display it on the serial monitor along with the potentiometer value and PWM duty cycle value.

Step 7: Repeat

The loop repeats every 100 milliseconds, allowing for smooth fan speed control.

Ensure everything is connected correctly and load the code onto the XIAO. Then give it a try.

Turning the potentiometer should change the fan speed, from stop to full speed.

Tachometer

Now let’s move on to the tachometer. As mentioned earlier, it is an open-collector output that is activated twice per motor rotation.

I conducted an experiment (which you can view in the accompanying video) in which I controlled the fan using a signal generator to produce the correct PWM signals. I also used two oscilloscopes, one to monitor the incoming pulses and the other to monitor the tachometer output.  The tachometer output is a square wave whose frequency changes in proportion to the motor’s rotation speed.

We have already connected the tachometer to the ESP32, so we can proceed with the code required to utilize it.

Tachometer Code

Here is the code we will use to monitor the tachometer’s output:

How the Code Works

Here is a breakdown of the sketch operation:

1. New Pin and Tachometer Variables

We start by defining the pin for the tachometer and creating a few variables to help us count its pulses.

  • const int TACH_PIN = 2;: We tell the ESP32 that the fan’s tachometer wire is connected to GPIO 2 (labeled D1 on the XIAO).
  • volatile unsigned long tachPulseCount = 0;: This special variable will hold our pulse count. The volatile keyword is important because this variable will be modified by an “interrupt,” a process that happens outside the main loop(). It tells the compiler to always read the variable’s latest value.
  • const unsigned long TACH_SAMPLE_TIME = 1000;: We define a sample period of 1000 milliseconds (1 second). This is how often we will calculate the RPM.

2. The Interrupt Service Routine (ISR)

This is the most important new piece of code. An Interrupt Service Routine (or ISR) is a special, super-fast function that the microcontroller runs immediately when a specific event happens on a pin.

  • void IRAM_ATTR tachISR(): We create a function called tachISR.
  • tachPulseCount = tachPulseCount + 1;: This is the only job of our ISR. Every single time a pulse from the fan’s tachometer wire is detected, this function runs and adds one to our tachPulseCount. Using an interrupt is much more accurate than trying to count pulses inside the main loop().

3. The setup() Function

In setup(), we configure the tachometer pin and tell the ESP32 to start listening for pulses.

  • pinMode(TACH_PIN, INPUT_PULLUP);: We set the tachometer pin as an input. The _PULLUP is important as it activates an internal resistor on the ESP32, which is needed to get a clean signal from the fan’s tachometer.
  • attachInterrupt(…): This powerful command tells the ESP32: “When you see a signal on TACH_PIN, immediately run the tachISR function. Trigger it specifically on the FALLING edge of the pulse (when the signal goes from high to low).”

4. The loop() Function

The main loop still controls the fan speed with the potentiometer, but now it also includes a timed section to calculate and display the RPM.

  1. Control the Fan: The first few lines are the same as before—they read the potentiometer, map the value, and set the fan’s PWM speed.
  2. Check the Time: The line if (currentTime – lastTachTime >= TACH_SAMPLE_TIME) checks if one second has passed since the last RPM calculation. This is a non-blocking way to time events.
  3. Calculate RPM: Inside the if statement, this formula is used: (tachPulseCount * 60000) / (TACH_SAMPLE_TIME * 2);
    • It takes the number of pulses we counted (tachPulseCount).
    • Multiplies by 60,000 to scale from milliseconds to a full minute.
    • Divides by the sample time (1000ms).
    • Divides by 2, because most computer fans produce two pulses for every single revolution. The result is the fan’s speed in RPM.
  4. Display and Reset: The code then prints all the values to the serial monitor. Finally, it resets tachPulseCount = 0; and updates lastTachTime to get ready for the next one-second sample period.

Load the code and observe the serial monitor as you turn the potentiometer. You should see results from the tachometer (check your wiring if you don’t). Try carefully stopping the fan to see what happens to the tach reading.

Temperature Controlled Fan

So now that we know how to both control and monitor our motor, we can put everything together and construct our project.

The project is a temperature controller for a 4-pin computer fan.  The fan utilizes an external power supply and operates at either 12 or 5 volts.

The controller has an OLED display that indicates temperature, fan speed, and fan status.  It also has a Stall LED, which is activated if the motor is obstructed. If the stall condition is detected, the motor PWM is stopped to protect the motor. After a 5-second countdown, the motor will spin again, provided the obstruction has been removed.

The controller can be operated in two modes:

  • Manual Mode – The potentiometer can be used to set speed, regardless of sensor temperature.
  • Auto Mode – The fan is controlled by the temperature sensor. If the temperature reaches a preset trigger point (35 degrees Celsius in the default design, which can be changed), the fan will be triggered at low speed. As the temperature rises, the speed will increase.

You select modes using the potentiometer. If the pot is turned entirely to the left, then the system will enter Auto mode. If you turn the pot clockwise (i.e., to increase the motor speed), then you will enter Manual mode. The current mode is displayed on the OLED.

Temperature Controlled Fan Hookup

We will start with the same circuit that we have been using for testing the PWM and tachometer functions. To this existing circuit, we will add the following components:

  • A 128 x 64 I2C OLED Display.
  • A TMP36 analog temperature sensor.
  • An LED (to use as a stall indicator).
  • A dropping resistor for the LED. I used a 150-ohm resistor.

Here is how we will hook this up:

You may wish to mount the TMP36 remotely from the circuit board, as it should be mounted close to the “hot spot” that you are trying to cool. If you need to extend it, you might need to shield its wires or provide a filter capacitor physically close to its power pins. This is especially true if you are operating in an area of high electrical noise.

Temperature Controlled Fan Code

Here is the code we will be using for our temperature-controlled fan controller:

How the Code Works

This is the final code for our fan controller project. It brings everything together: temperature sensing, manual control with a potentiometer, fan speed measurement (RPM), and a visual display. Let’s walk through how it operates.

1. Libraries and Global Settings

At the very top, we include the necessary libraries for the OLED display. We then define all our pins and set up key variables.

  • Configurable Settings: The most important variables for you are TEMP_FAN_ON and TEMP_FULL_SPEED. You can easily change these values to decide at what temperature the fan should turn on and at what temperature it should reach maximum speed.
  • System Variables: We also have variables like currentTemp, fanSpeedPercent, fanFailure, and manualMode to keep track of the system’s status at all times.

2. The setup() Function

The setup() function runs once to prepare all the hardware.

  • It starts the Serial Monitor and the I2C communication needed for the OLED display.
  • It initializes the OLED screen and prints a startup message.
  • It configures the ESP32’s PWM hardware with the standard 25 kHz frequency for our fan.
  • Finally, it sets up the tachometer pin as an input and attaches the tachISR() interrupt to it, which will count the fan’s pulses in the background.

3. The loop()

The loop() is where the main logic happens, running over and over. It follows a clear sequence of tasks each time it runs.

  1. Read Sensors: The first thing it does is read the current temperature from the TMP36 sensor and the current position of the 10K potentiometer.
  2. Choose a Mode (Auto vs. Manual): It checks the potentiometer’s value. If you’ve turned the knob, the code switches to manualMode. In this mode, the fan’s speed is set directly by the potentiometer. If the pot is turned all the way down, the code switches back to automatic temperature control.
  3. Calculate Fan Speed:
    • In automatic mode, it uses a series of if/else statements to compare the currentTemp to your TEMP_FAN_ON and TEMP_FULL_SPEED settings, calculating the appropriate fan speed percentage.
    • In manual mode, it simply maps the potentiometer’s reading to a fan speed percentage from 0 to 100%.

4. Checking the Fan’s Health

Every two seconds, a special timed block of code runs to check the fan’s RPM and health.

  • Calculate RPM: It uses the tachPulseCount (which has been updated in the background by our interrupt) to calculate the fan’s current speed in RPM.
  • Detect Failure: This is a key safety feature. The code checks if the fan should be spinning (i.e., fanSpeedPercent > 20) but its actual RPM is very low (currentRPM < 500). If both are true, it assumes the fan has stalled, sets the fanFailure flag to true, and turns on the warning LED. As a precaution, it also cuts power to the fan.

5. Applying the Speed and Updating the Display

After all the checks and calculations, the code takes action.

  • It converts the final fanSpeedPercent into a PWM value and sends it to the fan (unless a failure was detected).
  • It then calls the updateDisplay() function to draw all the fresh information onto the OLED screen.

The updateDisplay() function is responsible for neatly formatting all our data—temperature, speed percentage, RPM, and system status (AUTO, MANUAL, or FAILURE!)—and even draws a nice temperature bar graph at the bottom of the screen.

Load the code onto the XIAO and try it out. The OLED should activate instantly; if it doesn’t, check your wiring to ensure you haven’t reversed SDA and SCL. 

You should get a display of the temperature, fan speed, and mode. Move the pot and observe how it goes in and out of Manual mode.

Try carefully stopping the fan to verify that the stall detection is working. It should stop the PWM, light the LED, and show a short countdown on the OLED.

And find a source of heat to test the TMP36. Verify that the fan comes on when the first trip point is reached.

This controller would be excellent mounted on a small perfboard. If you go as far as to design a PCB for it, you could use the XIAO in surface-mount mode, as it has castellated pins for this purpose.

Conclusion

Cooling fans can be a vital component in computer or hardware design. The controlled temperature system constructed today can be used in conjunction with these fans to maintain temperatures within specifications efficiently.

Keep in mind that fans don’t need to be used just for electronics. A small 5-volt fan with a USB power input is a great way to build a personal cooling fan; you can reduce the trip point to about 20 degrees and the high point to 30 degrees for this application.

Whatever you use it for, you now know how to “keep your cool” in any situation by harnessing the power of a PWM-controlled fan!

Parts List

Here are some components you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.

Seeeduino XIAO ESP32-S3    Digikey    Amazon

SSD1306 128×64 OLED     Amazon

TMP36          Digikey    Amazon

 

Resources

Code Samples – All the code used in this article in one easy-to-use ZIP file!

Article PDF – A PDF version of this article in a ZIP file.

ESP32 PWM Fan Controller
Summary
ESP32 PWM Fan Controller
Article Name
ESP32 PWM Fan Controller
Description
Learn how DC Computer fans work and how to both control their speed and monitor their performance. We'll finish up by building a temperature-sensitive fan controller with an OLED display and stall protection.
Author
Publisher Name
DroneBot Workshop
Publisher Logo

4 Comments
Oldest
Newest
Inline Feedbacks
View all comments
J to the C
5 months ago

Hi. What is the positive power supply (or acting as such) to the esp32 in the wiring diagrams?

FransK
3 months ago

On cheaper (2-pin or 3-pin) units, this electronic controller is self-contained and can’t be controlled externally. However, 4-pin fans can be externally controlled using PWM. Good, usefull in-depth info. I’m working on a similar external fan for a small computer (Intel NUC) that freezes (the linux OS, because cpu overheating?) reguarly in summer temperatures. I will change my program a bit after having read this article. I recently came across somewhere a way to control the speed of a 3-wire fan : apply PWM control in the positive power line of the fan, you will need a high-side driver or… Read more »

Robert
15 days ago

I would love to have the final code that includes the fan Stall/Retry feature as shown in the video 27:40 thru 27:55.