排気温度計の自作

お久しぶりです バレルです
長い間放置となっていたこのブログ
特に理由があったわけではないですが書くこともなかったというのが実態です
そんな中ですが久々に記事にできそうなネタが出来たので投稿です。

製作理由

市販品でもいくつかCANを使用して排気温度を入力するモジュールは販売されています。
ただし市販品の多くが4ch~8chのものが多くまた価格も5万円ほどします
アルトワークスに使用するにあたっては正直1chあれば十分です
市販品でも1chのものがあるのですが入手性がいまいち良くないのもあって製作することにしました。

製作

用意するもの

Aruduino(Nano every等安いもので十分)
K型熱電対モジュール(今回はMAX31855)
K型熱電対(なるべく高温対応のもの)
温度表示用ディスプレイ(なくても良い)
CAN BUSモジュール
ユニバーサル基盤
・配線類

製作にあたって

今回Aruduinoを使用して作成するのですが、プログラムについての知識は皆無の為
自分でやるのはあまりにも無謀です
そこで今回頼りにしたのはChat GPTです
また今回はCANの同期処理までは行っておらずLINKECUと自作CANモジュールの1:1での接続までとしています。

製作開始

Arduino NANO EVERYです
サイズは小さく45mm×18mm程度です
まずは温度測定が出来るかどうかなのでMAX31855の熱電対モジュールとのみ接続し温度の測定が出来るかやってみます

Chat GPTにArduinoとMAX31855モジュールを使用して温度測定をしたいと質問すると接続のピン配置から
書き込むプログラムまで用意してくれます
本当にこんなので行けるのか・・・?と半信半疑ながらも機器の接続をしてプログラムを書き込むと・・・

測定出来てしまいました
C= に熱電対の温度が
Internal Tempに基板上で測定している校正用の温度が表示されています。
あまりにもあっけなく出来てしまいました

ということで次はCANモジュールの接続です

実際にLinkecuも用意し接続をするのですがここで一旦沼にハマりました
CANの送信IDやフレーム情報の設定がLinkecuとArduinoでどこを合わせればよいのかわからないのです

LINK ECUのCAN設定画面

  // CAN送信処理
  if (currentMillis - lastCANUpdate >= CAN_INTERVAL) {
    lastCANUpdate = currentMillis;

    int16_t temp_send = (int16_t)tempC;
    byte data[2] = { highByte(temp_send), lowByte(temp_send) };

    byte sndStat = CAN.sendMsgBuf(0x01, 0, 2, data);
    Serial.println(sndStat == CAN_OK ? "CAN message sent successfully." : "Error sending CAN message.");
  }

ArduinoのCAN送信コード部分

対応している部分がわからずしばらく設定を弄っては通信できず頭を抱えていました。
コードを書き換えたりChat GPTに聞いてみたりしますが最終的にはLinkecu側のCANの幅設定が間違っていたことがわかりました。
この部分もChat GPTの指示通りにしていたのですがAIの回答が間違っていたわけです・・・
この辺りは自分でもコードを理解しながらやらないといけない部分でした。
ただ、このトラブルのおかげである程度CAN部分のコードが理解できたので良かったです。









ということで無事にLinkEcuに入力出来ました。

機能としてはここまでです
初めてのArduinoは結果としてはやりたいことを全部で来たという感じです。
下記に今回使用したコードを参考までに記載しておきます

#include <SPI.h>
#include "Adafruit_MAX31855.h"
#include <mcp_can.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>

// MAX31855 - ソフトウェアSPI設定
#define MAXDO   5
#define MAXCS   6
#define MAXCLK  7
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);

// MCP2515 - ハードウェアSPI (CSピン10)
const int CAN_CS_PIN = 10;
MCP_CAN CAN(CAN_CS_PIN);

// ST7789 TFT ピン設定
#define TFT_CS        4
#define TFT_RST       2
#define TFT_DC        3
#define TFT_WIDTH     240
#define TFT_HEIGHT    240
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

int lastDisplayedTemp = -9999;

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // ST7789 初期化
tft.init(240, 240);  // または 240, 240 に画面サイズを合わせて
tft.setRotation(2);  // 必要に応じて向きを調整
tft.fillScreen(ST77XX_BLACK);
tft.setTextWrap(false);  // 折り返し防止
  

  Serial.println("Initializing MAX31855...");
  if (!thermocouple.begin()) {
    Serial.println("MAX31855 initialization failed!");
    tft.setCursor(10, 100);
    tft.print("ERR");
    while (1);
  }

  Serial.println("Initializing MCP2515...");
  if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
    Serial.println("MCP2515 initialized successfully.");
  } else {
    Serial.println("MCP2515 initialization failed!");
    tft.setCursor(10, 100);
    tft.print("CAN ERR");
    while (1);
  }

  CAN.setMode(MCP_NORMAL);
  
  delay(100);
}

unsigned long lastDisplayUpdate = 0;
unsigned long lastCANUpdate = 0;
const unsigned long DISPLAY_INTERVAL = 16;
const unsigned long CAN_INTERVAL = 10;

int prevNumDigits = 0;

void loop() {
  unsigned long currentMillis = millis();
  double tempC = thermocouple.readCelsius();
  int tempInt = (int)tempC;

  if (isnan(tempC)) {
    Serial.println("Thermocouple read error");
    tft.fillScreen(ST77XX_BLACK);
    tft.setCursor(80, 100);
    tft.setTextColor(ST77XX_RED);
    tft.setTextSize(3);
    tft.print("ERR");
    return;
  }
  //温度ごとの文字色
  if (tempInt != lastDisplayedTemp) {
    lastDisplayedTemp = tempInt;

    uint16_t textColor;
    if (tempInt > 350) {
      textColor = ST77XX_RED;
    } else if (tempInt > 300) {
      textColor = ST77XX_ORANGE;
    } else if (tempInt > 200) {
      textColor = ST77XX_YELLOW;
    } else if (tempInt > 100) {
      textColor = ST77XX_GREEN;
    } else {
      textColor = ST77XX_WHITE;
    }

    // 表示設定
    int textSize = 11;
    int charW = 6 * textSize;
    int charH = 8 * textSize;
    int numDigits = (tempInt >= 1000) ? 4 :
                    (tempInt >= 100) ? 3 :
                    (tempInt >= 10) ? 2 : 1;

    int actualWidth = numDigits * charW;
    int x = (240 - actualWidth) / 2;
    int y = (240 - charH) / 2;

    // 桁数が変化したときだけ全体を消去
    if (numDigits != prevNumDigits) {
      tft.fillRect(0, y, 240, charH, ST77XX_BLACK);
      prevNumDigits = numDigits;
    }

    // 数字描画
    tft.setTextSize(textSize);
    tft.setTextColor(textColor, ST77XX_BLACK);  // 背景指定で上書き描画
    tft.setCursor(x, y);
    tft.print(tempInt);
  }

  // CAN送信処理
  if (currentMillis - lastCANUpdate >= CAN_INTERVAL) {
    lastCANUpdate = currentMillis;

    int16_t temp_send = (int16_t)tempC;
    byte data[2] = { highByte(temp_send), lowByte(temp_send) };

    byte sndStat = CAN.sendMsgBuf(0x01, 0, 2, data);
    Serial.println(sndStat == CAN_OK ? "CAN message sent successfully." : "Error sending CAN message.");
  }
}

車載用モジュール化

機能としては問題なく作成できたので車載に向けて基板にまとめBOXに収めます

ユニバーサル基盤に乗せて裏面はイイ感じに配線します

納める箱は3Dプリンターで印刷で簡単に作成

それっぽい蓋も作ります

ディスプレイの台座も作成してモジュール化完了です

最後に排気温度計として使用出来る事を確認するためコンロで熱電対を炙って900℃越えまで測定出来る事を確認しました。









LinkecuのCANに触れるのも初めてArduinoも初めてということもあり正直上手くいくとは思っていませんでしたが
Chat GPTの力を借りたり翻弄されたりしてなんとか形にすることが出来ました。

電子部品の自作は感電、火災等の危険が有ります
また、本記事を参考にし自作した部品を車両に搭載しトラブルが発生した場合等の責任は一切取れません。
自作は自己責任という事をご理解ねがいます。

以上、久々のブログネタでした。