It was neat that I could control the speed of a 4-wire CPU cooling fan with just software a PWM signal from an ESP8266, but 4-pin fans with built-in power switching are in the minority. Most available fans have no built-in speed control and depend on external PWM circuitry to vary their input voltage level. If I wanted to control speed of such fans with an ESP8266, I'll need to get a power transistor of some sort into the circuit.

I've found several tutorials online for fan speed control via PWM, but they all use a transistor on low side of the fan. They do this because it makes the circuit easier. We connect the black fan wire to collector of the transistor, connect emitter to ground, and finally connect PWM signal from our microcontroller to transistor base. This will work because the transistor and the microcontroller share a common ground. A transistor needs only a volt or so on the base pin, easily delivered from any microcontroller.

The problem with this approach is that we could no longer directly read the tachometer signal of 3-pin fans, because they are open-drain to the "ground" which is no longer ground but low side of the fan. Depending on implementation details on the fan, the voltage level on that pin may rise too high for the microcontroller.

There are many valid ways to resolve this situation, the path I chose is to use a PC817 optocoupler. (*) Internally it is a LED pointed at a receiver. This optical system transmits a signal while electrically separating LED side from receiver side. In my case this I no longer need a common ground.

From here, there are two options forward: I could use the optocoupler to read the tachometer signals, or I could use the optocoupler to put the transistor on the fan's high side. I chose to switch the fan's high side so the fan has a common ground with the microcontroller, and I could directly read tachometer signal. Of course, it is valid to use optocoupler on both motor power PWM and for tachometer feedback. This is necessary for electrically noisy motor systems, but the brushless fan of a computer cooling fan (usually) does not require such measures.

I did have to add a capacitor to smooth out my PWM output, but that would have been necessary in any case. Having full PWM control means I can now switch the fan off completely with a 0% duty cycle, but it also means I am responsible for avoiding low PWM levels where a fan could not turn and stalls. After I proved I had PWM control via Home Assistant UI, I thought it would be fun to have the option to control fan based on temperature, so I added a TMP36 sensor.


ESPHome YAML excerpt for basic PWM fan control and reading fan tachometer. Note: this simple example lacks intelligence to avoid low PWM levels that would stall a fan.

sensor:
  - platform: pulse_counter
    pin: 12
    id: fan_rpm_counter
    name: "Fan RPM"
    unit_of_measurement: "RPM"
    accuracy_decimals: 0
    update_interval: 300s
    filters:
      - multiply: 0.5 # 2 pulses per revolution

output:
  - platform: esp8266_pwm
    pin: 14
    id: fan_pwm_output
    frequency: 1000 Hz

fan:
  - platform: speed
    output: fan_pwm_output
    id: fan_speed
    name: "Fan Speed Control"

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases.