いまどきの車では、OBD(OnBoardDialog)によりいろんなデータが取得できる。それを利用した各種インジケーターも販売されているが、自分の欲しいデータを数値で取得できれば、いろんな使い道がある、はずなんだよね。
まずは必要な機材をそろえよう。今回購入したのは以下の4点だ。
つづいてハード的な注意点(?)など
CANバスの終端には信号反射防止のための抵抗が必要で、CAN-BUSシールドでもデフォルトでは有効になっている。今回はとりあえずシールド裏面の【P1】をカッターで切断(基板内部で接続している)し無効にしてみた。有効のままでも通信できれば何ら問題はない。
ケーブル途中にスイッチがあり、OBD2コネクタからの電源供給をON/OFFできるんだけど、なんか違和感が...まぁ、覚えてしまえば問題なしだが。
また、ピンアサインは以下のとおりだ。
FL1シビックは、ISO15765-4のCANバス通信を使用しているが、車種によっては、J1850やK(L)LINEを使用した通信を行っているので注意が必要だ。ちなみに、CAN-BUSシールドは、J1850やK(L)LINEには対応していない。
CAN-BUSシールドをUNOにドッキングしてみよう...あれっ? USBコネクターの金属カバーとDSUBコネクターの足が干渉するんだけど...
実際に干渉しているのは、7番ピン(J1850+)1本だけで、今回は使用しない信号(シールド側は配線されていない、車体側でも信号は流れていない、はず)だったので問題なしとするが、シールド設計として、これは無いんじゃないの?
どうも最近のホンダ車は「ISO157654-4 29bitID」での通信らしい。いったいどんな通信なんだ?
その他、ネットを徘徊して情報を収集し、自分なりには下記の理解となった(間違ってたらゴメン)
CAN-ID | 動作 | 説明 |
---|---|---|
0x18db33f1 | リクエスト | 誰でもいいからリクエストに応えてちょうだい。 0x18db(固定値) 33(登録されているECU全てに対し) f1(外部機器からの問い合わせ) |
0x18daf1xx | レスポンス | ECUxxから外部機器f1に回答するよ。 0x18da(固定値) f1(外部機器に対し) xx(ECUxxから回答) |
0x18daxxf1 | リクエスト | ECUxxさん、リクエストに応えてちょうだい。 0x18da(固定値) xx(ECUxxに対し) f1(外部機器からの問い合わせ) |
さて、ハードの準備ができたら、Arduinoのスケッチ作成のため、ライブラリをダウンロード&インクルードしよう。
https://github.com/Seeed-Studio/Seeed_Arduino_CAN
インクルードしたら、さっそくサンプルスケッチを読み込みだ。
が、ここではっきりしておこう。これらのサンプルスケッチはあくまでのプログラムのサンプルであって、動作するサンプルでは無い!(なかには動くのもあるかもしれないが) よって、サンプルの「send_recieve」と「recieve_monitor」を参考にコーディングしていくことにする。
#include <SPI.h>
#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);
}
CAN-BUSシールド上のCAN通信デバイス「MCP2515」には、(車両にはいろんな信号が流れているので、欲しい信号だけを抽出できるよう)信号のフィルタリング機能(マスク1[フィルタ0、1と連動]、マスク1[フィルタ2、3、4、5と連動])が搭載されている。ここで、デフォルトがどんな状態か知らないので、念のため、全部通してもらうように設定しておく。
マスクの各ビット | 0 | 1 | ||
---|---|---|---|---|
フィルタの各ビット | 0 | 1 | 0 | 1 |
CAN-IDの各ビットは? | 不問 | 不問 | 0 | 1 |
マスク | 0x1fffffff | 1 1111 1111 1111 1111 1111 1111 1111 |
---|---|---|
フィルタ | 0x18daf100 | 1 1000 1101 1010 1111 0001 0000 0000 |
通るCAN-ID | 0x18daf100 | 1 1000 1101 1010 1111 0001 0000 0000 |
マスク | 0x1fffff00 | 1 1111 1111 1111 1111 1111 0000 0000 |
---|---|---|
フィルタ | 0x18daf100 | 1 1000 1101 1010 1111 0001 0000 0000 |
通るCAN-ID | 0x18daf1?? | 1 1000 1101 1010 1111 0001 ???? ???? |
OBD2での送信内容は以下のフォーマットによる。
パラメータ | 内容 | 備考 |
---|---|---|
0x02 | 実コマンドのバイト数 | 2バイト固定 |
0x01 | ServiceMode | どの種類のデータ? 例えば、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バイトにする必要あり |
前述のように、FL1シビックは「ISO157654-4 29bitID」での通信なので、CAN-BUSシールドの送信コマンドは下記になる。
パラメータ | 内容 | 備考 |
---|---|---|
0x18db33f1 | CAN-ID | 上記「FL1シビックのOBD2通信規格」参照。「誰でもいいから教えてちょうだい」 |
1 | 通信フォーマット | 1:拡張フォーマット(29bitIDでの通信) 0:標準フォーマット(7bitIDでの通信) |
8 | 送信バイト数 | 8バイト |
stmp | 送信内容 | 注2で作成したフォーマット |
受信データは以下のフォーマットになる。
データ | 内容 | 備考 |
---|---|---|
0x04 | 後に続く実データのバイト数 | 4バイト |
0x41 | モード | 送信したServiceMode(0x01)に対して0x40が加算された値 |
0x0c | PID | データの種類(0x0c:エンジン回転数) |
0x0d | データ上位 | エンジン回転数は上位+下位の2バイトで実データを求める。 |
0xf4 | データ上位 | (256*上位+下位)/4=(256*13[0x0d]+244[0xf4])/4=893(rpm) 参考:https://en.wikipedia.org/wiki/OBD-II_PIDs |
0x00 | ダミー | 全体で8バイトにする必要あり |
0x00 | ダミー | 全体で8バイトにする必要あり |
0x00 | ダミー | 全体で8バイトにする必要あり |
では実機での確認だ。無事にデータ取得できているようだ。
ちなみに、シビック車体側のOBD2コネクターと購入したケーブルの相性が悪いのかどうか、かなり力を入れて差し込まないと、通信ができなかったことを付け加えておく(実はかなり悩んだ。「電源はとれるのに通信できねぇーーー。いったい何が悪いんだ...」)
『ECUEF』さん、回答ありがとう。これからよろしく。
無事、通信できるようになったので、FL1シビックがサポートしているパラメータIDを調べてみよう(どんなデータが取れるの?)。そのためには、前項の『注2:送信内容』の『パラメータID』を下記に変更してリクエストを投げればokだ。(参考:https://en.wikipedia.org/wiki/OBD-II_PIDs)
????の値 | 内容 |
---|---|
0x00 | パラメータID0x01-0x20までのサポート状況 |
0x20 | パラメータID0x21-0x40までのサポート状況 |
0x40 | パラメータID0x41-0x60までのサポート状況 |
0x60 | パラメータID0x61-0x80までのサポート状況 |
0x80 | パラメータID0x81-0xA0までのサポート状況 |
0xA0 | パラメータID0xA1-0xC0までのサポート状況 |
0xC0 | パラメータID0xC1-0xE0までのサポート状況 |
で、返ってきたのが以下の値だ。
CAN-ID | レスポンス | 後に続く 実データ バイト数 | モード | パラメータID | 実データ各ビット 左からパラメータIDのアドレス0x01-0x20 | ダミー |
---|---|---|---|---|---|---|
18DAF110 | 064100B63CA81355 | 06 | 41 | 00 | 1011 0110 0011 1100 1010 1000 0001 0011 | 55 |
18DAF1EF | 0641009018801155 | 06 | 41 | 00 | 1001 0000 0001 1000 1000 0000 0001 0001 | 55 |
18DAF1EF | 0641200000000155 | 06 | 41 | 20 | 0000 0000 0000 0000 0000 0000 0000 0001 | 55 |
18DAF1EF | 0641404000000155 | 06 | 41 | 40 | 0100 0000 0000 0000 0000 0000 0000 0001 | 55 |
18DAF1EF | 0641600200000055 | 06 | 41 | 60 | 0000 0010 0000 0000 0000 0000 0000 0000 | 55 |
18DAF110 | 0641800000000255 | 06 | 41 | 80 | 0000 0000 0000 0000 0000 0000 0000 0010 | 55 |
おっと、『ECUEF』さんだけでなく、『ECU10』さんもデータを出してくれるらしいぞ。よろしく。
ビットが「1」のパラメータIDはECUが「データを出せるよ」と言ってくれているので、それを整理しよう。(参考:https://en.wikipedia.org/wiki/OBD-II_PIDs)
パラメータID | 対象ECU (16進) | データの種類 | バイト数 | 換算式 | |
---|---|---|---|---|---|
16進 | 10進 | ||||
01 | 1 | 10,EF | Monitor status | 4 | |
03 | 3 | 10 | Fuel system status | 2 | |
04 | 4 | 10,EF | エンジン負荷(%) | 1 | 値*100/255 |
06 | 6 | 10 | Short term fuel trim[%] | 1 | 値*100/255-100 |
07 | 7 | 10 | Long term fuel trim[%] | 1 | 値*100/255-100 |
0B | 11 | 10 | Intake manifold absolute pressure[kPa] | 1 | そのまま |
0C | 12 | 10,EF | エンジン回転数[rpm] | 2 | (256*[バイト1]+[バイト2])/4 |
0D | 13 | 10,EF | 車速(km/hr) | 1 | そのまま |
0E | 14 | 10 | 点火時期(上死点前)[度] | 1 | 値/2-64 |
11 | 17 | 10,EF | アクセル開度[%] | 1 | 値*100/255 |
13 | 19 | 10 | O2センサ位置 | 1 | 各ビットが1のところ |
15 | 21 | 10 | O2センサ2「電圧V、燃料調整%] | 2 | 2023-07-23修正 電圧:[バイト1]/200 燃料調整:100*[バイト2]/128-100 |
1C | 28 | 10,EF | OBD standards | 1 | |
1F | 31 | 10 | エンジン始動後経過時間[sec] | 2 | 256*[バイト1]+[バイト2] |
20 | 32 | 10,EF | パラメータID0x21-0x40 | 4 | |
40 | 64 | EF | パラメータID0x41-0x60 | 4 | |
60 | 96 | EF | パラメータID0x61-0x80 | 4 | |
67 | 103 | EF | 冷却水温度[℃] | 3 | バイト1:ビットでセンサー位置 センサー1:[バイト2]-40 センサー2:[バイト3]-40 |
9F | 159 | 10 | Fuel System Percentage Use | 9 |
注:PDI15のO2センサの値の参照が間違っていた! ここ以降の空燃比に関する記述は誤りだ
「このデータなら出してあげられるよ」とECUが教えてくれたデータから、楽しそうなデータを試しに取得してみた。おそらく取れているんじゃないかと。グラフの横軸の単位はmsecで、縦軸は各グラフ注釈を参照のこと。ちなみに取得したタイミングは別なので各グラフに相関関係は無い。
シリアルモニターでデータを確認するのも面倒なので、家に転がっていた液晶モニター(キャラクター表示用)を接続し出力してみた。表示文字数が限られているのでデータを厳選して連続取得した状況を載せておこう。うん、やっぱり、こうやって「見える化」すると(するのも)楽しいね。妄想は膨らむばかりだ。
#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);
}
この企画もページが長くなってきたので、別ページ『CIVIC(シビック)FL1のOBD2応用編:データロギングと解析』へ続く。