Делаем охлаждение серверного NVMe диска для микросервера HPE Proliant Gen 10 Plus

img_20250828_173823

Друзья, всем привет! Сегодня поговорим о том, как сделать апгрейд микросервера HPE Proliant Gen 10 Plus, установив на него серверный NVMe диск от Samsung (модель PM1725B) на 1600 гигабайт. NVMe SSD диски очень быстрые по сравнению с SATA SSD (быстрее в 10 -12 раз), и недавно я узнал, что можно установить в шину PCIe моего сервера HHHL (низкопрофильный) NVMe SSD диск от Samsung и впоследствии установить на него Linux (это будет тема следующей инструкции). Но проблема NVMe SSD в том, что они сильно нагреваются (данный диск в режиме idle уже был 54 градуса Цельсия). Решено было делать собственную систему охлаждения с ШИМ-вентилятором.

Что вам потребуется

Шаг 1 : Установка серверного NVMe диска

Устанавливаем серверный NVMe диск Samsung PM1725B в PCIe4 x16 слот, предварительно сняв железный кронштейн с Riser board, т. к. он будет мешать при установке ШИМ-вентилятора.

img_20250828_142901

Железный кронштейн

Включаем сервер и смотрим в UEFI, определяется ли диск.

Если все хорошо, загружаем Linux и проверяем температуру NVMe диска командой:

watch -n 2 'nvme smart-log /dev/nvme0n1 | grep -i temperature'

В моем случае она уже была 54 градуса в idle режиме, что достаточно много.

Выключаем сервер.

Шаг 2 : Прошивка микроконтроллера Seeeduino

Микроконтроллер Seeeduino c адаптером будем устанавливать в свободный USB 3.2 Gen 1 порт внутри микросервера Proliant Gen 10 Plus, изначально предназначенный для boot-флешки.

img_20250812_235123

Микроконтроллер Seeeduino

Прошиваем контроллер данным скетчем в Arduino IDE:

#define FAN_PWM   4  // PWM на GPIO4
#define FAN_TACH  3  // Tach на GPIO3

const int pwmFreq = 25000;     // Частота PWM (Гц)
const int pwmResolution = 8;   // Разрешение PWM (бит)

volatile unsigned long rpmPulses = 0;  // Кол-во импульсов с тахометра
unsigned long lastDataTime = 0;        // Время последнего получения температуры
bool emergencyMode = false;             // Режим аварии (нет данных)
const int pulsesPerRev = 2;             // Импульсов с тахометра на один оборот

int currentPwm = 25;  // Текущий PWM
int targetPwm = 25;   // Целевой PWM

// Кривая зависимости температуры и PWM вентилятора
struct FanCurvePoint {
  int temp;
  int pwm;
} fanCurve[] = {
  {30, 25},
  {45, 40},
  {55, 120},
  {65, 200},
  {75, 255}
};
const int curvePoints = sizeof(fanCurve) / sizeof(fanCurve[0]);

// Обработчик прерывания для подсчёта импульсов с тахометра
void IRAM_ATTR countRPM() {
  rpmPulses++;
}
// Конец: функция подсчёта импульсов тахометра

void setup() {
  Serial.begin(115200);
  delay(500);

  // Настройка PWM: используем ledcAttach() согласно Espressif Arduino 3.x
  ledcAttach(FAN_PWM, pwmFreq, pwmResolution);

  // Установка начального значения PWM
  ledcWrite(FAN_PWM, currentPwm);

  // Настройка входа для тахометра с подтягивающим резистором
  pinMode(FAN_TACH, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(FAN_TACH), countRPM, FALLING);

  lastDataTime = millis();

  Serial.println("=== FAN Controller started ===");
}
// Конец: инициализация и настройка

void loop() {
  // Чтение данных из Serial для получения температуры
  if (Serial.available()) {
    String input = Serial.readStringUntil('n');
    input.trim();

    if (input.startsWith("temperature")) {
      int temp = input.substring(input.indexOf(':') + 1).toInt();

      if (temp > 0 && temp < 120) {
        targetPwm = mapTemperatureToPwm(temp);
        lastDataTime = millis();
        emergencyMode = false;
      } else {
        Serial.printf("Invalid temp: %dn", temp);
      }
    }
  }

  // Если не получаем данные дольше 8 секунд — включаем аварийный режим с максимальным PWM
  if (millis() - lastDataTime > 8000) {
    emergencyMode = true;
    targetPwm = 255;
    Serial.println("EMERGENCY: No data from NVMe!");
  }

  smoothFanSpeed();

  // Каждые 2 секунды выводим RPM в Serial
  static unsigned long lastRpmTime = 0;
  if (millis() - lastRpmTime > 2000) {
    detachInterrupt(digitalPinToInterrupt(FAN_TACH));

    unsigned long rpm = (rpmPulses * (60000 / 2000)) / pulsesPerRev;
    Serial.printf("RPM: %lu (pulses: %lu)n", rpm, rpmPulses);

    rpmPulses = 0;
    attachInterrupt(digitalPinToInterrupt(FAN_TACH), countRPM, FALLING);

    lastRpmTime = millis();
  }
}
// Конец: основной цикл обработки

// Функция, которая по температуре возвращает PWM согласно кривой,
// выключает вентилятор при температуре ниже 30,
// и не даёт PWM опускаться ниже 24 при включенном вентиляторе.
int mapTemperatureToPwm(int temp) {
  if (temp < 30) return 0;  // Вентилятор выключен при temp < 30°C

  for (int i = 0; i < curvePoints - 1; i++) {
    if (temp <= fanCurve[i + 1].temp) {
      int pwm = map(temp, fanCurve[i].temp, fanCurve[i + 1].temp,
                    fanCurve[i].pwm, fanCurve[i + 1].pwm);
      if (pwm < 24) pwm = 24;  // Минимальный PWM для стабильного запуска вентилятора
      return pwm;
    }
  }
  return fanCurve[curvePoints - 1].pwm;
}
// Конец: функция маппинга температуры на PWM

// Функция плавного изменения скорости вентилятора к целевому значению
// Минимальный PWM 24, если targetPwm не 0, иначе полный стоп (0)
void smoothFanSpeed() {
  if (targetPwm < 24) {
    // Если цель ниже порога запуска — сразу выключаем вентилятор
    currentPwm = 0;
  } else {
    // Плавно приближаемся к targetPwm
    if (currentPwm < targetPwm) {
      currentPwm += 5;
      if (currentPwm > targetPwm) currentPwm = targetPwm;
    } else if (currentPwm > targetPwm) {
      currentPwm -= 2;
      if (currentPwm < targetPwm) currentPwm = targetPwm;
    }
    // Минимальный порог для запуска и поддержания
    if (currentPwm < 24) currentPwm = 24;
  }

  ledcWrite(FAN_PWM, currentPwm);
  Serial.printf("PWM set to: %dn", currentPwm);
}
// Конец: плавное изменение скорости вентилятора

Этот скетч управляет скоростью 4-х проводного вентилятора с ШИМ управлением и обратной связью по тахометру. Он регулирует скорость вращения вентилятора в зависимости от температуры, получаемой через последовательный порт от NVMe диска, используя предустановленную температурную кривую. Скетч включает аварийный режим с максимальными оборотами при отсутствии данных более 8 секунд, обеспечивает плавное изменение скорости и периодически выводит текущие показания оборотов в монитор порта.

Шаг 3 : Подключение микроконтроллера Seeeduino к ШИМ-вентилятору

img_20250810_191340

Подключение микроконтроллера Seeeduino к ШИМ-вентилятору и питанию 12 вольт

Вставляем микроконтроллер в USB 3.2 Gen 1 порт. Берем 2pin female и припаиваем черный провод к D1 (GPIO 3), а красный провод к D2 (GPIO 4) на микроконтроллере. Затем нам нужно будет разрезать соединительные провода от 4-Pin Molex, которые идут к белому 4 PWM коннектору (желтый и черный провод), а из черного 4 PWM коннектора нужно физически вытащить черный и красный провода с фиксирующими пинами (для этого придется испортить один черный 4 PWM коннектор, а также отрезать провода от 4-Pin Molex) и вставить фиксирующие пины в свободные отверстия белого 4 PWM коннектора (черный слева, т.к. это ШИМ).

Далее, черный и красный провод белого 4 PWM коннектора припаиваем к проводам соответствующего цвета 2pin male коннектора и соединяем его с 2pin female на микроконтроллере. На все пайки проводов надевайте термоусадки.

Питание в 12 вольт для ШИМ-вентилятора будем брать от разъема SATA 7+15pin на сервере с 4-го лотка. Берем кабель для передачи данных (Cablexpert CC-SATAMF-715-50CM) и отрезаем от female-разъема желтый (12 вольт) и два черных провода (Ground). Изолируем отрезанные места проводов от female-разъема и все контакты female-разъема посредством термоклея. Male-коннектор кабеля вставляем в SATA 7+15pin female в 4-м лотке.

img-20250828-wa0006

Мне пришлось пожертвовать 4-м лотком и взять питание с SATA, но в моей конфигурации дисков он всегда был пустым, поэтому это ни на что не повлияло. Припаиваем желтый и черный провода от коннектора к проводам соответствующего цвета на белом 4 PWM коннекторе. Второй черный провод припаиваем к Ground на микроконтроллере также через 2pin коннектор (откусив от него второй красный провод, т. к. он не нужен). Это не обязательно, т. к. Ground на SATA и Ground на микроконтроллере (через USB-A) должны быть общими. Я старался все делать через коннекторы, чтобы потом было все удобно подключать/отключать.

Сам ШИМ-вентилятор кладем на радиатор NVMe диска в режиме на обдув (т. е. будем дуть на радиатор). Также убираем заглушку с задней стенки сервера, предназначенной для iLO. С этого выреза на задней стенке будет осуществляться забор воздуха.

img_20250813_200134

Шаг 4 : Тестируем ШИМ-вентилятор

Включаем сервер и проверяем обороты и ШИМ-сигнал командой:

cat /dev/ttyACM0 | awk '/RPM:/ {rpm=$2} /PWM set to:/ {pwm=$4} rpm && pwm {print "RPM=" rpm, "PWM=" pwm}'

Если все хорошо, вы должны увидеть что-то типа такого:

RPM=1410 PWM=154
RPM=1410 PWM=154
RPM=1410 PWM=159
RPM=1800 PWM=159
RPM=1800 PWM=159
RPM=1800 PWM=159

Вы должны увидеть изменение оборотов и PWM (т. е. ШИМ).

Также проверяем температуру командой:

watch -n 2 'nvme smart-log /dev/nvme0n1 | grep -i temperature'

Вы должны увидеть что-то подобное:

temperature : 42 C
Warning Temperature Time : 0
Critical Composite Temperature Time : 0
Temperature Sensor 1 : 42 C
Temperature Sensor 2 : 41 C
Temperature Sensor 3 : 41 C

В моем случае температура сильно понизилась в режиме idle, что является отличным результатом.

Шаг 5 : Проектирование воздуховода

Чтобы сделать приток воздуха еще более эффективным, я спроектировал в Solidworks воздуховод для моего вентилятора. После печати воздуховода в шестигранное углубление я установил длинную гайку M3 (предварительно покрыв ее поверхность двухкомпонентным клеем Poxipol). Когда клей высох через 10 минут, я закрепил воздуховод на место заглушки для iLO посредством винта M3 и скрепил воздуховод с вентилятором посредством соединительных винтов.

large_display_air_duct

STL-модель воздуховода

Готовая деталь

Готовая деталь

А вот так это смотрится в микросервере

А вот так это смотрится в микросервере

Забор воздуха происходит через вырез от HHHL заглушки

Забор воздуха происходит через вырез от HHHL заглушки

П.С.

Profit :)

Дайте свою оценку данной инструкции

Оставьте свой отзыв

Вы должны Войти, чтобы оставлять отзывы.