4Pin 风扇控制器

对于风扇来说,只要供电就能工作。最开始的电脑使用的都是这种风扇。但是随着技术的发展,人们发现需要对这个风扇进行控制,因为风扇转的快噪音和功耗都会随之增加。于是,加上一根反馈线,让用户能够得知当前的转速。但是这样会遇到另外的两个问题:第一个问题是风扇电压变化,反馈线上的电压也会随之变化,范围大了读取这个反馈会很麻烦。第二个问题是:电压和风扇转速转速关系并不是线性的。比如,一个 12V 的风扇,12V时转速是 2000CPM,10V供电时转速时1000CPM,但是如果11V 供电时,转速很可能是 1100CPM。风扇的转速和风力噪音直接相关,用户想要得到一个大概的转速非常困难。最终 Intel 推出了一个方案:通过 PWM 来控制风扇转速,然后使用5V 信号作为当前转速反馈引脚。

我们最常见到的CPU的风扇通常都是 4 Pin的。供电要求12V,控制的PWM 为 5V 25KHz,转速反馈引脚为 5V  输出。

有些风扇是存在最低转速的,意思是哪怕有PWM 已经为0仍然能够旋转,有些不存在最低转速,PWM 比较低的时候就会停止转动。

了解了上述知识就可以得知我们为了实现控制 CPU 风扇,需要提供12V 电源,提供25Khz 的 PWM 信号,以及读取 5V 的转速输出。

电路设计如下,我们使用 Arduino Uno作为主控。外部12V 供电进入(DC1)之后,经过一个DC-DC 降压模块(来自DFROBOT 的DFR0831,DC-DC降压模块7~24V转5V/4A),降压为5V提供给 Arduino 作为风扇控制,然后风扇的转速反馈信号经过U3上拉后进入Arduino 即可读取。最终转速和当前的PWM设定显示在一个 1602LCD上。

PCB 设计如下,可以看到板子上带有4个按钮用于控制 PWM ,分别是-1、-10、+1、+10这样用户可以快速的改变 PWM 设置。

代码设计如下:

#include <LiquidCrystal_I2C.h>
// 保存当前设定的 PWM 值
#include<EEPROM.h>

LiquidCrystal_I2C lcd(0x3F, 16, 2);

// PWM 发生器相关定义
const byte OC1A_PIN = 9; //PWM输出引脚为 D9
// 定义 PWM 频率
const word PWM_FREQ_HZ = 25000; //Adjust this value to adjust the frequency
const word TCNT1_TOP = 16000000 / (2 * PWM_FREQ_HZ);
const int BTN1 = 13;
const int BTN2 = 12;
const int BTN3 = 11;
const int BTN4 = 10;

// 计算转速相关定义
const int TOTAL = 10; // 计算10次输出一次,这是一个简单的滤波
int InterruptPin = 2;// 接收风扇中断 D2
volatile long int counter = 0;
volatile long int t[TOTAL];
byte p = 0;
byte SavedPWM = 0;
byte lastPWM = -1;
byte pwm;
long int Elsp=0;

void setup() {
  pinMode(BTN1, INPUT_PULLUP);
  pinMode(BTN2, INPUT_PULLUP);
  pinMode(BTN3, INPUT_PULLUP);
  pinMode(BTN4, INPUT_PULLUP);

  pinMode(OC1A_PIN, OUTPUT);

  // Clear Timer1 control and count registers
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  // Set Timer1 configuration
  // COM1A(1:0) = 0b10   (Output A clear rising/set falling)
  // COM1B(1:0) = 0b00   (Output B normal operation)
  // WGM(13:10) = 0b1010 (Phase correct PWM)
  // ICNC1      = 0b0    (Input capture noise canceler disabled)
  // ICES1      = 0b0    (Input capture edge select disabled)
  // CS(12:10)  = 0b001  (Input clock select = clock/1)

  TCCR1A |= (1 << COM1A1) | (1 << WGM11);
  TCCR1B |= (1 << WGM13) | (1 << CS10);
  ICR1 = TCNT1_TOP;

  Serial.begin(115200);
  Serial.setTimeout(300);

  pinMode(InterruptPin, INPUT);
  attachInterrupt(0, speedX, FALLING );

  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("PWM Fan CNT");

  // PWM 存在地址0 上
  SavedPWM = EEPROM.read(0);

  if (SavedPWM>100) {
      SavedPWM=0;
    }
  // 将上一次保存的 PWM 设定进去
  setPwmDuty(SavedPWM);
  pwm = SavedPWM;
  lastPWM = pwm + 1;
}

void loop() {
  if (pwm != lastPWM) {
    Serial.println(pwm);
    setPwmDuty(pwm);
    lastPWM = pwm;
    Elsp=millis();
  }

  // 计算当前转速
  long int avg = 0;
  counter = 0;
  delay(1000);

  p = (p + 1) % TOTAL;
  t[p] = (long int)(counter * 60 / 2);

  for (byte i = 0; i < TOTAL; i++) {
    avg = avg + t[i];
  }

  // 输出转速
  Serial.print("Current speed: ");
  Serial.println(avg / TOTAL);

  lcd.setCursor(0, 1);
  lcd.print("Fan Speed:");
  lcd.print(avg / TOTAL);
  lcd.print("      ");

  counter = 0;
  pinMode(BTN1, INPUT_PULLUP);
  if (digitalRead(BTN1) == LOW) {
    delay(5);
    if ((digitalRead(BTN1) == LOW) && (pwm > 9)) {
      pwm = pwm - 10;
    }
  }
  if (digitalRead(BTN2) == LOW) {
    delay(5);
    if ((digitalRead(BTN2) == LOW) && (pwm > 0)) {
      pwm = pwm - 1;
    }
  }  
  if (digitalRead(BTN3) == LOW) {
    delay(5);
    if ((digitalRead(BTN3) == LOW) && (pwm <=100-10)) {
      pwm = pwm + 10;
    }
  }
  if (digitalRead(BTN4) == LOW) {
    delay(5);
    if ((digitalRead(BTN4) == LOW) && (pwm <100)) {
      pwm = pwm + 1;
    }
  }

  // 每隔5秒检查 pwm 是否已经改变,如果改变则保存
  if ((SavedPWM!=pwm)&&(millis()-Elsp>5000)) {
      Serial.println("Save current pwm");
      Elsp=millis();
      SavedPWM=pwm;
      EEPROM.write(0,pwm);
    }
}

void setPwmDuty(byte duty) {
  Serial.print("Set pwm "); Serial.println(duty);
  lcd.setCursor(0, 0);
  lcd.print("Current pwm:");
  lcd.print(duty);
  lcd.print("  ");
  OCR1A = (word) (duty * TCNT1_TOP)/ 100;
}


void speedX()//中断函数
{
  counter++;
}

4 Pin fan 的 spec:

本文提到的电路图和PCB:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注