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