PWM a 3-pin PC fan with an Arduino

Note: 4-pin fans already have a PWM signal

Aeroscope Measuring a Fan

A question came up on IRC regarding how to PWM a 3-pin PC fan with an Arduino using analogWrite(). Controlling the fan was seemingly straightforward. The problem was that the hall effect sensor, or TACH signal, was incredibly noisy. The noise made it impossible to measure the fan’s rotation. Working through the question, I found three issues to tackle:

  1. You need to use a PNP transistor
  2. Filter capacitors help
  3. Create a non-blocking RPM measurement (with millis())

This post addresses all three issues regarding how to PWM a 3-pin PC fan with an Arduino.

1. You need a PNP Transistor

Most tutorials show an NPN transistor driving a motor as a low-side switch. However, the problem with this approach is that you are switching the ground (GND) path. This method doesn’t provide a return path when the FAN is turned off. Regarding the fan’s operation, it will turn on and off as you expect. However, the lack of ground path means the switching can create electromagnetic interference (EMI).

NPN Low Side vs High Side Switch

Figure 1: Don’t do this!

The proper way to power a fan is with a high-side switch. This circuit type switches the high voltage on and off, instead of ground. But you can’t use a NPN. Figure 1 shows the problem. When configured as a high-side switch the voltage from across VBE remains 0.7 volts. Which means if there is 5 volts at the base, you only get 4.3 volts at the collector. You were probably expecting it to be 11.3 volts, weren’t you? Well, that’s not how a NPN BJT works.

Instead, we can use a PNP transistor for the high-side switch. This circuit does present a problem when using 5 volts on the base and 12 V on the emitter.

PNP Only

Figure 2: Oops. 12V rail with a 5V control.

So the problem is that when the I/O pin is HIGH or LOW, there isn’t enough voltage to turn off the transistor. And you create an awkward voltage divider between the I/O pin and the base of the transistor. If you looked at the collector (shown as pin 2 in this diagram) on an oscilloscope, you’d see it stay a steady DC voltage. Not very useful is it?

To solve the problem of how to use a PNP transistor with an Arduino, you need to add an NPN driver. It seems kind of silly doesn’t it? Well, doing things right isn’t always easy. Or something to that effect.

NPN-PNP Driver Example

If we add a NPN transistor before the PNP, it can be used to switch the high-voltage supply on and off. This driver would allow the PNP’s base to see a wide voltage range. The NPN can pull the PNP’s base, with its current limiting resistor, to 12 volts. This mode will prevent 0.7 volts from dropping across the Base-Emitter diode keeping the transistor OFF. When the NPN drops to ground, then VBE becomes active, and the transistor turns on.


Figure 3: I/O Pin is HIGH, Motor is ON

In Figure 3, I’m showing what happens with the NPN-PNP driver when the Arduino drives 5 volts. It turns on the NPN, which connected the PNP’s base resistor to ground. This path allows 0.7 volts to drop across the PNP’s Emitter to Base junction. The transistor turns ON and the motor spins.


Figure 4: I/O Pin is LOW, Motor is OFF.

Now, when the I/O pin goes LOW, something more interesting happens. The NPN turns into an open because it’s Base-Emitter junction is off. So that leaves the PNP’s base resistor floating. So that means if there is 12 volts on the Emitter, that is the anode of the junction diode. Since current can’t flow through the NPN, the cathode of the junction diode is effectively floating, meaning it will show the 12V connected to its anode. So the VBE becomes: 12v – 12v = 0v. This relationship keeps the PNP, and the motor, OFF.

2. Filter Capacitors Help when you PWM a 3-Pin PC fan with an Arduino

Once this was all setup, I connected my scope and saw the following:

Noisy RPM SIgnal with PWM

Figure 5: Noisy RPM Signal with PWM

All of the noise spikes shown are finding their way into the I/O pin. It falsely triggered the interrupt, messing up an RPM measurement. So what’s going on here? Where are those spikes coming from? The spikes are EMI from high frequencies in the PWM signal.

But wait, the PWM signal from the Arduino is only about 600 Hz. That isn’t very fast. The 600 Hz isn’t our issue. Instead, it is how fast the PWM signal switches from OFF to ON.

EMI comes from the Edge

The edge rate from the Arduino I/O pin, and the NPN connected to it, transitions from LOW to HIGH incredibly fast. There is a lot of what we call “high-frequency content” in that edge. That’s what is making its way onto the hall-effect sensor. All of those little spikes are EMI noise. A simple solution is to slow down the edge to the OFF to ON transition. Connecting a capacitor from the NPN’s collector to ground slows down the edge into the base of the PNP. The current limiting resistor (R1) and capacitor (C2) form an RC network, which reduces much of the noise.

pwm-3-pin-pc-fan-schematic v2

Figure 6: Final Arduino PWM PC Fan Circuit

Picking the PWM Filter Capacitor

Normally you could do a bunch of math to figure out an ideal resistor-capacitor combination. In my case, I just connected a 390nF Ceramic capacitor for C2 and all was good. I did look at using a 470nF and 1uF electrolytic capacitor. However, those had enough capacitance to even out the voltage creating a constant ~10V, preventing any switching. Additionally, their ESR causes substantial “shelves” to appear on the edge. Stick with ceramics or film capacitors for this type of filtering.

Depending on your circuit, you may need to play with that value. Basically, make it big enough to reduce the noise spikes, but low enough that the NPN can still switch the PNP off.

You’re probably going to want a scope for this measurement.

01 - Multiple Cycles from Hall Effect

Figure 7: Multiple Cycles from Hall Effect

The hall sensor is still a bit noisy, especially on the rising edge. The falling edge is pretty clean. So even though this signal is active HIGH, I decided to measure the falling edge. I did find adding some additional capacitance on the sensor signal helps clean it up a bit more. You could also consider playing around with an external pull-up resistor to for a similar RC like filter.

With my PWM signal driving working well and the hall sensor signal cleaned up, it was time to measure RPMs on the Arduino.

3. RPM Measurement with millis()

I created a simple serial interface (single character commands) to adjust the PWM speeds. There is a one-second interval used to display current RPM. The interrupt is used only for counting pulses from the hall effect sensor. However, I don’t think even that is necessary. (Maybe something to tackle in another tutorial.) RPM calculation is pretty basic, but seems to work. My fan is rated for 1200 RPM and I’m measuring 1250ish. The slowest I can seem to run this fan is 600 RPM.

As usual, no delays means you can easily incorporate this code into other Arduino sketches.

unsigned long previousRPMMillis;
unsigned long previousMillis;
float RPM;

unsigned long interval = 1000;
byte pwmValue = 125;
byte pwmInterval = 5;
const byte pwmMax = 255;
const byte pwmMin = 0;

const int reedPin = 2;
const int fanPin = 6;

volatile unsigned long pulses=0;
unsigned long lastRPMmillis = 0;

void countPulse() {
  // just count each pulse we see
  // ISRs should be short, not like
  // these comments, which are long.

void setup() {
  attachInterrupt(digitalPinToInterrupt(reedPin), countPulse, FALLING);
  pinMode(fanPin, OUTPUT); // probably PWM

unsigned long calculateRPM() {
  unsigned long RPM;
  float elapsedMS = (millis() - lastRPMmillis)/1000.0;
  unsigned long revolutions = pulses/2;
  float revPerMS = revolutions / elapsedMS;
  RPM = revPerMS * 60.0;
  lastRPMmillis = millis();
  /*Serial.print(F("elpasedMS = ")); Serial.println(elapsedMS);
  Serial.print(F("revolutions = ")); Serial.println(revolutions);
  Serial.print(F("revPerMS = ")); Serial.println(revPerMS); */
  return RPM;

void loop() {
  analogWrite(fanPin, pwmValue);
  if (millis() - previousMillis > interval) {
    Serial.print(F(" @ PWM="));
    previousMillis = millis();  

void handleSerial() {
boolean printValue = false;
  while(Serial.available()) {
    switch ( {
      case '+':
        pwmValue = pwmValue+pwmInterval;
        printValue = true;
      case '-':
        pwmValue = pwmValue-pwmInterval;
        printValue = true;
      case '!':
        pwmValue = pwmMax;
        printValue = true;

      case '=':
        pwmValue = 125;
        printValue = true;

      case '0':
      case '@':
        pwmValue = pwmMin;
        printValue = true;

      case '?':
        Serial.println(F("+ Increase"));
        Serial.println(F("- Decrease"));
        Serial.print(F("! PWM to ")); Serial.println(pwmMax);
        Serial.print(F("@ PWM to ")); Serial.println(pwmMin);
        Serial.println(F("= PWM to 125"));
        printValue = true;
  if (printValue) {
    Serial.print(F("Current PWM = "));


While this was a fun academic exercise in the proper way to switch motors, it really wasn’t necessary. This entire tutorial is based on a 3-pin PC fan. If you buy a 4-pin PC fan, the 4th pin is for PWM control. The filtering has already been taken care of in that fan. So while this works, it isn’t necessary if you buy the right fan at the start.

Long comments, URLs, and code tend to get flagged for spam moderation. No need to resubmit.

ALL comments submitted with fake or throw-away services are deleted, regardless of content.

Don't be a dweeb.

Leave a comment

54 thoughts on “PWM a 3-pin PC fan with an Arduino

  1. Hello James 🙂 , I’m a huge fan of your work (from Tunisia :p ) because it helped me a lot in understanding the basics of electronics. and I would appreciate if you can answer my question 🙂 . So in this article, in figure 3, you placed the fan in between the collector of the PNP transistor and the ground in this high-side switch situation. But in this article ( ) you said ” A high-side circuit is where the load is placed between the emitter and ground ” . So can you clear this for me a it more and tell me how to place the pnp correctly ? and thank you 🙂 . PS: i’m using this high-side switch to charge a 6v battery by a 9v input voltage.

    • Good catch. The older article was wrong. This article shows the correct configuration for a high-side switch.

      Keep in mind this type of circuit is not appropriate to directly charge a Lithium Polymer or LiPo style battery.

      • i’m using it to charge a gel battery. if it’s also inpropriate to use, Can you suggest me a type of circuit that i can use ?

    • Hey Bjorn, I’m currently just starting down this path myself (fake rpm signal to the controller, slower fan for less noise). I don’t suppose your code is online anywhere I could have a nosey at it?

  2. Hi,
    thanks for the interesting article. I was wondering what is J1 in the last schema, because I’am not able to understand it only by myself! Thank you!

  3. Hi James, a lil’ question: may you add at the post, or give me here the list of the components have you used for this example? For me It’s gonna be hard to replicate it without. sorry 🙂

    • A NPN transistor, A PNP transistor, two 1K resistors, wire, a breadboard, and a 3-pin PC fan. I don’t remember what transistors I used, but a 2n3904 and a 2n3906 would probably work, depending on the fan’s current draw.

  4. Hi there, awesome explanation this is just what I need for my project. However I cannot make the fan change speed, it just sits there at around 50% speed. Uploaded your sketch with the serial monitor control and I did nothing. I used a BC548 and a BC747. 100nF capacitor to signal, 330nF Capacitor in the PNP base to ground, and 2 1K ohm resistors at each base of the transistors. Any idea? Thanks! I follow on youtube, it´s great!

      • Sorry it was a typo. The BC747 was meant BC547. The BASE of the NPN transistor is attachedthrough 1k resistor to pin 9 of arduino, and hall sensor signal (with 100nF capacitor) to pin 2.

        I get readings, and was able to smoth the HALL signal as explaneid in your walkthrough but I cannot get the speed to change even though the PWM duty does.

        (At first I noticed that I reversed biased the PNP, corrected it and put a new one, and it´s the same)

        PS: Thanks for taking time to relive and old post and reply.

      • Thank you for this article. I quickly put together a fan circuit with an N FET and then realized the tacho wouldn’t be grounded during the pwm off cycle and needs either a PNP for switching or a zener on the tacho, so I looked up how others have done it.

        My reason for thinking PNP is different to yours and I’d like to understand why not having it grounded to 0V would cause more EMI, given that it would still be attached to 12V on one end, which I see as “grounded” to a higher potential if you get what I mean. I am also doing DAC and ADC in this circuit and having a clean ground is more important than having a clean 12V.

  5. Hey thanks for this awesome post.
    Can you let us know what NPN and PNP Transistor you used in your circuit?
    Thanks very much !!

  6. Could you explain the purpose of C1? Is this for filtering noise also?

    I have 9 x 12v 1000 rpm PC fans spread across several metres of shelving, some tacho wires are up to 3 metres long, at this length the signal is not stable enough to read using your example code, I’m running the fans directly from a clean 12V supply (240V AC to 12V DC) so no noise introduced via any PWM. Do you have any suggestions on cleaning it up? (I have no oscilloscope unfortunately so can’t look at the signal). The fans work great at 1 metre or less when using the Arduino 20k pull-up.

    • Yes C1 is a filter. For a long distance wire you probably need a much strong pull-up. Something in the 1-4.7K range. Anything below 1K and you should probably reconsider your design. Maybe an op-amp on the transmit (and receive) side to boost up the signal’s voltage to account for loss in the wire.

  7. You can actually run the PNP-transistor without an “awkward voltage divider” by simply adding a single diode instead of an additional NPN-transistor. Even for your open-collector driver you actually should provide a resistor between base and +12V for the PNP for highest reliability.

    • Sorry, uwezimmermann, I don’t follow your first comment. What “awkward voltage divider”? When the NPN transistor is driven with enough base current the C/E junction will saturate, effectively pulling the collector to ground (almost, maybe with a potential across the C/E junction of, say, 100mV, no transistor is a perfect switch). If the base isn’t driven hard enough, then yes, a “voltage divider” might be formed as the C/E junction isn’t fully saturated. When the C/E junction is saturated I really would class the transistor as a switch and not really forming an “awkward voltage divider”. Don’t you agree? I also don’t understand what you mean by replacing the NPN transistor with a single diode? This removes the switching capability if it was to replace the C/E junction of the transistor. If it was to used in place of the B/C of the NPN and was forward biased, then when the input was high the PNP transistor would be forced off (opposite the current configuration) and if the input was low, you would need to have a path to ground to still turn on the PNP transistor.

      I agree that adding a resistor from the base of the PNP transistor to Vcc would ensure that the transistor is truly “off” when the NPN is not driven, although I think you might find that the “leakage” current through the NPN (when off) is tiny and is not enough to switch the PNP transistor on (in this case).