車載ノートPC用の台座をアルミフレームで作成する方法

こちらの記事で、Windows OSのタブレット型ノートPCを車載する方法を書きました。
機種の選定理由や、電源(バッテリーレス)の処理はこちらを参照下さい。
sx99.hatenablog.com


今回はノートPCを載せる台座(テーブル)の作り方について説明します。

スマホ程度の大きさ、重さであれば、カー用品店で売っているスマホホルダーで固定できそうですが、タブレットPCを車載する場合、ある程度剛性が無いと、振動でPCがズレて来てしまうことが予想されたため、アルミフレームで台座となるテーブルを作成しました。

アルミフレームを個人で注文する場合、SUS株式会社か、NICオートテックの2択になると思われます。
個人の注文を受けてくれるのは、この2社ぐらいしかないので。

今回はNICオートテックのアルミフレームを使用しました。NICオートテックの方が、個人の注文に対しても、より多くのラインナップから選択できるので、性能や見た目、拡張性に拘るならNICがお勧めです。
手軽なのはSUS株式会社のG-funというイメージです。


簡単な形状なら、ホワイトボードにイメージ図を書いて、それを基に部品を発注してもいいのですが、
今は3D CADが簡単かつ無料で利用できるので、私は大体3Dモデルを作成して完成イメージを固めてから発注しています。

3D モデルを作成するメリットは、物のサイズ感や剛性感をイメージしやすいのと、発注する際の寸法やブラケットの個数を間違え難いことかなと思います。
アルミフレームを選ぶ際に、フレームの太さ(一辺の長さ)をどうするか悩むポイントかと思いますが、3Dで正しい寸法で形状を見ながら、華奢なのか、ゴツ過ぎる構成なのか、なんとなくイメージして作っています。
計算すれば適切な太さ、長さは出てくるのかもしれませんが、そこまで時間かけてやることもないかなという感覚です。

あと、ホワイトボードのイメージ図だけで発注すると、意外とブラケットの個数を間違えて、あとから追加で発注ってことになるので、そういう意味でも3D CADでモデルを作る方が個人的には安心です。

3D CADは Autodeskの"Fusion 360"を使用しています。個人で非商用利用なら無料で使用できます。無料版は機能制限がありますが、アルミフレームを組む程度なら全く問題になりません。

NICオートテックのホームページからアルミフレームとブラケットの3Dデータをダウンロードして、Fusion 360にインポートしたら、あとは、長さや向きを変えてくっ付けていくだけです。

完成形は以下のような形になります。

無料のフュージョン 360で3Dモデルを作成

車体との固定は、シフトレバーユニットを車体に固定しているボルトと共締めする形で、アルミフレームの下側で固定しています。
PCは上部の円パイプ部に下記のクランプで固定しています。タブレットPCサイズにちょうど良いし、がっちり固定されます。

この構成で、今回の用途としては十分な強度、剛性を得ることができました。
舗装されていない路面を走行しても、がっちり固定されていますし、不安定な感じは全くありません。

アルミフレームの部品代は送料込みで、9,140円でした。


次回は、これまでに車両CANの分析により特定できたCAN id、CAN信号について公開していきます。

Windows OS タブレット型ノートPCを車載する方法

この記事ではノートPCを車載する方法を紹介します。

私はこのような形でノートPCを車載しています。

レッツノートを車載
助手席側から。アルミフレームにクランプでPCを固定

アルミフレームで土台を作成して、そこにタブレット用のクランプでノートPCを固定しています。

PCはパナソニック製のLet’snote CF-AX3を使っています。
今回はPCで自作したProcessingのプログラムで、CAN信号をグラフ化したいという目的がありますので、アンドロイドOSのタブレット端末では私のスキル的にProcessingのプログラミングに支障が出そうだったので、WINDOWS OSのタブレットPCの中から選定しました。

Windows OSのタブレットPCだと、性能的にはサーフェイスが良さげなのですが、中古でも価格が高いので、中古品で安価なレッツノートを選びました。
2021年6月に中古で8,500円程度でした。

スペックは下記の通りです。
Processingを含め、全体的にソフトが起動するのが遅いので、もう少し高いスペックの物(型式の新しい物)を選んだ方が快適だと思います。

【型番】:Let's Note CF-AX3EDCCS
【CPU】 :Core i5-4200U 1.60GHz
【RAM】 :4GBメモリ
【HDD】 :SSD128GB
光学ドライブ】:無し
【液晶】:11.6型 Full HD
【グラフィックス】:(CPU内蔵)

普段は画面を折りたたんでタブレット状態で車載していますが、開けばキーボードも使えるので、出先でちょっとプログラム修正したい時などに便利です。

キーボードがあるので、一般的なノートPCとしても使える

このPCが車載用PCとして向いている点は、内蔵バッテリーを外してもアダプタを接続すれば起動できることです。
車に乗るたび毎回PCを積み下ろしする方はいいのかもしれませんが、常に載せたままにしますので、内蔵バッテリーを外しておかないと夏場は爆発する危険性があります。

調べた限りではAndroid OSのタブレット端末は、内蔵バッテリーを抜くとACアダプタを挿しても電源が入らない製品が多いようですが、Let’snote CF-AX3はバッテリーを抜いてもACアダプタを挿せば起動します。

内蔵バッテリーを外すためには、PCの筺体を分解する必要がありますが、ネットで検索すれば細かい作業まで説明しているサイトがありますので簡単です。(ホントなんでもネットにありますね、今の時代。)

内蔵バッテリーを抜いたら、PCの電源はインバータを車両に搭載して、ACアダプタで取ってもいいのですが、レッツノートはDC 16V電源で動きますので、車両の12V電源をDC/DCで昇圧して使った方がスマートです。
私はコレを使ってます。

パナソニックのPCで使う場合は、下記も必要です。

シガーソケットの電源は、車両の電源を落とすとオフになってしまう、つまりPCも同時に電源が落ちて強制的に終了してしまうので、シガーソケット裏の配線を加工して、常時電源にすることで、エンジンを止めてから、落ち着いてPCのシャットダウン操作ができるようにしています。
シガーソケットを常時電源にするには、ヒューズボックスから下記のような物で電源を分岐させて、接続するのが簡単です。

次回は、PCを載せているフレームの作成方法について説明します。

CAN信号を分析して、実際の値に変換する方法

車両のCAN信号を受信して、PCに表示させることができるようになったら、いよいよCAN信号の分析をしていきます。

過去の記事を基に、ハードとソフトを作成すれば、ProcessingでCAN idと16進のCANデータを表示させることができていると思います。

sx99.hatenablog.com
sx99.hatenablog.com
sx99.hatenablog.com


表示されているCAN値が何を示しているかは、車を操作した時の値の変化から、どのIDの何バイト目が対応しているのかを特定し、さらに、分解能(LSB)やオフセットを推定する必要があります。
全てを特定することはできませんが、ある程度推定することは可能です。

例えば、ステアリング角センサの出力信号を特定する場合は、以下のようにします。
(この記事で特定したCAN id 、CAN信号は私の車のものであり、CAN ID、CAN信号はカーメーカ、もしくは車種ごとに異なります。)

CANを読み出しながら、車両のハンドルを左右に回すと、id 119の3~6バイト目が変化します。
ハンドル操作したときだけ値が変化するため、ここがステアリング角度センサの出力値であることが分かります。

f:id:SX99:20220418031638p:plain
ハンドルを操作して値が変化する場所を見つける

ここで、3~4バイト目(赤枠)と5~6バイト目(黄枠)は2進数換算で"1"ズレているだけで、常にほぼ同じ値が出力されています。
これは、信号出力が2重系になっているか、またはどちらかが補正有り/無しなどの違いによるものだと思います。
走る、止まる、曲がるに関わる機能に使用する信号は、2重系にして信頼性を高める場合があるので、この信号もそのような用途のため2個出力されているのだと思います。
どちらを使っても問題無いと思いますが、今回は赤枠側を使用して説明します。

f:id:SX99:20220418031912p:plain
ほぼ同じ値が出力されている場合がある

ステアリング角の信号用にCANとしては2バイト分確保されていることが分かりました。
2バイト分のMAX値は"FF FF" なので、普通に考えれば、センター付近はその1/2に設定するはずです。
よって、ハンドルセンターの出力は "FFFF" x 1/2 = "7FFF" (10進換算で32,767) と推定します。

これはセンター付近の実際のCAN出力とも近い値なので、問題なさそうです。


次に、ハンドルを右に180°回転させてみると、出力は"78 F6" となります。
センターとの出力差は10進数に変換して計算すると
32,767 - 30,966 = 1,801
となります。

f:id:SX99:20220418032143p:plain
ハンドルを右に180°回転させた場合の出力を確認

今度はハンドルを左に180°回転させると、CAN出力は"87 17" なので、センターとの出力差は
32,767 - 34,583 = -1,816
となります。

右に180°の出力が1,801で、左に180°の出力が-1,816なので、
ここからCAN出力値を1/10にすれば、実際のステアリング角度と一致すると考えられます。

f:id:SX99:20220418032406p:plain
ハンドルセンターの値からCAN出力値を引いて、1/10すればOK

まとめると、CAN出力された値を16進から10進に換算して得られた値から、ハンドルセンターの値 "7FFF" を引いて、1/10すれば、CAN値からステアリング角度に換算することができるのです。

他のCAN信号も、このような感じで分析を進めていくことになります。


分析する上で、ヒントになる情報を以下に書いておきます。
・サービスマニュアルを確認する。
 CAN信号の一覧表が記載されていることがあります。
 記載されている信号が変化しそうな操作をして、どのバイト位置で出力されているか特定しましょう。

・コントローラのフューズを抜く。
 IGオンの状態でコントローラの電源フューズを抜けば、当然そのコントローラが送信していたCAN IDが
 出力されなくなるので、CAN idがどのコントローラの物なのか、特定できます。
 注意)ヒューズを抜くと、他のコントローラがCAN通信異常の故障を検出して、
    警告灯が点灯する可能性があります。

 CANで出力されている値は、基本的にそのコントローラが内蔵しているセンサ、または、ハードワイヤで接続している外部センサの値や、入力されたセンサの値から演算したデータですので、
 そういった観点で分析すると、多少絞り込めると思います。

・海外のフォーラムをチェックする。
 ネットで検索すると海外のサイトで、車両ごとのCAN信号の分析結果について、情報共有が行われていることがあります。後日別で記事にまとめる予定です。


データの分析する際に、PCを見ながら車を走行させる機会が出てくると思います。
次回はノートPCを車載する方法について説明します。


<注意>
走行する際はPCの画面を注視すること無く、周りに障害物の無い広い場所を使う等、
事故が発生しないように十分注意して下さい。
万が一事故が発生しても、当ブログは一切の責任を負いません。

車両のCAN通信のIDを特定する方法

車両のコントローラ間でやり取りされているCAN通信は、MAX 8バイト分のデータを一纏めにして送受信されています。
それらのデータには"CAN id"が付与されており、車両のCAN通信を解析するには、まずこのCAN idを特定する必要があります。

CAN idを特定するためには、以前説明した通信装置を車両に接続し、ARDUINOのシリアルモニタでCAN通信の生値をPCに出力します。

出力したデータをエクセル等でCAN idの部分だけ抽出します。


CANデータを読み出す通信装置は以下で説明しています。
sx99.hatenablog.com

ARDUINOで読み出したCANデータをシリアルモニタで出力するためには、以下のプログラムをARDUINOに書き込みます。


// ライブラリヘッダのインクルード
#include <mcp_can.h>
#include <SPI.h>

//設定値
#define CS_PIN  (10) // CSを10ピンとする(変更可能)
#define INT_PIN (9)  // INTを9ピンとする(変更可能)

long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];

MCP_CAN CAN0(CS_PIN);      

void setup() {
   Serial.begin(115200);
   CAN0.begin(CAN_STDID, CAN_500KBPS, MCP_8MHZ); // CANの通信速度を500kbpsにする
   pinMode(INT_PIN, INPUT); // 割り込みのためのピンを設定
   CAN0.setMode(MCP_NORMAL);
   Serial.println("MCP2515 Library Receive Example...");
}

void loop(){
   //受信
   if(!digitalRead(INT_PIN)) {  // 受信割り込みが発生したら、CANデータをReadする
       CAN0.readMsgBuf(&rxId, &len, rxBuf);
       Serial.print("ID: ");
       Serial.print(rxId, HEX);
       Serial.print("  Data: ");
       for(int i = 0; i<len; i++) {
           if(rxBuf[i] < 0x10) {
               Serial.print("0");
           }
           Serial.print(rxBuf[i], HEX);
           Serial.print(" ");
       }
       Serial.println();
   }
}


ARDUINOの電源がONになっていれば、常時CANデータを読み出すようになっているので、
車のIGをONして、ARDUINOのシリアルモニタを開くと、このようにCANデータを取得できます。

Arduino シリアルモニタによるCANログ読み出し結果

あとは、数秒分取得すれば全CAN idが1度は送信されているはずなので、
あとはこれをコピーしてエクセルに張り付け、
関数でCAN idの部分だけを抽出してやれば、CAN idのリストを作ることができます。

エクセルの関数を使えば簡単にCAN idのみを抽出できる


これで車両から取得したCANデータをある程度処理できる状態になりました。
次回は、取得したCANデータの分析方法について説明します。

車のコントローラ間に流れるCAN信号を読み取る装置の作り方 (Processing編)

これまで車両に流れるCAN信号を読み取るために必要なハードの作り方と、車両からCAN信号を読み出すARDUINOのプログラムを説明してきました。

今回はARDUINOで読み出したCAN信号をパソコンに表示させるプログラムについて説明します。
プログラムはProcessingという言語で作成します。

Processingを使う理由は、最終的な目的として、CAN信号をリアルタイムでPC画面上にグラフ表示させたいためです。
私はプログラミングに関して初心者ですので、ネットで検索してProcessing+Arduinoの組み合わせでデータのグラフ化をされている例がいくつか見つかりましたので、そちらを参考に作成してみました。

まずは、ARDUINOから送られてきたCAN信号を、IDごとに並べて表示するというシンプルなプログラムです。

〜
import processing.serial.*;
Serial port;
int[] id = new int[2]; //シリアル受信生値 id 2byte分 

//=============ウィンドサイズ設定==================
int WinSize_X = 1000;
int WinSize_Y = 700;

//=============CAN生値表示位置設定==================
float CanValPosi_X = WinSize_X*1/100;
float CanValPosi_Y = WinSize_Y*3/100;
int ValPosi = 0;

//=============ログ表示設定==================
int SetFps = 60;       //FPS設定

//============= シリアル受信生値 CAN値 8byte分 ==================
int[] output_024_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_116_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_119_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_11C_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_120_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_122_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_124_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_130_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_180_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_310_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_314_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_318_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_380_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_3A0_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_3D0_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_3F4_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int[] output_3F5_Val = new int[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};


//=============通信開始/終了用==================
int Comflag;

void settings() {
  size(WinSize_X, WinSize_Y);
}

void setup() {
  printArray(Serial.list());//シリアル通信に使用するポートを出力
  port = new Serial(this, Serial.list()[1], 115200);//Serial(this, 上で確認したポートを引数に設定, ボーレート)
  background(0, 0, 0);
  PFont font = createFont("MS Gothic",50);
  textFont (font);
  //noSmooth(); //noSmoothを有効にするとアンチエイリアスOFF

  frameRate(SetFps);// FPSは画面の更新頻度であるとともに、Processingの場合、draw()が実行される間隔となる。
}

void draw() {
// 描画エリア設定
  fill(0);
  noStroke();
  rect(0, 0, width, height);
  fill(255);
  textSize(height/40);
  textAlign(LEFT);
  int j = 1;

//CAN値の表示----------------------------------------------------------------------------
  text("024", CanValPosi_X, CanValPosi_Y*j);
   for(int i = 0; i < 8; i++){
     text(hex(output_024_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);
   }
     j++;
  text("116", CanValPosi_X, CanValPosi_Y*j);
   for(int i = 0; i < 8; i++){
     text(hex(output_116_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);
   }
     j++;
  text("119", CanValPosi_X, CanValPosi_Y*j);
   for(int i = 0; i < 8; i++){
     text(hex(output_119_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);
   } 
     j++;
  text("11C", CanValPosi_X, CanValPosi_Y*j);
   for(int i = 0; i < 8; i++){
     text(hex(output_11C_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);
   }
     j++;
  text("120", CanValPosi_X, CanValPosi_Y*j);
   for(int i = 0; i < 8; i++){
     text(hex(output_120_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);
   }
     j++;
  text("122", CanValPosi_X, CanValPosi_Y*j);
  for(int i = 0; i < 8; i++){
    text(hex(output_122_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);
  }
     j++;
  text("124", CanValPosi_X, CanValPosi_Y*j);
  for(int i = 0; i < 8; i++){
    text(hex(output_124_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);   
  }
     j++;
  text("130", CanValPosi_X, CanValPosi_Y*j);
  for(int i = 0; i < 8; i++){
    text(hex(output_130_Val[i],2), width*(0.05+(0.03*i)), CanValPosi_Y*j);   
  }
}


void serialEvent(Serial port){           // シリアルポートからデータを受け取ったら
  if (port.available() ==10) {           // 10byte分のデータがそろったら処理開始
          id[0] = port.read();
          id[1] = port.read();
            switch(id[0]*256+id[1]){
              case 0x024:          
                for(int i =0; i<8; i++){ 
                  output_024_Val[i] = port.read();
                }
              break;
              
              case 0x116:          
                for(int i =0; i<8; i++){ 
                  output_116_Val[i] = port.read();
                }
              break;

              case 0x119:          
                for(int i =0; i<8; i++){ 
                  output_119_Val[i] = port.read();
                }
              break;
              
              case 0x11C:          
                for(int i =0; i<8; i++){ 
                  output_11C_Val[i] = port.read();
                }
              break;
              
              case 0x120:          
                for(int i =0; i<8; i++){ 
                  output_120_Val[i] = port.read();
                }
              break;
              
              case 0x122:          
                for(int i =0; i<8; i++){ 
                  output_122_Val[i] = port.read();
                }
              break;
                           
              case 0x124:          
                for(int i =0; i<8; i++){ 
                  output_124_Val[i] = port.read();
                }
              break;
              
              case 0x130:          
                for(int i =0; i<8; i++){ 
                  output_130_Val[i] = port.read();
                }
              break;
              
              case 0x180:          
                for(int i =0; i<8; i++){ 
                  output_180_Val[i] = port.read();
                }
              break;
              
              case 0x310:          
                for(int i =0; i<8; i++){ 
                  output_310_Val[i] = port.read();
                }
              break;
              
              case 0x314:          
                for(int i =0; i<8; i++){ 
                  output_314_Val[i] = port.read();
                }
              break;

              case 0x318:          
                for(int i =0; i<8; i++){ 
                  output_318_Val[i] = port.read();
                }
              break;
              
              case 0x380:          
                for(int i =0; i<8; i++){ 
                  output_380_Val[i] = port.read();
                }
              break;
              
              case 0x3A0:          
                for(int i =0; i<8; i++){ 
                  output_3A0_Val[i] = port.read();
                }
              break; 
              
              case 0x3D0:          
                for(int i =0; i<8; i++){ 
                  output_3D0_Val[i] = port.read();
                }
              break; 
              
              case 0x3F4:          
                for(int i =0; i<8; i++){ 
                  output_3F4_Val[i] = port.read();
                }
              break;

              case 0x3F5:          
                for(int i =0; i<8; i++){ 
                  output_3F5_Val[i] = port.read();
                }
              break;
                            
              default:
                 while (port.available()>0)port.read();  // 受信対象外のidの場合はバッファを空にする。データのビットズレに対してもリセットがかかることになる。
              break;  
            }
         
    } else if (port.available() > 10){
        while (port.available()>0)port.read();  // バッファが10byteを超えた場合はバッファを空にし、エラーメッセージを表示
        println("エラー:バッファが10byteを超えました");
        
      }
}


//マウスクリックで通信開始⇔終了
void mouseClicked(){
  port.clear();                  //バッファ領域を空にする 

  if (Comflag == 0xFF){
    Comflag = 0X01;
  }else{
    Comflag = 0xFF;
  }
  
  port.write(Comflag);
}
〜

プログラムの中で、以下の部分は、プログラムを実行するとProcessingのメイン画面の下に、COMポートの番号リストが表示されますので、ARDUINOを接続しているCOMポートの番号を確認して、Serial.list()内に記入してください。
私の環境では”1”だったので、Serial.list()[1]としています。
printArray(Serial.list()); //シリアル通信に使用するポートを出力
port = new Serial(this, Serial.list()[1], 115200); //Serial(this, 上で確認したポートを引数に設定, ボーレート)


CANの読み出しをスタートさせるには、Processingの画面上をクリックする必要があります。(もう一度クリックすると一時停止)
読み出した値は下記のように表示されます。

f:id:SX99:20220324225854p:plain
CAN取得データ


このプログラムはあくまで、私の車(スズキ SX4)向けのプログラムなので、CAN idの部分は車に合わせて変更する必要があります。

では、車両に流れているCAN idをどうやって知るか、、、?
それを次回説明します。

車のコントローラ間に流れるCAN信号を読み取る装置の作り方 (ARDUINOプログラム編)

車両に流れるCAN信号をPCに表示させるためには、車両→ARDUINO→PC→Processingという流れで信号を処理します。

 

作成するソフトとしては、①ARDUINO用と、②Processing用の2つのソフトを作成する必要があります。

ネットで検索すればこの2つのソフトの組み合わせでCANを読み出すプログラムが何個か公開されていますので、そちらを参考に作成しました。

 

なお、プログラムに関しては初心者ですので、詳しくありません。

指摘やアドバイスがあれば、コメントをお願いします。

 

このソフトは「材料と接続編」で作成したハードを前提に作っていますので、そちらも合わせてご覧下さい。

sx99.hatenablog.com

 

ARDUINO用プログラム

// ライブラリヘッダのインクルード
#include <mcp_can.h> //あらかじめARDUINOのCANライブラリである"MCP_CAN_lib"をダウンロードしてライブラリに入れておきます。
#include <SPI.h>
 
//設定値
#define CS_PIN  (10) // CSを10ピンとする(”材料と接続編"で作成したハードと接続するピンをあわせます)
#define INT_PIN (9)  // INTを9ピンとする(こちらもハードにあわせます)
 
long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];
byte inBuf[1];

MCP_CAN CAN0(CS_PIN);      
 
void setup() {
    Serial.begin(115200); //PCとARDUINO間の通信速度の設定です。PC側と同じにする必要があります。基本的に115200bpsでOKです。
    CAN0.begin(CAN_STDID, CAN_500KBPS, MCP_8MHZ); // 車両とARDUINOのCAN通信速度の設定です。車両側のCAN通信速度と合わせる必要があります。500kbpsが一般的です。 
    pinMode(INT_PIN, INPUT); // 割り込みのためのピンを設定
    CAN0.setMode(MCP_NORMAL);
}

void loop(){
  if (Serial.available() == 1) {
      Serial.readBytes(inBuf, 1);
  }else{
    if (inBuf[0] == 0xFF) {         //processingから0xFFを受信したらCANを読み出しprocessingへ送信する。
                                      //Processing側でCANの読み出し開始/停止をコントロールできるようにしています。
        //以下に送信処理を記述
        if(!digitalRead(INT_PIN)) {            // 受信割り込みが発生したら、CANデータをReadする     
          CAN0.readMsgBuf(&rxId, &len, rxBuf); //CAN0.readMsgBuf("受信したデータのidを格納","受信したデータの長さを格納","受信したデータそのものを格納")
          Serial.write(highByte(rxId));        //CAN id送信 下位から2番目のバイトを送信
          Serial.write(lowByte(rxId));         //CAN id送信 下位1バイトを送信
          Serial.write(rxBuf, len);            //車両から受信したCAN値を送信
          for (int i=0; i < 8-len; i++){       //CAN idごとにCANのデータ長が異なるため、そのままだと処理が複雑になるので、
                                               //データ長が8byteに満たないidを送信する場合は、後ろにFFを付け足してデータ長を8byteに揃えています。
            Serial.write(0xFF);
          }
        }
    }
  }
 }

補足として、PC側でCANの読み出し開始/停止をコントロールできるように、

Prcessing側で画面をクリックしたら"FF"をARDUINOにシリアル通信で送信するようにしておき、Processingから"FF"をARDUINOが受信した時だけ、ARDUINOがCANの読み出し&送信をするようにしています。

 

車両から送られてくるCAN信号は、実際には下記のようにIDごとにデータ長が異なります。そのままでは、Processing側で受信したときに、IDを受信しているのか、CAN値なのか分からなくなるので、データ長が8バイトに満たないIDをARDUINOが受信した場合は、不足分のデータ長を"FF"で埋めて、PC側にはどのIDも常に8バイト分 送信するようにしています。

(この方法だと、仮にPC側が1バイト分でも受け損ねたら、その後の信号処理が全て受け損ねた分だけズレる可能性がありますが、今のところそのような現象は発生していません。)

f:id:SX99:20220215021556p:plain

車両のCAN信号にFFを追加して8バイトに統一する

次回はPC側のソフト(Processingプログラム編)を説明します。

車のコントローラ間に流れるCAN通信を読み取る装置の作り方 (材料と接続編)

車の状態をモニタするには、車に流れるCAN通信を読み取るのが有効です。

 

車にはエンジンコントローラ(ECM)、トランスミッションコントローラ(TCM)、ABSコントローラ(ABS)、、、等、いくつかのコントローラが搭載されており、各コントローラ間はCAN通信と呼ばれる通信方式でデータがやり取りされています。

 

車両に搭載されている様々なセンサは、各コントローラに入力され、一部はCAN送信されていますので、CANを読み取る機材があれば、センサを一つ一つハードワイヤで分岐させてモニタしなくても、ある程度の信号をまとめてモニタできるのです。

 

CAN通信自体は規格化されていますので、市販の機材とプログラムを用意すれば車両に流れるデータをモニタすることができます。

 

補足:

一部の車両はCANでは無く、別の通信方式を採用している場合があり、ここに記載の方法では読み出すことができない場合があります。

また、2015年あたりからセキュリティが強化され読み出せない車両が出てきています。

CANが一般的に普及する前の時代の車も当然ながら読み出すことはできません。

通信方式はサービスマニュアルに記載があると思いますので、そちらで確認してみてください。

いつかカーメーカー、機種、通信方式の一覧表を作成して公開できたらいいと思いますので、情報お持ちの方はコメント欄等にご連絡下さい。

 

<必要な材料>

ARDUINO UNO

・MCP2515

・各PINを接続するジャンパ線

・CANライン用の配線

・CANライン用のコネクタ

・100均のケース

・プラスチックねじ

 

完成形は下記のような感じになります。

f:id:SX99:20220126002524j:plain

CANインターフェース 外観

MCP2515が車両のCANを受信するCANドライバです。アマゾンで800円ぐらいで売ってます。CAN送信することも可能です。

 

CANの読み出しだけならば、ARDUINO UNOとMCP2515とそれを繋ぐ線があればいいのですが、車載して使用するのでケースに入れた方が良いです。

ケースは100円ショップにいろんなサイズが置いてあるので、おすすめです。

また、頻繁に車から外したり、付けたりするので、接続はコネクタ等で簡単に脱着できるようにするのをおすすめします。

 

ケースへの固定はプラスチックねじとナットで固定しています。

穴位置はネットで検索すれば公開されていますので、穴あけ位置をマーキングしてドリルで開ければOKです。

f:id:SX99:20220126002953j:plain

裏側 ARDUINOはプラスチックのボルトで固定

<接続>

ARDUINOとMCP2515のPINは、下の表の各行の左右に記載の端子を接続してください。

ARDUINO MCP2515
9 INT
10 CS
11 SI
12 SO
13 SCK
5V VCC
GND GND

 

f:id:SX99:20220126003846j:plain

ARDUINOとMCP2515の接続

次回はプログラムを説明します。