用刚学的 PCB 知识做一个优雅的树莓派风扇控制板
<blockquote>
<p>首先我不是EE(电子电路工程师),所以对电路一窍不通,但人就是这样,越菜越爱玩。所以如果你是EE的话,不要被我的电路图蠢到(不要骂我)。</p>
</blockquote>
<p>我的家里就好几个树莓派,一些是我大学的时候做小任务的低功耗服务器用,另外一些是工作的时候,自己测试一些硬件用,跑一些边缘服务之类的。</p>
<h2 id="树莓派旧风扇长期通电运行报废"><a href="#树莓派旧风扇长期通电运行报废" class="headerlink" title="树莓派旧风扇长期通电运行报废"></a>树莓派旧风扇长期通电运行报废</h2><p>我大学期间给树莓派 3B 配备的风扇在通电时间超过三年之后还是在最近寿终正寝了(作为一个4块钱的风扇而言,已经很抗造了),于是我就在想,为什么不做一个根据 CPU 温度动态控制风扇转速的模块,来延长风扇的寿命呢?说干就干。</p>
<p>众所周知,树莓派最新的 5B 系列已经有了成熟的风扇控制接口了。</p>
<p><img src="https://halo.gadore.top/upload/%E6%A0%91%E8%8E%93%E6%B4%BE5.webp"></p>
<p>图片来自:树莓派官网</p>
<h2 id="网络上风扇开关电路简单明确,但不够优雅简洁"><a href="#网络上风扇开关电路简单明确,但不够优雅简洁" class="headerlink" title="网络上风扇开关电路简单明确,但不够优雅简洁"></a>网络上风扇开关电路简单明确,但不够优雅简洁</h2><p>但我手里的好几个 3B 和 4B 这两个系列都没有呀,我也想要一个更加优雅的、模块化的接口,而不是临时的像这种:</p>
<p>基础原理图,来自 shumeipai.nxez.com</p>
<p><img src="https://halo.gadore.top/upload/20170713170656938-0.jpg"></p>
<p><img src="https://halo.gadore.top/upload/CSDN_luzze__123.jpeg" alt="CSDNluzze123jpeg"></p>
<p>图片来自:CSDN 用户 luzze_123</p>
<p><img src="https://halo.gadore.top/upload/CSDN_qq_40251961.jpeg"></p>
<p>图片来自:CSDN 用户 qq_40251961</p>
<h2 id="PCB-速成,嘉立创启动!"><a href="#PCB-速成,嘉立创启动!" class="headerlink" title="PCB 速成,嘉立创启动!"></a>PCB 速成,嘉立创启动!</h2><p>但我不会画电路板,更不了解各种电子电路相关的知识,只好去折磨 AI 了 😄。</p>
<p>但其实每个人都是有一定的电路基础的,正负极、开关。所以我选择了更加速成的方式:<br><a href="https://www.bilibili.com/video/BV1J24y1Z7cY/"><strong>【保姆级】二十分钟零基础PCB绘制打样一条龙教程(立创EDA专业版)</strong></a></p>
<p>只需要按照这个视频教程,你就可以在半小时左右学会最基础的 PCB 板的绘制技巧,我也是顺利地完成了第一版绘制</p>
<h3 id="第一版电路图"><a href="#第一版电路图" class="headerlink" title="第一版电路图"></a>第一版电路图</h3><p><img src="https://halo.gadore.top/upload/iShot_2025-10-11_14.46.51.png"></p>
<p><img src="https://halo.gadore.top/upload/iShot_2025-10-11_14.47.15.png"></p>
<p><img src="https://halo.gadore.top/upload/iShot_2025-10-11_14.47.33.png"> </p>
<p><img src="https://halo.gadore.top/upload/iShot_2025-10-16_19.24.08.png"></p>
<p>如果你是比较懂电路的话,你肯定已经看到问题所在了,没错,我把三极管放在了 5V 正极了,导致板子到手后风扇的转速非常慢,有时候甚至转不起来,即使已经通过 GPIO 把电路导通了,风扇还是不转或者转的非常慢,我就不瞎分析了,放出 AI 的分析:</p>
<blockquote>
<p><strong>把 8050 放在正极(高侧)当“开关”用了</strong> —— 8050 是 NPN 晶体管,做高侧开关时它会工作在发射跟随(emitter-follower)模式,发射极电压 ≈ 基极电压 − 0.7V。GPIO 给的是 3.3V,发射极最多 ~2.6V,风扇的电源是 5V → 风扇只拿到 ~2.6V,自然转得很慢。</p>
</blockquote>
<p>我理解下来就是:三极管放正极会对电压电流有较大损耗,导致风扇工作所需功率不足。</p>
<h3 id="第二版电路图"><a href="#第二版电路图" class="headerlink" title="第二版电路图"></a>第二版电路图</h3><p>于是重新去嘉立创领券,重新打板,重新焊接电路,重新测试,可以了!!!</p>
<p><img src="https://halo.gadore.top/upload/%E6%88%AA%E5%B1%8F2025-10-16%2017.53.58.png"></p>
<p>再配合 Python 脚本,和亿点点配置,一个全自动的风扇控制模块就做好了!</p>
<p><img src="https://halo.gadore.top/upload/IMG_0568.jpg"></p>
<p>安装效果图</p>
<p>模块大小示意(对比 Type-C 接口)</p>
<p><img src="https://halo.gadore.top/upload/IMG_0569.jpg"></p>
<p>总花销:</p>
<ul>
<li><p>买电子元器件(S8050、排针、排母):9元</p>
</li>
<li><p>电烙铁基础套装(松香、焊锡丝、锡棉,支架)36.9元</p>
</li>
</ul>
<p>然而这一趟折腾下来,我发现花销已经足够我买好多小风扇了,好在电烙铁可以反复使用,电子元器件也只消耗了一小部份,以后折腾其他东西也能用~</p>
<h2 id="解除-GPIO-14-的串口占用,用来当做开关信号"><a href="#解除-GPIO-14-的串口占用,用来当做开关信号" class="headerlink" title="解除 GPIO 14 的串口占用,用来当做开关信号"></a>解除 GPIO 14 的串口占用,用来当做开关信号</h2><p>当前这个模块占地较小,但也有代价,它使用的是GPIO 14进行电路开关控制,GPIO 14 在树莓派上原本是被串口通信功能占用,如果你跟我一样不使用串口功能,可以这样关闭掉:<br><code>sudo raspi-config</code></p>
<p>Interface Options → Serial Port</p>
<ul>
<li><p><code>Would you like a login shell to be accessible over serial?</code> → <strong>No</strong></p>
</li>
<li><p><code>Would you like the serial port hardware to be enabled?</code> → <strong>No</strong></p>
</li>
</ul>
<p>完成后退出并选择 <strong>Finish</strong>,允许它重启。</p>
<p>重启后:</p>
<p>sudo vim /boot/firmware/config.txt</p>
<p>确认或添加以下内容(顺序重要):</p>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 禁用蓝牙,释放 UART</span></span><br><span class="line"><span class="attr">dtoverlay</span>=disable-bt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 确保主 UART 关闭</span></span><br><span class="line"><span class="attr">enable_uart</span>=<span class="number">0</span></span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl <span class="built_in">disable</span> hciuart.service</span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl mask hciuart.service</span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo reboot</span><br></pre></td></tr></table></figure>
<p>验证 GPIO 14 状态:</p>
<p><code>pinctrl get 14</code></p>
<p>应显示:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GPIO 14: level=0 func=INPUT</span><br></pre></td></tr></table></figure>
<p>(或类似 <code>function INPUT</code>),<strong>没有出现</strong> <code>ALT0</code> <strong>/</strong> <code>TXD0</code> <strong>/</strong> <code>RXD0</code>。</p>
<p>此时即可用作普通 GPIO。</p>
<h2 id="如果你需要串口功能,则可以使用另一种方案代替"><a href="#如果你需要串口功能,则可以使用另一种方案代替" class="headerlink" title="如果你需要串口功能,则可以使用另一种方案代替"></a>如果你需要串口功能,则可以使用另一种方案代替</h2><p>这是另一个版本,它不占用串口功能,使用 GPIO 4 进行开关控制,最终组件也会相对应长一些</p>
<p><img src="https://halo.gadore.top/upload/%E6%88%AA%E5%B1%8F2025-10-16%2017.57.17.png"></p>
<p><img src="https://halo.gadore.top/upload/%E6%88%AA%E5%B1%8F2025-10-16%2018.02.05.png"></p>
<p><img src="https://halo.gadore.top/upload/iShot_2025-10-16_19.41.25.png"></p>
<p><img src="https://halo.gadore.top/upload/IMG_0571.png"></p>
<h2 id="风扇控制脚本"><a href="#风扇控制脚本" class="headerlink" title="风扇控制脚本"></a>风扇控制脚本</h2><p>Python 脚本:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> RPi.GPIO <span class="keyword">as</span> GPIO</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> signal</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 控制风扇的GPIO(BCM编号)</span></span><br><span class="line">FAN_GPIO = <span class="number">14</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 温度阈值(摄氏度)</span></span><br><span class="line">HIGH_TEMP = <span class="number">45.0</span> <span class="comment"># 高于此温度开启风扇</span></span><br><span class="line">LOW_TEMP = <span class="number">42.0</span> <span class="comment"># 低于此温度关闭风扇</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查间隔(秒)</span></span><br><span class="line">CHECK_INTERVAL = <span class="number">5</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置GPIO模式</span></span><br><span class="line">GPIO.setmode(GPIO.BCM)</span><br><span class="line">GPIO.setup(FAN_GPIO, GPIO.OUT)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始状态关闭风扇</span></span><br><span class="line">GPIO.output(FAN_GPIO, GPIO.HIGH)</span><br><span class="line">fan_status = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取CPU温度的函数</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_cpu_temperature</span>():</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">'/sys/class/thermal/thermal_zone0/temp'</span>, <span class="string">'r'</span>) <span class="keyword">as</span> f:</span><br><span class="line"> temp = <span class="built_in">float</span>(f.read()) / <span class="number">1000.0</span></span><br><span class="line"> <span class="keyword">return</span> temp</span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"无法读取CPU温度: <span class="subst">{e}</span>"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 控制风扇的函数</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">control_fan</span>(<span class="params">temperature</span>):</span><br><span class="line"> <span class="keyword">global</span> fan_status</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> temperature >= HIGH_TEMP <span class="keyword">and</span> <span class="keyword">not</span> fan_status:</span><br><span class="line"> GPIO.output(FAN_GPIO, GPIO.HIGH) <span class="comment"># 开启风扇</span></span><br><span class="line"> fan_status = <span class="literal">True</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"温度: <span class="subst">{temperature}</span>°C - 风扇开启"</span>)</span><br><span class="line"> <span class="keyword">elif</span> temperature <= LOW_TEMP <span class="keyword">and</span> fan_status:</span><br><span class="line"> GPIO.output(FAN_GPIO, GPIO.LOW) <span class="comment"># 关闭风扇</span></span><br><span class="line"> fan_status = <span class="literal">False</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"温度: <span class="subst">{temperature}</span>°C - 风扇关闭"</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> status = <span class="string">"开启"</span> <span class="keyword">if</span> fan_status <span class="keyword">else</span> <span class="string">"关闭"</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"温度: <span class="subst">{temperature}</span>°C - 风扇保持<span class="subst">{status}</span>"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 优雅退出处理</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">signal_handler</span>(<span class="params">sig, frame</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"\n正在关闭程序..."</span>)</span><br><span class="line"> GPIO.output(FAN_GPIO, GPIO.LOW) <span class="comment"># 确保风扇关闭</span></span><br><span class="line"> GPIO.cleanup() <span class="comment"># 清理GPIO资源</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"风扇已关闭,程序退出"</span>)</span><br><span class="line"> sys.exit(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 注册信号处理</span></span><br><span class="line">signal.signal(signal.SIGINT, signal_handler)</span><br><span class="line">signal.signal(signal.SIGTERM, signal_handler)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 主循环</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"开始监控CPU温度并控制风扇..."</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f"温度阈值: 高于<span class="subst">{HIGH_TEMP}</span>°C开启, 低于<span class="subst">{LOW_TEMP}</span>°C关闭"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"按 Ctrl+C 退出程序"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> temperature = get_cpu_temperature()</span><br><span class="line"> <span class="keyword">if</span> temperature <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> control_fan(temperature)</span><br><span class="line"> time.sleep(CHECK_INTERVAL)</span><br><span class="line"><span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line"> signal_handler(<span class="literal">None</span>, <span class="literal">None</span>)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"程序运行出错: <span class="subst">{e}</span>"</span>)</span><br><span class="line"> GPIO.output(FAN_GPIO, GPIO.LOW)</span><br><span class="line"> GPIO.cleanup()</span><br></pre></td></tr></table></figure>
<p>Python</p>
<h2 id="自动化服务配置"><a href="#自动化服务配置" class="headerlink" title="自动化服务配置"></a>自动化服务配置</h2><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line"></span><br><span class="line">Description=Fan control Service</span><br><span class="line"></span><br><span class="line">After=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line"></span><br><span class="line">Type=simple</span><br><span class="line"></span><br><span class="line">WorkingDirectory= <span class="comment"># 这里替换成python脚本所在目录</span></span><br><span class="line"></span><br><span class="line">ExecStart=/usr/bin/python3 <span class="comment"># 这里替换成python脚本的绝对路径</span></span><br><span class="line"></span><br><span class="line">Restart=always</span><br><span class="line"></span><br><span class="line">RestartSec=10</span><br><span class="line"></span><br><span class="line">StandardOutput=syslog</span><br><span class="line"></span><br><span class="line">StandardError=syslog</span><br><span class="line"></span><br><span class="line">SyslogIdentifier=fan-control</span><br><span class="line"></span><br><span class="line"><span class="comment"># 环境变量(如果需要)</span></span><br><span class="line"></span><br><span class="line">Environment=PYTHONUNBUFFERED=1</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line"></span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure>
<p>Makefile</p>
<p>把上边这个脚本保存到</p>
<p><code>/etc/systemd/system/fan-control.service</code></p>
<p>然后依次执行:</p>
<p>重新加载服务文件</p>
<p><code>sudo systemctl daemon-reload</code></p>
<p>启动风扇控制脚本服务</p>
<p><code>sudo systemctl start fan-control.service</code></p>
<p>检查运行状态</p>
<p><code>sudo systemctl status fan-control.service</code></p>
<p>设置服务开机自启动</p>
<p><code>sudo systemctl enable fan-control.service</code></p>
<p>如果发现服务启动异常,可以通过这个命令来查看异常原因</p>
<p><code>sudo journalctl -u fan-control.service -n 50</code></p>
<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Oct 16 13:23:42 raspberrypi fan-control[2004]: 温度: 44.008°C - 风扇保持关闭</span><br><span class="line">Oct 16 13:23:47 raspberrypi fan-control[2004]: 温度: 44.546°C - 风扇保持关闭</span><br><span class="line">Oct 16 13:23:52 raspberrypi fan-control[2004]: 温度: 45.084°C - 风扇开启</span><br><span class="line">Oct 16 13:23:57 raspberrypi fan-control[2004]: 温度: 45.084°C - 风扇保持开启</span><br><span class="line">Oct 16 13:24:02 raspberrypi fan-control[2004]: 温度: 44.008°C - 风扇保持开启</span><br><span class="line">Oct 16 13:24:07 raspberrypi fan-control[2004]: 温度: 43.47°C - 风扇保持开启</span><br><span class="line">Oct 16 13:24:12 raspberrypi fan-control[2004]: 温度: 43.47°C - 风扇保持开启</span><br><span class="line">Oct 16 13:24:17 raspberrypi fan-control[2004]: 温度: 42.932°C - 风扇保持开启</span><br><span class="line">Oct 16 13:24:22 raspberrypi fan-control[2004]: 温度: 41.856°C - 风扇关闭</span><br><span class="line">Oct 16 13:24:27 raspberrypi fan-control[2004]: 温度: 41.856°C - 风扇保持关闭</span><br><span class="line">Oct 16 13:24:32 raspberrypi fan-control[2004]: 温度: 41.856°C - 风扇保持关闭</span><br><span class="line">Oct 16 13:24:37 raspberrypi fan-control[2004]: 温度: 41.318°C - 风扇保持关闭</span><br></pre></td></tr></table></figure>
<h2 id="备注"><a href="#备注" class="headerlink" title="备注"></a>备注</h2><ul>
<li><p><a href="https://www.jlc.com/newOrder/#/collectCoupons?spm=JLC.MEMBER">嘉立创领券链接</a>,感谢嘉立创!😄</p>
</li>
<li><p>我跑通了这个PCB验证,也写完了这篇文章后,才发现很久之前(2021年)就有其他人做过:可以参考「<a href="https://www.quarkbook.com/?p=1194">夸克之书</a>」的文章,哎,早点儿看到就能少走很多弯路了。</p>
</li>
</ul>