ラズパイでCAN通信をして、車両の診断データを送受信してみた

f:id:apt-k-ueno:20200107183852j:plain

TL;DR

Raspberry PiでCAN通信を動かして、車両の診断データ(OBD-II)を見てみた話です。 SocketCANのISO-TPの機能が便利だったので、その紹介がメインになります。

はじめに

この記事はaptpod Advent Calendar 2019の24日目の記事です。 お送りするのは、組み込みソフトチームの松下です。

背景と目的

弊社の製品であるintdash Automotive Proは、車両CAN(Controller Area Network)データ等のデータロギング、可視化・解析などのワークフローをクラウドシステムで実現するソリューションです。 f:id:aptpod_tech-writer:20191220113017p:plain この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通信用モジュール

今回は弊社で開発したラズパイ用の拡張ボードを使用します。

f:id:aptpod_tech-writer:20191218223547j:plain:w500
Aptpod社製 ラズパイ拡張ボード

といっても、拡張ボード上はMCP2515をラズパイのSPIとGPIO25に接続する、一般的な方法で実装しています1

データシートや、「MCP2515 回路」等で検索すると出てくる回路図を参考にしてください(こちらの記事がわかりやすいです)。

もしくは、Amazonで売ってるMCP2515モジュールでも大丈夫だと思います。

宣伝 紹介ですが、弊社のラズパイ向け拡張ボードは、CAN通信以外にも、以下の機能を提供しています。

  • RTC(ハードウェアクロック)
  • 電源スイッチ機能
  • 電源管理機能(5V〜19Vの入力が可能)
  • 状態通知用のLED(アプリケーションから制御可能)

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後に、ifconfigcan0が認識できている事を確認できました。

$ 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

f:id:aptpod_tech-writer:20191219210242p:plain:w700

一般的な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のisotpsendisotprecv等のコマンドで簡単に使用できます。 また、下記のように、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を特定します。

  1. すべてのECUコマンドへリクエストを投げてみる (isotpsend)
  2. 応答してくれるECUのCAN-IDを見る(candump
  3. 受信元のCAN-IDを指定して、マルチフレームのCANデータを受信(isotprecv)してデータを見る

VINの取得を試してみる

実際に、弊社の所有するHonda FITのVINを取得してみます。 VIN(Vehicle Identification Number:車両識別番号)は最大17バイトの文字列なので、マルチフレーム通信しないと取得できません。

  1. ラズパイでターミナルを(送信と受信用)2個開きます
  2. 受信側は candump can0で待機しておきます
  3. 以下の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でも取得してみましたが、結果は一致していました。

f:id:aptpod_tech-writer:20191223112700j:plain:w500

注意と自己責任(免責事項)

本記事を参考にして、機器や車両の故障、事故等の被害が起きたとしても、弊社・個人としては責任は負いかねますので、ご了承願います。

あとがき

本当はデータモニターや故障コードの解説もしたかったのですが、分量が多くなってしまいましたので今回はここまでです。 最後も駆け足になってしまいました。orz

記事を書き終わった後に気がついたのですが、書籍のカーハッカーズ・ハンドブックに今回紹介した内容が記載されていました!詳しく知りたい方・興味を持った方は、この本を読んでください!

それでは、よいクリスマスをお過ごしください。

参考リンク(書籍)


  1. 弊社ボードは、更にESD保護やコモンモードチョークが入っています

  2. 最近のラズパイは -overlayをつけないので、参考リンク先の古い記事を読む方は注意してください!

  3. OBD2コネクタという表現が一般的かも

  4. 最近の車両はセキュリティの関係でCANバスの通信が見えない場合が多いです。ただし、ダイアグ通信に対してはゲートウェイを通してDLCから通信が見える模様です

  5. サポートしていない車両もあるはず

  6. 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

  7. データサイズを8バイト以上に拡張するCAN-FDの仕様があります。ぜひ、当ブログのCAN FDことはじめもご覧ください。

  8. 実際のsocketの作成方法は、isotpsend.c isotprecv.c を参考にするといいです。ソースのリンク

  9. https://www.raspberrypi.org/documentation/linux/kernel/headers.md

  10. 事前に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

  11. modprobe等によるモジュールの自動ロードの設定は割愛します。

  12. 詳細はISO15765-4の規格を参照。この記事がわかりやすいです。

  13. 弊社の所有するホンダFitは29bit、スバルXVは11bitでリクエストを受け付けました。

  14. カーハッカーズ・ハンドブック より抜粋