MCP2515を使った自作基板とRaspberry Piで自動車のECUにOBDリクエストを送る

[前回の記事](http://qiita.com/suzutsuki0220/items/8642b1c3ea51859a95ad) では、作成した基板で車両から流れてくるデータを受け取る所まで確認しましたが、今回はOBDのリクエストを送って、それに対するレスポンスを受け取るまでを行います

OBD-2 の6番ピンと14番ピンにはCAN High-Speed(CAN-C)のデータが流れています。CAN High-Speed(以下CANと略す)は車載ネットワークの種類の一つで、エンジンを制御する電子制御ユニット(以下ECUと表記)など主に走行に関わる機器との通信に使われています

# CANの概要

ここでは、実験に必要な説明にまでに留めています。詳しい説明は別のサイトや本などで確認してください

* 全てのデータがブロードキャストされ、ID(CAN-ID)によって機器が自分にとって必要なデータなのかを判断している
* 規格上の最大通信速度: 1Mbps (車両では CAN High-Speed で 500kbps、 CAN Middle-Speed で 250kbps が使われる)
* 一度に送信できるデータ量: 8Byte

## プロトコル

CANには「データフレーム」「リモートフレーム」「エラーフレーム」「オーバーロードフレーム」の4種類のフレームがあり、ECUからの情報取得にはデータフレームを使用します

データフレームには標準フォーマット(11bit CAN)と拡張フォーマット(29bit CAN)があります。乗用車では主に標準フォーマットが使用されており、拡張フォーマットはトラックやバスを中心に使われています。ただし、ホンダの乗用車では拡張フォーマットが採用されているようです

どちらのフォーマットでも送るデータには変わりはありません。後述するCAN-IDが変わるくらいです。ただし、標準フォーマットにしか対応していない車に拡張フォーマットのデータを流してしまうと、エラー扱いになりトラブルの原因になることがありますので注意が必要です。(逆に拡張フォーマット対応の車に標準フォーマットのデータを流すことは問題ないはず)

### 標準フォーマットのフレーム構造

|| 1 | 2 | 3 | 4 | 5 | 6 | ・・・|
|:--:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|名前| SOF | ID | RTR | 予約領域 | DLC | Data |・・・|
| Bit数 | (1) | (11) | (1) | (2) | (4) | (0～64) ||

* SOF: データの開始位置を示す(Start Of Frame)。0が入る
* ID: 送信先を識別する (Identifier)
* RTR: データフレームでは0になる。リモートフレームのリクエストの時に1になる (Remote Transmission Request)
* 予約領域: 0が入る
* DLC: データの長さを示す (Data Length Code)
* Data: 0～8バイトのデータ

Dataの後にはCRCフィールド等が続きますが、ソフトの方では意識することなくCANコントローラーの方で処理されるので省略します

### 拡張フォーマットのフレーム構造

|| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |・・・|
|:--:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|名前|SOF|ベースID|SRR|IDE|拡張ID|RTR|予約領域|DLC|Data|・・・
|Bit数|(1)|(11)|(1)|(1)|(18)|(1)|(2)|(4)|(0～64)|

標準フォーマットとほぼ同じで、IDがベースIDと拡張IDを使って表されるだけの違いです

IDの後に拡張フォーマットを示すためのSRRとIDEが続きます。これらは固定で1がセットされています

* SRR(Substitute Remote Request Bit)
* IDE(Identifier Extension Bit)

※フレーム構造の説明もかなり省いています。他のサイトではマイコンを使ってCAN通信を使っている例が多くあり、それらは、フレームのデータをシリアルで直接送っている物です。それに対し、この記事はLinuxアプリの can-utils を使うケースで、データ部のみを指定する物のため、それらの違いを説明するために記載しました

## 送信先 (CAN-ID)
送信先をCAN-IDで指定します。これはLANでいうIPアドレスに相当します。

ECUのCAN-IDは ISO15765-4 の規格で定められており、7e0(11bit CANの場合) か 18da00f1(29bit CANの場合) になっています。ECUが複数ある場合は7e1, 7e2, 7e3 ...(11bit CANの場合) か 18da01f1, 18da02f1, 18da03f1 ...(29bit CANの場合) になりますが、どのECUに送るか不明な場合は 7df(11bit CANの場合) や 18db33f1(29bit CANの場合) で送れば、応答できるECUの全てからレスポンスが返ってきます。しかし、一つのデータを取るために複数のECUがレスポンスを返してしまうと、無駄な通信になりますので、可能な限りECUを特定して送信先を決める方が良いでしょう

11bit CAN

| CAN-ID | 説明 |
|:------:|:----:|
| 7df | リクエストを理解できる全ての ECU へリクエスト |
| 7e0 | ECU#1 (ECM: エンジン制御モジュール) へのリクエスト |
| 7e8 | ECU#1 からのレスポンス |
| 7e1 | ECU#2 (TCM: トランスミッション制御モジュール) へのリクエスト |
| 7e9 | ECU#2 からのレスポンス |

29bit CAN

| CAN-ID | 説明 |
|:------:|:----:|
| 18 db 33 f1 | リクエストを理解できる全ての ECU へリクエスト |
| 18 da xx f1 | ECU#xx へのリクエスト |
| 18 da f1 xx | ECU#xx からのレスポンス |

※ xx には 00 からのhex値が入ります

## モード

目的に応じて以下のモードがあります。モードはサービスと呼ぶこともあります

01：現在の車両コンピューターの制御情報を得る
02：故障した時の車両コンピューターの制御情報を得る
03：現在発生している故障コードを得る
04：故障コード、故障履歴を消す
05：排気ガスセンサーの動作を検査する
06：時々しか作動しない装置（例：燃料タンク蒸発ガス吸収装置）の動作を検査する
07：過去に一度でも発生した故障コードを得る
08：外部テスター入力値でエンジン車両部品を動かす（例：ガソリン噴射量を増やす）
09：車両情報を得る（例：車の形式）

## PID

取得したいデータをPIDで指定します。PIDはwikipedia(英語)にまとめられています

https://en.wikipedia.org/wiki/OBD-II_PIDs

その中からよく見かけるPIDを取り上げてみました

Mode1

| PID | 内容 | 単位 | 数式 |
|:----:|:---:|:---:|:---:|
| 04 | エンジン負荷 | % | 100 / 255 * A |
| 05 | 冷却水温度 | ℃ | A - 40 |
| 06 | 短期燃調(STFT) | % | 100 / 128 * A - 100 |
| 07 | 長期燃調(LTFT) | % | 100 / 128 * A - 100 |
| 0B | 吸気マニホールド内気圧 | kPa | A |
| 0C | エンジン回転数 | RPM | (256 * A + B) / 4 |
| 0D | 速度 | km/h | A |
| 0E | 点火進角 | deg | A / 2 - 64 |
| 0F | 吸気温度 | ℃ | A - 40 |
| 11 | アクセル開放率 | % | 100 / 255 * A |
| 2F | 燃料タンク残量 | % | 100 / 255 * A |
| 46 | 外気温 | ℃ | A - 40 |

※各内容は勝手な意訳です。専門用語は英語のままの方がしっくりくるかもしれません

## データ

### リクエスト

リクエストデータの書式は次の通りです

|| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:--:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 説明 | データ長さ | モード | PID | 0 | 0 | 0 | 0 | 0 |
| 桁数 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |

* それぞれの値は16進数の2桁で表現します
* 長さはバイト数(16進数2桁で1バイト)です。ここではモードとPIDの2つのデータを送るので常に2になります
* 4桁目以降の0はデータを8バイトにするために埋めるダミーです

例: モード1のエンジン回転数を得るには以下のリクエストになります

<pre>
02010c0000000000
</pre>

### レスポンス

車両から得られるデータの書式は次の通りです

|| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|:--:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 説明 | データ長さ | モード | PID | データA | データB | データC | データD | データE |
| 桁数 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |

* それぞれの値は16進数の2桁で表現します
* 長さはバイト数(16進数2桁で1バイト)です。8に満たない余った部分は0で埋められます
* モードはリクエストのモードに対して0x40が加算されます

例: エンジン回転数のレスポンスは次のように返ってきます

<pre>
04410C0C7D000000
</pre>

# can-utilsのコマンド

CANのデータについて説明しましたが、次に実際にRaspberry Piからコマンドを送信する手順を説明します

## 送信

リクエストを送るコマンドは cansend を使います

```shell-session:コマンド書式
cansend ${インタフェース} ${CAN-ID}#${データ}
```

CAN-IDとデータの間に # を挟みます

```shell-session:例)エンジン回転数を取得する
# 11bit CAN の場合
$ cansend can0 7e0#02010c0000000000

# 29bit CAN の場合
$ cansend can0 18da01f1#02010c0000000000
```

## 受信

レスポンスを受け取るには candump を使います

```shell-session:コマンド書式
candump ${インタフェース},${CAN-ID}:${bitmask}
```

* CAN-IDはリクエストとレスポンスでそれぞれ違います
* bitmaskは取得するデータを振り分けるために指定します。ビット演算で受信したデータのCAN-ID & bitmask = 引数で指定したCAN-ID になった時に取得します

例: ECUからのリクエストを待ち受ける

```shell-session:データ送信
11bit CAN の場合
$ candump can0,7e8:7fe

29bit CAN の場合
$ can-utils-master/candump can0,98daf100:ffffff00
```

## 実験

1. 車両のOBD-2コネクタに自作ケーブルを繋ぎ、エンジンをかけます
2. Raspberry Piを起動し、ターミナルを2つ立ち上げます
3. 1つのターミナルで candump を実行してECUからのリクエストを待ち受け状態にします
4. もう1つのターミナルでcansendを実行します
5. candumpを実行したターミナルで取得したデータが表示されます  
取得イメージ
![image](https://qiita-image-store.s3.amazonaws.com/0/61404/c2421802-5f01-58ff-6315-4fdb16a741d7.png)
※ recv_11.sh は上のcandumpコマンドの例を実行している物です

### 取得したデータの見方

<pre>
04 41 0C 0C 7D 00 00 00
</pre>

最初の 04 はデータの長さ、41 はモード1のリクエストに対するレスポンスのモードで、続く 0C はエンジン回転数を示すPIDです。

0C と 7D がデータです。7Dより後の 00 は余りのデータなので無視します。このレスポンスはエンジンの回転数ですが、hexでは小数点やマイナスを表現できないので、PID毎に決められた数式にかけることで、値が求められます。

エンジン回転数の場合は以下の数式を使います

<pre>
(256 * A + B) / 4
</pre>

ここで、A には1桁目の 0C が入り、B には2桁目の 7D が入ります

分子の 256 * A + B は一見難しそうに見えますが、256 は hex で 0x100 なので、0x0C * 0x100 + 0x7D → 0x0C00 + 0x7D → 0x0C7D となり、単にhexを繋げて4桁にすれば良いだけです

これを10進数になおして計算すると 3197 / 4 = 799.25 となり、
エンジン回転数 799.25 rpm が取得できました

# 補足

## bootパラメーターについて

前回の手順で /boot/config.txt にSPI周りの設定を書きましたが、まれに受信エラーが出ていましたので、spi-dma を追加した方が良いかもしれません。

<pre>
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25
dtoverlay=spi-bcm2835
dtoverlay=spi-dma  ★ この行を追加 ★
</pre>

エラーとの関係は分かっていませんし、基板やケーブルの接触不良もあるかもしれませんが、何となく良くなったような気がしてますので、これで様子見してます

## OBD-2のデータを取ることについて

このように車両のデータを取ることで、何か悪い事をしているような後ろめたい気持ちが沸いてくるかもしれません。それについて、個人的な見解になりますが、全然気にする必要は無いと考えています。以下のように国土交通省も指針を出しています。

> 今般、自動車に備え付けらている排ガス装置について、ユーザーが円滑に点検整備を行い、自動車の性能を適切に維持することにより、自動車の環境の保全等を図ることを目的に、Ｊ―ＯＢＤ２（排気に係る装置の車載式故障診断装置：OBDとはOn Board Diagnosisのこと。）の装備が義務付けられた自動車（ガソリン又はＬＰＧを燃料とする乗車定員10人以下又は車両総重量3.5t以下の自動車等）を対象として、自動車製作者等が提供する情報の内容や方法を指針に定めました。

http://www.mlit.go.jp/report/press/jidosha09_hh_000041.html

つまり、OBD-2から得られる情報はユーザーにとっても有用なものであるから、自動車メーカーは提供すべきだということです。



## 注意と自己責任のお願い

Mode1のリクエストを送るくらいではトラブルになることは無いと思いますが、くれぐれも、結果の分からないPIDのリクエストや、不必要なリクエストの再送は止めた方良いでしょう。車種によってはECUの負担になってエンジンの制御がおろそかになるものもあるようです。

検査モードの実行など、ECUからの値を取得する以外のリクエストについては、この記事では想定していません

初めて自作基板を接続した場合は、予期せぬトラブルが起きても対処出来るように、今まで以上に安全に注意して運転してください。もし運転中に車の動作に異常を感じた場合は、安全な場所に停車してOBD-2ケーブルを外してください。

この記事を参考にして、機器や車両の故障、事故等の被害が起きたとしても責任は負えませんので、ご了承願います。

この記事は、自分が所有する車で行うことを想定しています