PC fan speed control

Show and share your project here. Having problem with your project? Ask the forum members.

PC fan speed control

Postby Bro_Az » Sun Sep 05, 2010 1:22 am

In the past week I have been experimenting with Arduino. First I built a DIY LCD shield so that I can use standard LCD for display and more importantly for troubleshooting use. I used a standard stripboard and male header pins since I could not find the long pin female type. This is OK since the LCD shield will always be on top most for obvious reason. Short female headers pin soldered to LCD so that it is removable. 6 digital outputs (pins 2 to 8) used for LCD 4-bit interfacing. Pin 0 and 1 reserved for Serial comm for later use. Here are some pictures.
ImageImage
ImageImage
Image

After that I connected a 4-wire 12V PC fan to Arduino. Connect Red to 12V (V in), Black to Gnd, Blue (PWM speed control) to PWM digital output and Yellow (tachometer) to digital input. I used 12V external power adaptor connected to Arduino power connector. Tachometer pulse signal connected to Digital input 8 configured as pull-up. Speed control PWM is connected to Digital output 11. No need for any extra component to control a 4-wire PC fan. Next, I connected a 10K potentiometer to Analog Input 0 for analog value adjustment.
Image

I programmed my version of PID speed controller, using the analog input as the adjustible set-point, calculate speed using the tachometer pulses and control the PWM output to the fan.

Setpoint, actual speed and PWM duty-cycle output are also sent to PC via serial comm and graphed in real-time on the PC using Processing software running modified Arduinoscope sketch.
Image

I learned a lot in a short time doing this project. I believe this is the most cost effective and practical way to learn PID automatic control. Try it.

Here are the codes for Arduinoscope-like realtime graph running on Processing on PC side:
Code: Select all
// Maurice Ribble
// 6-28-2009
// http://www.glacialwanderer.com/hobbyrobotics

// This takes data off the serial port and graphs it.
// There is an option to log this data to a file.

// I wrote an arduino app that sends data in the format expected by this app.
// The arduino app sends accelerometer and gyroscope data.
//
// Modified by Bro_Az August 2010 for PID monitoring.

import processing.serial.*;

// Globals
int g_winW             = 820;   // Window Width
int g_winH             = 600;   // Window Height
boolean g_dumpToFile   = false;  // Dumps data to c:\\output.txt in a comma seperated format (easy to import into Excel)
boolean g_enableFilter = false;  // Enables simple filter to help smooth out data.

cDataArray g_xAccel    = new cDataArray(200);
cDataArray g_yAccel    = new cDataArray(200);
cDataArray g_zAccel    = new cDataArray(200);
cDataArray g_vRef      = new cDataArray(200);
cDataArray g_xRate     = new cDataArray(200);
cDataArray g_yRate     = new cDataArray(200);
cGraph g_graph         = new cGraph(10, 190, 800, 400);
Serial g_serial;
PFont  g_font;

void setup()
{
  size(g_winW, g_winH, P2D);

  println(Serial.list());
  g_serial = new Serial(this, Serial.list()[10], 9600, 'N', 8, 1.0);
  g_font = loadFont("ArialMT-20.vlw");
  textFont(g_font, 20);
 
  // This draws the graph key info
  strokeWeight(1.5);
  stroke(255, 0, 0);     line(20, 420, 35, 420);
  stroke(0, 255, 0);     line(20, 440, 35, 440);
  stroke(0, 0, 255);     line(20, 460, 35, 460);
  stroke(255, 255, 0);   line(20, 480, 35, 480);
  stroke(255, 0, 255);   line(20, 500, 35, 500);
  stroke(0, 255, 255);   line(20, 520, 35, 520);
  fill(0, 0, 0);
  text("Speed Setpoint", 40, 430);
  text("Actual Speed", 40, 450);
  text("Controller Output", 40, 470);
  text("Analog 3", 40, 490);
  text("Analog 4", 40, 510);
  text("Analog 5", 40, 530);
 
  if (g_dumpToFile)
  {
    // This clears deletes the old file each time the app restarts
    byte[] tmpChars = {'\r', '\n'};
    saveBytes("c:\\output.txt", tmpChars);
  }
}

void draw()
{
  // We need to read in all the avilable data so graphing doesn't lag behind
  while (g_serial.available() >= 2*6+2)
  {
    processSerialData();
  }

  strokeWeight(1);
  fill(255, 255, 255);
  g_graph.drawGraphBox();
 
  strokeWeight(1.5);
  stroke(255, 0, 0);
  g_graph.drawLine(g_xAccel, 0, 1024);
  stroke(0, 255, 0);
  g_graph.drawLine(g_yAccel, 0, 1024);
  stroke(0, 0, 255);
  g_graph.drawLine(g_zAccel, 0, 1024);
  stroke(255, 255, 0);
  g_graph.drawLine(g_vRef, 0, 1024);
  stroke(255, 0, 255);
  g_graph.drawLine(g_xRate, 0, 1024);
  stroke(0, 255, 255);
  g_graph.drawLine(g_yRate, 0, 1024);
}

// This reads in one set of the data from the serial port
void processSerialData()
{
  int inByte = 0;
  int curMatchPos = 0;
  int[] intBuf = new int[2];

  intBuf[0] = 0xAD;
  intBuf[1] = 0xDE;
 
  while (g_serial.available() < 2); // Loop until we have enough bytes
  inByte = g_serial.read();
 
  // This while look looks for two bytes sent by the client 0xDEAD
  // This allows us to resync the server and client if they ever
  // loose sync.  In my testing I haven't seen them loose sync so
  // this could be removed if you need to, but it is a good way to
  // prevent catastrophic failure.
  while(curMatchPos < 2)
  {
    if (inByte == intBuf[curMatchPos])
    {
      ++curMatchPos;
     
      if (curMatchPos == 2)
        break;
   
      while (g_serial.available() < 2); // Loop until we have enough bytes
      inByte = g_serial.read();
    }
    else
    {
      if (curMatchPos == 0)
      {
        while (g_serial.available() < 2); // Loop until we have enough bytes
        inByte = g_serial.read();
      }
      else
      {
        curMatchPos = 0;
      }
    }
  }
 
  while (g_serial.available() < 2*6);  // Loop until we have a full set of data

  // This reads in one set of data
  {
    byte[] inBuf = new byte[2];
    int xAccel, yAccel, zAccel, vRef, xRate, yRate;
 
    g_serial.readBytes(inBuf);
    // Had to do some type conversion since Java doesn't support unsigned bytes
    xAccel = ((int)(inBuf[1]&0xFF) << 8) + ((int)(inBuf[0]&0xFF) << 0);
    g_serial.readBytes(inBuf);
    yAccel = ((int)(inBuf[1]&0xFF) << 8) + ((int)(inBuf[0]&0xFF) << 0);
    g_serial.readBytes(inBuf);
    zAccel = ((int)(inBuf[1]&0xFF) << 8) + ((int)(inBuf[0]&0xFF) << 0);
    g_serial.readBytes(inBuf);
    vRef   = ((int)(inBuf[1]&0xFF) << 8) + ((int)(inBuf[0]&0xFF) << 0);
    g_serial.readBytes(inBuf);
    xRate  = ((int)(inBuf[1]&0xFF) << 8) + ((int)(inBuf[0]&0xFF) << 0);
    g_serial.readBytes(inBuf);
    yRate  = ((int)(inBuf[1]&0xFF) << 8) + ((int)(inBuf[0]&0xFF) << 0);
   
    g_xAccel.addVal(xAccel);
    g_yAccel.addVal(yAccel);
    g_zAccel.addVal(zAccel);
    g_vRef.addVal(vRef);
    g_xRate.addVal(xRate);
    g_yRate.addVal(yRate);

    if (g_dumpToFile)  // Dump data to a file if needed
    {
      String tempStr;
      tempStr = xAccel + "," + yAccel + "," + zAccel + "," + vRef + "," + xRate + "," + yRate + "\r\n";
      FileWriter file;

      try 
      { 
        file = new FileWriter("c:\\output.txt", true); //bool tells to append
        file.write(tempStr, 0, tempStr.length()); //(string, start char, end char)
        file.close();
      } 
      catch(Exception e) 
      { 
        println("Error: Can't open file!");
      }
    }

    /*
    print(xAccel);  print(" ");   print(yAccel);   print(" ");    print(zAccel);     print(" ");
    print(vRef);    print(" ");   print(xRate);    print(" ");    println(yRate);
    */
  }
}

// This class helps manage the arrays of data I need to keep around for graphing.
class cDataArray
{
  float[] m_data;
  int m_maxSize;
  int m_startIndex = 0;
  int m_endIndex = 0;
  int m_curSize;
 
  cDataArray(int maxSize)
  {
    m_maxSize = maxSize;
    m_data = new float[maxSize];
  }
 
  void addVal(float val)
  {
   
    if (g_enableFilter && (m_curSize != 0))
    {
      int indx;
     
      if (m_endIndex == 0)
        indx = m_maxSize-1;
      else
        indx = m_endIndex - 1;
     
      m_data[m_endIndex] = getVal(indx)*.5 + val*.5;
    }
    else
    {
      m_data[m_endIndex] = val;
    }
   
    m_endIndex = (m_endIndex+1)%m_maxSize;
    if (m_curSize == m_maxSize)
    {
      m_startIndex = (m_startIndex+1)%m_maxSize;
    }
    else
    {
      m_curSize++;
    }
  }
 
  float getVal(int index)
  {
    return m_data[(m_startIndex+index)%m_maxSize];
  }
 
  int getCurSize()
  {
    return m_curSize;
  }
 
  int getMaxSize()
  {
    return m_maxSize;
  }
}

// This class takes the data and helps graph it
class cGraph
{
  float m_gWidth, m_gHeight;
  float m_gLeft, m_gBottom, m_gRight, m_gTop;
 
  cGraph(float x, float y, float w, float h)
  {
    m_gWidth     = w;
    m_gHeight    = h;
    m_gLeft      = x;
    m_gBottom    = g_winH - y;
    m_gRight     = x + w;
    m_gTop       = g_winH - y - h;
  }
 
  void drawGraphBox()
  {
    stroke(0, 0, 0);
    rectMode(CORNERS);
    rect(m_gLeft, m_gBottom, m_gRight, m_gTop);
  }
 
  void drawLine(cDataArray data, float minRange, float maxRange)
  {
    float graphMultX = m_gWidth/data.getMaxSize();
    float graphMultY = m_gHeight/(maxRange-minRange);
   
    for(int i=0; i<data.getCurSize()-1; ++i)
    {
      float x0 = i*graphMultX+m_gLeft;
      float y0 = m_gBottom-((data.getVal(i)-minRange)*graphMultY);
      float x1 = (i+1)*graphMultX+m_gLeft;
      float y1 = m_gBottom-((data.getVal(i+1)-minRange)*graphMultY);
      line(x0, y0, x1, y1);
    }
  }
}


Here are the codes for Arduino:
Code: Select all
/*

This sketch use PID controller to control PC fan speed and display result on a LCD.
Setpoint for speed is read from a potentiometer. Data also send thru serial to PC
running a real-time graph under Processing software.

Author - Bro_Az August 2010.

  The circuit:
* LCD RS pin to digital pin 7
* LCD Enable pin to digital pin 6
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* ends to +5V and ground
* 10k pot wiper to LCD VO pin (pin 3)

*/

#include <LiquidCrystal.h>

// If this is defined it prints out the FPS that we can send a
// complete set of data over the serial port.
//#define CHECK_FPS
 
int tachPin = 8;   // fan tachometer connected to digital pin 8
int fanPin = 11;  // fan drive connected to digital pin 11
int sensorPin = 0; // analog input 0 for the potentiometer

int sensorValue;  // variable to store the value coming from the sensor
unsigned long duration; // time duration between tachometer pulses
int max_rpm=4800; // max rpm at 100% PWM output
int setpoint; // setpoint in %
int speed; // fan speed in %
float rpm; // fan speed in RPM

int error;
int last_error=0;
int diff;
float integ=0;
float kp=1.0; // proportional gain
float ki=0.5; // integral gain
float kd=0.05; // derivative gain
int out; // PID controller output

boolean auto_manual=1; // setpoint manual or from pot
int manual_setpoint=77;
int sampletime=40; // Number of sample to average-out sampling values
unsigned long val; // Working variable to hold the accumulated value

unsigned int an0=0; // Variables for sending data to PC
unsigned int an1=0;
unsigned int an2=0;
unsigned int an3=0;
unsigned int an4=0;
unsigned int an5=0;
unsigned int offset=2;


// initialize the LCD library routine with the numbers of the interface pins
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

void setup() {
lcd.begin(16, 2); // set up the LCD's number of rows and columns:
digitalWrite(tachPin, HIGH);  // turn on pull-up resistor
Serial.begin(9600); // Serial port baud rate
}

void loop() {
 
unsigned int startTag = 0xDEAD;  // Analog port maxes at 1023 so this is a safe termination value
                                 // for sending values to PC.
#ifdef CHECK_FPS 
  unsigned long startTime, endTime;
  startTime = millis();
#endif

val=0;

for (int i=0;i<sampletime;i++) {
sensorValue = analogRead(sensorPin); // Read the value from the pot. AnalogRead values go from 0 to 1023.
val=val+sensorValue;
}
sensorValue=val/sampletime;

if (auto_manual) {
setpoint=float(sensorValue)*100/1024;  // convert to percentage
}
else {
setpoint=manual_setpoint;
}

duration=0;

for (int i = 0; i < sampletime; i++) { // Average out pulseIn time reading in microseconds
    duration = duration+pulseIn(tachPin, HIGH);   
}
   
    rpm=600000000/float(duration); // in RPM
    speed=rpm/max_rpm*100; // convert to percentage
    error=setpoint-speed;
    diff=error-last_error;
    integ=integ+error;
    out=kp*error+ki*integ+kd*diff;
   
    if (out<0) {out=0;integ=0;} //min limit, anti-reset wind-up
    if (out>255) {out=255;integ=integ-error;} //max limit, anti-reset wind-up
    analogWrite(fanPin, out);  // analogWrite values from 0 to 255, PWM output
         
    lcd.setCursor(0, 0); // Set for first line
    lcd.print(int(rpm)); // displays RPM value
    lcd.print(" RPM CO=");
    lcd.print(out);// displays Controller Output value
    lcd.print("   ");
   
    lcd.setCursor(0, 1); //set for second line
    lcd.print("SP=");
    lcd.print(setpoint);// displays setpoint value
    lcd.print("% ");
    lcd.print("PV=");// displays PV value
    lcd.print(speed);
    lcd.print("%   ");     
   
    if (auto_manual) { // if set-point in Auto, display "A"
      lcd.setCursor(15, 1);
      lcd.print("A");
     }
    else {
     lcd.setCursor(15, 1);// else display "M" for Manual
      lcd.print("M");
     }
   
    an0=setpoint*9+offset; // scaling for graph on PC
    an1=speed*9+offset;
    an2=out*3+offset;
   
  Serial.write( (unsigned byte*)&startTag, 2); // Send serial data to PC
  Serial.write((unsigned byte*)&an0, 2);
  Serial.write((unsigned byte*)&an1, 2);
  Serial.write((unsigned byte*)&an2, 2);
  Serial.write((unsigned byte*)&an3, 2);
  Serial.write((unsigned byte*)&an4, 2);
  Serial.write((unsigned byte*)&an5, 2);
 
#ifdef CHECK_FPS 
  endTime = millis();
  Serial.print(" - FPS: ");
  Serial.println(1.f / (endTime-startTime) * 1000);
#endif
}
Bro_Az
 
Posts: 15
Joined: Fri Aug 20, 2010 3:24 pm
Location: Kuala Lumpur

Re: PC fan speed control

Postby admin » Sun Sep 05, 2010 10:12 am

Very interesting :D

Thanks for sharing Bro_Az.
User avatar
admin
Site Admin
 
Posts: 22
Joined: Tue Aug 17, 2010 12:32 am

Re: PC fan speed control

Postby Grapezul » Tue Sep 07, 2010 7:35 am

Processing is indeed a very useful tool in addition to arduino
Grapezul
 
Posts: 56
Joined: Wed Aug 18, 2010 10:49 am

Re: PC fan speed control

Postby Tarmizi Sensei » Sat Sep 18, 2010 8:21 pm

i see u implement some PID control inside. Cool
User avatar
Tarmizi Sensei
 
Posts: 18
Joined: Wed Aug 18, 2010 11:34 am
Location: Malaysia


Return to My Project

Who is online

Users browsing this forum: No registered users and 1 guest

cron