ArduinoとC#でパソコンのCPU使用率を7SegLEDに表示してみた

# はじめに
今回は夏休みの自由研究と称して
7SegLEDにCPU使用率を表示してみたいと思います

FPS(主にApex)にはまっている自分がぱっと見でCPU使用率を把握して
ゲームでカクついているときに何が悪いのかをあたりを付けるという意味で作成しました

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [実現したいこと](#Chapter1)
1. [準備するもの](#Chapter2)
1. [回路の設計](#Chapter3)
1. [C#側プログラミング](#Chapter4)
1. [Arduino側プログラミング](#Chapter5)
1. [完成](#Chapter6)
1. [参考にしたサイト](#Chapter7)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# 実現したいこと
7segLEDに現在のCPU使用率を1秒おきに転送し、表示すること
完成したものはこちらです

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">夏休みの自由研究<br>Arduino uno + C#でCPU使用率を7セグLEDに表示してみた<br>マルチスレッドとかSerial.readStringの仕様とかいろいろ学ぶところあったので後日qiitaに投稿しようかな <a href="https://t.co/99eoPnAssx">pic.twitter.com/99eoPnAssx</a></p>&mdash; uechan (@uechang16) <a href="https://twitter.com/uechang16/status/1423230542762242051?ref_src=twsrc%5Etfw">August 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

![完成図.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705249/e649d3e8-9d5f-9cad-4eae-d1f64c2fcd7f.gif)
CPU-Zで負荷をかけるとそれに応じてタスクマネージャと一緒に7SegLEDの表示が変わっていることがわかると思います

<a id="#Chapter2"></a>
# 準備するもの
1. Arduino Uno
1. ブレッドボードおよびジャンパ線(回路試作のため)
1. 7SegLEDと4.7kΩ炭素被膜抵抗
1. ArduinoにつなげるUSBケーブル

ブレッドボードは回路の試作のためなので
いつかユニバーサル基盤に実装したいと思います

<a id="#Chapter3"></a>
# 回路の設計
まずは表示を担う7SegLEDの回路を設計します
今回使う7SegLEDは秋月電子通商で買える[こちら](https://akizukidenshi.com/catalog/g/gI-03954/)にしました
![I-03954.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705249/3aa1d70d-be3a-e139-c6cf-0723725406fb.jpeg)
ダイナミック接続４桁赤色７セグメントＬＥＤ表示器です

こいつのデータシートを見るとアノードコモンでダイナミック接続との記載があるので
アノード側の表示させたい桁に5Vをかけ
カソード側で表示する数字に対応する部分を出力する方式で行きます

回路図は
![Arudino7SegLED.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705249/d5896b50-c7bf-7919-a7f2-eaba6075c671.png)
デジタル10番ピンからからデジタル13番ピンまでがアノードで5Vをかけること
デジタル2番ピンからデジタル9番ピンまでは表示したい部分をLOWにすることで表示、
HIGHにすることで非表示という制御になりますので4.7kΩでプルダウンしてあります

例えば
デジタル10番ピンがHIGHの時にデジタル3番ピン,4番ピンにLOW
残りの2,5,6,7,8,9番ピンにHIGHがかかっていると7SegLEDの一桁目に「1」が表示されます

回路図をもとにブレッドボードに配線を行っていきます
![ArduinoHAisen.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705249/ac4bb2ba-482b-3b66-e6e7-a22d2a18a25a.jpeg)
ぐちゃぐちゃですがこんな感じ

<a id="#Chapter4"></a>
# C#側プログラム
C#(PC)側はCPUの使用率を取得し、シリアル経由でArduinoの文字列として送信する役割を持ちます

Arduinoとの通信はSerialPortで行います

```C#:シリアルポート探索
private void AnalyzeComPort()
{
    string[] ports = SerialPort.GetPortNames();
    foreach (string port in ports)
    {
        comboBox1.Items.Add(port);
    }
}
```
シリアルポートの利用可能なものを列挙して
選択式のコンボボックスに格納しています

```C#:シリアル通信開始/終了
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()で切断しています



``` c#:CPU使用率取得
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側のプログラムでわかります

<a id="#Chapter5"></a>
# Arduino側プログラム
Arduino側はPCからCPU使用率の数字を文字列(100.0とか58.5とか)として送信されたものを
しかるべきLEDに表示する役割を持ちます

こちらの記事 [arduinoで並列処理(ノンプリエンプティブ)](https://qiita.com/bunnyhopper_isolated/items/d4cf96895d665f5401af)を参考に
7segLEDの表示タスクと
USBのシリアル通信でCPU使用率の数字を受け取るタスクに分けて
並列処理として実装してます

```C++:CPUDisplay
#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までの表示は各関数に定義してどの桁でその表示を行うかを引数に渡すことでダイナミック点灯を実現しています

```C++:LEDTask
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;
   }
　/*
以下略
  */
}
```

シリアル通信タスクは少し試行錯誤しました


```C++:SerialTaskの第一弾
void Serialtask()
{
  if (Serial.available() > 0){
     str = Serial.readString();
  }
}
```
Serial.readString();でUSBシリアル通信で来た文字列を受信しているのですが
以下の様な動作になりました
![試行錯誤1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705249/2aa2703d-5599-7e43-3dc3-8142df3b78e9.gif)
データが来てから表示されるまでに謎のラグがあります
このラグをどうにかしてなくしたい！！
とArduinoの言語リファレンスをよく読んでみると
>Serial.readString()は、シリアルバッファから文字を読み、文字列に書き込む。この関数はタイムアウトすれば終了する

との記述を発見
この受信してからデータが表示されるまでのラグは
受信のタイムアウトをわざわざ待っていたということに気が付きました

その後リファレンスを見返すと
>Serial.readStringUntil()は、シリアルバッファから文字を読み、文字列に書き込む。この関数は、終端文字を検出するかタイムアウトすれば終了する(Serial.setTimeout()参照)。

**めちゃくちゃいい関数あんじゃん！！！**

ということで以下のように変更しました

```C++:SerialTaskの改良版
void Serialtask()
{
  if (Serial.available() > 0){
     str = Serial.readStringUntil('\0');
  }
}
```
Serial.readString()をSerial.readStringUntil()に書き換え
終端文字としてナル文字を指定、C#側でナル文字を文字列の末尾につけて送信するようにしました

すると…
![試行錯誤2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705249/10723ab5-179a-0b93-992f-48107eb9c6e6.gif)
ラグがなくなってすんなりと表示ができるようになりました

```C++:loop()
void loop() {
  // put your main code here, to run repeatedly:

  ledAction.check();
  serialAction.check();
}
```
冒頭で宣言したタスクをcheckすることで起動しています

```C++:digitalWriteTo1()
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までの表示を行っています

<a id="#Chapter6"></a>
# 完成!!
完成しました
これを表示しながらゲームするなり
レンダリングするなりベンチマークとったりすると非常に面白いです

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">夏休みの自由研究<br>Arduino uno + C#でCPU使用率を7セグLEDに表示してみた<br>マルチスレッドとかSerial.readStringの仕様とかいろいろ学ぶところあったので後日qiitaに投稿しようかな <a href="https://t.co/99eoPnAssx">pic.twitter.com/99eoPnAssx</a></p>&mdash; uechan (@uechang16) <a href="https://twitter.com/uechang16/status/1423230542762242051?ref_src=twsrc%5Etfw">August 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

夏休みの自由研究(暇つぶし)としてやってみましたが
所要時間3時間程度で終わってしまったため
また何かネタを探してちょろちょろやっていきたいと思います

<a id="#Chapter7"></a>
# 参考にしたサイト

https://qiita.com/bunnyhopper_isolated/items/d4cf96895d665f5401af

とても参考になりました
ありがとうございました