Microcontroller projects

Fan control: Silence your inverter

last updated: 2023-07-17

Quick links

Introduction

My Victron Multiplus-II 48/8000/100 inverter are quite noisy. First I measured the noise with my Noise and Temperature Meter and monitored the noise with openHAB.

To silence the Multiplus II, I added two quiet noctua fans per Multiplus. They get info about the DC current from an MQTT server and control the speed accordingly. The fans reduce the noise by 5-6 dBA. The first day is without the fans. They were installed the second day at around midday.

fan_control openhab data

Hardware

The ESP32 has enough pins to add 6 fans. But as we use 2 fans per inverter I used the Y-cable included to connect 2 fans together and reduce the amount of headers and cables. I recycled an old power supply from a printer.

BOM

Circuit

I added an ELKO, because I got brownout errors with 6 connected fans starting all at once before the PWM is ready.

circuit noise meter

FreeCAD and 3D print

I redesigned a holder for the fan as the holder found on the net was designed for a Multiplus II 5000 (https://community.victronenergy.com). I liked the idea of "tazer" to let the Multiplus housing untouched and used also 10 mmx3 mm super magnets to fix the holder. Glue them to the plastic. The fans are fixed with the enclosed rubber fasteners. Best enlarge the printed holes after printing by drilling them with 5 mm.

The KiCAD and stl-files for the circuit housing and the holder can be found on github https://github.com/weigu1/fan_control

fan control x2

fan control holder   fan control case

fan control holder   fan control case

fan control holder   fan control case

Software

The code is on github https://github.com/weigu1/fan_control.

I used my ESPToolbox library to connect per WiFi, program via OTA and debug via UDP. More infos about this code on this page: https://www.weigu.lu/microcontroller/esptoolbox/index.html.

Here only the part of the code regarding interrupts to get the speed and the PWM-generation. The MQTT callback function gets the DC current to calculate the speed of the fans.

    /*
     fan_control.ino
     www.weigu.lu*/
    ... 

    const byte PINS_PWM[] = {16, 17, 21};
    const byte PWM_CHANNELS[] = {0, 1, 2};
    const byte PINS_SPEED[] = {23, 19, 18};

    const byte nr_of_fans = sizeof(PINS_PWM) / sizeof(PINS_PWM[0]);
    volatile unsigned int fan_speeds[nr_of_fans] = {0};

    const unsigned int PWM_FREQ = 25000;   // freq limits depend on resolution
    const byte PWM_RES = 8;                //resolution 1-16 bits

    byte duty_cycle = 0;
    int dc_current = 0;


    ESPToolbox Tb;                                // Create an ESPToolbox Object

    /****** SETUP *************************************************************/

    void setup() {   
      delay(10000);  
      Tb.set_led_log(true); // enable LED logging (pos logic)  
      Tb.set_udp_log(true, UDP_LOG_PC_IP, UDP_LOG_PORT);  
      #ifdef STATIC
        Tb.set_static_ip(true,NET_LOCAL_IP, NET_GATEWAY, NET_MASK, NET_DNS);
      #endif // ifdef STATIC
      Tb.init_wifi_sta(WIFI_SSID, WIFI_PASSWORD, NET_MDNSNAME, NET_HOSTNAME);
      Tb.init_ntp_time();  
      MQTT_Client.setServer(MQTT_SERVER,MQTT_PORT); //open connection MQTT server
      MQTT_Client.setCallback(mqtt_callback);
      mqtt_connect();
      MQTT_Client.setBufferSize(MQTT_MAXIMUM_PACKET_SIZE);
      #ifdef OTA
        Tb.init_ota(OTA_NAME, OTA_PASS_HASH);
      #endif // ifdef OTA
      init_pins();
      Tb.blink_led_x_times(3);
      Tb.log_ln("Setup done!");
    }

    /****** LOOP **************************************************************/

    void loop() {
      #ifdef OTA
        ArduinoOTA.handle();
      #endif // ifdef OTA
      if (Tb.non_blocking_delay(5000)) { // every 5s
        for (byte i=0; i<nr_of_fans; i++) {
          // calculate fan speed in rpm (2 imp. per revolution!)
          fan_speeds[i] = fan_speeds[i]*6;
        }    
        if (dc_current > 255) { 
          duty_cycle = 255;
        }   
        else {
          duty_cycle = dc_current;
        }
        for (byte i=0; i<nr_of_fans; i++) {
          ledcWrite(PWM_CHANNELS[i], duty_cycle);
        }
        mqtt_publish();
        Tb.log_ln(String(duty_cycle) + "\t" + String(fan_speeds[0])+ "\t" +
                  String(fan_speeds[1])+ "\t" + String(fan_speeds[2]));    
        Tb.blink_led_x_times(1);    
        for (byte i=0; i<nr_of_fans; i++) {
          fan_speeds[i] = 0;  // reset fan speed counter
        }  
      }  
      if (WiFi.status() != WL_CONNECTED) {   // if WiFi disconnected, reconnect
        Tb.init_wifi_sta(WIFI_SSID, WIFI_PASSWORD, NET_MDNSNAME, NET_HOSTNAME);
      }
      if (!MQTT_Client.connected()) {        // reconnect mqtt client, if needed
        mqtt_connect();
      }
      MQTT_Client.loop();                    // make the MQTT live
      delay(10); // needed for the watchdog! (alt. yield())
    }


    /********** INIT and INTERRUPT functions ***********************************/

    void IRAM_ATTR isr_speed_0() {
      fan_speeds[0]++;
    }

    void IRAM_ATTR isr_speed_1() {
      fan_speeds[1]++;
    }

    void IRAM_ATTR isr_speed_2() {
      fan_speeds[2]++;
    }

    void init_pins() {
      for (byte i=0; i<nr_of_fans; i++) {
        ledcAttachPin(PINS_PWM[i], PWM_CHANNELS[i]); // assign pin to channel
        ledcSetup(PWM_CHANNELS[i], PWM_FREQ, PWM_RES);    
        pinMode(PINS_SPEED[i], INPUT_PULLUP);
      }  
      attachInterrupt(PINS_SPEED[0], isr_speed_0, FALLING);
      attachInterrupt(PINS_SPEED[1], isr_speed_1, FALLING);
      attachInterrupt(PINS_SPEED[2], isr_speed_2, FALLING);
    }

    /********** MQTT functions ***************************************************/

    // connect to MQTT server
    void mqtt_connect() {
      while (!MQTT_Client.connected()) { // Loop until we're reconnected
        Tb.log("Attempting MQTT connection...");
        if (MQTT_Client.connect(MQTT_CLIENT_ID)) { // Attempt to connect
          Tb.log_ln("MQTT connected");
          MQTT_Client.subscribe(MQTT_TOPIC_IN.c_str());
        }
        else {
          Tb.log("MQTT connection failed, rc=");
          Tb.log(String(MQTT_Client.state()));
          Tb.log_ln(" try again in 5 seconds");
          delay(5000);  // Wait 5 seconds before retrying
        }
      }
    }

    // MQTT get the time, relay flags ant temperature an publish the data
    void mqtt_publish() {  
      DynamicJsonDocument doc_out(1024);
      String mqtt_msg, we_msg;
      Tb.get_time();
      doc_out["datetime"] = Tb.t.datetime;    
      for (byte i=0; i<nr_of_fans; i++) {
        doc_out["fan_speeds_rpm"][i] = fan_speeds[i];
      }  
      doc_out["dc_current"] = dc_current;
      serializeJson(doc_out, mqtt_msg);
      MQTT_Client.publish(MQTT_TOPIC_OUT.c_str(),mqtt_msg.c_str());
    }

    void mqtt_callback(char* topic, byte* payload, unsigned int length) {  
      DynamicJsonDocument doc_in(1024);
      deserializeJson(doc_in, (byte*)payload, length);   //parse MQTT message 
      if (String(topic) == MQTT_TOPIC_IN) {      
          dc_current = abs(int(doc_in["value"]));
      }
    }

Links

Downloads