last updated: 2023-07-17
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.
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.
I added an ELKO, because I got brownout errors with 6 connected fans starting all at once before the PWM is ready.
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
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"]));
}
}