LoginSignup
3
0

More than 1 year has passed since last update.

【電子工作】CPU稼働率に合わせてひゅんひゅん動くアナログメーターを作る

Last updated at Posted at 2020-06-14

リアルタイムでPCの状態を監視

 要らん人には本当に要らないんだろうけど欲しい人には欲しいやつですね。私も欲しい派なんですが(Moo0製のSystemMonitorを使ってました)、監視ツールが画面上にあるとそれはそれで邪魔だなあと常々思っていたんですよ。画面外に……ッ、専用デバイスを、置くしかねえ……!! というわけで自作しました。

 最終的にはこんな感じになります。

今回使うあれそれ

・C#
・Arduino
・電圧計(5V)
・小型ディスプレイ(128*64)

とりあえずC#で値を取得

 Windows用に作るので、言語的に相性いいんじゃねと適当にC#を選びました。リアルタイムで目当ての値を取得できてシリアル通信ができる言語ならなんでもいいです。せっかくなのでCPU稼働率以外にもなんか値を取ろうと思い、今回は

・CPU稼働率
・メモリ使用率
・PC起動からの経過時間

で作ることにしました。

C#でCPU稼働率、メモリ使用率、PC起動からの経過時間を取得
static PerformanceCounter pfcounter_cpu = new PerformanceCounter("Processor", "% Processor Time", "_Total");
static PerformanceCounter upTime = new PerformanceCounter("System", "System Up Time");

 static float GetUseCpu(){
    float cpu = 0;
    const int QTY = 20;
    for (int i = 0; i < QTY; i++)
    {
        Thread.Sleep(10);
        cpu += pfcounter_cpu.NextValue();
    }
    return cpu / QTY; //20回取得して平均値を返す
}

static double GetUseMemory(){
    ManagementClass mc = new ManagementClass("Win32_OperatingSystem");
    ManagementObjectCollection moc = mc.GetInstances();

    double useable = 0;
    double max = 0;

    foreach (ManagementObject mo in moc)
    {              
        max = Convert.ToDouble(mo["TotalVisibleMemorySize"]) / 1024 / 1024; //合計物理メモリ
        useable = Convert.ToDouble(mo["FreePhysicalMemory"]) / 1024 / 1024; //利用可能な物理メモリ
    }
    return (max - useable) / max;
}

static String GetUpTime(){
    var span = new TimeSpan(0, 0, (int)upTime.NextValue());
    return span.ToString(@"dd\d\ hh\hmm\m"); //取得した秒数をフォーマット指定して日時分に変換
}


 CPU稼働率がタスクマネージャと比較してみたところあまりにも乖離が激しかったので、複数回取得した平均を使って近付けてます。たぶんきっと恐らく値を取得する頻度やタイミングがタスクマネージャとは違うというだけのお話です。

電圧計のお話

 小学校の理科とかで誰でも一度は触れたことがあると思います。今回使うArduino君は出力最大5Vなので、ここで15Vとか300Vとかのを持って来ると悲惨なことになります。0-5Vのものを用意しましょう。


 5Vまでなら任意の電圧で出力が可能なので、C#から受け取った値に応じて出力を調整すればメーターを良い感じに動かせますよということです。アナログピンを使うなら何も気にすることはありませんが、デジタルピンを使う場合はPWMに対応しているか調べておきましょう。

C#でArduinoへ値を渡す

Arduinoとシリアル通信
static SerialPort sp = new SerialPort();

static void Main(string[] args){

    sp.BaudRate = 9600;
    sp.Parity = Parity.None;
    sp.DataBits = 8;
    sp.StopBits = StopBits.One;
    sp.Handshake = Handshake.None;
    sp.PortName = "COM4";   // 適宜書き換えてね
    sp.Open(); 

    for (; ; )
    {

        int d1 = (int)(100 * GetUseMemory() + 0.55);
        int d2 = (int)(GetUseCpu() + 0.55);
        String d3 = GetUpTime();

        Console.WriteLine(d1 + "," + d2 + "," + d3);
        Thread.Sleep(250);
        Console.Clear();
        Send(d1 + "," + d2 + "," + d3);
    }
}

static void Send(String str){         
    try{               
        sp.Write(str+";"); //送信

        //! 受信データを読み込む.
        string data = sp.ReadExisting();
        Console.WriteLine(data);

    }catch(Exception e){
        Console.WriteLine(e);
        Console.ReadLine();
    }
}

 ここがワンポイントです。Arduino側でセミコロンを終端記号とするように作るので末尾に付け足しています。お好みで他の文字を使う場合はArduinoの方でも書き換えましょう。

ワンポイント
sp.Write(str+";"); 

Arduino側

Arduino側

#include<Wire.h>               //小型ディスプレイ用
#include<Adafruit_GFX.h>       //小型ディスプレイ用
#include<Adafruit_SSD1306.h>   //小型ディスプレイ用

Adafruit_SSD1306 display(-1);  //小型ディスプレイ用


String memory;
String cpu;
String upTime;
String dayCount;
String timeCount;


void setup() {

  Serial.begin( 9600 );
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);//小型ディスプレイ用

}


void loop() {

  String str;
  if ( Serial.available() ) {                 // 受信データがあった時だけ、処理を行う
    str = Serial.readStringUntil(';');        // セミコロンを終端とし文字列を読み込む


//--------------------------------------------------カンマ区切りで受信したデータを分割する
    int i1 = str.indexOf(','); 
    int i2 = str.indexOf(',', i1 + 1);

    memory = str.substring(0, i1);          //Arduino公式さんへ
    cpu = str.substring(i1 + 1, i2);        //Stringあるんだからsplitも実装してくれ
    upTime = str.substring(i2 + 1);         //                     うなせんより
//-------------------------------------------------------------------------------------
  }
  disp(upTime);//小型ディスプレイ用

  double d_cpu = (double)cpu.toInt();
  double d_memory = (double)memory.toInt();

  analogWrite(11, map(d_cpu, 0, 100, 0, 255));    //メーターへ出力
  analogWrite(10, map(d_memory, 0, 100, 0, 255)); //メーターへ出力

}


void disp(String str) {//小型ディスプレイ用

  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0, 0);
  display.setTextColor(WHITE);
  int i1 = str.indexOf(':');

  dayCount = str.substring(0, i1);    
  timeCount = str.substring(i1 + 1);

  display.println(str);
  display.setTextSize(1);
  display.println("CPU    :  " + cpu + "%");
  display.println("Memory :  " + memory + "%");

  display.display();
}

 ちらちらと記載がある小型ディスプレイはこんな感じになってくれます。
2020-06-15_01h45_03.png

仕上げ

 それっぽく動いてくれます


 が、これだけでは見栄えがあれな上に何の表示かよくわかりません。文字盤をなんとかしましょう。分解してみると簡単に取れました。
EaZPK-DUEAcColg.jfif
 自宅のプリンターでスキャンして加工しちゃいましょう。動作確認時にちょうど目盛り2つ分ズレていたのでそいつもついでに調整しました。
2020-06-13_22h43_32.png

 CPUとメモリだとわかるようピクトグラムを入れて、ついでにサークルロゴも入れちゃいます。
2020-06-13_23h17_24.png

 カットして換装します。わくわくしますね。

 なかなか良きです。

ここから手を加えなきゃいけないところ

【タスクトレイ常駐タイプに改修】
 現状ではC#側がCUIアプリケーションで動いてるんで邪魔です。記事には書いてませんがとりあえず非ウィンドウのタスクトレイ常駐化は済ませました。

【COM番号の自動特定】
 Arduinoを繋いでプログラム書き込む時に選ばされるあれです。いつも同じじゃないのはArduino触ったことある人はご存じのはずです。このままだとC#のプログラムを頻繁に書き換えなきゃなんで自動的に特定できるようにしてやる必要があります。解決策としてはArduinoから特定のメッセージを送り、C#側は接続できるデバイス全てに対し片っ端から接続、特定のメッセージを送ってくるデバイスのみ接続を維持するようにすればいいと思います。そのうち実装します。

3
0
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
3
0