Делаем охлаждение серверного 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 уже был 57 градусов Цельсия). Решено было делать собственную систему охлаждения с ШИМ-вентилятором, т.к. на материнской плате сервера нет свободных PWM разъемов.
Что вам потребуется
- 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 диска командой (путь к NVMе /dev/nvme0n1, у вас, возможно, будет свой):
watch -n 2 'nvme smart-log /dev/nvme0n1 | grep -i temperature'
В моем случае она уже была 57 градусов в 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 секунд, обеспечивает плавное изменение скорости и периодически выводит текущие показания оборотов в монитор порта.
Далее, включаем сервер.
В Linux создаем файл nvme_temp_sender.sh:
# nano /usr/local/bin/nvme_temp_sender.sh
И вставляем в него следующее:
#!/bin/bash
DEVICE="/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_XX:XX:XX:XX:XX:XX-if00"
NVME_DEVICE="/dev/nvme0n1"
while true; do
# Ждём появления устройства ESP32
while [ ! -e "$DEVICE" ]; do
echo "[WARN] ESP32 не найден, жду подключения..."
sleep 2
done
echo "[INFO] Подключение к $DEVICE"
# Открываем устройство на запись через дескриптор 3
exec 3> "$DEVICE"
while true; do
# Если устройство отключено — выходим из внутреннего цикла для переподключения
if [ ! -e "$DEVICE" ]; then
echo "[WARN] ESP32 отключен, перезапуск..."
exec 3>&-
break
fi
# Читаем температуру NVMe
TEMP=$(nvme smart-log "$NVME_DEVICE" 2>/dev/null | awk '/^temperature/ {print $3; exit}')
if [[ -z "$TEMP" ]]; then
echo "[ERROR] Не удалось получить температуру NVMe!"
TEMP=0
fi
# Отправляем строку в ESP32
echo "temperature: $TEMP" >&3
sleep 2
done
done
Путь к Seeeduino (DEVICE=»/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_XX:XX:XX:XX:XX:XX-if00″) у вас будет свой, найдите его по пути /dev/serial/by-id/. Также путь к NVMе (NVME_DEVICE=»/dev/nvme0n1″), у вас, возможно, будет свой.
Сохраните файл и сделайте его исполняемым:
# chmod +x /usr/local/bin/nvme_temp_sender.sh
Далее, создайте службу nvme_temp_monitor.service:
# nano /etc/systemd/system/nvme_temp_monitor.service
И вставьте в файл следующее:
[Unit] Description=NVMe Temperature Monitor for ESP32 over USB Serial After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/nvme_temp_sender.sh Restart=always RestartSec=5 StandardOutput=syslog StandardError=syslog SyslogIdentifier=nvme_temp_monitor [Install] WantedBy=multi-user.target
Сохраните файл и перечитайте все unit-файлы systemd без перезапуска самого systemd:
# systectl daemon-reload
Включаем автозапуск службы nvme_temp_monitor в текущей сессии:
# systemctl enable --now nvme_temp_monitor.service
Далее, запускаем службу nvme_temp_monitor:
# systemctl start nvme_temp_monitor
Вкратце, служба nvme_temp_monitor запускает скрипт (nvme_temp_sender.sh), который каждые 2 секунды считывает температуру NVMe-диска и отправляет её по серийному порту USB на Seeeduino. Если Seeeduino вытащить/вставить — скрипт сам переподключается.
Шаг 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 : 44 C Warning Temperature Time : 0 Critical Composite Temperature Time : 0 Temperature Sensor 1 : 44 C Temperature Sensor 2 : 43 C Temperature Sensor 3 : 43 C
В моем случае температура понизилась на 13 градусов в режиме idle, что является отличным результатом. Но потом посмотрим, как поведет себя температура под нагрузкой.
Шаг 5 : Проектирование воздуховода
Чтобы сделать приток воздуха еще более эффективным, я спроектировал в Solidworks воздуховод для моего вентилятора. STL-модель можно скачать здесь. После печати воздуховода в шестигранное углубление я установил длинную гайку M3 (предварительно покрыв ее поверхность двухкомпонентным клеем Poxipol). Когда клей высох через 10 минут, я закрепил воздуховод на место заглушки для iLO посредством винта M3 и скрепил воздуховод с вентилятором посредством соединительных винтов.
П.С.
Также рекомендую установить микросервер HPE Proliant Gen 10 Plus в вертикальном положении (для этого в комплекте предусмотрены резиновые противоскользящие накладки), чтобы забор воздуха для NVMe был внизу, а выдув основного кулерного вентилятора был вверху.
Profit :)

















