UM690に空冷機能を追加して実質UM690S化する

# 前置き
久々の投稿がこれになったけど、仕方ないんや。
MinisforumのUM690に後継機（というか機能改善）の[UM690S](https://store.minisforum.jp/products/minisforum-um690s)が出たのが悪いんや

[Minisforum](https://store.minisforum.jp/)は、割と性能の良いミニPCをお手頃価格で出している会社です。
で、前々からセカンドPCほしいなーと思ってて、数ヶ月前にUM690という製品を購入したのですが、この製品 M.2(SSD)が裏面にあるため、CPUファンの恩恵を受けれないんですよ。
ちゃんとヒートシンクはあるんで普段遣いでは困らないんですが、ちょっと負荷の高いゲームなどを実行すると熱暴走でPCが落ちたりします（CPU側冷却は問題なくて、M.2側の冷却が能力不足な感じです）

なので、物理的にDCファンつければ直るだろうけどちょっとめんどくさいなーと思って今まで放置していました。
でも、公式から空冷対応版がでちゃって悔しい（悲しい）ので自力でUM690に空冷機能を追加して、ついでにoLEDで温度表示もつけちゃおうというのをやりました。


出来たのがこちら。
![2023-11-14 14.45.41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/3b31f03a-0b57-cc08-5191-3e0b8d81b55f.jpeg)

# 作り方

Fusion360で下につけるケースをデザインする。

stlファイルは[こちら](https://www.dropbox.com/scl/fi/zhrl6smqlqp6na0v5t4t1/UM690_custom.zip?rlkey=jq4cr5izfqb7aa61ediqml85i&dl=0)。


Amazonで良さげなDCファンを探しつつ、裏面取付のSSDもそのまま使えるようにしつつ、ちょこちょとっとデザインを修正しつつ
※ [DCファン](https://www.amazon.co.jp/PENGLIN-3D%E3%83%97%E3%83%AA%E3%83%B3%E3%82%BF%E3%83%BC%E7%94%A8-%E5%86%B7%E5%8D%B4%E3%82%BF%E3%83%BC%E3%83%9C%E3%83%96%E3%83%AD%E3%83%AF%E3%83%BC%E3%83%95%E3%82%A1%E3%83%B3-%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%96%E3%83%99%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0-50x50x15mm/dp/B0C61T518J/ref=sr_1_16?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=15OQTB90ABMQI&keywords=DC%E3%83%95%E3%82%A1%E3%83%B3+5v&qid=1699942010&sprefix=dc%E3%83%95%E3%82%A1%E3%83%B3+5v%2Caps%2C212&sr=8-16)は、Amazonのブロアファン2個入りを使用。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/697d963b-7f3b-cdf9-c6af-e4f48a1f748a.png)

あと、温度も取得したかったので、サーミスタもAmazonでポチっておきました。
DCファンの回転数制御と温度管理はいえに転がってた [Seeed XIAO nRF52840](https://akizukidenshi.com/catalog/g/gM-17341/)を使用（arduinoで小さくて収まればなんでもいいと思う）

書き忘れてた。
OLED液晶はamazonで [こちら](https://www.amazon.co.jp/gp/product/B07FNFYZRV/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1)のを使ってます。
サーミスタもamazonで　[こちら](https://www.amazon.co.jp/Xiatiaosann-%E6%B8%A9%E5%BA%A6%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BCNTC%E3%82%B5%E3%83%BC%E3%83%9F%E3%82%B9%E3%82%BF%E3%83%97%E3%83%AD%E3%83%BC%E3%83%96-%E3%82%A8%E3%82%A2%E3%82%B3%E3%83%B3%E7%94%A8-%E3%83%95%E3%82%A1%E3%83%B3%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC-%E6%B8%A9%E5%BA%A6%E6%B8%AC%E5%AE%9A%E7%94%A8%E3%81%AE%E9%AB%98%E6%84%9F%E5%BA%A6%E3%80%81%E8%B5%A4%E3%80%8110%E5%80%8B/dp/B09BV65GMZ/ref=sr_1_16?keywords=%E3%82%B5%E3%83%BC%E3%83%9F%E3%82%B9%E3%82%BF&qid=1699952489&sr=8-16&th=1)を使ってます（100Kの方）

で、3Dプリンタで召喚して、その間に制御回路づくり（というほどのものでもないけど）

KiCADで回路図をカキカキ。
ショットキーとか各種抵抗やコンデンサなどはざっくり適当です。（ネットから情報拾って書き直しただけなので保証できません）
BSS138とかは秋月で25個パックとかで売ってるので入手しやすいと思うけど、リミット300mAなので、もうちょい余裕あったほうがいいのかもです（DCファンが0.1Aを2台なのでいけるっちゃいける？）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/3b66d512-6ff7-47ed-623c-893ccaae454a.png)

で、あとはユニバーサル基板を適当に切ってハンダして回路作ったら3Dプリンタで作ったガワに組み込み。

![2023-11-14 14.50.07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/861433f1-ee5b-66cd-3178-8a1fff6ea3f3.jpeg)

あとは、蓋をして制御プログラムの作成。

Arduino IDEでコードカキカキ（設定などは割愛。ぐぐってくだせぇ）

```arduino
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

const int fanPin = A0;
const int tempPin = A1;

const float Vref = 3.3; // 基準電圧 (3.3V と仮定)
const float Rfixed = 100000; // 分圧抵抗の値 (100KΩ)
const float B = 3950; // B 定数
const float Tnominal = 298.15; // ノミナル温度 25℃ をケルビンで表したもの
const float Rnominal = 100000; // ノミナル抵抗値 (100KΩ)

const int fanSpeedMin = 190;
const int fanSpeedMax = 255;
const float tempMin = 35.0;
const float tempMax = 55.0;
const float tempThresholdOn = tempMin + 8; // ファンがオンになる温度
const float tempThresholdOff = tempMin; // ファンがオフになる温度
bool fanOn = false; // ファンの状態を追跡するフラグ

int fanSpeed = fanSpeedMin;


void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  // Clear the buffer
  display.clearDisplay();
  display.display();

  pinMode(fanPin, OUTPUT);
  pinMode(tempPin, INPUT);
  analogWrite(fanPin, fanSpeedMax);

  delay(2000); // Pause for 2 seconds
}

void loop() {
  float temperature = readTemperature(); // 温度を読み取る

  fanSpeed = calculateFanSpeed(temperature);
  analogWrite(fanPin, fanSpeed);

  drawOLED(temperature, fanSpeed);  
  delay(1000);
}

float readTemperature() {
  int analogValue = analogRead(tempPin); // アナログピンから値を読み取る
  float Vout = (analogValue / 1023.0) * Vref; // 電圧へ変換
  float Rthermistor = Rfixed * (Vref / Vout - 1); // サーミスタの抵抗値を計算
  float lnR = log(Rthermistor / Rnominal); // サーミスタの抵抗値の自然対数を計算
  float T = (1 / (1/Tnominal + lnR/B)); // 温度を計算 (ケルビン単位)
  float temperature = T - 273.15; // ケルビンを摂氏に変換
  return temperature;
}



void drawOLED(float temperature, int fanSpeed) {
  display.clearDisplay();

  display.setTextSize(2);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  display.print(F("Temp:"));
  display.print(temperature, 1);
  display.println(F("C"));
  display.print(F("Fan :"));
  if (fanSpeed == 0) {
    display.println(F("OFF"));
  }else{
    int fanPersent = map(fanSpeed, 190, 255, 10, 100);
    display.print(fanPersent);
    display.println(F("%"));
  }
  display.display();
}

// 温度に基づいてファンスピードを指数関数的に計算する
int calculateFanSpeed(int temperature) {
  if (temperature >= tempThresholdOn) {
    fanOn = true;
  } else if (temperature <= tempThresholdOff) {
    fanOn = false;
  }

  if (fanOn) {
    // 温度が55度以上の場合は255を返す
    if (temperature > tempMax) {
      return fanSpeedMax;
    }
    // 温度が40度から55度の間の場合、指数関数的な増加を計算する
    float normalizedTemp = float(temperature - tempMin) / (tempMax - tempMin);
    return fanSpeedMin + (fanSpeedMax - fanSpeedMin) * pow(normalizedTemp, 2); // 2乗で指数的に増加
  }else{
    return 0;
  }
}
```

で、XIAOに書き込みして動作チェックして完成！



完成したのがこちら

![2023-11-14 14.45.41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/3b31f03a-0b57-cc08-5191-3e0b8d81b55f.jpeg)
![2023-11-14 14.46.45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/278cef67-34b9-cb3b-113a-975432c29539.jpeg)
![2023-11-14 14.46.57.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/d283f6bd-ec7f-ac96-ae7e-ccb9dacfe974.jpeg)
![2023-11-14 14.48.55-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51900/2770a8cd-e4d6-53e5-3fe6-a7b693f60ff4.jpeg)

うん、満足！