はじめに
今回は夏休みの自由研究と称して
7SegLEDにCPU使用率を表示してみたいと思います
FPS(主にApex)にはまっている自分がぱっと見でCPU使用率を把握して
ゲームでカクついているときに何が悪いのかをあたりを付けるという意味で作成しました
目次
実現したいこと
7segLEDに現在のCPU使用率を1秒おきに転送し、表示すること
完成したものはこちらです
夏休みの自由研究
— uechan (@uechang16) August 5, 2021
Arduino uno + C#でCPU使用率を7セグLEDに表示してみた
マルチスレッドとかSerial.readStringの仕様とかいろいろ学ぶところあったので後日qiitaに投稿しようかな pic.twitter.com/99eoPnAssx
CPU-Zで負荷をかけるとそれに応じてタスクマネージャと一緒に7SegLEDの表示が変わっていることがわかると思います
準備するもの
- Arduino Uno
- ブレッドボードおよびジャンパ線(回路試作のため)
- 7SegLEDと4.7kΩ炭素被膜抵抗
- ArduinoにつなげるUSBケーブル
ブレッドボードは回路の試作のためなので
いつかユニバーサル基盤に実装したいと思います
回路の設計
まずは表示を担う7SegLEDの回路を設計します
今回使う7SegLEDは秋月電子通商で買えるこちらにしました
ダイナミック接続4桁赤色7セグメントLED表示器です
こいつのデータシートを見るとアノードコモンでダイナミック接続との記載があるので
アノード側の表示させたい桁に5Vをかけ
カソード側で表示する数字に対応する部分を出力する方式で行きます
回路図は
デジタル10番ピンからからデジタル13番ピンまでがアノードで5Vをかけること
デジタル2番ピンからデジタル9番ピンまでは表示したい部分をLOWにすることで表示、
HIGHにすることで非表示という制御になりますので4.7kΩでプルダウンしてあります
例えば
デジタル10番ピンがHIGHの時にデジタル3番ピン,4番ピンにLOW
残りの2,5,6,7,8,9番ピンにHIGHがかかっていると7SegLEDの一桁目に「1」が表示されます
回路図をもとにブレッドボードに配線を行っていきます
ぐちゃぐちゃですがこんな感じ
C#側プログラム
C#(PC)側はCPUの使用率を取得し、シリアル経由でArduinoの文字列として送信する役割を持ちます
Arduinoとの通信はSerialPortで行います
private void AnalyzeComPort()
{
string[] ports = SerialPort.GetPortNames();
foreach (string port in ports)
{
comboBox1.Items.Add(port);
}
}
シリアルポートの利用可能なものを列挙して
選択式のコンボボックスに格納しています
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "Connect")
{
serialPort1.PortName = comboBox1.SelectedItem.ToString(); // 選択されたCOMをポート名に設定
serialPort1.Open(); // ポートを開く
button1.Text = "Disconnect";
}
else
{
serialPort1.Close();
button1.Text = "Connect";
}
}
ボタンが押されるとコンボボックスで選択されたCOMポート名を指定して
serialPort1.Open()で通信開始
serialPort1.Close()で切断しています
public void getCPUUsege()
{
var cpuUsage = new PerformanceCounter("Processor", "% Processor Time", "_Total");
Thread.Sleep(1000);
var firstCall = cpuUsage.NextValue();
while(true)
{
Thread.Sleep(1000);
CPUperStr = cpuUsage.NextValue().ToString();
label3.Text = "Now:" + CPUperStr;
if (button1.Text == "Disconnect")
{
serialPort1.Write(CPUperStr + "\0");
}
}
}
C#でCPU使用率をとるならPerformanceCounterでしょ!という安直な考えです…
ひたすらwhileで回してserialPort1.Write();で一秒おきに送信しています
SerialPort1.Write(CPUperStr + "\0");の最後になぜナル文字(\0)が入っているかは
Arudino側のプログラムでわかります
Arduino側プログラム
Arduino側はPCからCPU使用率の数字を文字列(100.0とか58.5とか)として送信されたものを
しかるべきLEDに表示する役割を持ちます
こちらの記事 arduinoで並列処理(ノンプリエンプティブ)を参考に
7segLEDの表示タスクと
USBのシリアル通信でCPU使用率の数字を受け取るタスクに分けて
並列処理として実装してます
# include <TimedAction.h>
String str = ""; //受信した文字列を格納する
double num = 0;
void LEDtask();
void Serialtask();
TimedAction ledAction = TimedAction(5,LEDtask); //LED表示タスクを5msのインターバルで実行する
TimedAction serialAction = TimedAction(30,Serialtask); //シリアル通信タスクを30msのインターバルで実行する
LED表示タスクは各桁を5msごとにダイナミック点灯させて制御しています
0-9までの表示は各関数に定義してどの桁でその表示を行うかを引数に渡すことでダイナミック点灯を実現しています
void LEDtask()
{
void digitalWriteTo0(int);
void digitalWriteTo1(int);
void digitalWriteTo2(int);
void digitalWriteTo3(int);
void digitalWriteTo4(int);
void digitalWriteTo5(int);
void digitalWriteTo6(int);
void digitalWriteTo7(int);
void digitalWriteTo8(int);
void digitalWriteTo9(int);
num = str.toDouble();
// 各桁に分解
int numBef = num * 10;
int num1 = numBef / 1000;
int num2 = numBef % 1000 / 100;
int num3 = numBef % 100 / 10;
int num4 = numBef % 10 ;
// 桁ごとに表示
switch(num1){
case 0:
digitalWriteTo0(1);
break;
case 1:
digitalWriteTo1(1);
break;
case 2:
digitalWriteTo2(1);
break;
case 3:
digitalWriteTo3(1);
break;
case 4:
digitalWriteTo4(1);
break;
case 5:
digitalWriteTo5(1);
break;
case 6:
digitalWriteTo6(1);
break;
case 7:
digitalWriteTo7(1);
break;
case 8:
digitalWriteTo8(1);
break;
case 9:
digitalWriteTo9(1);
break;
}
delay(5);
switch(num2){
case 0:
digitalWriteTo0(2);
break;
case 1:
digitalWriteTo1(2);
break;
case 2:
digitalWriteTo2(2);
break;
case 3:
digitalWriteTo3(2);
break;
case 4:
digitalWriteTo4(2);
break;
case 5:
digitalWriteTo5(2);
break;
case 6:
digitalWriteTo6(2);
break;
case 7:
digitalWriteTo7(2);
break;
case 8:
digitalWriteTo8(2);
break;
case 9:
digitalWriteTo9(2);
break;
}
/*
以下略
*/
}
シリアル通信タスクは少し試行錯誤しました
void Serialtask()
{
if (Serial.available() > 0){
str = Serial.readString();
}
}
Serial.readString();でUSBシリアル通信で来た文字列を受信しているのですが
以下の様な動作になりました
データが来てから表示されるまでに謎のラグがあります
このラグをどうにかしてなくしたい!!
とArduinoの言語リファレンスをよく読んでみると
Serial.readString()は、シリアルバッファから文字を読み、文字列に書き込む。この関数はタイムアウトすれば終了する
との記述を発見
この受信してからデータが表示されるまでのラグは
受信のタイムアウトをわざわざ待っていたということに気が付きました
その後リファレンスを見返すと
Serial.readStringUntil()は、シリアルバッファから文字を読み、文字列に書き込む。この関数は、終端文字を検出するかタイムアウトすれば終了する(Serial.setTimeout()参照)。
めちゃくちゃいい関数あんじゃん!!!
ということで以下のように変更しました
void Serialtask()
{
if (Serial.available() > 0){
str = Serial.readStringUntil('\0');
}
}
Serial.readString()をSerial.readStringUntil()に書き換え
終端文字としてナル文字を指定、C#側でナル文字を文字列の末尾につけて送信するようにしました
すると…
ラグがなくなってすんなりと表示ができるようになりました
void loop() {
// put your main code here, to run repeatedly:
ledAction.check();
serialAction.check();
}
冒頭で宣言したタスクをcheckすることで起動しています
void digitalWriteTo1(int port){
// アノードコモン側のどの桁に表示するかの選択
switch (port){
case 1:
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
digitalWrite(13, LOW);
break;
case 2:
digitalWrite(9, HIGH);
digitalWrite(10, LOW);
digitalWrite(11, HIGH);
digitalWrite(12, LOW);
digitalWrite(13, LOW);
break;
case 3:
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, HIGH);
digitalWrite(13, LOW);
break;
case 4:
digitalWrite(9, HIGH);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
digitalWrite(13, HIGH);
break;
}
// 数字の表示
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
digitalWrite(4, LOW);
digitalWrite(5, HIGH);
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
}
上記の様な感じで0-9までの表示を行っています
完成!!
完成しました
これを表示しながらゲームするなり
レンダリングするなりベンチマークとったりすると非常に面白いです
夏休みの自由研究
— uechan (@uechang16) August 5, 2021
Arduino uno + C#でCPU使用率を7セグLEDに表示してみた
マルチスレッドとかSerial.readStringの仕様とかいろいろ学ぶところあったので後日qiitaに投稿しようかな pic.twitter.com/99eoPnAssx
夏休みの自由研究(暇つぶし)としてやってみましたが
所要時間3時間程度で終わってしまったため
また何かネタを探してちょろちょろやっていきたいと思います
参考にしたサイト
とても参考になりました
ありがとうございました