An(other) intelligent 4-wire fan speed controller


The internet isn’t exactly short of fan speed controller projects – nor am I. I’ve probably got about four or five variants of these I’ve built over the years. Recently I’ve taken the time to round up the “best of” these into a single open-source friendly AVR based project. Historically it was based on PIC microcontrollers, using various expensive proprietary compilers – the main reason I’ve not released it to-date.

The initial reason I embarked on this is because back 10 or so years ago when I first built one, PC motherboards lacked intelligent fan speed control, and if they did have it, it was limited to the point of being practically useless. The situation is a lot better these days, but the level of control sometimes desired isn’t a given. I still have quite a few of these in use for non PC scenarios, for example, inside networking equipment with excessively noisy overkill cooling arrangements.

Trapezoid control algorithm

This is what I call the algorithm which I’ve used since the beginning. Essentially, it always gives me exactly what I want, no matter what the scenario.

The name comes from the four configurable points which is used to calculate the fan PWM duty based on the temperature reading:

  • Minimum Temperature (tempmin)
  • Maximum Temperature (tempmax)
  • Minimum Fan Duty (fanmin)
  • Maximum Fan Duty (fanmax)

Just in case the previous graphic isn’t clear, here are some example calculations the algorithm would make:

tempmintempmaxfanminfanmaxMeasured temperatureFinal fan duty


Is not supported at present. It could be added fairly easily.

Software versions

There are two variations of the software for this board, with different use-cases:

Dual thermal zone

This was my original implementation. It supports two fans and two sensors only. The second fan/sensor can be disabled if only one is required. Each sensor and fan have individually configurable set-points and operate entirely separate from each other.

The obvious (and original) use-case for this is within a PC, with one sensor/fan on the CPU, and the second for the chassis.

Single thermal zone

This build is designed for the case where there is a set of fans on the rear of a chassis, with various items / areas within that chassis being monitored individually, all in a “single” thermal area. This build has been more useful to me in recent years.

Only a single set of configuration parameters is supported, however many temperature sensors are supported (default max is 4). The effective temperature used for the calculation is the maximum of all installed sensors.

The PWM duty of the two fan headers are always set to the same value, as it is assumed that the fans are both the same. Tachos are still monitored individually.

I do have versions of this which can monitor up to 6 fans (with the extra tachos connected to the SPARE header) however haven’t ported this code across to the version on github at this time.


No special software is required to configure the board, simply open up a terminal emulator to the attached COM port at 9600 baud and press Ctrl+C during power on to enter the configuration prompt. Help can be viewed by entering the command “?”.

This is the output from the “dual zone” build:



                Show current configuration

                Load the default configuration

                Save current configuration

                Exit this menu and start

        fan1max [0 to 100]
        fan2max [0 to 100]
                Sets the maximum duty cycle for fan

        fan1min [0 to 100]
        fan2min [0 to 100]
                Sets the minimum duty cycle for fan

        fan1start [0 to 100]
        fan2start [0 to 100]
                Sets the duty cycle to use between reset and first calculation
                and when in the configuration prompt

        fan1minrpm [0 to 65535]
        fan2minrpm [0 to 65535]
                Sets the stall-restart threshold RPM for fan

        fan1minoff [0 or 1]
        fan2minoff [0 or 1]
                Set to '1' to power off fan below minimum temp

                Stall checking is not performed when set to '1'

        temp1max [-55.0 to 125.0]
                Sets the temperature at which fan is set to the maximum
                configured duty cycle

        temp1min [-55.0 to 125.0]
                Sets the temperature threshold at which fan starts to
                increase from the minimum configured duty cycle

        temp1hyst [0 to 180.0]
                Sets hysteresis when using 'minoff'. The fan will not switch off
                until current temp is less than temp1min, minus temp1hyst

        temp2max [-55.0 to 125.0]
        temp2min [-55.0 to 125.0]
        temp2hyst [0 to 180.0]
                Configuration for sensor 2 will apply to fan 2 if it is
                connected. Otherwise fan 2 uses sensor 1 with temp2max/min/hyst

        fan2enabled [0 or 1]
                Set to '1' if fan 2 is connected

        temp1desc [desc]
        temp2desc [desc]
                Sets descriptions (15 chars max)

                Probe and read out all attached sensors

                Check authenticity of attached DS18B20 sensors

        manualassignment [0 or 1]
                Set to '1' to enable manual assignment of sensor address-to-index

        sensor1addr [addr or 'none']
        sensor2addr [addr or 'none']
                Sets addresses of sensors


Counterfeit DS18B20 detection

The matter of counterfeit DS18B20’s only came to my attention relatively recently, when I was trying to attach some to the end of a long noisy line, and observed that one worked, and the other (which turned out to be counterfeit) didn’t.

There is a brilliant project on github including some source code for detecting and classifying clones, i.e. what kind of clone you are dealing with, there are many.

I’ve included this code in the project. It can be run with the “authcheck” command from the configuration prompt:


Found 2 of 4 maximum sensors

28:FF:C9:D5:73:16:05:6B: ROM does not follow expected pattern 28:XX:XX:XX:XX:00:00:CRC. Error.
Scratchpad Register: 64/01/01/01/7F/FF/0C/10/03
Info only: Scratchpad bytes 2,3,4 (01/01/7F): not Maxim default values 4B/46/7F.
Scratchpad byte 5 (0xFF): ok.
Scratchpad byte 6 (0x0C): ok.
Scratchpad byte 7 (0x10): ok.
0x4E modifies alarm registers: ok.
0x4E accepts 10 bit resolution: ok.
0x4E preserves reserved bytes: no, got: FF/3F/10. Error.
0x4E accepts 12 bit resolution: ok.
0x4E preserves reserved bytes: no, got: FF/7F/10. Error.
Checking byte 6 upon temperature change: not necessary. Skipped.
--> Sensor appears to be counterfeit based on 3 deviations.
28:FF:C9:D5:73:16:05:6B: Family B2 (Clone).

28:9B:50:17:0D:00:00:6C: ROM ok.
Scratchpad Register: 63/01/4B/46/7F/FF/0D/10/15
Info only: Scratchpad bytes 2,3,4 (4B/46/7F): Maxim default values.
Scratchpad byte 5 (0xFF): ok.
Scratchpad byte 6 (0x0D): ok.
Scratchpad byte 7 (0x10): ok.
0x4E modifies alarm registers: ok.
0x4E accepts 10 bit resolution: ok.
0x4E preserves reserved bytes: ok.
0x4E accepts 12 bit resolution: ok.
0x4E preserves reserved bytes: ok.
Checking byte 6 upon temperature change: not necessary. Skipped.
--> Sensor responded like a genuie Maxim.
28:9B:50:17:0D:00:00:6C: Family A1 (Genuie Maxim).

See for more information.

In the above example, it turns out that one of my sensors is a 7Q-Tek QT18B20, despite it being labelled as “DALLAS DS18B20”. I knew it. £1 per sensor was too good to be true. My saving grace is that apparently the QT18B20 is a fairly good clone. Nonetheless, I won’t be buying any more off eBay.

Building one

I have provided all of the gerbers and source code, as well as pre-compiled builds in the links below.

Unfitted components

As can be seen in the pictures, there are quite a few components which aren’t installed. These are mostly for my own various special use-cases and should not be installed for any of the cases described on this page.

EDIT: RS-232? Say what?

Having only just published this, the usage of antique interfaces has already been pointed out. In my defence the project is quite old, adding USB-to-serial would have bought in surface mount components. There is sort-of a workaround:

If a USB connection is desired, simply omit the MAX232 and supporting capacitors, fit a header to the one under the DE-9 connector and use a cheap USB to RS-232 TTL cable.

Building sources and programming the AVR

As with every project I publish including source code, and in this instance happening to use the same MCU as the Arduino UNO, this is not an Arduino project in any respect. If re-compiling, sources must be compiled with GNU binutils and AVR-GCC.

I’ll leave you to do your own research as to how to program the AVR. The programmer will need the pre-compiled .HEX file, and the following fuse settings:

  • LFUSE: 0xDF
  • HFUSE: 0xD1
  • EFUSE: 0xFC


Leave a Reply

Your email address will not be published. Required fields are marked *