对于风扇来说,只要供电就能工作。最开始的电脑使用的都是这种风扇。但是随着技术的发展,人们发现需要对这个风扇进行控制,因为风扇转的快噪音和功耗都会随之增加。于是,加上一根反馈线,让用户能够得知当前的转速。但是这样会遇到另外的两个问题:第一个问题是风扇电压变化,反馈线上的电压也会随之变化,范围大了读取这个反馈会很麻烦。第二个问题是:电压和风扇转速转速关系并不是线性的。比如,一个 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++;
- }
复制代码
|