Делаем охлаждение серверного NVMe диска для микросервера HPE Proliant Gen 10 Plus
Друзья, всем привет! Сегодня поговорим о том, как сделать апгрейд микросервера 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 градуса Цельсия). Решено было делать собственную систему охлаждения с ШИМ-вентилятором.
Что вам потребуется
- 1600 ГБ Серверный NVMe Samsung PM1725B [MZPLL1T6HAJQ-00005] — 1шт.
- Микроконтроллер Seeeduino Seeed Studio XIAO ESP32-C3 — 1шт.
- Адаптер USB-А — USB-C — 1шт.
- Вентилятор Noctua NF-A6X15 — 1шт.
- Соединительный кабель 4-Pin Molex to 4 PWM — 1шт.
- Соединительные провода 2pin x 2pin — 2шт.
- Кабель для передачи данных Cablexpert CC-SATAMF-715-50CM — 1шт.
- Набор длинных гаек M3 длиной 7 мм
- Воздуховод, напечатанный на 3D-принтере — 1шт.
Шаг 1 : Установка серверного NVMe диска
Устанавливаем серверный NVMe диск Samsung PM1725B в PCIe4 x16 слот, предварительно сняв железный кронштейн с Riser board, т. к. он будет мешать при установке ШИМ-вентилятора.
Включаем сервер и смотрим в 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-флешки.
Прошиваем контроллер данным скетчем в 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 к ШИМ-вентилятору
Вставляем микроконтроллер в 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-м лотке.
Мне пришлось пожертвовать 4-м лотком и взять питание с SATA, но в моей конфигурации дисков он всегда был пустым, поэтому это ни на что не повлияло. Припаиваем желтый и черный провода от коннектора к проводам соответствующего цвета на белом 4 PWM коннекторе. Второй черный провод припаиваем к Ground на микроконтроллере также через 2pin коннектор (откусив от него второй красный провод, т. к. он не нужен). Это не обязательно, т. к. Ground на SATA и Ground на микроконтроллере (через USB-A) должны быть общими. Я старался все делать через коннекторы, чтобы потом было все удобно подключать/отключать.
Сам ШИМ-вентилятор кладем на радиатор NVMe диска в режиме на обдув (т. е. будем дуть на радиатор). Также убираем заглушку с задней стенки сервера, предназначенной для iLO. С этого выреза на задней стенке будет осуществляться забор воздуха.
Шаг 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 воздуховод для моего вентилятора. STL-модель можно скачать здесь. После печати воздуховода в шестигранное углубление я установил длинную гайку M3 (предварительно покрыв ее поверхность двухкомпонентным клеем Poxipol). Когда клей высох через 10 минут, я закрепил воздуховод на место заглушки для iLO посредством винта M3 и скрепил воздуховод с вентилятором посредством соединительных винтов.
П.С.
Profit :)