Управление компьютерными кулерами от температуры (+ бонус)

adrusha
Offline
Зарегистрирован: 02.03.2015

Уважаемые дамы и господа!

Относительно недавно завершил свой очередной проект, и решил довести его до конечной рабочей точки но с прогрессирующем развитием.
Главная задача заключается в регулировании скорости оборотов компьютерного вентилятора, а точнее 6-ти вентиляторов по 3-м каналам.
У меня стоит водяное охлаждение с внешним 3-х диновым (трех кулерным) радиатором, в который постоянно забивается пыль, пару раз чистил, не особо составляет радость это занятие, но лень поборола лень, как это обычно бывает :)
Сначала у меня была совершенно простая приблуда без ардуины, на обычном npn транзисторе через переменный резистор. Это было весьма неудобно и временно. Настало время сделать временное постоянным. Для этого потребовалось

3 npn транзистора D882;
3 npn транзистора 2n2222;
6 резисторов 1k;
3 резистора 4.7k;
3 резистора 6.3k;
1 arduino nano v3.0;
6 2-х контактных коннекторов папа-мама;
3 3-х контактных коннекторов папа-мама;

Выглядит все вот так:

Сама плата:

В симпатичном корпусе:

Код arduino:

#include <PWM.h>
#include <MsTimer2.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 5
#define FAN1 9
#define FAN2 10
#define FAN3 3
#define FAN_LED 13
#define BYTES 64 // количество принимаемых байтов для буфера данных (самая длинная строка в запросе это FAN:255:255:255 равняется 15 символам, а тут 64. так, на запас.)
//#define MAX_STRING_LEN 20 // like 3 lines above, change as needed.

// speed control's
int high = 255;
int medium = 150;
int normal = 75;
int low = 40;
int off = 0;

// уровни температур при которых меняется скорость вращения
float temp_high = 70;
float temp_medium = 60;
float temp_normal = 45;
float temp_low = 39;
float temp_off = 35;

// текущая температура
float temp = 0;
bool _temp = true; // переменная используется для получения температуры (временная переменная)
// количество оборотов насоса охлаждающей жидкости
volatile int rpm = 0;// текущие обороты насоса (обновляет раз в секунду)
volatile int rpmcount = 0;// счетчик оборотов насоса
// активирован (найден ли) датчик температуры
bool thermometer = false;
// меню показано/спрятано
bool show = false;

int fanPins = 3;
int fanPin[3] = {9, 10, 3};
int fanVol[3] = {255, 255, 255};
int fanVolLast[3] = {0,0,0};
long fanTimer[3] = {0,0,0};
int fanPWM[3] = {0,0,0};
int fanAuto[3] = {1,1,1};

char buffer[BYTES] = {}; // буфер данных получаемых с компьютера

// задержка в мкс для разгона вентиляторов (при смене скорости с 0 на любое положительное)
long timer_delay = 2000;

// время отсчета с момента запуска
unsigned long time;
unsigned long previousTime = 0;

// Частота модуляции (in Hz) это позволяет избежать писка/жужжания мотора
int32_t frequency = 35000;

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// arrays to hold device address
DeviceAddress insideThermometer;

void setup(void)
{
  InitTimersSafe();
  sensors.begin();
  Serial.begin(115200);
  Serial.setTimeout(500);
  attachInterrupt(0, rpmcounter, FALLING); // для подсчета оборотов, вызываем всякий раз после смены сигнала на 0
  
  MsTimer2::set(1000, rpmclear); // 1000 ms  = 1 s
  MsTimer2::start();
  // индикация работы (ардуино будет мигать если температура критическая)
  pinMode(FAN_LED, OUTPUT);
  digitalWrite(FAN_LED, LOW);
  
  for(int i=0; i temp_medium && time/t % 2) {
    digitalWrite(FAN_LED, HIGH);
  } else {
    digitalWrite(FAN_LED, LOW);
  }
  
  // задние кулеры (радиатор)
  if(fanAuto[0] && fanTimer[0] < time) {
    if(temp > temp_low) fanVol[0] = map(temp, temp_low, temp_medium, low, medium);
  }
  // фронтальные кулеры (вдув)
  if(fanAuto[1] && fanTimer[1] < time) {
    if(temp < temp_low) fanVol[1] = off;
    if(temp > temp_normal) fanVol[1] = map(temp, temp_normal, temp_medium, normal, high);
  }
  // задний одиночный кулер (выдув)
  if(fanAuto[2] && fanTimer[2] < time) {
    if(temp > temp_low) fanVol[2] = map(temp, temp_low, temp_medium, low, high);
  }
  
  // обрабатываем каналы вентиляторов, корректируем и устанавливаем им значение PWM сигнала
  for(int f=0; f temp_high) fanVol[f] = high;
    // если было 0, а стало какое-то положительное значение, нужно раскрутить вентиля на указанное время @timer_delay
    if(fanVolLast[f] == 0 && fanVol[f]) { fanVol[f] = high; fanTimer[f] = millis() + timer_delay; }
    // 0..255 (не больше не меньше)
    fanVol[f] = constrain(fanVol[f], 0, 255); //fanVol[f] = max(min(fanVol[f], 255), 0);
    // PWM control
    fanWrite(f);
    // предыдущее значение скорости
    fanVolLast[f] = fanVol[f];
  }
  
  // показываем статус если меню спрятано
  if(!show) {
    Serial.print("TEMP:");
    Serial.println(temp);
    
    Serial.print("VOL1:");
    Serial.println(fanVol[0]);
    
    Serial.print("VOL2:");
    Serial.println(fanVol[1]);
    
    Serial.print("VOL3:");
    Serial.println(fanVol[2]);
    
    Serial.print("PUMP:");
    Serial.println(rpm);
    
    Serial.print("TIME:");
    Serial.println(time);
    
    Serial.println("#");
  }
  delay(100);
}


void serialEvent(){
  int i = 0;
  String str;
  while(Serial.available()) {
      // считываем байт
      char byte = Serial.read();
      if(byte == '\n') break;
      str += String(byte);
  }
  if(str.length()) {
    int i = str.indexOf(":"); // ищем двоеточие для разделения на действие и значение
    String action = str.substring(0, i); // действие (что сделать)
    String value = str.substring(i+1, str.length()); // значение
    
    // изменить скорость вращения вентилятора. Пример: VOL1:150 (от 0..255) значение Fan 1 теперь будет от 0 до 255
    if(action.substring(0, 3) == "VOL") {
      int f = action.substring(3, 4).toInt();
      fanVol[f] = value.toInt();
      fanAuto[f] = 0; // авторегулировка отключена
    } else
    // изменить скорость вращения всем вентиляторам. Пример: FAN:150:255:100 (от 0..255) все значения FAN1,FAN2,FAN3 будут равны 150,255,100
    if(action == "AUTO") {
      for(int f=0; f 0) temp = t;
}

void fanWrite(int f) {
  if(fanPWM[f]) 
    pwmWrite(fanPin[f], fanVol[f]); 
  else
    analogWrite(fanPin[f], fanVol[f]);
}

Прога на delphi 7
Скачать: http://do.waix.ru/597aafab6b02.rar

Пожалуй, единственное что осталось сделать, так это побороть писк вентилятора на высоких оборотах в районе значений 150 - 240. Почему он проявляется только на них при частоте 35кГц вообще не понятно.
Может кто подскажет? Кондер ставил начиная с 0.22мкф заканчивая 1000мкф, чем больше тем хуже результат. Под плохим результатом я подразумеваю что во первых из-за заряженности кондера время работы кулера увеличивается ненамного, во вторых при значении 128 без кондера вентилятор крутиться на его настоящих 128 (т.е 50% не больше не меньше) но с кондером любого номинала, он при тех же значениях крутиться уже порядка 65-70% оборотов.

adrusha
Offline
Зарегистрирован: 02.03.2015

Творчеством навеяло с этого поста: http://www.roboremo.com/arduino-bluetooth-dc-motor-speed.html

Почему то код arduino обрезался, кому надо скину на почту или архив сюда.

Сейчас главным образом стоит как сгладить PWM сигнал так, чтоб значения не отступали от "реальных" для точной регулировки скорости и не было писка в диапозоне 150 - 240

adrusha
Offline
Зарегистрирован: 02.03.2015

Немного доработал программу на стороне ПК.

Скачать программу: http://ns75.ru/FanControl.exe

Скетч на ардуино в архиве: http://ns75.ru/FanControl_sketch.rar

Внес изменения и убрал баги вызванные передвижением ползунков. Добавил обратную связь ползункам, теперь они меняют свое значение автоматически относительно температуры. Уменьшил время обратной связи с ардуино.

Я разрабатываю программу для себя и мне будет приятно если она и вам пригодится. Все последующие изменения которые я буду вносить в программу, буду загружать по выше указанной ссылке.

blik
Offline
Зарегистрирован: 07.02.2016

Для того, чтобы убрать писк, нужно поднять частоту ШИМ. Вот видео, как это сделать:

http://www.youtube.com/watch?v=cU9oF3OKIlY

там же указаны ссылки на таблицы с нужными Вам данными.

adrusha
Offline
Зарегистрирован: 02.03.2015

blik пишет:

Для того, чтобы убрать писк, нужно поднять частоту ШИМ. Вот видео, как это сделать:

http://www.youtube.com/watch?v=cU9oF3OKIlY

там же указаны ссылки на таблицы с нужными Вам данными.

Писк удалось убрать именно понижением частоты. Сейчас на частоте 30Hz не слышно никаких шумов. Единственное, что на светодиодах установленных на самом кулере заметно мерцание, на они же в корпусе, а вообще их можно выпаять раз на то пошло :)

Увеличение частоты, я делал на 30kHz даже на такой частоте слышно писк, он высокачастотный и его заметно только в определенном значении в диапозоне от 140 до 190 (digitalwrite or pwmwrite) arduino