aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

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

TL;DR

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

はじめに

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

背景と目的

弊社の製品である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通信用モジュール

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

Aptpod社製 ラズパイ拡張ボード

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

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

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


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

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

また、弊社ではCANバスに接続しUSBによってデータを取り出すCAN-USBインターフェイスもハードウェア製品として販売しております。 もしご興味がございましたら、弊社のコーポレートサイトからお気軽にお問い合わせください

www.aptpod.co.jp

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

一般的な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でも取得してみましたが、結果は一致していました。

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

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

あとがき

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

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

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


弊社ソリューションのご紹介

弊社では、自動車からのCANデータの収集や、遠隔適合のためのソリューションとして、以下のプロダクトをご提供しております。

自動車向け遠隔計測ソリューション(CAN/CAN-FD対応)

www.aptpod.co.jp

自動車ECU向け遠隔適合ソリューション

www.aptpod.co.jp

今回の記事では、Raspberry Piで簡易的に実現する方法をご紹介しましたが、実際にシステムとして長期に安定稼働させるにはその他にも様々な検討・開発が必要になります。上記のような弊社のソリューションを採用いただければ、そういった個別開発の工数を省略しオールインワンパッケージとして遠隔計測・遠隔適合システムを導入いただけます

個別の要件に応じたカスタマイズについても可能な範囲で対応させていただきます。

まずはお気軽に、弊社サイトのお問い合わせフォームよりご相談ください

www.aptpod.co.jp


参考リンク(書籍)


  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. カーハッカーズ・ハンドブック より抜粋