ArduinoとCAN-BUS ShieldでCANデータを取得しSDカードに保存

前回は、ArduinoとCAN-BUS Shield を組み合わせて車両からCANデータを取得し、USB経由でPCのシリアルモニターに表示するプログラムを作成した(こちら)。

あわせて読みたい
ArduinoとCAN-BUS Shield V2.0でクルマのCAN情報を取得してみた 車両には、様々なECU(Electronic Control Unit)が搭載されている。これらのECUは互いに、CAN(Controller Area Network)という通信方式で通信を行っている。そこで、Ar...

しかしながら、毎度パソコンを車両に持ち込んで、USBで接続し、シリアルモニターのデータをコピーペーストしてファイルに保存する、なんてことをやるのは面倒。そこで、作成したArduino+CAN-BUS Shieldに実装されているmicroSDカードスロットルを利用して、microSDカードに取得したCANデータを保存するプログラムを作成した。

なお、Arduinoのプログラム(=スケッチ)で指定するピンがUnoとMegaでは違うが、ピン番号を書き換えればArduino Unoでも使える。

目次

用意するもの

Arduino

今回はArduinoMegaで実装したが、Arduino Unoでも動くはず。

CAN-BUS Shield V2.0

Arduino UnoでもArduino Megaでも、特に問題なく使えると製造元に書いてあったため、これを利用した。

スイッチサイエンス
¥7,100 (2024/11/21 04:52:26時点 Amazon調べ-詳細)

microSDカード(SDHC)

CAN-BUS Shieldに実microSDカードスロットルが実装されているので、それに合うmicroSDカードが必要。SDXC規格のものは使えないので、SDHCを選択する必要がある。カメラや動画データを保存するわけではないため、16GBで十分。相性などはあまりないとは思うが、今回使用したSDカードを紹介しておく。

キオクシア(KIOXIA)
¥550 (2024/11/21 08:43:59時点 Amazon調べ-詳細)

CAN-BUS Shield V2.0へSDカードをセットする

CAN-BUS Shieldに、以下の手順でSDカードをセットする。

ArduinoでのSDカード読み書きプログラム

使用するライブラリの定義

SdFat.hを使用する。SD.hだと、書き込み速度が遅いし、ファイル名が8.3filename形式である必要があるためだ。もともとテストプログラムはSD.hを利用していたが、SdFat.hに変更している。

#include <SPI.h>
#include <SdFat.h>
#include "string.h"
#include "mcp2515_can.h"

ピンの設定

ピンは、以下のように設定している。SS_defaultPin Arduino unoは10, megaは53を設定する必要がある。

// ピンの設定
// CAN通信用のCSピン(digital 9)
const int SPI_CAN_CS_PIN = 9;
// SDカード通信用のCSピン(digital 4)
const int SPI_SD_CS_PIN = 4;
// SS_defaultPin Arduino unoは10, megaは53
const int PSI_SS_defaultPin = 53;
// 動作確認用のLED
const int LED_PIN = 13;

CAN受信IDのフィルタ設定

周期の早いCANデータが大量に出力されている場合、目的のIDのデータを取りこぼしてしまう恐れがある。数フレームであれば実用上問題ないのだが、断続的に続くと、計測精度が低下する。そこで、CAN-BUS Shieldに搭載されているmcp2515では、不要なデータがArduinoアプリ側に流れてこないようにフィルターする機能がある。フィルター機能についてはこちらの記事を参照。

あわせて読みたい
Arduino CAN-BUS Shield V2.0 のフィルター/マスクの設定方法 車両には膨大なCANデータが流れている。これらを解析するに当たり、ArudinoとCAN-BUS Shieldの組み合わせで全て受信して保存しようとすると、データの取りこぼしが発生...

SDカードへの保存(上書き回避)

グローバル変数で予めSdFatのインスタンスSDを定義しておき、setup()でSDとのやり取りを開始する。.begin()コマンドでSD通信を初期化。初期化が完了したら、ファイル名の存在を確認。ファイルが存在するならファイル名を1つインクリメントしたものを新規ファイルとして書き込んでいく処理とした。

// SD通信開始の初期化
  if (!SD.begin(SPI_SD_CS_PIN)) {
    loggingFlag = false;
    SERIAL_PORT_MONITOR.println("SD card initialized Error !!") ;
  }else{
    // 初期化完了
    SERIAL_PORT_MONITOR.println("SD card initialized OK !!") ;
    
    logFileName = base_logFileName + String(fileNum);
    SERIAL_PORT_MONITOR.println(logFileName + LOG_FILE_EXT);
    SERIAL_PORT_MONITOR.println(SD.exists(logFileName + LOG_FILE_EXT));
    while(SD.exists(logFileName + LOG_FILE_EXT)){
       fileNum++;
       if(fileNum==250){
        break;
       }
       logFileName = base_logFileName + String(fileNum);
       SERIAL_PORT_MONITOR.println("log file" +  logFileName);
    }
    if(fileNum>=250){
      SERIAL_PORT_MONITOR.println("Files in SD card are too many...") ;
      loggingFlag = false;
    }else{
      SERIAL_PORT_MONITOR.println("SD log file Name : " +  logFileName);
      loggingFlag = true;
    }
  }

シリアルモニターに出力していた情報をCANから取得するのは、以前作成したプログラムの通り。今回はシリアルモニターに出力していた情報をそのままSDカードに書き込む。

リセットボタンが押されたとき

Arduino UNO/Megaともに、動作中にリセットを行えるボタンが用意されている(下図赤◯)。これを押すと、プログラムが最初から実行されて、先程記載したSDカードの初期化~ログの上書き回避処理が走り、SDカード内部に別名でログデータが一つ作られる動きになる。

これで、気が向いた時にボタンを押すと新しいログファイルを作成することができるようになった。

CANの取得とSDカードへの保存のプログラム

ここまでの解説したプログラムをまとめると以下のようになる。

/*
 * File:   CanPowerCheck.ino
 * Author: 岡本一車
 * https://kurumashikou.com/
*/
#include <SPI.h>
#include <SdFat.h>
#include "string.h"
#include "mcp2515_can.h"
// 計測モード用のフィルタ設定
#define MASK0 0x7FF
// S660 WheelSpeed CAN ID 0x1D0
//#define Filter0 0x1D0
// HA36S WheelSpeed CAN ID 0x1B8
#define Filter0 0x1B8
#define Filter1 0x000
#define MASK1 0x7FF
#define Filter2 0x000
#define Filter3 0x000
#define Filter4 0x000
#define Filter5 0x000
#define LOG_FILE_EXT ".CSV"
// ピンの設定
// CAN通信用のCSピン(digital 9)
const int SPI_CAN_CS_PIN = 9;
// SDカード通信用のCSピン(digital 4)
const int SPI_SD_CS_PIN = 4;
// SS_defaultPin Arduino unoは10, megaは53
const int PSI_SS_defaultPin = 53;
// 動作確認用のLED
const int LED_PIN = 13;
// グローバル変数 ///////////////////////////////////////////
// SDfatのインスタンス
SdFat SD;
// タイムスタンプ用変数
unsigned long timestamp = 0;
unsigned long timestamp_old = 0;
// 受信メッセージ格納用
unsigned long can_ID = 0;
unsigned char len = 0;
unsigned char buf[8];
// 送信メッセージ格納用
unsigned char sendMsg[8] = {0, 0, 0, 0, 0, 0, 0, 1};
// CANデータ書き込み値を保持する
String logDataStr; 
// CANトランシーバにCSピン設定をセットし、CAN通信インスタンスを生成
mcp2515_can CAN(SPI_CAN_CS_PIN);
// 保存するログファイル
File logDataFile;
// ログファイル名
String base_logFileName = "DATALOG_";
String logFileName = "";
// ログファイル通し番号
unsigned char fileNum = 0;
// ログデータのステータス
//false:ログ取得中ではない /  true:ログ中
bool loggingFlag = false;
// 初期設定 ////////////////////////////////////////////////
void setup() {
  // シリアルモニタの設定
  SERIAL_PORT_MONITOR.begin(115200);
  // シリアルポートの準備が整うまで待つ
  while(!Serial);
  
  // 動作確認用LEDのポート設定
  pinMode(LED_PIN, OUTPUT);
  
  // SDファイル初期化処理 //////////////////////////////////////////
  // SSピンのdefaultをOUTPUTに設定する
  pinMode(PSI_SS_defaultPin, OUTPUT) ;
  // SD通信開始の初期化
  if (!SD.begin(SPI_SD_CS_PIN)) {
    loggingFlag = false;
    SERIAL_PORT_MONITOR.println("SD card initialized Error !!") ;
  }else{
    // 初期化完了
    SERIAL_PORT_MONITOR.println("SD card initialized OK !!") ;
    
    logFileName = base_logFileName + String(fileNum);
    SERIAL_PORT_MONITOR.println(logFileName + LOG_FILE_EXT);
    SERIAL_PORT_MONITOR.println(SD.exists(logFileName + LOG_FILE_EXT));
    while(SD.exists(logFileName + LOG_FILE_EXT)){
       fileNum++;
       if(fileNum==250){
        break;
       }
       logFileName = base_logFileName + String(fileNum);
       SERIAL_PORT_MONITOR.println("log file" +  logFileName);
    }
    if(fileNum>=250){
      SERIAL_PORT_MONITOR.println("Files in SD card are too many...") ;
      loggingFlag = false;
    }else{
      SERIAL_PORT_MONITOR.println("SD log file Name : " +  logFileName);
      loggingFlag = true;
    }
  }
  // CANの初期設定 ///////////////////////////////////////////////
  if (CAN.begin(CAN_500KBPS) != CAN_OK) {
    SERIAL_PORT_MONITOR.println("CAN initialized error ..............");
    while(1);
  }else{
  SERIAL_PORT_MONITOR.println("CAN initialized OK !!");
  }
  /*
   * set mask
   */
  // there are 2 mask in mcp2515, you need to set both of them
  CAN.init_Mask(0, 0, MASK0);
  CAN.init_Mask(1, 0, MASK1);
  CAN.init_Filt(0, 0, Filter0);
  CAN.init_Filt(1, 0, Filter1);
  CAN.init_Filt(2, 0, Filter2); 
  CAN.init_Filt(3, 0, Filter3);
  CAN.init_Filt(4, 0, Filter4);
  CAN.init_Filt(5, 0, Filter5);
  // CANフィルタ設定の確認
  SERIAL_PORT_MONITOR.print("mask0= ");
  SERIAL_PORT_MONITOR.print(MASK0);
  SERIAL_PORT_MONITOR.print(" / mask1= ");
  SERIAL_PORT_MONITOR.println(MASK1);
  
  SERIAL_PORT_MONITOR.print("Filter0= ");
  SERIAL_PORT_MONITOR.print(Filter0);
  SERIAL_PORT_MONITOR.print(" / Filter1= ");
  SERIAL_PORT_MONITOR.print(Filter1);
  SERIAL_PORT_MONITOR.print(" / Filter2= ");
  SERIAL_PORT_MONITOR.print(Filter2);
  SERIAL_PORT_MONITOR.print(" / Filter3= ");
  SERIAL_PORT_MONITOR.print(Filter3);
  SERIAL_PORT_MONITOR.print(" / Filter4= ");
  SERIAL_PORT_MONITOR.print(Filter4);
  SERIAL_PORT_MONITOR.print(" / Filter5= ");
  SERIAL_PORT_MONITOR.println(Filter5);
  
  // シリアルポートにヘッダを書き込み
  SERIAL_PORT_MONITOR.println("TimeStamp  CAN_ID  Len Data");
}
// メイン処理 ////////////////////////////////////////////////
void loop() {
  
  digitalWrite(LED_PIN, loggingFlag);
  
  // CANメッセージ受信したときの動作
  if (CAN_MSGAVAIL == CAN.checkReceive()) { 
  // 受信したデータをシリアルモニタに送信 ////////////////////////////////////
  if(timestamp_old == 0){
    timestamp_old = millis();
  }
  // CANデータを取得
  CAN.readMsgBufID(&can_ID, &len, buf);
  // タイムスタンプの取得
  timestamp = millis() - timestamp_old;
  logDataStr  = "";
  logDataStr += String(timestamp) + ",";
  logDataStr += String(can_ID) + ",";
  logDataStr += String(len) + ",";
  for (int i = 0; i < len-1; i++) {
    logDataStr += (String(buf[i]) + String(","));
  }
  logDataStr += String(buf[len-1]);
  logDataStr += "\r\n";
  loggingFlag = writeSD_card(logDataStr);
  
  SERIAL_PORT_MONITOR.print(logDataStr);
  }
}
// SDカードへの書き込み
bool writeSD_card(String logDataStr){
// ファイルの書込みオープン(動作確認用)1
  logDataFile = SD.open(logFileName + LOG_FILE_EXT , FILE_WRITE);
  
  if (logDataFile) {
    // 文字列を書込む
    logDataFile.print(logDataStr);
    // ファイルのクローズ
    logDataFile.close() ;
    return true;
    
  } else {
    // ファイルのオープンエラー
    SERIAL_PORT_MONITOR.println("SD file opening ERROR ......") ;
    SERIAL_PORT_MONITOR.println("------------------------") ;
    return false;
  }
}

(これはアルトワークスHA36S用。S660の場合は、上記プログラムの12行~14行目を以下の様に変更する必要がある)

#define MASK0 0x7FF
// S660 WheelSpeed CAN ID 0x1D0
#define Filter0 0x1D0
// HA36S WheelSpeed CAN ID 0x1B8
// #define Filter0 0x1B8

まとめ

Arduino+CAN-BUS Shieldに実装されているmicroSDカードスロットルを利用して、microSDカードに取得したCANデータを保存するプログラムを作成した。Arduinoのプログラム(=スケッチ)で指定するピンがUnoとMegaでは違うが、そこさえ書き換えればArduino Unoでも使える。

今回作成したCANデータ取得ユニットを使って、S660の馬力を計測してみた記事はこちら

あわせて読みたい
【DIY】CANデータからS660の馬力を計測してみた 自分の車両の馬力がどれくらいで、どのような出力特性をしているかは、車好きならば気になるところ。例えば、ECUを交換するなどのパワー系チューニングを実施した結果、...

アルトワークスのCAN解析と馬力計測の記事はこちら

あわせて読みたい
【DIY】CANデータからアルトワークスの馬力を計測してみた S660に続き、アルトワークスのCANを解析して、馬力を計測してみた。S660の場合は、HondaのCAN解析を行っているプロジェクトがあったのでそちらを参考にしたが、HA36Sア...
この記事をSNSでシェアする
  • URLをコピーしました!

コメント

コメントする

目次