家用万兆网络实践:紧凑型家用服务器静音化改造(二)

大家好,这篇文章我们继续分享家里网络设备的万兆升级和静音改造经验,希望对有类似需求的朋友有所帮助。

写在前面

在上一篇《家用网络升级实践:低成本实现局部万兆(一)》中,我们留下了一些待解决的问题。现在让我们一起来完成这些未完成的部分。

还记得曾经提到的上面的配件嘛

回顾一下上篇文章提到的内容:

不过这台设备本身运行的时候,就会有一些风扇声音,启动的时候比较大声,运行的时候相对安静,但是还有一些声音。作为计划不再按需启动使用,而是和其他设备一样长时间开机使用的设备,我计划对它进行一些改造,在改造结束后,再进行测试,这样对我而言更有意义。

这次改造的是图中上面的设备:Gen10 Plus

关于这台设备的详细介绍,我在之前写过一篇文章《省心和颜值兼顾的 HomeLab 设备:HPE MicroServer Gen10 Plus v2(一)》,感兴趣的朋友可以去看看,这里就不重复介绍了。

其实把服务器搬回家里使用,既能提供安静可靠的实验环境,又能实现高性价比,这并不是我一个人的想法。网上有很多志同道合的玩家,都在分享各种实用的解决方案。

先回顾一下,2021年我写过一篇《廉价的家用工作站方案:前篇》,主要介绍了基于笔记本的方案。文章里我详细对比了实验开发环境中,在相同配置 (核心、内存、磁盘)下服务器的使用成本。这些“家用服务器”到现在还在我家里稳定运行,发挥着它们的价值。算下来,从 2015 年开始,这个方案已经使用了 10 年之久。

不过除了这种性价比方案外,传统服务器作为专业计算设备还是有其独特优势的:可以插入大量ECC内存、提供更稳定的虚拟化支持。特别是在需要持续运行服务或处理大规模数据时,这些优势就特别明显。(相信经常处理海量数据的朋友们都深有体会)

这篇文章能够成文,要特别感谢 zhaoyingpu 以及众多先行者的实践验证和探索。在接下来的内容中,我会详细介绍这篇文章凝聚了多少人的心血。

让我们开始吧。

定义问题

在明确改造方案之前,我们先来理清楚目前遇到的问题。

设备使用环境与噪音困扰

这台设备作为一台家用服务器使用,最让人头疼的就是噪音问题。即便我把服务器放在机柜里,确实能降低不少运行噪音,但那个服务器使用的工业风扇的“呼呼”声依然很明显。

如果你也用过 HPE 的 Gen8、Gen10 这类服务器,肯定知道它们对硬件特别“挑剔”。一旦安装了不在官方认证列表里的 PCIe 扩展卡,风扇就会立刻开启“狂暴模式”。问题是,我们实际需要用的扩展卡,不可能都在它的认证列表里。

另外,服务器自带的 iLO 芯片也是个“麻烦制造者”。用着用着它就会发热,然后又把风扇调到最大转速,制造吵闹的噪音。

考虑到这是要长期使用的设备,我不想和之前一样只能在需要的时候才开机。特别是完成万兆网络改造后,如果噪音问题得不到解决,那就太影响日常使用了。

值得一提的是,硬盘噪音倒不是问题。因为我用的都是SSD,既安静又不会产生太多热量。

噪音从何而来?

通过前面的分析,我们可以看到设备噪音主要是来自风扇。而风扇为什么会发出噪音,主要有两个原因:一是设备预设的温控策略不够优化,二是当某些部件温度升高时,采用了简单粗暴的降温方案。

这其实和产品的目标市场有关。在海外市场,很多用户会把设备放在办公室、机房,甚至是车库、阳台使用。这些场景下,用户对噪音的容忍度都比较高。从厂商角度看,优化降噪方案需要投入额外成本,但这些投入并不能提升计算、存储、网络性能。对主力用户群来说,他们更在意性能而不是噪音,所以厂商也就缺乏改进的动力。

另外,让设备在更低温的环境下运行,也能降低厂商的维护和售后成本。因为芯片和其他零部件出现故障,往往是因为过热、过冷或者环境太潮湿导致的。所以对厂商来说,让风扇保持高速运转是个省事又省钱的选择,反正用的是用户的电。

应对问题的挑战的策略

想解决设备噪音问题,最直接的方法无非是两个思路:一是更换静音风扇,二是想办法降低设备温度,避免风扇高速运转。听起来简单,但实际操作中还是有不少挑战需要克服。

让我们来看看具体有哪些难点:

首先,HPE服务器用的风扇接口与市面上常见的消费级静音风扇不兼容。这就限制了我们能用的风扇选择范围。

其次,就算找到合适的风扇可以更换,服务器默认的风扇控制策略是全速运转。即便装上了静音风扇,在这种策略下依然可能达不到理想的降噪效果,毕竟这是物理规律决定的。

再者,降噪改造必须在保证设备运行安全的前提下进行。我们需要确保更换风扇后,各个零部件不会因为散热不足而过热损坏。

最后还要考虑成本问题。这不仅包括初次改造的投入,还要考虑到后续的备件更换,以及其他设备改造的可能性。我们需要一个长期可持续的解决方案,而不是一次性的权宜之计。

既然已经理清了这些挑战,接下来就让我们一个个攻克它们。先从最基础的风扇选择开始说起吧。

解决方案

从问题到解决方案的转变,让我想起之前在知乎社区学到的一个观点:只要有问题,就一定能找到答案。

选择服务器风扇的替代

先说说我的改造经历。在 2021 年时,我在《近期家用设备(NUC、猫盘、路由器)散热升级记录》这篇文章中分享过给 NUC 8 迷你主机做散热改造的经验。

2021,NUC8 迷你主机散热改造

当时遇到的问题和现在很类似,这类小巧机箱的设计往往没有充分考虑散热需求,或者说它们的设计初衷可能就是低负载运行,某种程度上确实浪费了设备性能。我当时选择了经典的黑色“猫扇”来进行替换,效果相当不错:设备在高负载时只有轻微噪音,平时更是安静得几乎听不到声音。

这次的改造,我依然选择了猫扇,具体型号是经典配色的 NF-A8 。

计划中使用的猫扇:经典配色 NF-A8

选这款的原因也很简单:我这台服务器平时就是用来跑些虚拟化环境和一些突发性的轻量应用,再加上处理一些万兆网络中转任务。在这种低负载场景下,低转速的猫扇优势特别明显(当然,如果是高转速场景就另当别论了)。

不过在动手改造前,我们还是得先了解下新旧风扇的性能差异。

HPE Gen10 Plus v2 原装风扇性能参数

原装的风扇是台达电子生产的 PFB0812GHE(80x80x38mm 12V DC 10.2W,产品规格 datasheet),最高能转到 6100 转,噪音 55.5 分贝,气流量 86.4 CFM(约146.8 m³/h),风压 22 mmAq。

NF-A8 性能参数

而我们选的NF-A8,虽然尺寸完全一致(免去了改装螺丝位的麻烦),但满载时只有 17.7 分贝,甚至能降到 13.8 分贝(1750转)。满载转速只有 2200 转,最低能到 450 转,气流量是 55.5 m³/h,风压 2.37 mm H₂O。

虽然不能简单地用线性计算来比较两个风扇的性能,但从这些数据也能看出一些门道。新风扇的最大转速只有原装的“六二折”左右,气流量是“四七折”,风压更是只有原来的十分之一。所以要提醒大家的是**:既然最大散热能力减弱了,用新风扇时就得特别注意控制整机的发热情况,不然真的会“压不住”。**

合理规划降低热量产生

前文提到,我们选择了性能较弱但更安静的风扇,虽然让耳朵舒服了,但机器的散热能力相应下降。因此,我们需要合理规划设备的使用方式。

在我的使用方案中,这台设备仅运行 ESXi 虚拟化服务,主要负责 IO、存储和网络类服务,尽可能减少 CPU 这个主要发热源的热量产生。

查阅惠普官方介绍,HPE ProLiant MicroServer Gen10 Plus v2 被定位为一款经济实惠、小巧的入门级服务器。

设备可用的 CPU 型号对比

官方提供的 CPU 选项包括 Intel Xeon E-2314(2.80GHz)和 Intel Pentium Gold G6405(4.10GHz),TDP 分别为 65W 和 58W,核心配置为 4c4t 和 2c4t。我选择的 CC 150 CPU 实际功耗约 60W 出头,但拥有更多核心(8c16t)。由于其功耗与官方高配 E-2314 相近(甚至略低),理论上产生的热量应该不会更多,无需额外增加散热能力。

存储方面,我仅使用固态硬盘,并优先选择可搭配金属支架的 2.5 寸 SATA SSD。相比机械硬盘,SSD 本身发热就更少;而 SATA 接口相比 NVMe,由于速度较慢且散热面积更大,发热量也更低。配合散热支架使用后,风扇主要只需要负责 CPU、主板和网卡的散热工作。

实际效果如何?这是运行一天后的数据:

设备运行时的前半部分温度采样

设备运行时的后半部分温度采样

改造后的温度数据显示,规划内的设备温度都保持在较低水平。与其他用户相比,AHCI HD Max 温度低了约 10 度,BMC(iLO 位置)温度也降低了接近 10 度。如果想进一步降温,可以参考 HPE 海外社区用户的改装方案(有损改装)。

此前提到,iLO 控制芯片过热会导致风扇疯转以加强换气,产生噪音。如果再插入 PCIe 扩展卡,更会影响风道,让风扇徒劳运转,徒增声噪。

为解决这个问题,我们需要两个改进。首先是增加 iLO 的主动散热能力,比如加装散热器。参考了 Chiphell 社区用户 zhengwenxi1989 在 2022 年分享的《Gen10 Plus iLO 芯片低成本完美散热》方案。我暂时选择让气流带走散热器上的热量,而不是导向大散热器,后续可能会调整。

iLO 使用的散热器

虽然原本打算采用同款方案,但考虑到物流时间,加上手头有一块理论散热性能更好的利民 SSD 散热器,就先用起来了。我用 3M 普通胶带替换了原有双面胶,后续可能还会更换,先观察一段时间效果。

除了通过更好的散热器增加被动散热面积外,我也借鉴原方案增加了主动散热。

降低 iLO 温度的主动散热小风扇

这款风扇和原始方案中的略有不同,使用方式也有变化,下文详细展开。

接管设备风扇调度策略

接下来要解决的是一个比较有挑战的问题,如何接管惠普服务器的风扇控制。

这个问题的复杂之处在于,惠普服务器使用的是特殊的风扇控制方案,与普通消费级风扇有很大区别。如果我们想用噪音更小的风扇替换原装的,不仅要考虑接口兼容性的问题,还需要调整控制策略,避免低转速风扇一直以高速运转。

本文使用的小巧的 “控制器” 方案

这个问题其实早有开源社区的先行者们在探索解决方案。2020年,GitHub 用户Max-Sum 分享了一个项目 Max-Sum/HP-fan-proxy。这个方案是在 Reddit 用户executivul 在2017年提出的方案(Arduino nano 实现代码)基础上优化而来,通过简化硬件设计,采用软件方案来解决问题。

惠普风扇接口定义

这些前期工作的意义重大,不仅提供了代码参考,还给出了如何制作兼容惠普风扇接口(2x3 PHD2.0 connector)的细节,以及明确的接口定义说明。

在此特别感谢七年前那些先行者们。不管是进行静音改造的实践者,还是参与可行性讨论并提供建议的社区成员,比如这些讨论帖 7vxo5n/hp_dl380e_g8_arduino_fan_control_project/、72k3jf/faking_the_fan_signal_on_a_dl380_g7/,都为这个方案的最终成型贡献了力量。

九个月前,一个专门针对 Gen10 Plus 和猫扇的新方案出现了:zhaoyingpu/hpe-gen10-fan-proxy。这个项目参考了 2019 年的一篇技术笔记《Solving the DL180 G6 Fan Controller Problem》,并在 Max-Sum 方案的基础上做了改进。感谢这位 70 后 DIY 玩家的分享,让我在配件选购方面省去了很多摸索的时间,可以更专注于软件部分的优化。

由于我在硬件方面还算是个新手,我 fork 了 zhaoyingpu 的项目 soulteary/hpe-gen10-fan-proxy,增补了相关资料。后续的实验性改动我也会记录在这个仓库中(目前已经把之前的改动还原了,等稳定后再更新到仓库)。

核心代码逻辑如下:

#include <FastLED.h>

/* 定义引脚相关的宏 */
#define pinOfPin(P) (((P) >= 0 && (P) < 8) ? &PIND : (((P) > 7 && (P) < 14) ? &PINB : &PINC))
#define pinIndex(P) ((uint8_t)(P > 13 ? P - 14 : P & 7))
#define pinMask(P) ((uint8_t)(1 << pinIndex(P)))
#define isHigh(P) ((*(pinOfPin(P)) & pinMask(P)) > 0) /* 判断引脚是否为高电平 */
#define isLow(P) ((*(pinOfPin(P)) & pinMask(P)) == 0) /* 判断引脚是否为低电平 */

/* 引脚和常量定义 */
const int pwmInPin = A1;     /* 输入PWM信号的引脚 */
const int pwmOutPin = 9;     /* 输出PWM信号的引脚 */
const int pwm2OutPin = 10;   /* 第二个输出PWM信号的引脚 */
const int rpmInPin = 4;      /* RPM输入引脚 */
const int hpeTachPin = A2;   /* HPE转速信号输出引脚 */
const int normalTachPin = 3; /* 正常转速信号输出引脚 */
const uint16_t pwmTop = 320; /* PWM的顶值 */
const int sample = 25000;    /* 读取PWM的样本数量 */
const float pwmMap[4] = {0.1, 0.2, 0.5, 1.0}; /* PWM映射值 */
/* const float pwmMap[4] = {0, 1.0, 0, 1.0}; */
const int rpmUpper = 6000;                    /* 最大RPM */
const int rpmLower = 60;                      /* 最小RPM */
const float timer2Freq = 16000000 / 1024 / 2; /* Timer2频率 */
const int timer2RpmLower = 900;               /* Timer2最低RPM */
const float pwm2OutDutty = 0.45;              /* 第二个PWM输出的占空比 */

volatile uint32_t pulseCount = 0; /* 脉冲计数,使用volatile确保在中断中安全更新 */

/* 脉冲计数函数 */
void counter() { pulseCount++; }

/* 中断服务例程,处理RPM输入引脚的中断 */
ISR(PCINT2_vect) {
  if (digitalRead(rpmInPin) == HIGH) /* 检查引脚状态 */
    pulseCount++;                    /* 增加脉冲计数 */
}

void setup() {
  /* 配置Timer 1以产生25 kHz的PWM信号 */
  TCCR1A = 0;                                                                     /* 重置配置 */
  TCCR1B = 0;                                                                     /* 重置配置 */
  TCNT1 = 0;                                                                      /* 重置计数器 */
  TCCR1A = _BV(COM1A1) /* 非反向PWM信号 */ | _BV(COM1B1) /* 同上 */ | _BV(WGM11); /* 模式10:相位校正PWM,TOP = ICR1 */
  TCCR1B = _BV(WGM13) /* 同上 */ | _BV(CS10);                                     /* 预分频器 = 1 */
  ICR1 = pwmTop;                                                                  /* 设置TOP值为320 */

  /* 配置Timer 2以产生7.8125 kHz的PWM信号,用于转速模拟 */
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2 = 0;
  TCCR2A = _BV(COM2B1) /* 非反向PWM信号 */ | _BV(WGM20);              /* 模式5:相位校正PWM,TOP = OCR2A */
  TCCR2B = _BV(WGM22) /* 同上 */ | _BV(CS20) | _BV(CS21) | _BV(CS22); /* 预分频器 = 1024 */
  OCR2A = 255;                                                        /* 设置OCR2A为255 */

  /* 设置引脚模式 */
  pinMode(pwmInPin, INPUT); /* 输入PWM引脚 */
  /* Tachometer output signal need pullup to 5v, ref https://noctua.at/pub/media/wysiwyg/Noctua_PWM_specifications_white_paper.pdf */
  pinMode(rpmInPin, INPUT_PULLUP); /* RPM输入引脚,启用上拉电阻 */
  pinMode(pwmOutPin, OUTPUT);      /* 输出PWM引脚 */
  pinMode(pwm2OutPin, OUTPUT);     /* 第二个输出PWM引脚 */
  pinMode(hpeTachPin, OUTPUT);     /* HPE转速信号输出引脚 */
  pinMode(normalTachPin, OUTPUT);  /* 正常转速信号输出引脚 */

  /* 设置初始状态 */
  digitalWrite(hpeTachPin, LOW);                     /* HPE转速信号初始化为LOW */
  analogWrite25k(pwm2OutPin, pwm2OutDutty * pwmTop); /* 输出第二个PWM信号 */

  /*
   * 启用中断
   * attachInterrupt(digitalPinToInterrupt(rpmInPin), counter, RISING);
   * Enable PCIE2 Bit3 = 1 (Port D)
   */
  PCICR |= B00000100; /* 启用PCIE2中断 */
  /* Select PCINT20 Bit4 = 1 (Pin D4) */
  PCMSK2 |= _BV(rpmInPin); /* 选择要监测的引脚 */

  /* 禁用Nano Mini板的RGB灯 */
  CRGB leds[3] = {
      {0, 0, 0},
      {0, 0, 0},
      {0, 0, 0},
  };
  FastLED.addLeds<WS2812, 2, GRB>(leds, 3); /* 添加LED */
  FastLED.show();                           /* 更新LED状态 */

  Serial.begin(115200);           /* 初始化串口通信 */
  Serial.println("HP fan proxy"); /* 输出初始化信息 */
}

/* 读取HPE PWM信号 */
float readHpePWM(int pin, int sample) {
  uint32_t total = 0; /* 总样本计数 */
  uint32_t low = 0;   /* 低电平计数 */
  for (uint16_t i = 0; i < sample; i++) {
    low += isLow(pin); /* 统计低电平持续时间 */
    total++;           /* 增加总样本计数 */
  }
  /* 返回反转的PWM值 */
  return ((float)low / total);
}

/* 读取Intel PWM信号 */
float readIntelPWM(int pin, int sample) {
  uint32_t total = 0; /* 总样本计数 */
  uint32_t low = 0;   /* 高电平计数 */
  for (uint16_t i = 0; i < sample; i++) {
    low += isHigh(pin); /* 统计高电平持续时间 */
    total++;            /* 增加总样本计数 */
  }
  /* 返回正常的PWM值 */
  return ((float)low / total);
}

/* 在指定引脚输出PWM信号 */
void analogWrite25k(int pin, int value) {
  switch (pin) {
    case 9:
      OCR1A = value; /* 设置引脚9的PWM值 */
      break;
    case 10:
      OCR1B = value; /* 设置引脚10的PWM值 */
      break;
    default:
      /* 不支持的引脚 */
      break;
  }
}

uint32_t pulseFanPrev = 0;
uint32_t clockPrev = 0;

/* 将输入值映射到指定范围 */
float map_to_float(float x, float in_min, float in_max, float out_min, float out_max) {
  return ((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
  ;
}

void loop() {
  /* 处理第一次循环 */
  if (pulseFanPrev == 0 && clockPrev == 0) {
    pulseFanPrev = pulseCount; /* 记录脉冲计数 */
    clockPrev = millis();      /* 记录时间 */
    delay(1000);               /* 等待1秒 */
    return;                    /* 返回以开始下一次循环 */
  }

  /* 计算RPM */
  uint32_t clockLoopBegin = millis();          /* 记录循环开始时间 */
  uint32_t pulse = pulseCount - pulseFanPrev;  /* 计算脉冲数 */
  uint32_t clock = clockLoopBegin - clockPrev; /* 计算时间间隔 */
  pulseFanPrev = pulseCount;                   /* 更新上一次脉冲计数 */
  clockPrev = millis();                        /* 更新上一次时间 */

  /* 计算RPM(每分钟转速) */
  uint32_t rpm = pulse * 1000 * 60 / 2 / clock; /* 根据脉冲和时间计算转速 */

  digitalWrite(hpeTachPin, rpm > 0 ? LOW : HIGH); /* 根据转速状态输出信号 */

  /* 读取反转PWM信号并输出映射值 */
  float pwmOut;
  float pwmIn = readHpePWM(pwmInPin, sample); /* 读取PWM输入信号 */
  if (pwmIn >= pwmMap[1])                     /* 根据输入PWM值选择输出 */
    pwmOut = pwmMap[3];
  else if (pwmIn <= pwmMap[0])
    pwmOut = pwmMap[2];
  else
    pwmOut = map_to_float(pwmIn, pwmMap[0], pwmMap[1], pwmMap[2], pwmMap[3]); /* 映射输入PWM */

  uint16_t out = uint16_t(pwmOut * pwmTop); /* 将输出PWM值转换为整型 */
  analogWrite25k(pwmOutPin, out);           /* 输出PWM信号 */

  /* 打印调试信息 */
  Serial.print("iLO: ");
  Serial.print(pwmIn * 100);
  Serial.print("% Out: ");
  Serial.print(pwmOut * 100);
  Serial.print("% RPM: ");
  Serial.print(rpm);
  Serial.print(" : ");
  Serial.print(pulse);
  Serial.print(" / ");
  Serial.println(clock);

  /* 固定1Hz的控制,并输出正常的转速信号 */
  clock = millis() - clockLoopBegin; /* 计算循环持续时间 */
  int rpmIn = pwmIn * rpmUpper;      /* 将输入PWM转换为RPM值 */
  if (rpmIn > timer2RpmLower) {
    int freq = timer2Freq / (rpmIn * 2 / 60); /* 根据RPM计算频率 */
    OCR2A = freq;                             /* 更新OCR2A值 */
    analogWrite(normalTachPin, freq / 2);     /* 输出正常转速信号 */
    delay(1000 - clock);                      /* 等待剩余时间 */
  } else if (rpmIn > 0) {
    int count = max(rpmIn, rpmLower) * 2 / 60; /* 计算输出脉冲的数量 */
    float half = 1000.0 / count / 2;           /* 计算每个脉冲的持续时间 */
    for (int i = 0; i < count * 2 - 1; ++i) {
      digitalWrite(normalTachPin, (i & 1) ? LOW : HIGH); /* 交替输出高低电平 */
      uint32_t now = millis() - clockLoopBegin;          /* 记录当前时间 */
      /* Serial.println(now); */
      uint32_t next = clock + half * (i + 1); /* 计算下一个脉冲的时间 */
      delay(next - now);                      /* 等待下一个脉冲 */
    }
    digitalWrite(normalTachPin, LOW);  /* 结束脉冲输出 */
    clock = millis() - clockLoopBegin; /* 更新循环时间 */
    if (clock < 1000) {
      delay(1000 - clock); /* 等待直到1秒结束 */
    }
  } else {
    delay(1000 - clock); /* 如果RPM为0,等待剩余时间 */
  }
}

代码具体的使用方法,我们先按下不表,等讨论完所有相关问题的解决方案后再详细展开。

确保备件来源可持续

在这个改造方案中,我们需要准备以下几类配件:

  • HPE Gen10 系列替换用的大号风扇
  • 适配 HPE 主板的风扇接口配件
  • Arduino Nano Mini 开发板
  • iLO 散热器
  • iLO 区域芯片专用小风扇
  • 各类连接线材(包括数据线、电源线)、接线端子、绝缘胶带等

在 zhaoyingpu/hpe-gen10-fan-proxy 项目主页上,作者提供了各个关键零件的网店地址。经过验证,这些配件都有稳定的供应渠道和替换方案,不用担心断货问题(不必担心损耗)。

改造使用的配件

上图展示了我这次改造所使用的具体配件型号。如果你能找到更经济的替代品,完全可以自行更换。考虑到制作过程中可能出现的失误和后期使用中的耗损,我准备了一些备用配件,总成本为 250.07 元。如果你有把握一次成功,实际成本可以控制在 175.9 元左右。

定制数据线接口

由于我没有专业的压线工具和配件座子的簧片端子,所以需要寻求硬件 DIY 达人的帮助。在北京同城找到了一家店铺,店主非常耐心地为我讲解制作流程,并帮我压制了六套线材(具体制作过程会在后文详述)。

数据线半成品

这部分花费了 179 元。如果你对自己 DIY 整合有信心,只需要制作一套线材的话,并且不着急使用设备的话,成本可以降到 99 元内(同城高时效运费占据成本大头)。

值得一提的是,在完成这次改造后,我发现了一个更优的方案,可以将这部分成本降至 20 元,而且硬件可靠性更高。

在解决了上述四个主要技术难题后,我们就可以开始动手改造了。

动手实战

理论部分已经讲完,现在让我们开始动手实践环节。

软件部分:Arduino 控制程序的刷写

参考的原始项目和开发板的网店店铺对于开发板的软件使用说明都比较有限。不过经过一番研究,我找到了最简单的使用方法。(如果你想深入了解,我已经把相关资料整理在了 GitHub 仓库中:soulteary/hpe-gen10-fan-proxy/vendor/docs、soulteary/hpe-gen10-fan-proxy/vendor/misc)

首先,我们需要搭建Arduino的开发环境。请从 Arduino 官网下载 IDE(我使用的是2.3.4版本)。

切换界面语言

为了让教程更容易理解,建议先把 IDE 的界面语言调整为中文。

连接 Arduino 开发板到电脑后,我们就可以测试是否能正常进行程序烧录了。如果你的系统无法识别开发板串口,可以在这里下载对应驱动:soulteary/hpe-gen10-fan-proxy/vendor/drivers。

允许加载三方驱动

对于 macOS 较新系统版本的用户,你需要在系统安全设置中允许“CH34x VCP Driver”,然后重新插入开发板,这样系统就能识别设备串口了。

安装必要的插件

无论是测试程序还是后续的风扇控制程序,我们都会用到LED功能,所以需要在IDE中安装 “FastLED” 和 “Atmega328 IO” 这两个插件。

接下来,为了验证开发板能够正常工作,我编写了一个有趣的程序,可以让开发板的LED灯随机闪烁并产生呼吸灯效果。如果你也想尝试,这段代码已经开源在 soulteary/hpe-gen10-fan-proxy/arduino-test.ino。

#include <FastLED.h>
#define uchar unsigned char
#define RGB_PIN   2         // RGB灯使用IO2进行控制
#define NUM_LEDS  3         // RGB灯的数量
CRGB leds[NUM_LEDS];       // CRGB是结构体类型

// 呼吸效果参数
const int BREATH_STEPS = 50;  // 呼吸渐变步数
const int BREATH_DELAY = 20;  // 每步延时(ms)
float breathBrightness = 0;   // 当前亮度
float breathDelta = 1.0;      // 亮度变化方向

// 定义颜色结构体
struct Color {
  uchar r;
  uchar g;
  uchar b;
};

void RGB_Init(void) {
  FastLED.addLeds<WS2812, RGB_PIN, GRB>(leds, NUM_LEDS);
}

// 带亮度控制的RGB灯控制函数
void RGB_Control(uchar cresset, uchar red, uchar green, uchar blue, float brightness) {
  leds[cresset] = CRGB(
    red * brightness,
    green * brightness,
    blue * brightness
  );
  FastLED.show();
}

// 生成随机颜色
Color getRandomColor() {
  Color c;
  // 随机选择一个主色调
  switch(random(6)) {
    case 0:
      c = {255, 0, 0};    // 红
      break;
    case 1:
      c = {0, 255, 0};    // 绿
      break;
    case 2:
      c = {0, 0, 255};    // 蓝
      break;
    case 3:
      c = {255, 255, 0};  // 黄
      break;
    case 4:
      c = {255, 0, 255};  // 紫
      break;
    case 5:
      c = {0, 255, 255};  // 青
      break;
  }
  return c;
}

void setup() {
  Serial.begin(115200);
  RGB_Init();
  randomSeed(analogRead(0)); // 初始化随机数生成器
  
  // 设置输出引脚
  for(int i = 3; i <= 13; i++) {
    pinMode(i, OUTPUT);
    digitalWrite(i, LOW);
  }
}

void loop() {
  // 为每个LED生成随机颜色
  Color colors[NUM_LEDS];
  for(int i = 0; i < NUM_LEDS; i++) {
    colors[i] = getRandomColor();
  }
  
  // 呼吸效果循环
  for(int step = 0; step < BREATH_STEPS; step++) {
    // 更新呼吸亮度
    breathBrightness += breathDelta * (1.0 / BREATH_STEPS);
    
    // 在到达最大或最小亮度时改变方向
    if(breathBrightness >= 1.0) {
      breathBrightness = 1.0;
      breathDelta = -1.0;
    } else if(breathBrightness <= 0.0) {
      breathBrightness = 0.0;
      breathDelta = 1.0;
      // 在完成一次呼吸周期后重新生成随机颜色
      for(int i = 0; i < NUM_LEDS; i++) {
        colors[i] = getRandomColor();
      }
    }
    
    // 更新所有LED
    for(int i = 0; i < NUM_LEDS; i++) {
      RGB_Control(i, 
        colors[i].r, 
        colors[i].g, 
        colors[i].b, 
        breathBrightness
      );
    }
    
    delay(BREATH_DELAY);
  }
  
  // 随机延时,增加闪烁的不规则性
  delay(random(100, 500));
}

让我们开始上传程序:首先在IDE中选择正确的主板型号,然后将程序代码复制粘贴进去。

将测试程序上传开发板

找到界面左上角的上传按钮(看起来像个“前进”箭头),点击它就会开始编译和上传程序到开发板。

LED 灯光测试

等待几秒钟后,你就能看到开发板上的LED开始闪烁变幻,宛如 “闪亮的灯球”。

完成调度程序编译上传

当我们确认开发板能正常工作、编译和运行测试程序后,就可以用相同的方式上传我们真正需要的风扇调度程序了。将上文“接管设备风扇调度策略”中的代码复制粘贴到 IDE 中,进行相同的编译、上传动作后,这样我们就完成了第一版惠普服务器风扇控制程序的部署。

硬件部分:最终部件制作

我们先来制作主板侧的风扇连接线。根据前文中开发者 Max-Sum 提供的风扇接脚定义(用于控制风扇调度)和开发板官方提供的接口定义,将定制好的接线插入 2x3 PHD2.0 连接器中:

开发板接口定义

开发板和连接器的对应关系如下:

  • 1 号位:红色线,连接开发板 VIN
  • 2 号位:预留空位
  • 3 号位:蓝色线,连接开发板 A1
  • 4 号位:黄色线,连接开发板 A2
  • 5、6 号位:接地线,连接开发板 GND(其中 5 号位需要制作特殊的 “Loop” 线与 6 号位连通)

制作主板风扇连接线

完成后和原厂版本对比,除了特殊的 “Loop” 线外,基本一致。

那么开发板如何与风扇连接呢?还记得我们购买的猫扇包装里就配备了需要的材料。

其他两根线的制作

之前提到的猫扇配件中包含延长线、减速线和一转二线。由于我们需要连接两个风扇,可以直接使用一转二线。具体步骤是:

  • 将一转二线风扇接口对侧的电源接口剪掉,剥出金属线待焊接
  • 把延长线剪下来,取其外层热缩管,剪成合适长度套在新制作的线材上,起到保护作用

接线方案相对简单,不过因为用了成品线,颜色会和之前有些不同:

  • 3PIN 线:蓝色接开发板 D1,黄色接 VIN
  • 4PIN 线:蓝色接开发板 D2,黄色接 VIN,绿色接 D4
  • 两根线的黑色都接开发板 GND

将线和开发板进行焊接组装

最终完成后的开发板造型别致,像是游戏里一个有着三条长腿的生物。

将开发板和风扇、主板连接

接下来的步骤是拆除原装风扇,安装上猫扇,并用我们事先焊接好的开发板将 HP 主板和两个风扇连接起来。

iLO 散热风扇处理细节

我选用的 iLO 降温风扇采用上透风下密封设计,底部是带线塑料结构,顶部是金属。为避免短路,我用绝缘胶带重新包裹了风扇,并用 3M 双面胶垫高固定。未来可能会进一步优化,提升底部芯片的散热效果,不过还是先使用一段时间再说。

iLO 散热器装配

与 Chiphell 原帖子中的方案不同,我没有选择朝向 CPU 散热器的风向设计。这是因为在 Gen10 Plus v2 机箱中,PCIe 扩展插槽的垂直挡板会阻挡气流。特别是在安装了双万兆网卡之后,合上机箱后能够保持的有效风道其实就只剩下从机箱前部到后部的这条直通路径了。

虽然散热器自带的硅胶垫具有一定粘性,但为了稳妥起见,我还是额外使用了 3M 胶带来固定。在进风口位置,我安装了一个主动散热风扇,将冷空气直接吹向大面积的散热器表面。散热器的末端正对着机箱背部,配合机箱风扇的气流方向,理论上能够有效地将热量导出机箱。不过,这个散热方案的实际效果,还需要通过更长期的使用来验证和优化。

到这里,我们的硬件静音改造就告一段落了,也解决了开篇提到的日常安静使用需求。

接下来,让我们回到万兆网络实践这个话题。

万兆网络实践

在上一篇《家用网络升级实践:低成本实现局部万兆(一)》的“Gen 10 Plus 启用万兆网络”部分,我们将万兆网卡安装到了 Gen10 Plus,系统直接识别了这张网卡(免驱动)。

iLO 和 ESXi 默认识别双万兆网卡

ESXi 系统内识别情况

在 ESXi 系统中,我们可以看到系统正确识别了网卡的速率。

支持切换硬件直通

在 ESXi 系统中,我们还可以设置硬件直通,分配给某个操作系统使用。

Gen10 Plus 机箱,漂亮的网络灯光跃动

如果仅仅是给设备增加两个网口来实现万兆组网,未免太过“儿戏”了。毕竟还要考虑后续增加其他设备的问题,而且不是所有设备都支持光口的万兆连接。

计划升级网络接入的设备

还记得上一篇文章中提到的那款支持双万兆网口、4个2.5G网口的交换机吗?

再次入手便宜的螃蟹芯片交换机

上次购买之后,正好我又获得了一些购物补贴,就再入手了一台这个待机功耗仅 1 瓦的设备吧。

计划升级网络接入的设备

将新交换机放在设备旁边,看起来还真有点“父与子”的感觉。

计划升级网络接入的设备

我最终选择的网络拓扑方案是:从主交换机引出一根10G线连接到 Gen10 Plus,Gen10 Plus 的另一个 10G 光口连接到交换机,这台交换机的另一个光口则连接下一台交换机或其他光口设备。这样不仅满足了 10G 连接需求,还能顺便完成多个设备的 2.5G 网络接入,即便所有设备同时和 Gen10 Plus 进行数据交互,也不会出现数据阻塞。

在保证安静运行的前提下,这是目前发热最少、成本最低的方案。交换机无需风扇,也不会产生多余的热量(相比之下,多口交换机还是会有一定发热和噪音)。

交换机电源略大

当然,这个方案也存在一些不足。多个交换机就需要多个电源接口,而且因为是经济型交换机,电源适配器没有特别注重体积和美感设计,三个适配器就占用了整个插板的空间,造成了一定的插座资源(插口)浪费。

最后

好了,写到这里,我们的第二部分的改造就完成了。

我还有一些东西在陆续的物流过程中,关于文章中提到的改进,我想在后续的文章中继续补充和展开。包括并不仅限于更低成本、更简单的 DIY 配件验证,对于这台设备和网络中其他设备的可靠性保障、风扇代理开源程序的持续优化等等。

我们下篇文章再见!

–EOF


我们有一个小小的折腾群,里面聚集了一些喜欢折腾、彼此坦诚相待的小伙伴。

我们在里面会一起聊聊软硬件、HomeLab、编程上、生活里以及职场中的一些问题,偶尔也在群里不定期的分享一些技术资料。

关于交友的标准,请参考下面的文章:

致新朋友:为生活投票,不断寻找更好的朋友

当然,通过下面这篇文章添加好友时,请备注实名和公司或学校、注明来源和目的,珍惜彼此的时间 😄

关于折腾群入群的那些事


如果你觉得内容还算实用,欢迎点赞分享给你的朋友,在此谢过。

如果你想更快的看到后续内容的更新,请戳 “点赞”、“分享”、“收藏” ,这些免费的鼓励将会影响后续有关内容的更新速度。


本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2025年01月05日
统计字数: 19422字
阅读时间: 39分钟阅读
本文链接: https://soulteary.com/2025/01/05/homelab-10-gigabit-network-practice-silent-transformation-of-compact-home-servers.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948751.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【STC库函数】Compare比较器的使用

如果我们需要比较两个点的电压&#xff0c;当A点高于B点的时候我们做一个操作&#xff0c;当B点高于A点的时候做另一个操作。 我们除了加一个运放或者比较器&#xff0c;还可以直接使用STC内部的一个比较器。 正极输入端可以是P37、P50、P51&#xff0c;或者从ADC的十六个通道…

东京大学联合Adobe提出基于指令的图像编辑模型InstructMove,可通过观察视频中的动作来实现基于指令的图像编辑。

东京大学联合Adobe提出的InstructMove是一种基于指令的图像编辑模型&#xff0c;使用多模态 LLM 生成的指令对视频中的帧对进行训练。该模型擅长非刚性编辑&#xff0c;例如调整主体姿势、表情和改变视点&#xff0c;同时保持内容一致性。此外&#xff0c;该方法通过集成蒙版、…

海思Linux(一)-Hi3516CV610的开发-ubuntu22_04环境创建

目 录 前 言 一、芯片介绍 二、环境搭建 2.1 前提准备 2.2 虚拟机创建 2.3 ubuntu环境安装 2.4 基础ubuntu环境搭建 2.5 使用MobaXterm登陆ubuntu 前 言 芯片选型:HI3516CV610 选择的开发板是&#xff1a;酷电科技馆的Hi3516CV610-MINI开发板 上一篇文章&#xf…

vue elementUI Plus实现拖拽流程图,不引入插件,纯手写实现。

vue elementUI Plus实现拖拽流程图&#xff0c;不引入插件&#xff0c;纯手写实现。 1.设计思路&#xff1a;2.设计细节3.详细代码实现 1.设计思路&#xff1a; 左侧button列表是要拖拽的组件。中间是拖拽后的流程图。右侧是拖拽后的数据列表。 我们拖动左侧组件放入中间的流…

Spring boot 项目 Spring 注入 代理 并支持 代理对象使用 @Autowired 去调用其他服务

文章目录 类定义与依赖注入方法解析createCglibProxy注意事项setApplicationContext 方法createCglibProxy 方法 类定义与依赖注入 Service: 标识这是一个 Spring 管理的服务类。ApplicationContextAware: 实现该接口允许你在类中获取 ApplicationContext 对象&#xff0c;从而…

应用程序越权漏洞安全测试总结体会

应用程序越权漏洞安全测试总结体会 一、 越权漏洞简介 越权漏洞顾名思议超越了自身的权限去访问一些资源&#xff0c;在OWASP TOP10 2021中归类为A01&#xff1a;Broken Access Control&#xff0c;其本质原因为对访问用户的权限未进行校验或者校验不严谨。在一个特定的系统或…

JAVA:Spring Boot 集成 Quartz 实现分布式任务的技术指南

1、简述 Quartz 是一个强大的任务调度框架&#xff0c;允许开发者在应用程序中定义和执行定时任务。在 Spring Boot 中集成 Quartz&#xff0c;可以轻松实现任务的调度、管理、暂停和恢复等功能。在分布式系统中&#xff0c;Quartz 也支持集群化的任务调度&#xff0c;确保任务…

改善 Kibana 中的 ES|QL 编辑器体验

作者&#xff1a;来自 Elastic Marco Liberati 随着新的 ES|QL 语言正式发布&#xff0c;Kibana 中开发了一种新的编辑器体验&#xff0c;以帮助用户编写更快、更好的查询。实时验证、改进的自动完成和快速修复等功能将简化 ES|QL 体验。 我们将介绍改进 Kibana 中 ES|QL 编辑器…

【深度学习入门_基础篇】线性代数本质

开坑本部分主要为基础知识复习&#xff0c;新开坑中&#xff0c;学习记录自用。 学习目标&#xff1a; 熟悉向量、线性组合、线性变换、基变换、矩阵运算、逆函数、秩、列空间、零空间、范式、特征指、特征向量等含义与应用。 强烈推荐此视频&#xff1a; 【官方双语/合集】…

【SpringBoot】当 @PathVariable 遇到 /,如何处理

1. 问题复现 在解析一个 URL 时&#xff0c;我们经常会使用 PathVariable 这个注解。例如我们会经常见到如下风格的代码&#xff1a; RestController Slf4j public class HelloWorldController {RequestMapping(path "/hi1/{name}", method RequestMethod.GET)publ…

VBA(Visual Basic for Applications)编程|excel|一系列网址或文件路径快速转换为可点击的超链接

很多时候&#xff0c;我们需要把导入的数据某一列转换成超链接&#xff0c;比如URL形式的列。 那么&#xff0c;大批量的情况下&#xff0c;无疑一个个手动点击是非常愚蠢的办法&#xff0c;这个时候我们就需要VBA编程来编写宏&#xff0c;通过编写宏来简化这些手动操作并不现…

小程序开发全解析 快速构建高效应用的核心指南

内容概要 小程序开发是当前数字世界中炙手可热的领域&#xff0c;吸引了无数开发者和企业的关注。随着技术的进步&#xff0c;小程序成为了提升用户体验、增强品牌曝光以及增加客户互动的重要工具。了解小程序的基本概念&#xff0c;就像是打开了一扇通往新世界的大门。 在这…

SQL—Group_Concat函数用法详解

SQL—Group_Concat函数用法详解 在LC遇见的一道很有趣的SQL题&#xff0c;有用到这个函数&#xff0c;就借这道题抛砖引玉&#xff0c;在此讲解一下group_concat函数的用法。&#x1f923; GROUP_CONCAT([DISTINCT] expression [ORDER BY expression] [SEPARATOR separator])…

Edge Scdn的应用场景有哪些?

酷盾安全Edge Scdn 具备强大的安全防护能力&#xff0c;通过多层防御机制&#xff0c;如防火墙、DDoS 攻击防护、入侵检测和防御、数据加密等&#xff0c;有效抵御各种网络攻击&#xff0c;包括 DDoS 攻击、CC 攻击、SQL 注入攻击、XSS 跨站脚本攻击等&#xff0c;保障网站和应…

流光效果

1、流光效果是什么 在 Unity Shader 中的流光效果是一种动态的视觉效果&#xff0c;通常用于给材质增加一种闪光或光线移动的效果&#xff0c;使物体表面看起来像是有光在流动。这种效果常用于武器光效、能量护盾、传送门等等&#xff0c;可以让物体看起来更加生动富有科技感 …

滑动窗口——串联所有单词的子串

一.题目描述 30. 串联所有单词的子串 - 力扣&#xff08;LeetCode&#xff09; 二.题目解析 题目前提&#xff1a;s是一个字符串&#xff0c;words是一个字符串数组&#xff0c;里面所有的字符串的长度都是相等的。 题目要求&#xff1a;找到s中的一段连续的子串&#xff0…

【微软,模型规模】模型参数规模泄露:理解大型语言模型的参数量级

模型参数规模泄露&#xff1a;理解大型语言模型的参数量级 关键词&#xff1a; #大型语言模型 Large Language Model #参数规模 Parameter Scale #GPT-4o #GPT-4o-mini #Claude 3.5 Sonnet 具体实例与推演 近日&#xff0c;微软在一篇医学相关论文中意外泄露了OpenAI及Claud…

SpringBoot Maven 项目 pom 中的 plugin 插件用法整理

把 SpringBoot Maven 项目打包成 jar 文件时&#xff0c;我们通常用到 spring-boot-maven-plugin 插件。 前面也介绍过&#xff0c;在 spring-boot-starter-parent POM 和 spring-boot-starter POM 中都有插件的管理&#xff0c;现在我们就撸一把构建元素中插件的用法。 一、…

UE5AI感知组件

官方解释&#xff1a; AI感知系统为Pawn提供了一种从环境中接收数据的方式&#xff0c;例如噪音的来源、AI是否遭到破坏、或AI是否看到了什么。 AI感知组件&#xff08;AIPerception Component&#xff09;是用于实现游戏中的非玩家角色&#xff08;NPC&#xff09;对环境和其…

【数据仓库】hive on Tez配置

hive on Tez 搭建 前提是hive4.0hadoop3.2.2数仓已搭建完成&#xff0c;现在只是更换其执行引擎 为Tez。搭建可参考【数据仓库】hive hadoop数仓搭建实践文章。 Tez 下载 下载地址 https://archive.apache.org/dist/tez/ 官网地址 https://tez.apache.org/releases/apac…