TL;DR
Raspberry PiでCAN通信を動かして、車両の診断データ(OBD-II)を見てみた話です。 SocketCANのISO-TPの機能が便利だったので、その紹介がメインになります。
はじめに
この記事はaptpod Advent Calendar 2019の24日目の記事です。 お送りするのは、組み込みソフトチームの松下です。
- TL;DR
- はじめに
- 背景と目的
- 準備
- CAN通信の環境構築
- OBD-II通信の環境構築
- OBD-II通信してみる
- 注意と自己責任(免責事項)
- あとがき
- 弊社ソリューションのご紹介
- 参考リンク(書籍)
背景と目的
弊社の製品であるintdash Automotive Proは、車両CAN(Controller Area Network)データ等のデータロギング、可視化・解析などのワークフローをクラウドシステムで実現するソリューションです。このAutomotive Proをベースに、車両診断(ダイアグ通信)のワークフローをクラウド化できないか?という要望があり、車両診断に関する機能も開発中です。
この開発の過程で、実際にRaspberry Piを使って車両診断(OBD-II)通信を試したので、そのやり方を紹介します。
車両診断とは?
診断機能の概略については、Vector社のはじめての診断をご一読ください。 本記事では、CANを使ったOBD-IIによるダイアグ通信を紹介します。
準備
車両とCAN通信するために、Raspberry Piを用いた方法を簡単に説明します。
Raspberry PiでCAN通信する方法は、MCP2515を接続する方法が数多く紹介されています。 本記事でも、このMCP2515を使用します。 (詳細は、参考リンクをご覧ください)
確認環境
以下、筆者の動作確認環境です。
- Raspberry Pi 3B+
- Aptpod社製 ラズパイ拡張ボード
- 車両は弊社所有のHonda FIT
Raspberry Pi
- Raspberry Pi 3B+
- OS: Raspbian buster
$ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster
CAN通信用モジュール
今回は弊社で開発したラズパイ用の拡張ボードを使用します。
といっても、拡張ボード上はMCP2515をラズパイのSPIとGPIO25に接続する、一般的な方法で実装しています1。
データシートや、「MCP2515 回路
」等で検索すると出てくる回路図を参考にしてください(こちらの記事がわかりやすいです)。
もしくは、Amazonで売ってるMCP2515モジュールでも大丈夫だと思います。
宣伝 紹介ですが、弊社のラズパイ向け拡張ボードは、CAN通信以外にも、以下の機能を提供しています。
- RTC(ハードウェアクロック)
- 電源スイッチ機能
- 電源管理機能(5V〜19Vの入力が可能)
- 状態通知用のLED(アプリケーションから制御可能)
また、弊社ではCANバスに接続しUSBによってデータを取り出すCAN-USBインターフェイスもハードウェア製品として販売しております。 もしご興味がございましたら、弊社のコーポレートサイトからお気軽にお問い合わせください。
CAN通信の環境構築
下準備
can-utilsをビルドするので、git等をインストールしておきます。
$ sudo apt update $ sudo apt install git build-essential
can-utilsをインストール
SocketCAN通信を使うにはcan-utilsが便利なので、 ソースコードからbuild & installしておきます。
$ git clone https://github.com/linux-can/can-utils.git $ cd can-utils $ make $ sudo make install
以下のコマンドがインストールされます。
- candump
- cansend
- isotpsend
- isotprecv ...
CAN Interfaceの有効化
/boot/config.txt
に、以下の設定を追記します2。
# Enable MCP2515 dtoverlay=spi-bcm2835 dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25 dtoverlay=spi-dma
再起動後、以下のようにSocketCAN interfaceのcan0
が見えるようになりました。
$ ip link 4: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can
SocketCAN interfaceの設定
CANのビットレートは500kbpsに設定します。
$ ip link set can0 type can bitrate 500000 $ ip link set can0 up
can0
のNICをUp後に、ifconfig
でcan0
が認識できている事を確認できました。
$ ifconfig can0: flags=193<UP,RUNNING,NOARP> mtu 16 unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 10 (UNSPEC) RX packets 1992 bytes 15936 (15.5 KiB) RX errors 0 dropped 1992 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
車両に接続する
車両のDLC(データリンクコネクタ)3と接続して、candump
コマンドでCAN通信をダンプしてみます。
$ candump can0 can0 140 [8] 1F 39 00 00 00 1F 15 00 can0 141 [8] 89 2A 3F A8 00 C0 38 08 can0 144 [8] 00 01 00 00 00 A8 38 00 can0 140 [8] 1F 3A 00 00 00 1F 15 00 can0 141 [8] 89 2A 3F A8 00 C0 38 08 can0 360 [8] 00 00 28 FF 49 40 20 00 can0 361 [8] 00 1A 00 DC 20 00 00 00 can0 362 [8] 12 7A 82 96 00 00 00 01 can0 140 [8] 1F 3D 00 00 00 1F 15 80 ...
CAN通信を確認できました! 4
OBD-II通信の環境構築
CAN通信の環境準備ができたので、OBD-IIのダイアグ通信を試してみます。
OBD-II通信とは?
ものすごく簡単に説明すると、OBD-IIは多くの車両に搭載されている車両診断を行うための規格です。5
OBD-IIでは以下の機能が定義されています6。
- 故障コード(DTC:Diagnostic Trouble Code)の取得要求
- フリーズ・フレームデータの取得
- エンジン系のECUのパラメーター取得
- アクティブテスト機能
- 車両情報等
- etc..
ここで、CAN通信上でOBD-IIのデータを送受信するには、ISO 15765-2の規格に対応する必要があります。
ISO 15765-2
一般的なCAN通信7の場合、1つのCANメッセージに載せられるデータは最大で8byteです。 しかし診断データをやりとりする上で8byte以上のデータをやり取りしたいケースも出てきます。 そこで、長い診断メッセージを分割し、複数のCANメッセージを使って伝送する(以降、マルチフレーム)方式としてISO 15765-2の規格が採用されています。
こちらの記事ではデータの1byte目にデータ長さを設定していますが、実はこれもISO 15765-2の通信規格で定められたフォーマットだったりします。
ISO-TP
ISO-TPはlinuxのsoketcan上でISO 15765-2に準拠した通信機能を提供するプロトコルドライバーです。
このISO-TPの機能を使う事で、上述の面倒なCANの分割/結合/フロー制御の処理を実装する事無く、ダイアグ通信が可能となります。
ISO-TPの機能は、can-utilのisotpsend
、isotprecv
等のコマンドで簡単に使用できます。
また、下記のように、CAN_ISOTP
のプロトコルでsocketを作成して通信もできます8。
int s; struct sockaddr_can addr; static struct can_isotp_options opts; addr.can_addr.tp.tx_id = 0x07e0; addr.can_addr.tp.rx_id = 0x07e8; s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP); setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts)); addr.can_family = AF_CAN; addr.can_ifindex = if_nametoindex("can0"); bind(s, (struct sockaddr *)&addr, sizeof(addr)): write(s, buf, buflen);
実際に使ってみると…
実際にISO-TPのコマンド使うと、Protocol not supported
と表示されてしまいました。 OTL
$ echo 09 02 | isotpsend -s 7e0 -d 7e8 can0 -p 0:0 socket: Protocol not supported
can-isotpのカーネルモジュールをビルド&インストールする
こちらのページにしたがって、カーネルモジュール(can-isotp.ko)をビルドします。
$ git clone https://github.com/hartkopp/can-isotp.git $ cd can-isotp $ make
kernel moduleをbuildするためのヘッダーがない!と怒られる場合、
make[1]: *** /lib/modules/4.19.75-v7+/build: No such file or directory. Stop.
ラズパイのkernelのヘッダーファイルをダウンロードします 9。
$ sudo apt install raspberrypi-kernel-headers
無事、ヘッダーファイルがインストールされました。使用しているkernelとヘッダーのVersionも揃っています。
$ ls /lib/modules/4.19.75-v7+/ build ... $ uname -r 4.19.75-v7+
ビルドができたら、モジュールをインストールします10。
$ sudo make modules_install $ sudo insmod ./net/can/can-isotp.ko
can-isotp
モジュールがインストールされました11。
$ lsmod | grep can can_raw 20480 0 can_isotp 24576 0 can 28672 2 can_isotp,can_raw can_dev 28672 1 mcp251x
OBD-II通信してみる
リクエスト時のCAN-IDについて
送信先のCAN-IDの指定方法ですが、11bit(通常)と29bit(拡張)の2種類があります12。 どちらの種別を受け付けるかは、筆者が確認する限りでは車種(メーカー)で異なる模様です13。
以下に、リクエスト時のCAN-IDの定義について抜粋しておきます。
種別 | CAN-ID | 説明 |
---|---|---|
11bit (通常) | 7DF | リクエストを理解できる全てのECUへのリクエスト |
29bit (拡張) | 18 DB 33 F1 | リクエストを理解できる全てのECUへのリクエスト |
リクエスト時のSID,PIDについて
リクエスト時のデータセクションの最初のバイトはモードを指定します。OBD-IIではSID(Service ID)と呼ばれてます。 そしてデータセクションの2バイト目には、各モードに応じてPID(Parameter ID)を指定します。 SIDとPIDのリストはwikipediaにまとめられています。
以下、SIDの例を示します14。
SID | 命令内容 |
---|---|
0x01 | 現在のデータを表示する |
0x02 | フリーズ・フレームデータを表示する |
0x04 | DTCを消去し診断履歴を消去する |
0x09 | 車の情報を要求する |
さらに、SIDとPIDの組み合わせの例を以下に示します。
SID & PID | Description |
---|---|
01 05 | 冷却水の温度 |
01 0C | RPM(エンジン回転数) |
01 0D | 車速 |
01 1F | エンジン始動時からの稼働時間 |
09 02 | VIN |
09 04 | キャリブレーションID |
09 20 | ECUの名前 |
たとえば、車の情報(SID=0x09)
からVIN(PID=02)
をを取得したい場合は09 02
を送信します。
また、現在のデータ(SID=0x01)
からエンジン回転数(PID=0x0C)
を取得したい場合は、01 0C
という具合です。
ISO-TPを使った、コマンドの送受信方法
ISO-TPのコマンドを使うには、送信元(-s
)と実際の受信相手(-d
)をCAN-IDで事前に指定する必要があります。
しかし、どのECUが返答してくれるかは今はわからないので、以下の手順でCAN-IDを特定します。
- すべてのECUコマンドへリクエストを投げてみる (
isotpsend
) - 応答してくれるECUのCAN-IDを見る(
candump
) - 受信元のCAN-IDを指定して、マルチフレームのCANデータを受信(
isotprecv
)してデータを見る
VINの取得を試してみる
実際に、弊社の所有するHonda FITのVINを取得してみます。 VIN(Vehicle Identification Number:車両識別番号)は最大17バイトの文字列なので、マルチフレーム通信しないと取得できません。
- ラズパイでターミナルを(送信と受信用)2個開きます
- 受信側は
candump can0
で待機しておきます - 以下のVIN取得の要求コマンドで、応答があるか確認します
$ echo "09 02" | isotpsend -s 7df -d 7e8 can0 -p 0:0
- CAN-IDの
7DF
はリクエストを理解できる、すべてのECUへリクエスト
です - どのECUが応答してくれるかわからないので、
7E8
は適当です - 念の為、padding(
-p 0:0
)も設定しておきます
$ candump can0
むむ、応答がありません。CAN-IDが7E*
あたりから返答を期待したのですが。。。
拡張CAN-IDで再トライ
ここで諦めず、CAN-IDを拡張(29bit)に変えてみます。
$ echo "09 02" | isotpsend -s 18DB33F1 -d 18DA33F1 can0 -p 0:0
- CAN-IDの
18DB33F1
はリクエストを理解できる、すべてのECUへリクエスト
です -d
のCAN-IDは適当です
実際に送信してみると、CAN-ID=18DAF10E
から応答がありました!
$ candump can0 can0 18DB33F1 [8] 02 09 02 00 00 00 00 00 can0 18DAF10E [8] 10 14 49 02 01 47 50 35
しかし、以降のマルチフレームのCANデータが受信できていません。
これは、ECUからの受信に対して、ラズパイ側がフロー制御の応答を返していないからです。
そこで、以下のコマンドでマルチフレームの受信処理を有効にします。
-d
には、さきほど応答があったCAN-IDの18DAF10E
を指定します。
$ isotprecv -s 18DB33F1 -d 18DAF10E -l can0
この状態で、送信用のターミナルから要求を投げると...
$ echo "09 02" | isotpsend -s 18DB33F1 -d 18DAF10E can0 -p 0:0
無事、応答が来ました!↓^15
$ isotprecv -s 18DB33F1 -d 18DAF10E can0 49 02 01 47 50 35 2D 31 32 30 31 ** ** ** 00 00 00 00 00 00
実際のcandumpの結果は以下の通り。
$ candump can0 can0 18DB33F1 [8] 02 09 02 00 00 00 00 00 can0 18DAF10E [8] 10 14 49 02 01 47 50 35 can0 18DB33F1 [8] 30 00 00 00 00 00 00 00 can0 18DAF10E [8] 21 2D 31 32 30 31 ** ** can0 18DAF10E [8] 22 ** 00 00 00 00 00 00
併せて、isotpdumpの結果も示します。マルチフレームのフロー制御が動作していますね。
$ isotpdump -s 18DB33F1 -d 18DAF10E can0 can0 18DB33F1 [8] [SF] ln: 2 data: 09 02 can0 18DAF10E [8] [FF] ln: 20 data: 49 02 01 47 50 35 can0 18DB33F1 [8] [FC] FC: 0 = CTS # BS: 0 = off # STmin: 0x00 = 0 ms can0 18DAF10E [8] [CF] sn: 1 data: 2D 31 32 30 31 ** ** can0 18DAF10E [8] [CF] sn: 2 data: ** 00 00 00 00 00 00
応答のデータフォーマットの詳細は割愛しますが、最後の17バイトのASCIIコードが、そのままVINの情報です。
47 50 35 2D 31 32 30 31 ** ** ** 00 00 00 00 00 00
変換するとGP5-1201***
となります。
実際に、市販されている故障診断機のJDiag JD101でも取得してみましたが、結果は一致していました。
注意と自己責任(免責事項)
本記事を参考にして、機器や車両の故障、事故等の被害が起きたとしても、弊社・個人としては責任は負いかねますので、ご了承願います。
あとがき
本当はデータモニターや故障コードの解説もしたかったのですが、分量が多くなってしまいましたので今回はここまでです。 最後も駆け足になってしまいました。orz
記事を書き終わった後に気がついたのですが、書籍のカーハッカーズ・ハンドブックに今回紹介した内容が記載されていました!詳しく知りたい方・興味を持った方は、この本を読んでください!
それでは、よいクリスマスをお過ごしください。
弊社ソリューションのご紹介
弊社では、自動車からのCANデータの収集や、遠隔適合のためのソリューションとして、以下のプロダクトをご提供しております。
自動車向け遠隔計測ソリューション(CAN/CAN-FD対応)
自動車ECU向け遠隔適合ソリューション
今回の記事では、Raspberry Piで簡易的に実現する方法をご紹介しましたが、実際にシステムとして長期に安定稼働させるにはその他にも様々な検討・開発が必要になります。上記のような弊社のソリューションを採用いただければ、そういった個別開発の工数を省略しオールインワンパッケージとして遠隔計測・遠隔適合システムを導入いただけます。
個別の要件に応じたカスタマイズについても可能な範囲で対応させていただきます。
まずはお気軽に、弊社サイトのお問い合わせフォームよりご相談ください。
参考リンク(書籍)
- Raspberry PiでOBD-II (CAN)の情報を取得するための基板を自作する (Qiita)
- Raspberry Pi で CAN通信(準備)
- MCP2515を使った自作基板とRaspberry Piで自動車のECUにOBDリクエストを送る (Qiita)
- Raspberry Pi と MCP2515 で CAN 通信
- はじめての診断 by Vector (pdf)
- OBD (wikipedia)
- OBD-II PIDs (wikipedia)
- SocketCan (wikipedia)
- SocketCAN ISOTP (GitHub)
- カーハッカーズ・ハンドブック
- 弊社ボードは、更にESD保護やコモンモードチョークが入っています↩
-
最近のラズパイは
-overlay
をつけないので、参考リンク先の古い記事を読む方は注意してください!↩ - OBD2コネクタという表現が一般的かも↩
- 最近の車両はセキュリティの関係でCANバスの通信が見えない場合が多いです。ただし、ダイアグ通信に対してはゲートウェイを通してDLCから通信が見える模様です↩
- サポートしていない車両もあるはず↩
- https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%B3%E3%83%BB%E3%83%9C%E3%83%BC%E3%83%89%E3%83%BB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%8E%E3%83%BC%E3%82%B7%E3%82%B9↩
- データサイズを8バイト以上に拡張するCAN-FDの仕様があります。ぜひ、当ブログのCAN FDことはじめもご覧ください。↩
- 実際のsocketの作成方法は、isotpsend.c isotprecv.c を参考にするといいです。ソースのリンク↩
- https://www.raspberrypi.org/documentation/linux/kernel/headers.md↩
-
事前にCANのモジュールがloadされている必要があります。ロードできない時は、
modprobe can
も試してください。When the PF_CAN core module is loaded ('modprobe can') the ISO-TP module can be loaded into the kernel with
↩ - modprobe等によるモジュールの自動ロードの設定は割愛します。↩
- 詳細はISO15765-4の規格を参照。この記事がわかりやすいです。↩
- 弊社の所有するホンダFitは29bit、スバルXVは11bitでリクエストを受け付けました。↩
- カーハッカーズ・ハンドブック より抜粋↩