2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GPU自動冷却システム (AF System)

Posted at
  • 3DやPCVRの使用でGPUの温度が上がり過ぎて熱暴走するのでGPUコア自動冷却システムを開発しました。(ECCS、EjectCoreCoolingSystem)

  • PCケースUSBファンのON,OFFを手動でやってましたが結構面倒です。常時ファンを回すと騒音が気になるので自動化しました。

必要となる技術は下記の4点です。

  • 1.PC側でGPU状況の取得
  • 2.PC側管理画面作成とシリアルデータ送信
  • 3.Arduino側のシリアル受信
  • 4.ファンを稼働させるハードウェア

LM35温度センサによる測定からnvidia-smiでデータを取る方式に作り直し。

  • nvidia-smiは上記リンクに詳しく載ってます。

  • データの流れは GetGPUData() -> nvidia-smi -> 整形 -> ListBox

  • GPU温度等を取得し閾値を超えると、Arduino基板にシリアルでフラグを送り、MOSFET ONにしてファンを回す。

  • 閾値を切ると停止。閾値項目等は自由に変更可。

  • nVidia以外の場合はLibreHardwareMonitorLib等を使用しGPU情報を取得するなど少し工夫する必要があります。


1.起動するとタスクトレイに常駐(TSR)します

1123.jpg

  • アイコン(この場合は音符)をダブルクリックするか右ボタン->設定でAF System管理画面が出ます。
  • また管理画面が出てる状態でアイコンダブルクリックで非表示化。トグルです。

2.AF System (WPF) 管理画面

  • 初期は自動モードになっています。

AAAA.jpg

  • 15秒毎にGPU情報を表示している様子。

  • xを押すと画面は消えますがトレイに常駐・稼働してます。

  • nvidia-smiは0,40,11,719,17.79 という形式で出力させてるので切り出して整形。0 は回転割合、40はGPU温度。

  • [自動スクロール]...更新があると最終行に移動。履歴を見る時はオフにします。

  • [自動ボタン]...45W以上又はGPU搭載ファンが30%以上で稼働させる自動モード。項目、閾値は自由に変更可。

  • [強制稼働]...GPUの状況に関係なくファンを回す。強制停止はその反対。

  • 自動モードで閾値以下の場合、[自動停止中]と表示。

3.高負荷時の例

1.jpg

  • VR等行うと180W超えてGPU温度も上がっているのが分かる。
  • 最大200W消費していた事を履歴で確認。

4.温度低下を示す表示

1123.jpg

  • 06:36:04はファンが停止中。15秒後には30%で回転を始めたので併せて外付けファンが起動。
  • 2分後には23度温度降下。アイドル中なので下がりやすい。
  • 60度以上が続くと内蔵ファンが起動する設定の模様。

5.リアルタイムモニタ

AAAA.jpg

  • 一行表示まで縮めるとリアルタイムモニタになる。
  • ハード作成を省いて、GPU監視アプリとしても利用可。

6.AF System 回路図 / Arduino UnoR3(MPU ATmega328P)

1 - コピー.jpg

  • フラグ増やしてPWM制御にする事も可能

7.Arduino Uno R3 / C++ USB電源制御コード

//--------------------------
// Arduino AF System R1.00 
// inf102 2025.
//--------------------------

void setup() {
    Serial.begin( 9600 );
    pinMode(8, OUTPUT);
}

void loop() {
    char key;
    
    // digitalWrite(8, HIGH);
  
    // 受信データがあった時だけ処理を行う
    if ( Serial.available() ) { // 受信データがあるか?
        // digitalWrite(8, HIGH);
        key = Serial.read();     
        if (key=='1') digitalWrite(8, HIGH);
        if (key=='0') digitalWrite(8, LOW);
    }
}

8.試験運用

試作の段階ではブレッドボードを使用すると良い

P1450159.JPG

9.ラグ版で組み立て(本番環境、アッセンブリ化用)

MosFETヒートシンクは適当なのがあったので取り付け。

12450163.JPG

10.自作基板とArduino基板を結合させてアッセンブリ化

  • アッセンブリ化して装置に3つのコネクタを刺すだけで良い作りにした

P1450169.JPG

11.制御装置をPCケースに設置

  • ファンはUSB電源供給されると回る物にする。
  • トグルスイッチ式等。そうでないと自動化されない。

P1450171.JPG

12.WPF (制御画面)

  • タスクトレイに常駐します。アイコンファイルは適当に用意して下さい。
  • nvidia-smi.EXEのパス等も変えて下さい。ドライバが入っていれば何処かに入ってます。
/////////////////////////////////////////////
// AF System R1.00 EjectCoreCoolingSystem
// WPF .NETFRAMEWORK / inf102 2025.
////////////////////////////////////////////

using System;
using System.Diagnostics;
using System.Drawing;         // Icon用
using System.IO.Ports;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Media;   // NotifyIcon用

// 参照追加 System.Windows.Forms / System.Drawing
// 二重起動防止はApp.xaml.cs

namespace TSRTEST01 {

    public partial class MainWindow : Window {

        const int WAIT=15000; // 更新間隔 mmSec 1000以上

        static bool AUTO=true;
        static int ENA=3; // 0=強制停止 1=強制稼働 

        private NotifyIcon notifyIcon;

        public void m(string s) {
            System.Windows.MessageBox.Show(s);

        }

        public void L1(string s){
            if (listBox1.Items.Count >=5000 ) listBox1.Items.Clear();
            listBox1.Items.Add(s);       
        }

        // VS上では例外がでることある その際は削除
        public void L2() {
            var border = VisualTreeHelper.GetChild(listBox1, 0) as Border;
            var listBoxScroll  = border.Child as ScrollViewer;
            listBoxScroll.ScrollToEnd();
        }

        public MainWindow() {

            InitializeComponent();
            SetupNotifyIcon();

            this.Hide(); // 起動時にwindow出したい時は消す

            L1("AFSystem R1.00.");
            L1("\r\nGPU情報.");

            // GPU初期表示
            string cmd= "c:\\tmp\\nvidia-smi.EXE \" --query-gpu=name,fan.speed,temperature.gpu,utilization.gpu,memory.used,power.draw --format=csv,nounits\"";
                          
            ProcessStartInfo psInfo = new ProcessStartInfo();
            psInfo.FileName = cmd;
            psInfo.CreateNoWindow = true;
            psInfo.UseShellExecute = false;
            psInfo.RedirectStandardOutput = true;

            Process p = Process.Start(psInfo);
            string output = p.StandardOutput.ReadToEnd();
            output = output.Replace("\r\r\n", "\n");
            L1(output);
            
            L1( (WAIT/1000).ToString() +"秒毎に更新されます.");
            L1("自動モードでは消費電力45W以上又はファン回転率30%以上で稼働.");
        
            FAN_STOP();
            AutoMode();    
            STA.Content="自動モード";
            AUTO=true;

            // MAIN-LOOP
            ST();
        }
        
        // MAIN-LOOP 
        public async void ST() {
                                  
            while (true){
                             
                string outst="";
                
                // GPUデータの取得と分割
                string[] words=GetGPUData().Split(' ');

                // 消費電力
                double wat=float.Parse(words[7]);

                // 回転率
                int ter=int.Parse(words[0]);

                // 自動モード
                if (AUTO == true) {
                    ENA=3;

                    if (wat >= 45 || ter >=30) {
                        outst=DateTime.Now.ToString("HH:mm:ss") + " 自動稼働";
              
                        STA.Content="自動稼働中";
                        FAN_START();
                    }
                    else {
                        outst=DateTime.Now.ToString("HH:mm:ss") + " 自動停止";
                        STA.Content="自動停止中";
                        FAN_STOP();
                    }
                }
                
                // 強制稼働モード
                if (ENA == 1) {
                    AUTO=false;
                    outst=DateTime.Now.ToString("HH:mm:ss") + " 強制稼働";
                    STA.Content="強制稼働中";
                    FAN_START();
                }
                
                // 強制停止モード
                if (ENA == 0) {
                    AUTO=false;
                    outst=DateTime.Now.ToString("HH:mm:ss") + " 強制停止";
                    STA.Content="強制停止中";
                    FAN_STOP();
                }   

                L1 (outst + " : ファン回転率 " +int.Parse(words[0]).ToString("00")  +"% : GPU "+words[2]+"度 : 使用率 "+
                    int.Parse(words[3]).ToString("00")+"% : 使用メモリ "+ int.Parse(words[5]).ToString("0000")+"MB : 消費電力 "+ Math.Ceiling(decimal.Parse(words[7])).ToString()+"W");
                
                await Task.Delay(WAIT);
                
                // 強制スクロール
                if (AUTO_S.IsChecked == true ) L2();
            }
        }
            
        private void SetupNotifyIcon(){

            notifyIcon = new NotifyIcon();
            notifyIcon.Icon = new Icon("C:\\TMP\\ICON.ico");  // アイコンファイル
            notifyIcon.Visible = true;
            notifyIcon.Text = "AFSystem";

            // 右クリックメニューの作成
            var contextMenu = new ContextMenuStrip();

            // MENU1 (WINDOW OPEN) ////////////////////////////////////
            var menuItem = new ToolStripMenuItem("設定");
            menuItem.Click += (s, e) => {
                this.Show();
                this.WindowState = WindowState.Normal;
                this.Activate();
            };

            contextMenu.Items.Add(menuItem);
            //////////////////////////////////////////////////////////                 
            // MENU2 (EXIT) ///////////////////////////////////////////
            menuItem= new ToolStripMenuItem("終了");
            
            menuItem.Click += (s, e) => {
                notifyIcon.Visible = false;
                System.Windows.Application.Current.Shutdown();
            };
            contextMenu.Items.Add(menuItem);
            //////////////////////////////////////////////////////////     

            // メニュー追加
            notifyIcon.ContextMenuStrip = contextMenu;

            // ダブルクリックでウィンドウを表示
            notifyIcon.DoubleClick += (s, e) =>{

                // 非表示化
                if (this.IsVisible){
                    this.Hide();
                    return;
                }

                this.Show();
                this.WindowState = WindowState.Normal;
                this.Activate();
              
            };

            // 最小化時にウィンドウを隠す
            this.StateChanged += (s, e) =>{
                if (this.WindowState == WindowState.Minimized){
                    this.Hide();
                }
            };
        }

        protected override void OnClosed(EventArgs e)        {
            FAN_STOP();

            notifyIcon.Visible = false;
            notifyIcon.Dispose();

            base.OnClosed(e);
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            this.Hide();
            e.Cancel = true;
        }

        // 自動ボタン
        private void Button_Click3(object sender, RoutedEventArgs e) {
            AutoMode();
            if (AUTO_S.IsChecked == true ) L2();
        }

        public void AutoMode(){
            AUTO=true;
            ENA=3;

            L1(DateTime.Now.ToString("HH:mm:ss") + " -- 自動モード --");
            STA.Content="自動モード";       
        }

        // 強制稼働ボタン
        private void Button_Click(object sender, RoutedEventArgs e) {
            AUTO=false;
            ENA=1;

            L1(DateTime.Now.ToString("HH:mm:ss") + " -- 強制稼働 --");
            STA.Content="強制稼働中";
            FAN_START();

            if (AUTO_S.IsChecked == true ) L2();
        }

        public void FAN_START() {
            try {
                System.IO.Ports.SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); // N81XN
                serialPort.NewLine = Environment.NewLine;
                serialPort.Open();

                byte[] SendData = new byte[10];
                SendData[0] = (byte)('1'); 

                serialPort.Write(SendData, 0, 1); 
                serialPort.Close();
                serialPort.Dispose();
            }
            catch(Exception ex) {
                m(ex.ToString());
            }
        }

        // FAN強制停止ボタン
        private void Button_Click2(object sender, RoutedEventArgs e) {
            AUTO=false;
            ENA=0;

            L1 (DateTime.Now.ToString("HH:mm:ss") + " -- 強制停止 --");
            STA.Content="強制停止中";

            FAN_STOP();
            if (AUTO_S.IsChecked == true ) L2();

        }

        public void FAN_STOP() {
            try {
                System.IO.Ports.SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
                serialPort.NewLine = Environment.NewLine;
                serialPort.Open();

                byte[] SendData = new byte[10];
                SendData[0] = (byte)('0'); 

                serialPort.Write(SendData, 0, 1); 
                serialPort.Close();
                serialPort.Dispose();   
            }
            catch(Exception ex) {
                m(ex.ToString());
            }
        }

        // GPU DATA 
        public string GetGPUData() {
            // command = "c:\\tmp\\nvidia-smi.EXE \" --query-gpu=name,fan.speed,temperature.gpu,utilization.gpu,memory.used,power.draw --format=csv,nounits\"";
            
            string cmd= "c:\\tmp\\nvidia-smi.EXE \" --query-gpu=,fan.speed,temperature.gpu,utilization.gpu,memory.used,power.draw --format=csv,noheader\"";
                        
            ProcessStartInfo psInfo = new ProcessStartInfo();

            psInfo.FileName = cmd;
            psInfo.CreateNoWindow = true;
            psInfo.UseShellExecute = false;
            psInfo.RedirectStandardOutput = true;

            Process p = Process.Start(psInfo);
            string output = p.StandardOutput.ReadToEnd();
            p.Close();
            p.Dispose();

            output = output.Replace("\r\r\n", "\n");
     
            return(output);
        }
    }
}

13.XAML (制御画面)

<Window x:Class="TSRTEST01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TSRTEST01" mc:Ignorable="d"
        Title="AFSystem R1.00" Height="450" Width="920" Closing="Window_Closing">

    <Grid>
        <Grid Margin="0,0,0,0">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="36"/>
            </Grid.RowDefinitions>

            <ListBox x:Name="listBox1" Margin="2,2,2,2" Background="LightBlue" FontSize="20" Foreground="Black" />
            <Button Content="強制稼働(_R)" FontSize ="15" HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Center" Width="90" Height="30" Click="Button_Click" Margin="104,0,0,0"  />
            <Button Content="強制停止(_S)" FontSize ="15" Grid.Row="1" VerticalAlignment="Center" Height="30" Click="Button_Click2" Margin="199,0,0,0" HorizontalAlignment="Left" Width="90"  />
            <Button Content="自動(_A)" FontSize ="15"  HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Center" Width="90" Height="30"   Click="Button_Click3" Margin="9,0,0,0"/>
            <CheckBox Content="自動スクロール(_K)" x:Name="AUTO_S" IsChecked="True" FontSize="13" HorizontalAlignment="Left" Margin="315,0,0,0" Grid.Row="1" VerticalAlignment="Center"/>
            <Label Content="Label" x:Name="STA" Foreground="RED"  HorizontalAlignment="Left"  Margin="522,0,0,0" FontSize="25" VerticalAlignment="Bottom" Width="192" Grid.RowSpan="2"  />
        </Grid>
    </Grid>
</Window>
  • 参考 部品諸元表示/試験機 MosFETの表示、ドレイン・ソース間オン抵抗値まで出てる。
    P1450173.JPG
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?