RaspberryPiのファンを制御してみた

どもです。

以前から思っていたことがあります。
それは、

RaspberryPiのファンは、常に回し続ける必要があるのだろうか?

です。
RaspberryPiをサーバーにして、一日中動かしっぱなしにしていることがあります。
このとき、ファンも当然動かしっぱなしになるのですが、このファンが「カリカリカリカリ」音を立て、とてもうるさいです。
そこで、必要に応じてファンのON/OFFを切り替えられるようにしました。

0. 開発環境

今回の開発環境は、以下になります。

RaspberryPi
項目 内容
HW RaspberryPi3 model B
OS Raspbian GNU/Linux
buster
python Ver.3.7.3

1. CPU温度の測定

初めに、CPU温度の測定方法です。
RaspberryPi3/Pythonでは、直接CPUの温度を取得する方法(API)は提供されていません。
そこで、RaspberryPiのコマンドをpythonから使用することで、CPUの温度を取得します。
CPUの温度を取得するRaspberryPiのコマンドは、以下。

vcgencmd measure_temp

このコマンドを実行することで、以下のような形式でCPU温度が取得できます。




では、これをpythonで実行して、CPU温度を数値で取得してみます。
実際のコードは、以下になります。

import subprocess
import time
import sys
import re

class CpuMonitor:

	def __init__(self):
		'''Constructor
		'''

	def GetCpuTemperature(self):
		cpuinfos = subprocess.getoutput("vcgencmd measure_temp").split('=')
		temperature = cpuinfos[1]               #Get CPU temperature
		temperature = temperature[:-2]  #Remove unit from temperature string.

		temperature_float = float(temperature)  #Convert string to float type.

		return temperature_float

このコードについて、簡単に説明します。
pythonでRaspberryPiのコマンドを使用するためには、subprocessが必要なので、これをimportします。
また、「vcgencmd」が返す値は、「=」で接続された文字列になります。
そのため、subprocessの結果を「=」で分割して、CPUの温度部分を抜き出します。
さらに言うと、分割後のCPU温度の値は単位(℃)を示す「’C」が末尾に付与されています。
これを削除し、最終的にfloat型に変換しています。

このようにして、CPUの温度をfloat型で取得ができます。

2. 温度の判定/境界値

ファンの動作をON/OFFする境界値ですが、コレは「テキトー」に決めます。
ナゼナラ、正確な数値が分からないから。
ただ、一般的なCPUの動作温度は、40℃~80℃と言われています。
温度が上がりすぎても問題なので、CPUの温度が40℃~50℃の範囲に収まるようにしたいです。
このことから、48℃でファンの電源をONし、44℃でファンの電源をOFFすることにしました。
ただし、温度は必ずしも一定ではないので、48℃以上、あるいは44℃以下の温度が規定時間継続した場合に、電源をON/OFFするようにします。
これをpythonで実装すると、以下のコードになります。

class FanJudge:

	upper_border_over_count = 0
	lower_border_under_count = 0

	upper_border = 48.0
	lower_border = 44.0

	def __init__(self):
		print("FanJudge")

	def judge(self, temperature):
		if self.upper_border < temperature:
				self.upper_border_over_count += 1
		elif temperature < self.lower_border:
				self.lower_border_under_count += 1
		else:
				self.upper_border_over_count = 0
				self.lower_border_under_count = 0

		judge_mode = 2          # No operation
		if 10 < self.upper_border_over_count:
				judge_mode = 1  #Turn On
				self.upper_border_over_count = 10

		if 10 < self.lower_border_under_count:
				judge_mode = 0  #Turn Off
				self.lower_border_under_count = 10

				return judge_mode

コードについて、簡単に説明します。
温度は、先述のクラス/コードで取得した値を使用します。
judge()の前半で、上限境界値を越えた回数、下限境界値を下回った回数をそれぞれ更新します。
次に、この「回数」の判定を行います。
それぞれの回数が「10」を越えた場合に、ファンの動作のON/OFFを示す値を返します。
ココでは、ONする場合には「1」を、OFFする場合には「0」を、現状の動作を維持する場合には「2」を返します。

3. ファンの動作の制御

次に、ファンの動作のON/OFFを実際に制御します。
今回は、ON/OFFの状態を視覚的に分かるようにするために、以下のような回路を組み込ます。




pythonによるGPIOの制御には、RPiを使用します。
RPiは、以下のコマンドでインストールします。

sudo pip3 install rpi.gpio

なお、python3なので、インストールの際には「pip3」を使用するので、間違え無いように注意が必要です。
今回は、上図のようにGPIO2のピンにファンの「+」端子を接続します。
ファンを制御するpythonコードは、以下になります。

import RPi.GPIO as GPIO

class GPIOFan:

	gpio_pin = 0

	def __init__(self):
		self.gpio_pin = 0
		GPIO.setwarnings(False)

	def __init__(self, gpio_pin):
		self.gpio_pin = gpio_pin
		GPIO.setwarnings(False)

	def On(self):
		'''Turn on FAN.
		'''
		GPIO.setmode(GPIO.BCM)
		GPIO.setup(self.gpio_pin, GPIO.OUT)

		GPIO.output(self.gpio_pin, GPIO.HIGH)

	def Off(self):
		'''Turn off FAN.
		'''
		GPIO.setmode(GPIO.BCM)
		GPIO.setup(self.gpio_pin, GPIO.OUT)

		GPIO.output(self.gpio_pin, GPIO.LOW)

このコードを、簡単に説明します。
RPi/GPIOの「setmode」メソッドで、GPIOピンの配置のタイプを指定します。
ココでは、「BCM」を指定します。
次にsetupメソッドで、GPIOピンのIN/OUTを設定します。
次に、outputメソッドでGPIOピンのHIGH/LOWを設定します。

4. 制御のまとめ

これまでのコードをまとめて、実際にファンの動作を制御します。
制御のコードは、以下になります。

import GPIOFan
import CpuInformation
import FanJudge
import RegistData
import time

def fan_control_method():
	cpuInfo = CpuInformation.CpuMonitor()
	fan_judge = FanJudge.FanJudge()
	gpio_fan = GPIOFan.GPIOFan(2)
	regist_data = RegistData.RegistData()

	while (1):
		cpu_temp = cpuInfo.GetCpuTemperature()
		fan_action = fan_judge.judge(cpu_temp)
		if 1 == fan_action:
			gpio_fan.On()
		elif 0 == fan_action:
			gpio_fan.Off()

		regist_data.Regist(cpu_temp, fan_action)

		time.sleep(1)

if __name__ == '__main__':
	fan_control_method()

プログラムでは、CPUの温度を取得するオブジェクト、ファンの動作を決定するオブジェクト、ファンの動作を制御するオブジェクトをそれぞれ生成します。
後は、これらのオブジェクトを無限ループの中で使用して、ファンのON/OFFを制御します。

5. 制御してみた

このコードを実行した結果が、以下になります。




このように、CPUの温度が指定以上の状態が、規定時間継続した場合にファンがONになり、CPUの温度が低下し始めるのが分かります。
同様に、CPUの温度が指定以下の状態が、規定時間継続した場合にファンがOFFになり、CPUの温度が情報し始めるのが分かります。
この動作が繰り返され、CPUの温度が一定の範囲内を保つことができます。
また、ON/OFFの切り替えを繰り返すことにより、ファンが動作し続けて「カリカリカリカリ」と音が発生することを回避できるようになりました。
グラフの真ん中辺りに、動作モードが「1」の状態が維持されている時間があります。
これは、ブレッドボードのピンの接続が悪く、ファンが回転しなかったことで温度が変化せず、これにより状態の変化が発生しなかったことが原因です。
実際にピンの接続を修正した後に、温度および状態が変化していることが分かります。

6. まとめ

今回の投稿では、RaspberryPiのファンの「カリカリカリカリ」という音の発生を回避するために、ファンの制御を試みました。
結果として、CPUの温度によってファンのON/OFFを切り替えるように制御することで、この音の発生を回避できるようになりました。
これにより、一日中RaspberryPiをサーバと動作させていた場合でも、この音に悩まされずに済むようになりました。

ではっ!

ex. 公開しています

今回のエントリで作成したコードを、GitHubにて公開しています。
エントリの中では触れなかった、取得した温度と、それに基づいて決定した動作モードを保存するコードも、コチラに含まれています。
本文とあわせて、コチラのコードも参考にしていただけたら、幸いです。