This project will focus on using Arduino to build a smart fan controller. Fan controllers are an effective way to limit or boost the rotation speed of the cooling fans in a desktop computer. While doing something that is CPU and GPU intensive, it may be preferable to have more airflow through the computer case. Quite the opposite is true when there is little load on the CPU and GPU. Silence is golden when performance is not necessary. Typically, case fans are noisy and are connected directly to the 12 Volt power supply. This means the fans are on full strength whether it is needed or not. By limiting the effective voltage in certain situations, one can reduce the noise produced by the fans without affecting performance when needed. The primary objective here is to set a temperature threshold and have the case fans ramp up as needed to keep the case and components cool. To implement a reasonable system where the fans do not kick on and off, proportional-integral-derivative (PID) will be implemented. This feedback mechanism should make sure the fans stay quiet while providing adequate cooling in the computer case. The case fans in any desktop computer are controlled in one of two ways: either they are connected directly to a 12 volt branch of the power supply or they are controlled by the motherboard. Typically, at least the CPU heat-sink’s fan is controlled by the motherboard. The heat-sink’s fan is critical and is excluded from this project. The case fans move air through the chassis, providing cool air across all of the components. The RPM of each case fan is directly related to the voltage applied to the pins cased in their Molex connector. By regulating this voltage, the RPM of each fan can be controlled. By regulating the RPM of the fans, the noise can be reduced when needed.
The first task is to reliably read the temperature, so the PID algorithm in the next section can do its job to regulate the fans. A Dallas One-Wire digital temperature sensor is used to get the temperature (1). As seen later in the wiring diagram (Figure 3), the reading is taken from the middle pin with a pull up resistor. There is a One-Wire library for Arduino, so it is pretty easy to call upon the sensor to get a reading in the main loop.
PID and How it Controls Temperature
PID is a standard way of setting a temperature and having the cooling or heating system reach that temperature without overshooting or undershooting (2) (3). The plot of temperature over time is a smooth curve that reaches the desired temperature. Let’s jump in head first. Here is the equation for PID:
There are three terms here: the proportional gain, the integral gain and the derivative gain. The proportional gain is most influential term in the equation. e(t), the error, calculates the distance between the current temperature and the desired temperature and sets the proper adjustment to the output. The constant, Kp, determines the magnitude of change due to the proportional gain. The integral gain adds up the difference between the set point and the desired point over time. Given the history of the error it calculates the required change in output. In plainer terms, it determines the amount of acceleration that is required to reach the set point. Again, the constant, Ki, determines the magnitude of change due to the integral gain. Finally, the last term is the derivative gain. It balances the integral gain by lessening the potential for overshooting the set point. It does so by calculating the slope of the error over time and slows the change in output. Similarly to the previous two terms it also has a constant, Kd, which determines the magnitude of change due to the derivative gain. Taking a step back, one can see that the proportional gain is responsible for adjusting the output to manipulate the temperature; the integral gain is responsible for accelerating or decelerating that change; and the derivative gain is responsible for limiting the aggression of the integral gain when nearing the set point. By manipulating the constants in front of each term one can tune the system to perform within the scale that is needed. For the purpose of a fan controller inside of a computer, the output is in RPM and the set point is a temperature in Celsius. Obviously a higher RPM pushes more air through the case to cool the components. By tweaking the output (RPM) using PID, the temperature can be maintained at a set point with minimal noise and effort from the fans.
How to implement PID in code
Luckily there already exists a library for PID control for Arduino (4). In terms of code, the tunings are set and the adjustment to the output is computed in a loop. For every temperature reading, the output is computed with the PID library command, Compute(), and the output of the fan is set to the value that the library returns. The library also makes a distinction between aggressive PID and conservative PID. This is done so that when the temperature is close to the desired temperature the change in output is more conservative than when the temperature is far away from the desired temperature. In this example, the code specifies that swing to be one degree Celsius. When the temperature is within a degree of the desired temperature the PID responds more slowly and consistently. Conversely, when the temperature is farther than a degree away from the desired temperature, the PID output will be more drastic with larger tuning values.
Results of the PID
There are many methods of setting the tunings. For testing, the temperature sensor (1) was taped to a heating element which was placed in front of the case fan. To get the results displayed here, the integral and derivative gains were initially eliminated to zero. Once the proportional gain was behaving by oscillating around the set point, the last two terms were added in until the plot of temperature over time seemed to be stable. The results are displayed below in Figure 1. In addition, Figure 2 shows the output of the PID to the fan over the same time scale. The output pin on the Arduino microcontroller can be a value between zero and 255. At maximum speed this particular fan rotates at 1200 RPM.
User Interface with a Rotary Encoder and an LCD Panel
The secondary objective is to apply an effective user interface using an LCD panel and a rotary encoder. Since the voltage on the pins of the fan controls the RPM, the noise can be significantly reduced without any decrease in performance. The user should be able to set a temperature in the case and the fans will ramp up accordingly. Of course the hardware must be protected, so if a critical temperature is reached, the fans will turn on with full force and lower the temperature.
The rotary encoder allows the user to set the desired temperature in the case. Rotary encoders work with two pins. Each pin is either high or low. Each pin moves in a certain pattern through high and low states. This is an implementation of a concept called a “Gray code,” because their patterns are the same but shifted by one cycle. As you can see in the below illustration, if there is a low to high transition on pin A and pin B is low, then the wheel must be spinning counterclockwise (left in the diagram). Likewise, if there is a low to high transition on pin A and pin B is high then the wheel must be spinning clockwise (right in the diagram).
In the code, interrupts must be used so the turning of the rotary encoder does not disturb the PID loop. When the rotary encoder is turned, it generates an interrupt. The interrupt pauses the main PID loop and iterates a variable in the proper direction. The desired temperature is then set to equal the rotary encoder’s value. The end result is that the user can twist the rotary encoder and the fans will work to make the temperature inside the case equal the temperature set by the user. The LCD panel reflects the current set temperature, the current temperature, and the approximate RPM of the fan. The RPM of the fan is derived from the value that comes off the Arduino pin. In the main loop where the PID is computed and set, the LCD is also updated. The LCD is compatible with the HD44780 chipset. This is a well documented Hitachi chipset, which eases the programming. Have a look at the diagram for the pin out of the LCD panel.
Putting it All Together
There are a few more considerations in putting all of the components together and running the code on the Arduino Uno. The Arduino will be mounted in a 5.25” storage container with the front punched out to make room for the LCD panel and the rotary encoder. The assembly must take power from a typical computer power supply, which is 12 volts. The microcontroller takes the 12 volts from the power supply and regulates it into five volts. The catch is that the temperature sensor needs five volts to operate, but the fan needs the full 12 volts to operate at maximum RPM. The full schematic of the fan controller is below.
To achieve the fan control with the voltage from the power supply, a mosfet is used to handle the switching of the power supply on and off with pulse-width-modulation (PWM). PWM is performed by the Arduino. It is simply a square wave of high voltage and low voltage where the ratio of time off and on is regulated. At its highest, the controller outputs the full 12 volts. At effective half voltage the PWM is on for half the time and at its lowest the controller outputs zero volts. Effectively, this controls the RPM of the fan by quickly switching the motor on and off for a certain amount of time, which is determined by the PID process. This scheme of fan control should provide quiet fans that effectively react to the temperature inside the case and near the critical components while handing control over to the user through the LCD UI. In addition to control, the user should also be able to get real-time temperature and fan statistics from the LCD panel. As a result we have an Arduino-powered smart fan system that is open source and helps users control the temperature and airflow of their desktop computers.
1. Burton, Miles. Dallas Temperature Control Library. MilesBurton.com. [Online] [Cited: May 1, 2011.] https://www.milesburton.com/Dallas_Temperature_Control_Library.
2. Wikipedia. PID Controller. Wikipedia. [Online] [Cited: May 1, 2011.] http://en.wikipedia.org/wiki/PID_controller.
3. Matlab. Control Tutorials for Matlab: PID Tutorial. [Online] [Cited: May 1, 2011.] http://www.engin.umich.edu/group/ctm/PID/PID.html#controller.
4. Arduino Playground. PID Library. Arduino Playground. [Online] [Cited: May 1, 2011.] http://www.arduino.cc/playground/Code/PIDLibrary.
5. —. Reading Rotary Encoders. Arduino. [Online] [Cited: May 1, 2011.] https://www.arduino.cc/playground/Main/RotaryEncoders.
Appendix A: Bill of Materials
|1kO Variable Resistor||1||1.49|
|120mm 3 pin Fan||2||8.99|
Appendix B: The Code
* Chris Barnes, Smart Fan Controller with PID
* Notes: Fan will not kick in at any value under 90, noise stops around 240
* The MIT License (MIT)
* Copyright (c) 2014 Chris Barnes
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#define FAN 9 // Output pin for fan
#define ONE_WIRE_BUS 8 // Temperature Input is on Pin 2
#define click 3 //Rotary Encoder Click
#define encoder0PinA 2 //Rotary Encoder Pin A
#define encoder0PinB 4 //Rotary Encoder Pin B
#define CRITICAL 50.00 //Critical temperature to ignore PID and turn on fans
volatile unsigned int encoder0Pos = 0; //Encoder value for ISR
LiquidCrystal lcd(12, 11, 13, 5,6,7); //set up LCD
//Setup Temperature Sensor
double Setpoint, Input, Output; //I/O for PID
double aggKp=40, aggKi=2, aggKd=10; //original: aggKp=4, aggKi=0.2, aggKd=1, Aggressive Turning,50,20,20
double consKp=20, consKi=1, consKd=5; //original consKp=1, consKi=0.05, consKd=0.25, Conservative Turning,20,10,10
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, REVERSE); //Initialize PID
// start serial port for temperature readings
sensors.begin(); //Start Library
sensors.requestTemperatures(); // Send the command to get temperatures
Input = sensors.getTempCByIndex(0); //Set Input to Current Temperature
Setpoint = 28; //Inintialize desired Temperature in Deg C
//TCCR2B = TCCR2B & 0b11111000 | 0x01; //adjust the PWM Frequency, note: this changes timing like delay()
pinMode(FAN, OUTPUT); // Output for fan speed, 0 to 255
pinMode(click, INPUT); // Click button is an input
digitalWrite(encoder0PinA, HIGH); // Turn on pullup resistor
digitalWrite(encoder0PinB, HIGH); // Turn on pullup resistor
//Set up Interupts
attachInterrupt(1, clicked, RISING); // Click button on interrupt 1 - pin 3
attachInterrupt(0, doEncoder, CHANGE); // Encoder pin on interrupt 0 - pin 2
//Setup LCD 16x2 and display startup message
lcd.print(" Smart Fan");
lcd.print(" Starting Up");
//Get temperature and give it to the PID input
//print out info to LCD
lcd.print((int)Output*4.7059); //Scaling factor calculated with a regression, read yellow wire for real RPM
//Compute PID value
double gap = abs(Setpoint-Input); //distance away from setpoint
//Close to Setpoint, be conservative
myPID.SetTunings(consKp, consKi, consKd);
//Far from Setpoint, be aggresive
myPID.SetTunings(aggKp, aggKi, aggKd);
//Write PID output to fan if not critical
//pinA and pinB are both high or both low, spinning forward, otherwise it's spinning backwards
if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB))
Serial.println (encoder0Pos, DEC); //Print out encoder value to Serial
lcd.print("clicked!"); //This is unused, but feel free to use the click for something using this interrupt