PWM Fan Controller

I’m not picking on you or anything, I’m just saying for future reference. Adding one or a couple of these things, pictures, a config, a link and then a to the point description of the problem would have been more helpful than a 3 or 4 paragraph thing. All that extra stuff over multiple paragraphs, it makes it more confusing a lot of the time. I really mean no offense but, you put some time and a few minutes into that post and it’s obvious you meant well and you tried but, it’s all mostly not important, not useful and now confusing.

Would you be talking about a server rack ran controller with rpm sensor?

Thanks for this code, it’s just what i need, but…i’d like it to work with a dallas sensor instead. I’ve tried but just cannot get it to work. Would anyone be so kind as to post up the code, but for a dallas sensor instead of a dht ?

Thank you in advance
Lee

Go look at the documentation , it explains how to do it. You gotta be a big boy and make an effort.

I already done it. I’m sorry but I forgot to post up to say so.
Thank you so much for your reply, it means the world to me

This is ace.
Am going to do this.
I have three NF-F12 Industrial 3000RPM fans.
If I were to extend this circuit for controlling the three fans individually, I assume:

  • Add PWM & RPM for Fan 2 to D3 and D4
  • Add PWM & RPM for Fan 3 to D5 and D6
  • Would need 2 extra 10k Resistors
  • would keep the single DHT11

Rather old thread but I still like to add, that based on what I read here and elsewhere I also did my fan controller to upgrade my deye inverter (remove the noise). You can find my code here: GitHub - sker65/esphome-fan-controller: Yet another esphome based fan controller. Can be controlled either manually or by temperature sensor.

From my point of view the code is cleaner (simple linear temperatur characteristics), simple switch between manual and temp controlled, optional MOSFET to turn the fans off. See some more comments on the githubwiring

3 Likes

Just clarifying it a little bit about what @fr8tra1n said about the 12V:

The OP said he was using a 10KΩ resistor from the 12V to the fan tachometer pin. This pin is also connected to the ESP8266, which he assumed, correctly, won’t survive to be exposed to 12V very long.

Quick clarification: the tachometer is not “sending pulses”. The line is always UP (12V), and the tachometer connects it to ground (GND) 2 times per revolution. Yeah, it’s “sending pulses” in the sense that the line is always at 12V (pulled up by the pull-up 10KΩ resistor) and those “pulses” are 0V (GND).

The tachometer may not work correctly with a lower voltage, let’s say, 3.3V, because, as the OP said before, the RPM readings were not accurate.

I’ve seen people trying to use a voltage divider (other 2 resistors) to bring the 12V line down to 3.3V, a voltage that the ESP8266 can stand, before reaching the ESP8266 pin. I didn’t test it yet but, in principle, it should work. These voltage dividers can be configured in many ways and there are many calculators on the web that you can use for helping with that. For example:

Voltage Divider Conversion Calculator | DigiKey

You can use, let’s say, a 27KΩ resistor for R1 and a 10KΩ resistor for R2, bringing the 12V input to 3.2V.

The formula is simple:
Vin / (R1 + R2) * R2 or, in our case, 12V / (27KΩ + 10KΩ) * 10KΩ which gives us 3.24V

Notice that Ohm’s Law (I = V / R) units are still obeyed in this formula. So, when we divide V / (R1 + R2), we have the current (I). But then we multiply it by R2, and I * R = V, so having our result in volts (V) is correct.

2 Likes

Then someone have try to use two resistors? Do you have exact value and schematic?
I am newbie

Is right this schematic, vin as output of tachimeter fan, and vout to D1 mini ?

Thank you for the great code. It was extremely helpful, not only for managing my fan but also for clarifying many things in ESPhome.

In my system it spins almost at a maximum speed of 1700 rpm, whereas the specification is 1800 rpm. I’m using an Arctic 120mm PWM fan with a 560R resistor at 3.3v.

This is the way was realized in Arduino and I directly used it in ESPhome."

Hi,
i have a problem with the Fan control. If i start the ESP8266, the Controller won’t work. The only thing who works is the function, when i turnend on and off the overridet function.

Can anyone help me?

  - platform: bmp280
    temperature:
      name: "Temperature"
      id: rack_fan_temperature
      oversampling: 16x
      accuracy_decimals: 1
      on_value_range:
        - above: 40.0
          then:
            if:
              condition:
                fan.is_off: rack_fan_toggle
              then:
              - logger.log: "Set fan level 100 over 40C"
              - output.set_level:
                  id: rack_fan_speed
                  level: 100%
        - above: 30.0
          then:
            if:
              condition:
                fan.is_off: rack_fan_toggle
              then:
              - logger.log: "Set fan level 66 over 30C"
              - output.set_level:
                  id: rack_fan_speed
                  level: 66%
        - above: 20.0
          then:
            if:
              condition:
                fan.is_off: rack_fan_toggle
              then:
              - logger.log: "Set fan level 33 over 20C"
              - output.set_level:
                  id: rack_fan_speed
                  level: 33%
        - below: 20.0
          then:
            if:
              condition:
                fan.is_off: rack_fan_toggle
              then:
              - logger.log: "Set fan level 15 under 20C"
              - output.set_level:
                  id: rack_fan_speed
                  level: 15%

And when the temperature decreases. E.g. from 30 to 25 °C, the fan doesn’t reduce the speed in accordance with the given % values.

I found this thread (and many others) very helpful, so I wanted to share my current solution for a 4-Pin PWM Fan controller with speed monitoring and a temperature sensor. This is to go into a cupboard where I want to put a NAS and mini-server but need to increase the airflow for cooling.

ESPHome YAML Code
esphome:
  name: fancontroller01
  friendly_name: fancontroller01

esp8266:
  board: esp01_1m

dallas:
  - pin: GPIO5 #D1
    update_interval: 10s #Low Interval for DEBUGGING

# Enable logging
logger:
  
# Enable Home Assistant API
api:
  encryption:
    key: "VeryLongAndVeryRandomisedMagicEncryptionKey="

ota:
  password: "SuperSecureHighlySecretPassword1"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Static IP
  manual_ip:
    static_ip: 192.168.0.11
    gateway: 192.168.0.1
    subnet: 255.255.255.0

captive_portal:

# Notes on Pins
# FanPin1 | "Black" | Gnd                      | 
# FanPin2 | "Red"   | +12v                     | 
# FanPin3 | "Green" | RPM Signal/Sense/Tach    | Pin D7 = GPIO13
# FanPin4 | "Blue"  | PWM Control Signal (+5v) | Pin D6 = GPIO12

sensor:
  # Send WiFi signal strength & uptime to HA                                                                                                                                                                                             
  - platform: wifi_signal
    name: WiFi Strength
    update_interval: 10s #Low Interval for DEBUGGING

  # RPM Signal from Fan                                                                                                                                                                                                                  
  - platform: pulse_counter
    pin: GPIO13 #D7
    name: RPM Signal
    id: fan_RPM_pulse
    unit_of_measurement: 'RPM'
    filters:
      - multiply: 0.5
    count_mode:
      rising_edge: INCREMENT
      falling_edge: DISABLE
    update_interval: 10s #Low Interval for DEBUGGING

  - platform: dallas
    address: 0x9400000000000000
    name: Temp01

output:
  - platform: esp8266_pwm
    id: fan_speed
    pin: GPIO12 #D6
    frequency: "25000 Hz"
    min_power: 10%
    max_power: 100%

fan:
  - platform: speed
    output: fan_speed
    speed_count: 10
    name: fan speed control
    id: fan_speed_toggle


The 2nd fan piggy-backs off the first with an included daisy-chain cable and I don’t need independent control of them.

I used a power module like this (Similar AliExpress Link) so I could pull both 12v and 3.3V/5V off a single power supply.

I still have a few things I want to do to it before installing:

  • Add 2nd temperature sensor (2 shelves/zones)
  • Code temp>speed (need to get in place and monitor to work out the right levels first) rather than HA Manual Control
  • Add code to send data into InfluxDB

Hi,

You might like to look at the post I wrote at 4-pin fan with d1mini and esphome: how to read current rpm of the fan? - #18 by MrFusion

I go over the spec for 4-wire fans and how to connect an ESP.

The Tacho output of the fan is (according to the spec meant to be) open collector/open drain. This means that the output is switched between being tied to ground and floating - which in turn means that you then need a pull-up resistor in order to reliably read the output (ie Tacho out ↔ GPIO input has a resistor to a positive supply).

Unfortunately, tying this to 12v can be problematic. Espressif’s official position is never feed any pin with more than 3.3V (see, for instance this post by ESP_Sprite).

The only reason that tying it to 12V hasn’t fried your ESP is generally more down to good luck than good planning. You’re actually relying on the ESP’s built-in ESD protection diodes (plus any other parasitic loads on the pin in questions) to carry the load whenever the Tacho output is ‘high’ (ie output floats so power going to the GPIO pin is coming via the pull-up resistor). This is generally not a great idea as the protection diodes generally can only carry a very small current, plus you don’t really know or have any control of what protection is in place per GPIO (and with ESPs there’s sometimes less than you would like on some pins…).

The way it works in this case (assuming a 10k pull-up) is that, when output is high:

  • Power flows from 12V rail through the pull-up resistor to the GPIO pin
  • Internally the voltage is seen by the input as well as the anode of the protection diode which then shunts the angry pixies through to the ESP’s internal positive supply rail
  • This looks like 12V → 10kOhm → junction of chip input and >diode> → 3.3v
  • Chip input is ‘high-Z’ (unless you’ve set internal pull-up or pull-down)
  • Forward bias voltage of the diode is approximately 0.7V

So

  • the voltage at the junction of chip input and diode is approximately 3.3-0.7 = 2.6V
  • therefore the voltage drop across the pull-up resistor is 12 - 2.6V = 9.4V
  • therefore the current flowing through the resistor & diode is 9.4V / 10,000 Ohms = is about 9.4mA… let’s call it 10mA

And 10mA is right at the limit of what many on-die ESD diodes can carry for any sustained period of time.

These numbers are just approximate but easy enough to be more precise - measure with a multimeter:

  • Power up the ESP & fan, and then stall the fan at a point where the Tacho output is high (or you could just connect the resistor directly between 12v and GPIO and leave the fan out of it - but I recommend involving the fan as well because you then have a chance to discover how well the fan actually implements the spec, or if it maybe has it’s own pull-up resistor, etc, etc)
  • Measure the voltage present at the ESP GPIO pin (guesstimate is 2.6V but could be quite a bit higher or lower)
  • Measure the pull-up resistor’s actual resistance
  • Re-do the calcs

Suggestions:

  • The ‘best’ (and recommended by Espressif as well as, tangentially, by Intel) - simply power the 10k pull-up resistor via the 3.3V line instead of 12V. Downsides - for many & varied reasons (usually related to the fan tacho’s implementation) the ESP may not see a clear enough signal & this might require a scope to diagnose & resolve
  • Alternative ‘best’ - feed direct from Tacho to GPIO (with no other components) and set GPIO mode to INPUT_PULLUP
  • Second ‘best’ - implement additional components at the GPIO input to guarantee voltage never goes above 3.3V (along with the 10k pull-up resistor, a 3V zener to ground & a schottky to positive should do it)
  • Third ‘best’ - increase the pull-up resistor value. Try, for instance, 100k or more to bring the current through the built-in ESD diode right down

Hope this all makes sense & that someone finds it helpful!

4 Likes

Hi,

I would like to recreate this. The temperature and humidity are good and when I connect a Cooler Master fan the control is perfect.

But I need a bigger fan. I have an Arctic F12 PWN. But if I want to control this fan, I can’t do that. The fan is always on.

Can someone help me?

Thanks

I’m using the schemtic for my pwm fan controller with a mofset, I think it’s a good idea to add a transistor between the mofset and the esp since when starting/programming the esp the pins are high so having a transistor will prevent the fan from spining when not configured. Also I use a levelshifter on the input/output pins of the fan and it works so well.
This also allows you to completely turn of power to the fan, adding a shutdown fan. In this case you can set the fun to turn off whem duty cycle is 0

Hi Guys,

@Stu1811 @vinicius.vbf @steve6502

Firstly, thank you for documenting your sketch and findings.

I’m not so brilliant at this, but none-the-less, I’ve made an attempt using Stu1811 original sketch to work from for my own needs.
I’m having mixed results and findings from my manipulations.
I’m trying (and succeeded), in controlling a dimmable led strip, and also trying to control a Noctua S12A PWM 12v fan.
I’m also using the ESPThings ET-AL01 5 channel LED controller, one channel for the 12v led strip, and another channel for the PWM fan control.
Here’s my work so-far, based from Stu1811 original work;

esphome:
  name: test-display-fan
  friendly_name: Test Display Fan

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxx"

ota:
  - platform: esphome
    password: "xxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "XXXXXX"
    password: "xxxxxx"

captive_portal:
    
output:
# ESP32 PWM=LEDC LED Light GPIO PIN 05 from Terminal Block via Mosfet
# GPIO 23 = PWM From Terminal Block to Fan via Mosfet
# ESP32 GPIO 16 Tacho Input
  - platform: ledc
    pin: GPIO05
    id: gpio_05

  
  - platform: ledc
    pin: GPIO23
    id: fan_speed
    frequency: "25000 Hz"
    min_power: 0%
    max_power: 100%
    zero_means_zero: True

light:
  - platform: monochromatic
    output: gpio_05
    name: "Display Cabinet 1"


fan:
  - platform: speed
    output: fan_speed
    name: Disp Fan speed
    id: disp_fan
  
    on_turn_on: 
      - output.set_level: 
           id: fan_speed
           level: !lambda return id(fan_speed_control).state/100.0;

    on_turn_off: 
      - output.set_level: 
           id: fan_speed
           level: !lambda if (id(fan_speed_control).state < 1){return 0;};
        
        

number:
  - platform: template
    name: Fan Speed Control
    id: fan_speed_control
    internal: False
    max_value: 100.0
    min_value: 0.0
    step: 1
    optimistic: True
    mode: SLIDER
    on_value: 
      #then:
      
           #fan_control_speed: disp_fan 
           #then:  
        - output.set_level: 
           id: fan_speed
           level: !lambda "return x/100;"


# RPM Signal from Fan
sensor:
  - platform: pulse_counter
    pin: GPIO16
    name: Fan Speed Tacho
    id: fan_speed_tacho
    unit_of_measurement: "RPM"
    accuracy_decimals: 1
    filters:
      - multiply: 0.5
    count_mode:
      rising_edge: INCREMENT
      falling_edge: DISABLE
    update_interval: 5s

I have the LED strip on gpio 05 via the mosfet output from the ESPThings driver board.
12v and - (negative) for the fan are connected to the 12v and - (negative powering the ESPThings driver board.
gpio 23 via the mosfet on the ESPThings driver board is the fan PWM.
gpio 16 is the tacho input directly to the D1 board, via the header on the ESPThings driver board.
The LED strip and dimming seems to work fine. What I’m finding with the fan is when I power up, the Slider is at 0 (zero), and the Fan switch is OFF, but the fan is running at Full 1300RPM.
If I turn the fan switch ON, Slider is at 0 (zero), the fan is still running at Full 1300 RPM.
Fan switch is ON, slider a 100%, fan is OFF (zero), RPM
Fan switch is OFF, Slider at 100%, fan is running a full 1300RPM

Now I’m guessing I need to add a line of inverted: True, either in the Fan section or Number section or both.
Then I’m guessing that the fan switch doesn’t work due to my lambda in the fan section.
As previously stated, I’m relatively new to yaml, I’ve never used the number component before, and not used lambda much before either.
I’ve read many of the documents and many posts that can end up taking far away from what I’m trying to accomplish.
Do I need to map the number component to the fan component?

I’d be very grateful for pointers of where I’m going wrong.

You shouldn’t operate that fan with some driver board, it has driver inside. PWM nominal voltage is 5V, but quite sure it accepts 3.3V pwm from your Esp. Just connect fan pwm wire to Esp pmw output pin. Common ground between 12V PSU and Esp.

Don’t try to switch off the 12V negative to fan, it might cause the driver inside fan to sink 12V to Esp pin. Fan wouldn’t stop and eventually damages Esp.

A-ha,

Thank you @Karosm , I didn’t even realise the driver was inside the fan.

Now that works, with the exception of the on-off toggle in the front end.

I’ll go an work on that part, as I’ve no got any instructions in there yet to tell ESPHome what to do if the switch is toggled in HA.

I’ll have a play and report back so others can benefit.

Thank you.

Hi Guys,

Well I’ve tried and failed in finding how to “Update” the frontend UI toggle switch.

As you will see I’ve left most of what I’ve tried in the sketch as commented out text.

What Is happening (forgetting about the LED parts), is the Fan slider and the Fan toggle switch both work in the UI, but if i drag the slider to zero the toggle switch stays in ON position.
I the UI Fan switch is ON and say the slider at 100%, if I toggle the UI Fan switch to OFF the slider remains at 100% rather than dropping to zero.

Am I expecting to much from this?
I’ve read many documents and many posts, and possibly confused my myself more than needed.
I basically know that I need the UI/Frontend to update the number entity, and the number entity to update the UI/Frontend, but can’t fathom it out?

esphome:
  name: test-display-fan
  friendly_name: Test Display Fan

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxx"

ota:
  - platform: esphome
    password: "xxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "xxxxxx"
    password: "xxxxxx"

captive_portal:
    
output:
# ESP32 PWM=LEDC LED Light GPIO PIN 05
# GPIO 22 = PWM From Terminal Block to Fan via Mosfet
# ESP32 GPIO 16 Tacho Input
  - platform: ledc
    pin: GPIO05
    id: gpio_05

  
  - platform: ledc
    pin: GPIO22
    id: fan_speed
    frequency: "25000 Hz"
    min_power: 0%
    max_power: 100%
    zero_means_zero: False

light:
  - platform: monochromatic
    output: gpio_05
    name: "Display Cabinet 1"


fan:
  - platform: speed
    output: fan_speed
    name: Disp Fan speed
    id: disp_fan
     #on_value_range:
     # - above: 1.0
     #   then:
     #     if:
     #       condition:
     #         fan.is_on: disp_fan
  
    on_turn_on: 
      - output.set_level: 
           id: fan_speed
           level: !lambda return id(fan_speed_control).state/100.0;
           #if (id(fan_speed)raw.state < 1){return 0;}
            # if:
            #  level: !lambda return id(fan_speed_control).state<1{return 0;}

    on_turn_off: 
      - output.set_level: 
           id: fan_speed
           level: !lambda if (id(fan_speed_control).state < 1){return 0;};

        
        

number:
  - platform: template
    name: Fan Speed Control
    id: fan_speed_control
    internal: False
    max_value: 100.0
    min_value: 0.0
    step: 1
    optimistic: True
    mode: SLIDER
    on_value:
      #if:
      #  condition:
      #    number.in_range:
      #      id: fan_speed_control
      #      above: 1
      #  then:
      #     : fan_speed 
     # then:
     #   if:
     #     - output.set_level:
     #       id: fan_speed_control
     #       level: !lambda 
      then:
             - output.set_level: 
                id: fan_speed
                level: !lambda "return x/100;"
      #if (id(fan_speed)raw.state < 1){return 0;}
      #  update(fan_speed)
      #update()


# RPM Signal from Fan
sensor:
  - platform: pulse_counter
    pin: GPIO16
    name: Fan Speed Tacho
    id: fan_speed_tacho
    unit_of_measurement: "RPM"
    accuracy_decimals: 1
    filters:
      - multiply: 0.5
    count_mode:
      rising_edge: INCREMENT
      falling_edge: DISABLE
    update_interval: 5s

I have tried many more things than the commented out parts shown above, but never thought to keep them all in until a couple of hours ago, but it shows some of the many different fan and number properties I’ve tried.
Anyway, I’m grateful for anyone that reads this and has any input.

I’m not able to help for the HA part, but I don’t see any switch on your yaml.

Hi Guys,

@Karosm @Stu1811 @vinicius.vbf @steve6502

The way I understand it is a toggle switch on the frontend/UI is created with the following;

light:
    name: "Display Cabinet 1"

and also with,

fan:
    name: "Disp Fan speed"
    mode: SLIDER

And the slider on the UI is created with;

number:
    name: "Fan Speed Control"

Screen Shot 2024-12-05 at 07.52.05

I haven’t found how to get the number (Fan Speed Control slider on UI), entity to update the fan (Disp Fan speed toggle switch in UI).
Or the other way around where fan (Disp Fan speed UI toggle switch) controls the the number (UI slider 0%-100% or 100%-0%).

I think I need to somehow get it to update or map, either that or I’m over thinking things with reading so many documents?

I’ll have another crack at it later.