0 更新记录

  • 2024-07-11:初始版本
  • 2024-07-15:添加TL;DR,删除了文中的长代码,完整代码上传到 Github

最近要用到吃灰已久的树莓派 4B,发现原来的 TF 卡已经损坏。更换 TF 卡写入最新 64bit 的 RaspiOS-Lite,配置时发现之前使用的温控风扇的程序文件也找不到了,索性直接重新整理一下。

买外壳时自带了一个风扇,是普通的不支持 PWM 调速的 5V 风扇,只需要用程序根据 CPU 温度控制风扇启停即可。

TL;DR

完整代码已经发布在 Github 仓库pi_auto_fan

控制程序及设置控制程序自启动的步骤略繁琐,使用Makefile把需要操作的步骤打包成了installuninstall,直接运行sudo make insatll即可完成编译、创建系统服务、启动服务、设置自启动,注意必须使用sudo,因为创建系统服务需要使用root权限。

运行sudo make install之前仍然需要按照文章中编译测试部分的步骤安装依赖。

默认使用GPIO 14作为控制 IO(fan_pin=14),上限阈值为 60°(temp_high=60.0),下限阈值为 50°(temp_low=50.0),如需修改需要在文件auto_fan.c中修改对应代码。

1
2
3
4
5
6
7
8
git clone https://github.com/gsh1209/pi_auto_fan.git
cd pi_auto_fan
# 仅编译,无需sudo
make
# 安装
sudo make install
# 卸载
sudo make uninstall

查看 GPIO 状态的一些命令

1
2
raspi-gpio get | grep "GPIO 14"
gpio readall

1 控制电路

找到了之前画的驱动电路,使用了两个三极管(型号均为 S8050)级联来进一步减轻 GPIO 输出电流的负担。电路很简单,不过当时还在嘉立创白嫖了一块 PCB,PCB 源文件已经找不到了,只能找到一个布局的图片。

风扇驱动电路图
风扇驱动电路图
风扇驱动 PCB 布局
风扇驱动 PCB 布局

这个电路是经过长时间验证的,曾经用树莓派跑了大半年的局域网打印机共享服务器和 SMB 文件共享服务器,使用的散热方案就是这套电路,长时间工作时三极管也不会发热。

风扇控制 PCB
风扇控制 PCB
控制板与树莓派的连接
控制板与树莓派的连接

树莓派上的控制 IO 使用的是GPIO 14,紧挨着 5V 电源和 GND,可以方便的使用 3PIN 的杜邦线进行连接,不过 GPIO 14 其实也是 UART 串口的TXD,如果需要用到串口可以使用其他 IO 作为风扇控制 IO。

树莓派 4B 的 GPIO 定义
树莓派 4B 的 GPIO 定义
风扇控制使用的 GPIO
风扇控制使用的 GPIO

GPIO 定义图片来自树莓派文档

2 控制程序

之前的程序使用的是 Python 来控制,但是由于控制程序需要长期运行,Python 脚本占用的系统资源更高一些,尤其对于我的 1GB 内存版本有些不友好,这次使用 C 语言来写控制程序。

2.1 CPU 温度读取

树莓派系统中提供了可供读取的文件来获取 CPU 温度,文件位于/sys/class/thermal/thermal_zone0/temp,文件内容是整数形式的 CPU 温度,包含三位小数。如果需要将温度输出到终端显示需要将获取的数值除以 1000 进行转换。

1
printf("CPU temperature: %.2f°C\n", atof(cpu_temp_str) / 1000.0);

这里的温控风扇的程序运行在后台,不需要向终端显示温度,为了简单我暂时也不需要打印 log,所以这里的获取温度的函数不做转换直接返回文件内容转换为整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 从文件获取 CPU 温度
* 返回文件内容,不转换为浮点数
*/
int getCpuTemp() {
FILE *file;
char cpu_temp_file[] = "/sys/class/thermal/thermal_zone0/temp";
char cpu_temp_str[10];
file = fopen(cpu_temp_file, "r");
if(NULL == file) {
perror("fopen");
return 1;
}
if(NULL == fgets(cpu_temp_str, sizeof(cpu_temp_str), file)) {
fprintf(stderr, "read error!\n");
fclose(file);
return 1;
}
fclose(file);
return atoi(cpu_temp_str);
}

2.2 启停控制逻辑

控制程序每 2s 读取一次温度,和温度阈值进行比较,控制风扇的启停。为了防止风扇在阈值附近反复的开启和关闭,控制程序设置了上限阈值temp_high和下限阈值temp_low两个阈值,当温度超过上限时风扇启动,当温度低于下限时风扇停止。程序中设置上下限阈值为 60℃和 50℃,可根据环境温度等情况按需调整。

上下限阈值是直接写到程序代码中的,如果需要修改则要重新编译,合理的做法是作为参数传入或者使用其他方式配置,包括用于控制的 GPIO 也是这样,但是因为懒暂时就不写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
float temp_high = 60.0;
float temp_low = 50.0;
typedef enum { ON, OFF } fan_status;
int main(int argc, char *argv[]) {
initFanGpio();
fan_status status = OFF;
while(1) {
int cpu_temp = getCpuTemp();
if(OFF == status) {
if(cpu_temp > (int)(temp_high * 1000)) {
digitalWrite(fan_pin, HIGH);
status = ON;
}
} else {
if(cpu_temp < (int)(temp_low * 1000)) {
digitalWrite(fan_pin, LOW);
status = OFF;
}
}
sleep(2);
}
return 0;
}

2.3 编译测试

在编译程序之前,需要确保已经安装需要的依赖

1
sudo apt update && sudo apt install build-essential git -y

还需要安装 GPIO 控制库 WiringPi,我使用源码编译安装,也可以直接下载Releases中编译好的二进制包安装。

1
2
3
git clone https://github.com/WiringPi/WiringPi.git
cd WiringPi
./build

使用gpio readall命令来验证WiringPi是否安装成功。

编译时需要添加-l参数链接WiringPi库:

1
gcc auto_fan.c -o auto_fan -lwiringPi

使用sysbench命令让 CPU 快速升温,测试风扇能否按照预期工作,sysbench需要安装。

1
2
sudo apt update && sudo apt install sysbench -y
sysbench --num-threads=4 --test=cpu --cpu-max-prime=200000 run

测试确保控制程序auto_fan功能正常后,按照 Linux 系统中文件的组织习惯将其复制到/usr/bin目录。

1
sudo cp auto_fan /usr/bin

3 设置控制程序自动启动

在树莓派中设置程序自动启动和一般 Linux 系统中的方法类似,如使用定时任务crontab@reboot,将运行程序的命令写入rc.local等,这里使用systemd自定义系统服务的方法来实现自动启动控制程序。

3.1 创建系统服务

systemctl 的服务配置文件存放在目录/usr/lib/systemd中,有系统 system 和用户 user 之分,需要开机不登录就能运行的程序,存放在系统服务中即/usr/lib/systemd/system目录下。风扇控制程序应当创建为系统服务,在/usr/lib/systemd/system目录新建一个auto_fan.service配置文件,需要使用sudo,内容如下:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Auto Fan Service by gsh1209

[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/auto_fan &
KillMode=mixed

[Install]
WantedBy=multi-user.target

其中关键的配置项有ExecStart=/usr/bin/auto_fan &KillMode=mixed

  • ExecStart表示服务启动时运行的命令,这里在后台(使用参数&)运行编译后的风扇控制程序,参考man systemd.service
  • KillMode表示服务终止时的操作,设置为mixed表示主进程将收到SIGTERM信号,子进程收到SIGKILL信号,参考man systemd.kill

设置KillMode的目的是在服务停止时需要恢复 GPIO 的状态,尤其是如果此时风扇已被打开,服务停止风扇应当被立即关闭,这里通过在程序中接收SIGTERM信号,并自定义信号处理函数的方式实现服务停止时,程序退出前恢复 GPIO 为默认状态。

还可以使用ExecStop=指定一个服务停止时运行的命令来实现恢复 GPIO 状态这个功能,命令既可以使用系统提供的raspi-gpio,也可以使用由WiringPi库提供的gpio命令,还可以使用 C 语言单独写一个实现释放 GPIO 的程序。

根据树莓派 4B 的文档BCM2711 的 Datasheet(GPIO_PUP_PDN_CNTRL_REG0 Register),树莓派 GPIO 14 复位后的默认状态为:输入,内部下拉。

3.2 设置服务开机自动启动

使用命令设置服务为enable即可开机自启动

1
sudo systemctl enable auto_fan.service

3.3 systemctl 常用命令

  • 启动、停止、重启服务
1
sudo systemctl start/stop/restart auto_fan.service
  • 开启、关闭开机自启
1
sudo systemctl enable/disable auto_fan.service
  • 查看运行状态
1
sudo systemctl status auto_fan.service
  • 重载配置文件(服务启动时修改了 service 文件,需要重载配置)
1
sudo systemctl daemon-reload

4 其他

最后对比了一下 Python 版本和 C 版本的程序,在 CPU 占用上没什么差别。在内存占用上的差距也没有想象中那么大:

1
2
3
4
5
6
# Python程序的pid 3669
pi@raspberrypi:~ $ cat /proc/3669/status | grep VmRSS
VmRSS: 7680 kB
# C程序的pid 3561
pi@raspberrypi:~ $ cat /proc/3561/status | grep VmRSS
VmRSS: 1408 kB

使用的 Python 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import time
import RPi.GPIO as GPIO


def get_cpu_temp():
with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
return float(f.read()) / 1000


fan_pin = 14
temp_high = 60.0
temp_low = 50.0

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(fan_pin, GPIO.OUT, initial=GPIO.LOW)
is_fan_off = True

try:
while True:
temp = get_cpu_temp()
if is_fan_off == True:
if temp > temp_high:
GPIO.output(fan_pin, GPIO.HIGH)
is_fan_off = False
else:
if temp < temp_low:
GPIO.output(fan_pin, GPIO.LOW)
is_fan_off = True
time.sleep(5.0)
except:
pass

GPIO.cleanup(fan_pin)

本站由 @gsh1209 使用 Stellar 主题创建
Copyright © 2023 - BG3LNT.XYZ
Favicon图标来自 @ChenCJ
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处
正在计算运行时间...

蒙ICP备2022000455号-2