CIVIC(シビック)FL1でのOBD2データ取得(Arduino使用)基礎編

目次

このページの目的

いまどきの車では、OBD(OnBoadrdDialog)によりいろんなデータが取得できる。それを利用した各種インジケーターも販売されているが、自分の欲しいデータを数値で取得できれば、いろんな使い道がある、はずなんだよね。

必要な機材

まずは必要な機材をそろえよう。今回購入したのは以下の4点だ。

SUNFOUDER Arduino UNO (互換機)
ちょっと安かった、気がする。
SeeedStudio CAN-BUSシールド V2
https://wiki.seeedstudio.com/CAN-BUS_Shield_V2.0/
個別にパーツをそろえて組み上げるより楽ちんだ。
SeeedStudio DB9-OBD2ケーブル(スイッチ付)
ケーブル製作しなくていいので楽ちんだ。(注意点あり。後述)
UNO用アクリルケース
ネットショップから適当に購入
ArduinoUNO互換機+ケース
ArduinoUNO互換機+ケース
SeeedStudio CAN-BUS Shield V2
SeeedStudio CAN-BUS Shield V2
SeeedStudio DB9-OBD2ケーブル
SeeedStudio DB9-OBD2ケーブル

▲目次に戻る

ハードウェアセッティング

つづいてハード的なの注意点(?)など

CANバスの終端抵抗

CANバスの終端には信号反射防止のための抵抗が必要で、CAN-BUSシールドでもデフォルトでは有効になっている。今回はとりあえずシールド裏面の【P1】をカッターで切断(基板内部で接続している)し無効にしてみた。有効のままでも通信できれば何ら問題はない。

CANバス終端抵抗を無効に
CANバス終端抵抗を無効に
参考資料:
https://files.seeedstudio.com/wiki/CAN-BUS-Shield-V2.0/res/CAN-BUS Shield v2.0.pdf

ケーブル

ケーブル途中にスイッチがあり、OBD2コネクタからの電源供給をON/OFFできるんだけど、なんか違和感が...まぁ、覚えてしまえば問題なしだが。

ケーブル途中のスイッチ(これでON)
ケーブル途中のスイッチ(これでON)

また、ピンアサインは以下のとおりだ。

DSUBピンアサイン
DSUBピンアサイン
番号信号番号信号番号信号番号信号番号信号
1GND2GND3CAN H4K LINE5CAN L
6J1850 -7J1850 +8L LINE9V

FL1シビックは、ISO15765-4のCANバス通信を使用しているが、車種によっては、J1850やK(L)LINEを使用した通信を行っているので注意が必要だ。ちなみに、CAN-BUSシールドは、J1850やK(L)LINEには対応していない。

CAN-BUSシールドとUNO互換機

CAN-BUSシールドをUNOにドッキングしてみよう...あれっ? USBコネクターの金属カバーとDSUBコネクターの足が干渉するんだけど...

DSUB足とUSBコネクターが干渉
DSUB足とUSBコネクターが干渉

実際に干渉しているのは、7番ピン(J1850+)1本だけで、今回は使用しない信号(シールド側は配線されていない、車体側でも信号は流れていない、はず)だったので問題なしとするが、シールド設計として、これは無いんじゃないの?

▲目次に戻る

FL1シビックのOBD2通信規格

どうも最近のホンダ車は「ISO157654-4 29bitID」での通信らしい。いったいどんな通信なんだ?

参考リンク:
https://sourceforge.net/p/rusefi/tickets/_discuss/thread/65c5390c/5610/attachment/iso_15765-4.pdf

その他、ネットを徘徊して情報を収集し、自分なりには下記の理解となった(間違ってたらゴメン)

OBD2 29bit CAN通信
CAN-ID動作説明
0x18db33f1リクエスト誰でもいいからリクエストに応えてちょうだい
0x18db(固定値) 33(登録されているECU全てに対し) f1(外部機器からの問い合わせ)
0x18daf1xxレスポンスECUxxから外部機器f1に回答するよ
0x18da(固定値) f1(外部機器に対し) xx(ECUxxから回答)
0x18daxxf1リクエストECUxxさん、リクエストに応えてちょうだい
0x18da(固定値) xx(ECUxxに対し) f1(外部機器からの問い合わせ)

▲目次に戻る

Arduinoスケッチ1(動作確認用)

さて、ハードの準備ができたら、Arduinoのスケッチ作成のため、ライブラリをダウンロード&インクルードしよう。
https://github.com/Seeed-Studio/Seeed_Arduino_CAN

インクルードしたら、さっそくサンプルスケッチを読み込みだ。

CAN-BUSシールドサンプルスケッチ
CAN-BUSシールドサンプルスケッチ

が、ここではっきりしておこう。これらのサンプルスケッチはあくまでのプログラムのサンプルであって、動作するサンプルでは無い!(なかには動くのもあるかもしれないが) よって、サンプルの「send_recieve」と「recieve_monitor」を参考にコーディングしていくことにする。


#include 
#include "mcp2515_can.h"

const int CAN_CS_PIN = 9;
const int CAN_INT_PIN = 2;   //サンプルだと「10」の場合あり。「int」ピンは「2」(機器仕様)。今回は使わない

mcp2515_can CAN(CAN_CS_PIN);

unsigned char len = 0;
unsigned char buf[8];
uint32_t id;

void setup() {
  SERIAL_PORT_MONITOR.begin(115200);
  while(!Serial);
  if (CAN.begin(CAN_500KBPS) != 0) {
    SERIAL_PORT_MONITOR.println("CAN-BUS init error!");
    while(1);
  }
  SERIAL_PORT_MONITOR.println("CAN-BAS init ok!");

  // フィルターの設定 注1
  CAN.init_Mask(0 , 1 , 0x00000000);
  CAN.init_Filt(0 , 1 , 0x00000000);
  CAN.init_Filt(1 , 1 , 0x00000000);
  
  CAN.init_Mask(1 , 1 , 0x00000000);
  CAN.init_Filt(2 , 1 , 0x00000000);
  CAN.init_Filt(3 , 1 , 0x00000000);
  CAN.init_Filt(4 , 1 , 0x00000000);
  CAN.init_Filt(5 , 1 , 0x00000000);
}

unsigned char stmp[8] = {0x02 , 0x01 , 0x0C , 0x00 , 0x00 , 0x00 , 0x00 , 0x00};  //注2
void loop() {
  unsigned long t = millis();

  CAN.sendMsgBuf(0x18DB33F1 , 1 , 8 , stmp);    //注3

  CAN.readMsgBuf(&len , buf);
  id = CAN.getCanId();

  SERIAL_PORT_MONITOR.print(t); SERIAL_PORT_MONITOR.print(":");
  SERIAL_PORT_MONITOR.print(id , HEX); SERIAL_PORT_MONITOR.print(":");
  for (int i = 0; i < len; i++) {
    SERIAL_PORT_MONITOR.print(buf[i] , HEX); SERIAL_PORT_MONITOR.print(" ");    //注4
  }
  SERIAL_PORT_MONITOR.print("|");
  SERIAL_PORT_MONITOR.print((buf[3]*256 + buf[4])/4);    //注4
  SERIAL_PORT_MONITOR.println("rpm");
  delay(100);
}

注1:フィルター設定

CAN-BUSシールド上のCAN通信デバイス「MCP2515」には、(車両にはいろんな信号が流れているので、欲しい信号だけを抽出できるよう)信号のフィルタリング機能(マスク1[フィルタ0、1と連動]、マスク1[フィルタ2、3、4、5と連動])が搭載されている。ここで、デフォルトがどんな状態か知らないので、念のため、全部通してもらうように設定しておく。

マスクとフィルタの関係
マスクの各ビット01
フィルタの各ビット0101
CAN-IDの各ビットは?不問不問01
マスク・フィルタ適用例1
マスク0x1fffffff1 1111 1111 1111 1111 1111 1111 1111
フィルタ0x18daf1001 1000 1101 1010 1111 0001 0000 0000
通るCAN-ID0x18daf1001 1000 1101 1010 1111 0001 0000 0000
マスク・フィルタ適用例2(0x18daf100から0x18daf1ffまで通る)
マスク0x1fffff001 1111 1111 1111 1111 1111 0000 0000
フィルタ0x18daf1001 1000 1101 1010 1111 0001 0000 0000
通るCAN-ID0x18daf1??1 1000 1101 1010 1111 0001 ???? ????

注2:送信内容

OBD2での送信内容は以下のフォーマットによる。

0x02 0x01 0x0C 0x00 0x00 0x00 0x00 0x00
パラメータ内容備考
0x02実コマンドのバイト数2バイト固定
0x01ServiceModeどの種類のデータ? 例えば、01:現在値
参考:https://en.wikipedia.org/wiki/OBD-II_PIDs
0x0CパラメータIDなんのデータ? 例えば、0C:エンジン回転数
参考:https://en.wikipedia.org/wiki/OBD-II_PIDs
0x00ダミー全体で8バイトにする必要あり
0x00ダミー全体で8バイトにする必要あり
0x00ダミー全体で8バイトにする必要あり
0x00ダミー全体で8バイトにする必要あり
0x00ダミー全体で8バイトにする必要あり

注3:送信コマンド

前述のように、FL1シビックは「ISO157654-4 29bitID」での通信なので、CAN-BUSシールドの送信コマンドは下記になる。

CAN.sendMsgBuf(0x18DB33F1 , 1 , 8 , stmp);
パラメータ内容備考
0x18db33f1CAN-ID上記「FL1シビックのOBD2通信規格」参照。「誰でもいいから教えてちょうだい」
1通信フォーマット1:拡張フォーマット(29bitIDでの通信) 0:標準フォーマット(7bitIDでの通信)
8送信バイト数8バイト
stmp送信内容注2で作成したフォーマット

注4:受信データ

受信データは以下のフォーマットになる。

例:0x04 0x41 0x0c 0x0d 0xf4 0x00 0x00 0x00
データ内容備考
0x04後に続く実データのバイト数4バイト
0x41モード送信したServiceMode(0x01)に対して0x40が加算された値
0x0cPIDデータの種類(0x0c:エンジン回転数)
0x0dデータ上位上位+下位の2バイトで実データを求める。
エンジン回転数の場合:(256*上位+下位)/4=(256*13[0x0d]+244[0xf4])/4=893(rpm)
参考:https://en.wikipedia.org/wiki/OBD-II_PIDs
0xf4データ上位
0x00ダミー全体で8バイトにする必要あり
0x00ダミー全体で8バイトにする必要あり
0x00ダミー全体で8バイトにする必要あり

動作例

では実機での確認だ。無事にデータ取得できているようだ。

ちなみに、シビック車体側のOBD2コネクターと購入したケーブルの相性が悪いのかどうか、かなり力を入れて差し込まないと、通信ができなかったことを付け加えておく(実はかなり悩んだ。「電源はとれるのに通信できねぇーーー。いったい何が悪いんだ...」)

OBD2通信動作確認
OBD2通信動作確認

『ECUEF』さん、回答ありがとう。これからよろしく。

▲目次に戻る

FL1シビックがサポートしているPIDは?

無事、通信できるようになったので、FL1シビックがサポートしているパラメータIDを調べてみよう(どんなデータが取れるの?)。そのためには、前項の『注2:送信内容』の『パラメータID』を下記に変更してリクエストを投げればokだ。(参考:https://en.wikipedia.org/wiki/OBD-II_PIDs

サポートしているパラメータIDを確認する
コマンド:0x02 0x01 ???? 0x00 0x00 0x00 0x00 0x00
????の値内容
0x00パラメータID0x01-0x20までのサポート状況
0x20パラメータID0x21-0x40までのサポート状況
0x40パラメータID0x41-0x60までのサポート状況
0x60パラメータID0x61-0x80までのサポート状況
0x80パラメータID0x81-0xA0までのサポート状況
0xA0パラメータID0xA1-0xC0までのサポート状況
0xC0パラメータID0xC1-0xE0までのサポート状況

で、返ってきたのが以下の値だ。

車両からの回答
コマンド:0x02 0x01 ???? 0x00 0x00 0x00 0x00 0x00
CAN-IDレスポンス後に続く
実データ
バイト数
モードパラメータID実データ各ビット
左からパラメータIDのアドレス0x01-0x20
ダミー
18DAF110064100B63CA813550641001011 0110 0011 1100 1010 1000 0001 001155
18DAF1EF06410090188011550641001001 0000 0001 1000 1000 0000 0001 000155
18DAF1EF06412000000001550641200000 0000 0000 0000 0000 0000 0000 000155
18DAF1EF06414040000001550641400100 0000 0000 0000 0000 0000 0000 000155
18DAF1EF06416002000000550641600000 0010 0000 0000 0000 0000 0000 000055
18DAF11006418000000002550641800000 0000 0000 0000 0000 0000 0000 001055

おっと、『ECUEF』さんだけでなく、『ECU10』さんもデータを出してくれるらしいぞ。よろしく。

ビットが「1」のパラメータIDはECUが「データを出せるよ」と言ってくれているので、それを整理しよう。(参考:https://en.wikipedia.org/wiki/OBD-II_PIDs

FL1シビックから取得できる(はずの)パラメータ
パラメータID対象ECU
(16進)
データの種類バイト数換算式
16進10進
01110,EFMonitor status4
03310Fuel system status2
04410,EFエンジン負荷(%)1値*100/255
06610Short term fuel trim[%]1値*100/255-100
07710Long term fuel trim[%]1値*100/255-100
0B1110Intake manifold absolute pressure[kPa]1そのまま
0C1210,EFエンジン回転数[rpm]2(256*[バイト1]+[バイト2])/4
0D1310,EF車速(km/hr)1そのまま
0E1410点火時期(上死点前)[度]1値/2-64
111710,EFアクセル開度[%]1値*100/255
131910O2センサ位置1各ビットが1のところ
152110O2センサ2「空燃比、電流値mA]4空燃比:(256*[バイト1]+[バイト2])/32768
電流値:(256*[バイト3]+[バイト4])/256-128
1C2810,EFOBD standards1
1F3110エンジン始動後経過時間[sec]2256*[バイト1]+[バイト2]
203210,EFパラメータID0x21-0x404
4064EFパラメータID0x41-0x604
6096EFパラメータID0x61-0x804
67103EF冷却水温度[℃]3バイト1:ビットでセンサー位置
センサー1:[バイト2]-40
センサー2:[バイト3]-40
9F15910Fuel System Percentage Use9

データサンプル

「このデータなら出してあげられるよ」とECUが教えてくれたデータから、楽しそうなデータを試しに取得してみた。おそらく取れているんじゃないかと。グラフの横軸の単位はmsecで、縦軸は各グラフ注釈を参照のこと。ちなみに取得したタイミングは別なので各グラフに相関関係は無い。

インマニ圧力[kPa絶対圧]@アイドリング
インマニ圧力[kPa絶対圧]@アイドリング
点火時期[deg上死点前]@アクセル操作あり
点火時期[deg上死点前]@アクセル操作あり
アクセル開度[%]
アクセル開度[%]
空燃比λ
空燃比λ(=空燃比現在値/理論空燃比?)
冷却水温[℃]
冷却水温[℃]

データ取得状況の確認

シリアルモニターでデータを確認するのも面倒なので、家に転がっていた液晶モニター(キャラクター表示用)を接続し出力してみた。表示文字数が限られているのでデータを厳選して連続取得した状況を載せておこう。うん、やっぱり、こうやって「見える化」すると(するのも)楽しいね。妄想は膨らむばかりだ。

購入品(今回は家内パーツストックから使用)
液晶ディスプレイ(16x2キャラクタ表示用):OYOSO I2C 1602 LCD
液晶ディスプレイを追加
液晶ディスプレイを追加
今回の取得データ
表示位置データ
左上インマニ絶対圧[kPa]
右上回転数[rpm]
左下スロットル開度[%]
右下空燃比λ

OBD2データ取得状況

Arduinoスケッチ


#include <SPI.h>
#include <mcp2515_can.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2);

const int CAN_CS_PIN = 9;
const int CAN_INT_PIN = 2;
mcp2515_can CAN(CAN_CS_PIN);

unsigned char len = 0;
uint32_t id;
String unit;
float data;
unsigned char Col;
unsigned char Row;
unsigned char fct;
unsigned char buf[8];
unsigned char stmp[] = {0x02, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char cmnd[] = {0x0B, 0x0C, 0x11, 0x15};
unsigned long trgt[] = {0x18DA10F1, 0x18DAEFF1, 0x18DAEFF1, 0x18DA10F1};
unsigned long ti;

void setup() {
  lcd.init();
  lcd.backlight();
  SERIAL_PORT_MONITOR.begin(115200);
  while(!Serial);
  if (CAN.begin(CAN_500KBPS) != 0) {
    lcd.setCursor(1,0);
    lcd.print("CAN-BUS init error!");
    while(1);
  }
  lcd.setCursor(1,0);
  lcd.print("CAN-BAS init ok!");

  CAN.init_Mask(0 , 1 , 0x00000000);  //全部通過
  CAN.init_Filt(0 , 1 , 0x18DAF110);
  CAN.init_Filt(1 , 1 , 0x18DAF1EF);
  CAN.init_Mask(1 , 1 , 0xFFFFFFFF);  //0x00000000のみ通過
  CAN.init_Filt(2 , 1 , 0x00000000);
  CAN.init_Filt(3 , 1 , 0x00000000);
  CAN.init_Filt(4 , 1 , 0x00000000);
  CAN.init_Filt(5 , 1 , 0x00000000);

  delay(1000);
  lcd.clear();
}

void loop() {
  for (int i=0; i<4; i++) {
    ti = millis();
    unit = "";
    stmp[2] = cmnd[i];
    CAN.sendMsgBuf(trgt[i], 1,8, stmp);
    if(CAN.checkReceive()) {
      CAN.readMsgBuf(&len, buf);
      switch (buf[2]) {
        case 0x0B:
          data=buf[3];
          fct = 0;
          unit = "kPa";
          Col = 0; Row=0;
          break;
        case 0x0C:
          data=((float)256*buf[3]+buf[4])/4;
          fct = 0;
          unit = "rpm";
          Col = 8;Row=0;
          break;
        case 0x11:
          data=(float)100*buf[3]/255;
          fct = 1;
          unit = "%";
          Col=0;Row=1;
          break;
        case 0x15:
          data=((float)256*buf[3]+buf[4])/32768;
          fct = 2;
          unit = "AFR";
          Col=8;Row=1;
          break;
      }
    }
    lcd.setCursor(Col,Row);lcd.print(data,fct);lcd.print(unit);
    SERIAL_PORT_MONITOR.print(ti);SERIAL_PORT_MONITOR.print(":");
    SERIAL_PORT_MONITOR.print(buf[2],HEX);SERIAL_PORT_MONITOR.print(":");
    SERIAL_PORT_MONITOR.print(data, fct);SERIAL_PORT_MONITOR.print(unit);SERIAL_PORT_MONITOR.print("|");
    //delay(500);
  }
  SERIAL_PORT_MONITOR.println("");
  delay(200);
}

▲目次に戻る

『応用編』に続く...

この企画もページが長くなってきたので、別ページ『OBD2データ取得応用編』へ続く。