こんにちはイチケンです。
これまでの記事で、LiDARカメラ搭載ラズパイと三菱PLCの連携によりFAで使えるな、というとこまでは来ました。しかし長期安定運用しようと思うと環境耐性や熱コントロールが重要になってきますよね。
ポーリングに全力出されちゃうのは無駄だなーと考えていたところ、いつも参考にさせていただいているたのじぃさんがちょうどラズパイのオーバークロックを再起動なしでテストしていたので、これを応用させていただきました。
ラズパイ オーバークロック限界に挑む!結論としてはPythonプログラム内からラズパイのCPUクロックをコントロール可能です。今回はその手順と、実験で使ったPID制御プログラムを紹介します。
この記事でわかること
ラズパイCPUクロックをPythonプログラムでコントロールする方法がわかります。また今回は熱コントロールにPID制御を用いたので、実際にPID制御する方法もわかります。
- ラズパイのCPUオーバークロック
- PythonからCPUクロックコントロール
- PID制御を実際にやる方法
- マルチスレッド
前提条件
まず前提条件となる私の環境です。パッシブクーリングケースを使っているので全力処理を続けるとあっという間に85℃に到達してしまいます。
Raspberry Pi | 4 Model B 8GB |
---|---|
Raspberry Pi OS | Raspbian GNU/Linux 10 (buster) |
VSCode | 1.55.2 |
Python | 3.7.3 |
ラズパイのPython開発環境はVSCodeを使用します。インストールはこちらを参照してください。
【IoT×キャンプ|Python開発環境VSCode編】意地でもラズパイとキャンプへ!らずキャン△プロジェクト第6回サンプルコードの実行結果
サンプルでは負荷をかけながらCPU温度がターゲット温度になるようにPID制御しています。
実際に実行したときの上昇時のグラフはこちら。青:CPUクロック周波数(GHz) 赤:CPU温度(℃)。ターゲットを70℃に設定しています。67℃付近までは周波数が2.0GHzに張り付いていますが、67℃を超えてからは調整のために上下しています。
次のグラフが70℃到達後の動きです。周波数をコントロールすることで温度をざっくり一定に保っていることがわかります。
CPUのオーバークロック設定
では実際に試していきましょう。オーバークロックする方法は検索ですぐ見つかります。ただしどれも再起動が必要ですが。公式はこちら。
Overclocking options in config.txtサンプルコードでは下限を0.5GHz、上限を2.0GHzにしています。まずは再起動が必要なオーソドックスな方法でオンデマンドの上下限をこの値に設定しましょう。下記コードをLXTerminalで実行してファイルを開きます。
sudo nano /boot/config.txt
スクリーンショットのように設定値を追記します。最大2GHzにオーバークロックし電圧も上げておきます。
over_voltage=6
arm_freq=2000
arm_freq_min=500
数値を間違えていると立ち上がらなくなる可能性があるので注意してください。「Ctrl+O」で保存し「Ctrl+X」で終了します。その後ラズパイを再起動してください。オーバークロックの設定は以上です。
再起動が完了して無事立ち上がれば成功です。この時点での動きは「0.5GhZ ~ 2.0GHzの間でクロックを動的に変化させる」です。
もし立ち上がらない場合は、別のラズパイへUSBアダプタでmicroSDを挿入し、config.txtを直接編集すれば復旧可能です。
CPUクロックコントロール
次にCPUをコマンドでコントロールするためのツールをインストールします。
LXTerminalで次のコマンドを実行してください。
sudo apt install cpufrequtils
これを使うことにより、ターミナルからコマンドでCPUクロックをコントロールできるようになります。変更時に再起動も不要です。
governorと呼ばれるものをuserspaceとondemandに切り替えることでクロックを固定にするか動的にするかを選べます。ただし、userspaceを意図的に設定する必要はなく、クロックを設定すると自動的にモードがuserspaceになります。コマンド例は次の通り。実行にはsudoが必要なのでご注意を。クロックを2GHzに固定します。
sudo cpufreq-set -f 2.0GHz
動的クロックに戻す場合のコマンドはこちら
sudo cpufreq-set -g ondemand
次のコマンドで情報を確認できます。オプションは-hで何があるか見れます。-fを指定すると現在のクロックのみ表示できます。
cpufreq-info
これでコマンドでクロックをコントロールできるようになりましたね。では実際にPythonコードからコントロールしてみましょう。
PythonコードでCPUクロックコントロールするサンプル
Githubのリポジトリはこちら
https://github.com/ichiken-usa/raspi-temp-control結果確認のためグラフ出力にmatplotlibを使用しています。未導入の方はインストールしてください。
sudo apt install python3-matplotlib
サンプルコードはこちら。クロック変更にsudoパスワードが必要です。init内のpasswordの部分をご自身のパスワードに変えてください。ソフトにsudoパスワード直打ちはかなり脆弱な作りなので、そのうちなんとかしたいですね。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# インストール必要
# sudo apt install cpufrequtils
# sudo apt install python3-matplotlib
import time
import subprocess
import threading
import matplotlib.pyplot as plt
import numpy as np
# CPUクロックをPIDコントロールするスレッド
class ControlCpuThread(threading.Thread):
def __init__(self):
super(ControlCpuThread, self).__init__()
self.freq_MAX = 2.0 # デフォルトは1.5GHz
self.freq_MIN = 0.5
self.password = 'password'+'\n' # sudoパスワード
return
def read_temp(self):
result = subprocess.run('vcgencmd measure_temp', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
CpuTemp = result.stdout.split()
temp = CpuTemp[0]
temp = float(temp[5:9])
return temp
def set_freq(self,freq):
if self.freq_MIN <= freq and freq <= self.freq_MAX:
subprocess.run('sudo -S cpufreq-set -f '+str(freq)+'GHz',shell=True,input=self.password,text=True)
#result = subprocess.run('cpufreq-info -f',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
print(f'Freq: {freq} GHz')
def set_ondemand(self):
result = subprocess.run('sudo -S cpufreq-set -g ondemand',shell=True,input=self.password,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
print('set ondemand')
def output(self,power):
freq = round(power * self.freq_MAX, 1) #小数点一桁丸め
# 上限調整
if freq > self.freq_MAX:
freq = self.freq_MAX
# 下限調整
if freq < self.freq_MIN:
freq = self.freq_MIN
self.set_freq(freq)
return freq
def p(self,temp, target, kp):
d = target - temp
if d < 0:
return 0
power = d / target * kp
return power
def i(self,prev, now, target, ki):
if prev == 0:
return 0
d1 = target - now
d2 = target - prev
if d1 < 0:
return 0
if d2 < 0:
d2 = 0
return (d1 + d2) / 2 * ki
def d(self,prev, now, kp):
d = prev - now
if d < 0:
return 0
return d * kp
def run(self):
target = 70 # 狙いのCPU温度設定(摂氏)
prev=0
# グラフ用
x = []
y_freq = []
y_temp = []
try:
for k in range(300):
print('------------------------------------')
temp = self.read_temp()
print (f'Target = {target}℃ Now = {temp}℃ Deff = {temp-target:.1f} ℃')
# PID Coefficient
pg = self.p(temp, target,6)
ig = self.i(prev, temp, target, 0.4)
dg = self.d(prev, temp, 0.1)
power = pg + ig + dg
prev = temp
print(f'P ={pg:.3f}')
print(f'I ={ig:.3f}')
print(f'D ={dg:.3f}')
print(f'power = {power:.2f}')
# CPUクロック設定
freq = self.output(power)
# グラフ用配列にデータ格納
x.append(k)
y_freq.append(freq)
y_temp.append(temp)
time.sleep(1)
except Exception as e:
print(f'Exception: {e}')
finally:
self.set_ondemand()
# グラフ表示
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x,y_temp,color='red')
ax2.plot(x,y_freq)
ax1.set_ylim([50,80])
ax2.set_ylim([0,4.0])
plt.show()
print('負荷スレッドは動作継続中。Ctrl+Cで抜けてください。')
# 負荷をかけるスレッド
class LoadThread(threading.Thread):
def run(self):
while True:
rand1 = np.random.rand(1000,1000)
rand2 = np.random.rand(1000,1000)
rand3 = rand1 * rand2
if __name__ == "__main__":
thread1 = LoadThread()
thread2 = ControlCpuThread()
# 各スレッド起動
thread1.start()
thread2.start()
サクサク解説
PID制御
PID制御に関してはこちらのサイトを参考にさせていただきました。シミュレーションしてるサイトはたくさんあるのですが、実際にモノをコントロールしているサイトが全然なくて探すのに苦労しました。
ラズベリーパイで低温調理器を作る!(ソフトウェア編)ぴったり70℃にコントロールすることが目的ではないので、パラメータはほどほどにしかチューニングしていません。
CPUクロックコントロール
subprocessを使ってコマンドでCPU温度を取ってきてpowerを決定します。
def read_temp(self):
result = subprocess.run('vcgencmd measure_temp', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
CpuTemp = result.stdout.split()
temp = CpuTemp[0]
temp = float(temp[5:9])
return temp
それに基づいてsubprocessでCPUクロックを設定しています。実際にコマンドを送っている部分はこちら。
def set_freq(self,freq):
if self.freq_MIN <= freq and freq <= self.freq_MAX:
subprocess.run('sudo -S cpufreq-set -f '+str(freq)+'GHz',shell=True,input=self.password,text=True)
#result = subprocess.run('cpufreq-info -f',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
print(f'Freq: {freq} GHz')
マルチスレッド
負荷をかける処理を分けるためにマルチスレッドにしています。PythonではThreadクラスを継承してrunメソッドをオーバーライトすることで、引数を与えることができるスレッドが作れます。今回は引数与えてませんが。
ややこしい言葉が並んでしまったので、実際にどうするんだ?というポイントを簡単にまとめます。
- class クラス名(threading.Thread):でマルチスレッドにしたい処理のクラスを作る。
- そのクラス内にrunメソッドを作ってメインの部分を書く。
- スレッドクラスのインスタンス作ってstartで開始。(runじゃない)
最後に
いかがでしたか?
この機能を使えば、ポーリング中はクロックを極力下げて発熱を抑制し、トリガーがかかるとクロックを上げて高負荷処理をこなす、みたいな作りのソフトにできるのではと思います。
Githubのリポジトリはこちら
https://github.com/ichiken-usa/raspi-temp-control
0 件のコメント:
コメントを投稿