Arduino Fan Temp Speed Control

I decided that I would like to have a programmable temperature-based fan speed controller for a fume extractor I am building from scrap parts, and the logical thing to do seemed to be to use an Arduino Nano as modern fans use a 5 volt PWM signal to control their speed, and also report back their speed with another 5 volt signal. They are also wicked cheap if you are willing to use clones.

The basic scheme goes like this: use the cheapest convenient microcontroller with the cheapest convenient temperature sensor to control the speed of a "PWM fan" using code cobbled from samples found via google in as little time as possible — around as long as it takes to blog about it.

fan speed control


Arduino Nano

The Nano is my go-to Arduino because although it is small, it has onboard USB, an onboard wide input voltage regulator, and all the AVR features that you expect like variable-rate hardware PWM and multiple interrupt pins, albeit only two of them. The clones with the CH340G USB-to-Serial chip are available for around $3 each; I actually prefer this chip since FTDI started playing funny games with their drivers. We will use all of these features for this device. This code should run on most Arduinos, with some changes to hardware pin assignments; it should work on the Pro Mini without modification.

The Fan

If you've got a small DC fan with continuously variable speed and more than two wires, then it's almost certainly a PWM-controlled fan. Typically, you've got a black wire for the ground, a red wire for 12 volt power, a yellow wire for a 5 volt, 25 kHz PWM speed signal, and then possibly a blue wire for a 5 volt tachometer return. Both the 3-wire and 4-wire fans are in common use.

The fan I'm using here is a small-diameter but deep (60x60x38mm) 4-wire fan salvaged from a server by someone else; I bought four of these Delta PFC0612DE fans at a flea market for a couple of dollars. They draw a peak of 1.68 amps at 12 volts and peak out at 12,000 RPM and 67.85 CFM, which makes them fairly beastly — perfect for this job. The idea is to fiddle around with fan speeds to see what makes the most sense — do I need more fan speed when it's cooler, or when it's warmer? Anyway, the fan followed the typical color code and is labeled with the direction of rotation and exhaust, so there were really no surprises there.

Since these fans had been used in a custom design with pairs of fans attached to a cute little cardedge connector which also bore LEDs, they had no connectors. In order to provide a fan connector both on the fan and the controller, I cut a fan extension lead which came with a braided cover. After shortening the cover appropriately, I wrapped the end with masking tape and then slipped a piece of heat shrink tubing over the tape. The tape could then be removed and the heat shrink tubing slipped down into place, to secure the cut end of the braided cover. I twisted some color-coded power input leads together with the matching red and black leads on the male end of the cut cable, and soldered those to the VIN and GND leads on one side of the Nano. I soldered the yellow lead to D9 and the blue lead to D2 — more on these choices later.

The Temperature Sensor

There were some surprises in the sensor department. After searching hither and yon, I found the sample code for using the device I had bought from eBay — the breadboardable KY-013 thermistor sensor. These were by far the cheapest breadboardable temperature sensor available (which is what I was looking for when I ordered some) and they are plenty accurate for my purposes. The vendor provided code which produces a centigrade temperature measurement. Unfortunately, they also provide mismarked modules; the power and ground leads are the reverse of marked. If you swap those pins, the code works great, the sensor works great, everyone is happy. I cut a servo extension lead and repinned the power and ground leads. The brown wire is the ground, and soldered to a GND pin on the other side of the Nano. The red wire goes to the 5V pin (which is fed by the voltage regulator on the Nano) and the orange wire goes to A0.


25 kHz PWM

The really interesting thing about this project is the 25 kHz PWM. While AVR chips (and thus Arduinos and their clones) provide numerous PWM channels, they do not all have the same capabilities. On the atmega168 and 328 running at 16 MHz, you have access to two hardware PWM channels capable of doing the 25 kHz PWM expected by typical PWM fans. You get access to that by tweaking the PWM timers. I did not have to figure this out at all myself, since the specific example comes up fairly frequently. Pins D9 and D10 are capable of 25 kHz PWM.

KY-013 temp sensor

The two big pieces of this project (besides the Arduino itself) are the fan and the thermistor. Thermistors are typically wired up as a divider with a static resistor, and that's what's provided on the KY-013. Reading it is very simple; a straightforward analogRead(A0) is all it takes to get the raw data. The manufacturer provides the following code for making sense of it:

double Temp;
Temp = log(((10240000/RawADC) - 10000));
Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
Temp = Temp - 273.15;// Convert Kelvin to Celcius
return Temp;

...where RawADC is the passed int value from analogRead. This works pretty well. The sensor heats up very readily, and cools down in a reasonable period.

Serial Output

Since the Nano has onboard USB to serial, why not make use of it? The code outputs the temperature in both centigrade and Fahrenheit measurements, as well the desired fan speed as a value from 0-320.1 If the fan speed monitoring function is enabled (the default in the code so far) then the actual fan speed is printed as well.


Because this is a 4-pin fan, it also supports reading the fan speed back by counting pulses. This must be done on pin D2 or D3, which are the pins on the Nano which support interrupts. It is relatively simple to count these pulses, do a little math, and report the speed of the fan in RPM. To this end, the tachometer pin is wired to D2, one of only two interrupt pins on the Nano. The other is D3. This signal requires a pull-up resistor, usually 10k. The internal pull-up resistor is used here.

The only tricky part is figuring out how many pulses your fan sends per revolution. If you have a transparent fan with no sticker, you can simply count the poles of your motor and come up with the likely answer. If there is a data sheet for your fan (as I found for mine) you can simply look up the number of poles and try using that as the value of fanDiv. It's also possible for the fan to simply send one pulse per revolution, in which case you simply set it to 1. If you're using your own optical sensor to count revolutions, it's either 1 (for your reflective sticker) or the number of fan blades, if they're sufficiently reflective.

interrupts(); // enable interrupts for counting fan pulses
attachInterrupt(digitalPinToInterrupt(hallsensor), pickrpm, RISING);
digitalWrite(hallsensor, HIGH);

Future Plans?

One obvious improvement would be to add run-time configuration and EEPROM support so that new values for the fan do not have to be compiled in, but could instead be set by serial port. The interface could also allow selecting fan speed directly. Another obvious improvement would be to add support for a second fan, which the device has sufficient power and I/O to support. Fan speed auto-calibration could be implemented for fans with tachometers; fans have a minimum start power which could be calculated, and the output power could be calibrated from the fan's maximum RPM automatically, to permit limiting maximum RPM. Fan speed control could easily benefit from some hysteresis, and the RPM monitoring algorithm could be improved. And finally, this code could reasonably be the basis of a PID BBQ controller, if the thermistor were swapped out for a thermocouple interface.


This is a cheap little project which uses interrupt handling, basic timing loops based on the internal millis() timer, hardware serial, defines, and an internal pull-up, analog reads, odd PWM timing, as well as the on-board LED :) You can bang this one out for around five to seven bucks worth of parts, especially if you solder your fan direct to the Nano. This is actually considerably cheaper than buying a decent fan speed controller, although most of those will also work with two-wire fans. The serial output could be easily modified to be monitored by software, if necessary, although it is not very difficult to parse as-is.

  • 1. For internal reasons, though this could be changed easily enough.
File attachments: 


This is just a PWM signal to the smart, 4-pin fan which tells it what percentage of its maximum RPM to spin

Add new comment

(If you're a human, don't change the following field)
Your first name.
(If you're a human, don't change the following field)
Your first name.
(If you're a human, don't change the following field)
Your first name.


  • Use [fn]...[/fn] (or <fn>...</fn>) to insert automatically numbered footnotes.
  • You may link to images on this site using a special syntax
  • Web page addresses and e-mail addresses turn into links automatically.
  • To post pieces of code, surround them with <code>...</code> tags. For PHP code, you can use <?php ... ?>, which will also colour it based on syntax.
  • Internal paths in single or double quotes, written as "internal:node/99", for example, are replaced with the appropriate absolute URL or path. Paths to files in single or double quotes, written as "files:somefile.ext", for example, are replaced with the appropriate URL that can be used to download the file.
  • Filtered words will be replaced with the filtered version of the word.
  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote> <q>


  • Lines and paragraphs break automatically.
  • To post pieces of code, surround them with <code>...</code> tags. For PHP code, you can use <?php ... ?>, which will also colour it based on syntax.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>

Drinking Game

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img> <p> <br> <pre> <h2> <h3> <h4>
  • Images may be embedded like: [image:node_id align=alignment hspace=n vspace=n border=n size=label width=n height=n nolink=(0|1) class=name style=style-data node=id] Leave off any attributes you don't want.
  • [img_assist|...] tags will be displayed, maybe. Please don't make more of them.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
This question is for testing whether you are a human visitor and to prevent automated spam submissions.