Solving the DL180 G6 Fan Controller Problem

Some time ago I acquired a DL180-G6 rack mount server for very little money as these go for next to nothing on eBay in most configurations and one version has 12×3.5″ drive bays. Unfortunately there seems to be a bit of a problem with these systems where if you change any of the hardware (such as an unsupported raid card) or a whole variety of other things the on board fan controller defaults to high speed and it will sound like a jet engine. The fans are high flow high speed fans and so while they do work well they really scream at high speeds. Disconnecting the fans won’t help because if you do that the system will refuse to boot entirely.

The Fans

I decided that I needed to come up with a solution to this problem because well…how hard can it be?! Fundamentally these are standard 4 pin PWM type fans which have the following connections:

1 – GND
2 – +12V
3 – Tach
4 – PWM

Unfortunately the ones in the DL180G6 are a special case in that they have 6 pin connectors which follow a slightly different layout:

1 – +12V
2 – GND
3 – Blank
4 – Tach
5 – PWM
6 – Blank

The blank pins on the fan connector may seem a bit pointless at first but they’re like that because the motherboard support redundant pairs of fans in some configurations (I saw this on a Storageworks x1600g2 which shares the same main components). Due to this the motherboard needs a second tach (RPM) signal to monitor the second fan in the redundant pair and presumably due to the high power consumption of these fans a second +12V supply. This means you can either connect a single fan direct to the motherboard or a pair via the splitter which comes in the storage works (HP p/n  534358-001). The motherboard header looks like this:

1 – +12V-1
2 – GND
3 – +12V-2
4 – Tach – 1 (Inlet)
5 – PWM
6 – Tach – 2 (Outlet)

The good news is this means you only need one PWM per pair of fans in this configuration.

Arduino 25kHz PWM

At this point I decided I needed to build something I could fully control and so I decided to try to control it with an Arduino using the on board hardware PWM. This isn’t as easy as it sounds because PWM fans operate on a PWM control frequency of 25kHz which isn’t a standard configurable frequency on an Arduino IDE because it isn’t a direct division of the system clock. Thankfully there is a way round this. Timer 1 on the Arduino nano is a 16 bit timer and by directly manipulating the timer registers we can make the timer reset after a number of cycles so we can basically create a timer of any frequency we need. In this case  rather than being limited to prescalers (divide by 1,8,64,256,1024) from the main clock (16MHz) and 0-255 (0% – 100% duty) by using the 16 bit timer we can set a clock reset every 320 cycles which gives 25kHz at this clock frequency. I found some code which nicely did this on stack exchange and so just went with it.

https://arduino.stackexchange.com/questions/25609/set-pwm-frequency-to-25-khz

The clever bits are shown here:

    // Configure Timer 1 for PWM @ 25 kHz.
    TCCR1A = 0;           // undo the configuration done by...
    TCCR1B = 0;           // ...the Arduino core library
    TCNT1  = 0;           // reset timer
    TCCR1A = _BV(COM1A1)  // non-inverted PWM on ch. A
           | _BV(COM1B1)  // same on ch; B
           | _BV(WGM11);  // mode 10: ph. correct PWM, TOP = ICR1
    TCCR1B = _BV(WGM13)   // ditto
           | _BV(CS10);   // prescaler = 1
    ICR1   = 320;         // TOP = 320
void analogWrite25k(int pin, int value)
{
    switch (pin) {
        case 9:
            OCR1A = value;
            break;
        case 10:
            OCR1B = value;
            break;
        default:
            // no other pin will work
            break;
    }
}

The first bit sets up the timers to run at 25kHz as we need. The second bit is a special analogue write function to make sure we set the time right. We will use this function to update the timer value. We now have two PWM channels on pins 9 and 10 of our Arduino Nano.

Temperature Control

Next up I decided to add temperature control to these fans using a couple old DS18B20 temperature sensors I had lying about. These are good because multiple sensors can be daisy chained to a single data pin and the value doesn’t need scaling or anything unlike an analogue input pin and a thermistor. They cost a little more but for convenience they’re hard to argue with. They usually use +5V, Gnd and one signal pin. The signal pin needs a pullup resistor to work – this is normally specified as 4.7k. Technically these are a “1wire” device which can be run on parasitic power so the +5V isn’t actually required at the device end but if used in parasitic power mode you short it’s Vdd pin to ground. More information can be found here:

https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf

There are specific Arduino libraries for using these devices so they’re really easy to use but they have one issue in this application. To do what I intended and have two separate fan zones we need to know which sensor is which. Thankfully included with the Onewire library is an example function called “DS18x20_Temperature” which will scan for devices on a specified pin. In the example this is called “ds” and it pin 10 by default but for my purposes this won’t work because we specifically need pin 9 and pin 10 for the two PWM outputs so this needs to be relocated somewhere else.

#include <OneWire.h>

// OneWire DS18S20, DS18B20, DS1822 Temperature Example
//
// http://www.pjrc.com/teensy/td_libs_OneWire.html
//
// The DallasTemperature library can do all this work for you!
// http://milesburton.com/Dallas_Temperature_Control_Library

OneWire ds(10); // on pin 10 (a 4.7K resistor is necessary)

void setup(void) {
Serial.begin(9600);
}

Changing the pin to whatever you choose and running the example with serial monitor open you’ll get an output of the devices it finds. If you do this with one device connected at a time you can note down the device address for each. I marked them as #1 and #2 before hand to keep track of these later.

The Circuit

The next bit is bringing it all together electronically. We already looked at the Onewire sensors so these are really easy to connect up, I used two normal 3 pin PC fan connectors to allow these to be disconnectable for easy installation. The next bit is the fans. The PWM on fans are controlled using a pulse signal as described earlier but this isn’t a direct voltage control input. The fans need the PWM pin pulled to ground to control the signal but the output pins on the Arduino aren’t capable of absorbing the current required for direct connection so we need to add a transistor to do this for us. I went for a 2N7000 Mofset for each PWM channel because..well basically I had a bag full of them to hand and they’ll work at the output voltage of the Arduino. These will connect the fan PWM connection to ground when the Arduino output is driven high which unfortunately will reverse our PWM direction but that doesn’t matter because we can flip it back the right way in code. To correctly drive a a mosfet from an arduino you will need a gate drive resistor (to limit inrush to the Mosfet gate capacitance as this could damage the Arduino) and a gate pulldown resistor to make sure the mosfet fully turns off when the Arduino is no driving it. People will suggest all sorts of value for this but I went for a 220Ω for the gate drive and a 10kΩ for the pull down. The capacity of this Mosfet means we can use a single one for a large number of fans.

For simplicity I decided to use another of the eBay 6 pin fan connectors to connect up the cables for the fans. I intend to pass the power and tach signals directly to the motherboard so I’ll come up with a breakout cable to just isolate the PWM signal from the motherboard and allow us to feed in our own. Due to doing this we only actually need 4 pins (+12V, GND, PWM1 and PWM2) running up to the Arduino to control the fan but since I had them anyway why not!

Arduino Nano PWM Fan Breadboard

So this is the result the two connections for the DS18B20’s are on the right and the 6 pins for the fan/motherboard loom in the middle.

The Complete Code

This is where we start to bring everything together:

#include <DallasTemperature.h>
#include <OneWire.h>

// PWM output @ 25 kHz, only on pins 9 and 10.
// Output value should be between 0 and 320, inclusive.

int fanspeed1 = 25;
int fanspeed2 = 25;

float temp1;
float temp2;

#define onewirepin 3
OneWire oneWire(onewirepin);
DallasTemperature sensors(&oneWire);

DeviceAddress TempAdd1 = {0x28, 0x83, 0xC8, 0xE6, 0x3, 0x0, 0x0, 0x15};
DeviceAddress TempAdd2 = {0x28, 0x4A, 0xDE, 0xE6, 0x3, 0x0, 0x0, 0xFC};

void analogWrite25k(int pin, int value)
{
    switch (pin) {
        case 9:
            OCR1A = value;
            break;
        case 10:
            OCR1B = value;
            break;
        default:
            // no other pin will work
            break;
    }
}

void setup()
{

    Serial.begin(9600);    //Start diagnostic serial port
    
    // Configure Timer 1 for PWM @ 25 kHz.
    TCCR1A = 0;           // undo the configuration done by...
    TCCR1B = 0;           // ...the Arduino core library
    TCNT1  = 0;           // reset timer
    TCCR1A = _BV(COM1A1)  // non-inverted PWM on ch. A
           | _BV(COM1B1)  // same on ch; B
           | _BV(WGM11);  // mode 10: ph. correct PWM, TOP = ICR1
    TCCR1B = _BV(WGM13)   // ditto
           | _BV(CS10);   // prescaler = 1
    ICR1   = 320;         // TOP = 320

    // Set the PWM pins as output.
    pinMode( 9, OUTPUT);
    pinMode(10, OUTPUT);

    sensors.begin (); // Initialize the sensor and set resolution level
    sensors.setResolution(TempAdd1, 10);
    delay(1000);
    sensors.setResolution(TempAdd2, 10);
    delay(1000); 
}

void loop()
{

    sensors.requestTemperatures(); // Command all devices on bus to read temperature
    delay(1000);
    temp1 = sensors.getTempC(TempAdd1);             //Read Temp probe 1
    fanspeed1 = constrain(map((int)temp1,20,60,15,100),15,100);   
//Map fan speed for channel 1 to 25% at 30C to 100% at 60C and constrain the values to 25-100% range 
//(prevents fan from either going below 25% or trying to be set to above 100%)
    Serial.print ("Temp 1 is : ");
    Serial.print (temp1);
    Serial.print ("  Speed 1 is : ");
    Serial.println (fanspeed1);
    delay(1000);
    temp2 = sensors.getTempC(TempAdd2);            //Read Temp probe 2
    fanspeed2 = constrain(map((int)temp2,20,75,15,100),10,100);   
//Map fan speed to 25% at 30C to 100% at 75C and constrain the values to 25-100% range
    //(prevents fan from either going below 25% or trying to be set to above 100%)
    Serial.print ("Temp 2 is : ");
    Serial.print (temp2);
    Serial.print ("  Speed 2 is : ");
    Serial.println (fanspeed2);
    delay(1000);
  
    // Update Fan Speeds:
    analogWrite25k( 9, map(fanspeed1,0,100,320,0));         // Scale % fan speed from sensor one to actual PWM value for 25kHz output      
    analogWrite25k(10, map(fanspeed2,0,100,320,0));         // Scale % fan speed from sensor two to actual PWM value for 25kHz output   
    
   
}

Ok, it may not be perfect but it works.  The main bits to note are as follows:

1. The two “DeviceAddress” fields, these are the ID’s for our two DS18B20’s that we identified earlier.

2.  This line – fanspeed1 = constrain(map((int)temp1,20,60,15,100),15,100); which maps a temperature (converted to a float) between 20°C and 60°C to a fan speed of between 15% and 100% which is then constrained to this range so a temperature of 10°C doesn’t cause the fan to be given a 0% demand. You may want to change these constraints to minimise fan noise but I was being cautious and wanted to be sure the fans didn’t even slow down too far that anything overheated or triggered a BIOS warning. You can also tweak the temp vs speed mapping to optimise for a specific volume or temperature.

Map and ConstrainThere is another line like this for fanspeed2 so we can set different scaling ranges for the two sets of fans. You might only need a lower fan speed to maintain a temperature through the raid card area for example.

3. This line : analogWrite25k( 9, map(fanspeed1,0,100,320,0));
It maps our 0-100% fan speed to a value between 320-0 respectively so that we correct the signal inversion caused by using the N channel Mosfet.

The Wiring Loom

This bit is actually really simple but takes ages, buy or borrow a proper crimp tool for fan connectors a soldering iron and a good selection of heat shrink to cover everything up.

With this loom you can actually wire it up to a standard PWM fan controller off eBay or whatever if you just want a quick solution without all the soldering for the breadboard bit since it gives you power, ground and the PWM signals. Just be aware ONLY PWM fan controllers (the 4 pin ones). By doubling up the tach lines you could also take out several fans if that’s what you want so run the tach line from one fan to multiple motherboard headers if thats what you want. Here’s just how to intercept the right signal from the fan headers the way I did it:

DL180G6 Fan Wiring

Here’s what we end up with:

DL180g6 Fan Breakout

 

The Result

Finished Arduino DL180G6 Fan Controller
As mentioned earlier this will also work with the redundant fan systems because it passes through the tach signal of the second fan in the pair and a single PWM controls both. If doing this be careful, different servers seem to have different fan inputs configured depending on exactly what they are and you need to make sure you connect up to the right ones or the server will error on fan failure. This will probably work OK with other similar servers but can’t guarantee anything so  your mileage may vary. The other good news is that if PWM fans don’t get a PWM signal they default to 100%.

With the settings I have the fans will spin up to 100% briefly as the Arduino powers up but then spin down a few seconds later- this is normal behaviour for these fans. When the system is idling the fans seem to sit at the 15% most of the time occasionally going a little faster and now the power supply is by far the loudest part of the server so I probably wouldn’t bother setting a speed below 15% but the system is drastically quieter overall. You probably still won’t want it in your bedroom but you won’t be able to hear it everywhere in your building any more!

Other developments

When I get a bit more time I’m hoping to wire the Arduino USB connection up to an internal USB header so I can monitor the temperatures and even reprogram it when the server is running to fine tune the settings in operation. Technically its also possible to write a program to monitor CPU load and ramp the fan accordingly but this might just be too much effort.

Hopefully this will help those of you out there who have been having problems with these servers. Good luck