IoT4Car: Arduino MKR1000で車両テレメトリー取得・クラウド可視化 – OBD-II対応
必要な部品と材料
| Arduino MKR WiFi 1000 (SAMD21, 3.3V動作) × 1 |
| SparkFun Logic Level Converter (双方向, 5V⇔3.3V) × 1 |
| SparkFun OBD-II UART ボード (STN1110チップ搭載, ELM327コマンドセット対応) × 1 |
| SparkFun OBD-II - DB9 ケーブル × 1 |
| LCD 1602 (I2Cモジュール付き推奨) × 1 (車内リアルタイム表示用) |
| ブレッドボード、ジャンパーワイヤー × 1式 |
| スマートフォン (テザリング用 Wi-Fi ホットスポット) × 1 |
使用クラウドサービス
- dweet.io: 超シンプルな IoT データ受信・保存 (キー/バリュー、REST API)
- freeboard.io: dweet.io データをダッシュボード可視化 (ゲージ、グラフ等)
プロジェクト概要
車両の OBD-II ポート (16ピン) に接続し、エンジン回転数、車速、燃料残量、冷却水温度などのリアルタイムデータを取得。Arduino MKR1000 の Wi-Fi 機能でクラウド (dweet.io → freeboard.io) にアップロードし、どこからでもブラウザで車両状態を監視できるシステムです。MKR1000 は 3.3V 動作のため、5V の OBD-II UART ボードとの間にロジックレベルコンバーターが必須です。
OBD-II とは
- On-Board Diagnostics 第2世代。1996年以降の米国車、2001年以降のEUガソリン車、2004年以降のEUディーゼル車に装備義務
- 16ピン コネクター (J1962) で車両診断バスにアクセス
- プロトコル: ISO 15765-4 CAN (2008年以降米国車必須) 等5種類
- STN1110 チップが各プロトコルを UART (シリアル) に変換
ハードウェア接続
| MKR1000 | レベルシフター (HV側) | レベルシフター (LV側) | OBD-II UART |
|---|---|---|---|
| TX (D14) | → HV1 | LV1 → | RX |
| RX (D13) | ← HV2 | LV2 ← | TX |
| GND | GND | GND | GND |
| 3.3V | - | LV (VCC) | - |
| 5V (USB給電時) | HV (VCC) | - | VCC |
※ MKR1000 のハードウェアシリアルは Serial1 (D13/D14) です。Serial は USB-CDC (PC通信用) なので注意。
LCD 1602 (I2C) 接続
- SDA → D11, SCL → D12 (MKR1000のI2Cピン)
- VCC → 5V (または3.3V対応モジュールなら3.3V), GND → GND
OBD-II コマンドとレスポンス解析
サービスモード 0x01 (現在データ) の主要 PID
| PID (Hex) | 名称 | 単位/変換式 | コマンド |
|---|---|---|---|
| 0x0D | 車速 | km/h (そのまま) | 010D |
| 0x0C | エンジン回転数 | RPM = (A×256 + B) / 4 | 010C |
| 0x2F | 燃料レベル | % = A / 255 × 100 | 012F |
| 0x05 | 冷却水温度 | °C = A - 40 | 0105 |
レスポンス形式
送信: "010D\r"
応答: ">1 0D 00\r" (">1 " = エコー+モード, "0D" = PID, "00" = データ)
送信: "010C\r"
応答: ">1 0C 09 8C\r" (データ2バイト: 0x098C = 2444 → 2444/4 = 611 RPM)
データ抽出ロジック
受信バッファー rxData に文字列格納。キャリッジリターン \r で終了判定。データ部分は 6文字目以降 (0-indexed) から開始。
完全スケッチ (IoT4Car_code)
/* OBDII-UART-Serial v9
* Arduino MKR WiFi 1000 + SparkFun OBD-II UART + Level Shifter + LCD1602
* dweet.io / freeboard.io 連携
*/
#include <SPI.h>
#include <WiFi101.h>
#include <LiquidCrystal.h>
// Wi-Fi設定
char ssid[] = "YOUR_WIFI_SSID"; // スマホテザリングSSID
char pass[] = "YOUR_WIFI_PASSWORD";
char server[] = "www.dweet.io";
unsigned long lastConnectionTime = 0;
const unsigned long postingInterval = 10L * 1000L; // 10秒ごと送信
WiFiClient client;
int wifiStatus = WL_IDLE_STATUS;
// LCD設定 (ピン配置は配線に合わせて)
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// OBD通信バッファ
char rxData[20];
char rxIndex = 0;
char inChar = 0;
String message;
// 車両データ
int vSpeed = 0; // mph
int vRPM = 0; // rpm
int vFuel = 0; // %
int vTemp = 0; // °C
void setup() {
lcd.begin(16, 2);
lcd.clear();
// Wi-Fiシールド確認
if (WiFi.status() == WL_NO_SHIELD) {
lcd.println("WiFi shield missing");
while (true);
}
// Wi-Fi接続 (テザリング対応)
while (wifiStatus != WL_CONNECTED) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.println("Connecting WiFi...");
wifiStatus = WiFi.begin(ssid, pass);
delay(5000);
}
lcd.setCursor(0, 1);
lcd.println("Connected!");
// OBD用シリアル (Serial1 = D13/D14)
Serial1.begin(9600);
resetBuffer();
}
void loop() {
// Wi-Fi再接続チェック
while (wifiStatus != WL_CONNECTED) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.println("Reconnecting WiFi...");
wifiStatus = WiFi.begin(ssid, pass);
delay(5000);
}
// 4項目順次取得
getSpeed(); // 010D
getRPM(); // 010C
getFuel(); // 012F
getCoolTemp(); // 0105
// クラウド送信 (10秒間隔)
if (millis() - lastConnectionTime > postingInterval) {
httpRequest();
lastConnectionTime = millis();
}
}
void getSpeed() {
message = "010D";
Serial1.println(message);
delay(200);
clearLcdLine(0, 0, 8); // 1行目前半クリア
lcd.setCursor(0, 0);
getResponse();
vSpeed = strtol(&rxData[6], 0, 16); // km/h
vSpeed = vSpeed * 0.621371; // mph変換
lcd.print(vSpeed);
lcd.print(" mph");
}
void getRPM() {
message = "010C";
Serial1.println(message);
delay(200);
clearLcdLine(8, 0, 8); // 1行目後半クリア
lcd.setCursor(8, 0);
getResponse();
// 2バイトデータ (rxData[6][7]と[9][10]? 実際はスペース区切りで6,9文字目から)
int high = strtol(&rxData[6], 0, 16);
int low = strtol(&rxData[9], 0, 16);
vRPM = (high * 256 + low) / 4;
lcd.print(vRPM);
lcd.print(" rpm");
}
void getFuel() {
message = "012F";
Serial1.println(message);
delay(200);
clearLcdLine(0, 1, 8); // 2行目前半クリア
lcd.setCursor(0, 1);
getResponse();
vFuel = strtol(&rxData[6], 0, 16);
vFuel = 1.0 * vFuel / 255 * 100;
lcd.print(vFuel);
lcd.print(" %");
}
void getCoolTemp() {
message = "0105";
Serial1.println(message);
delay(200);
clearLcdLine(8, 1, 8); // 2行目後半クリア
lcd.setCursor(8, 1);
getResponse();
vTemp = strtol(&rxData[6], 0, 16);
vTemp = vTemp - 40;
lcd.print(vTemp);
lcd.write(0xDF); // 度記号 (HD44780キャラコード)
lcd.print("C");
}
void getResponse() {
while (Serial1.available() > 0) {
if (Serial1.peek() == '\r') {
inChar = Serial1.read();
rxData[rxIndex] = '\0';
rxIndex = 0;
} else {
inChar = Serial1.read();
rxData[rxIndex++] = inChar;
}
}
}
void resetBuffer() {
for (int i = 0; i < 20; i++) rxData[i] = 0;
}
void clearLcdLine(int col, int row, int len) {
lcd.setCursor(col, row);
for (int i = 0; i < len; i++) lcd.write(' ');
}
void httpRequest() {
client.stop();
if (client.connect(server, 80)) {
String data = "POST /dweet/for/mkr1000?RPM=";
data.concat(vRPM);
data.concat("&Speed=");
data.concat(vSpeed);
data.concat("&Fuel=");
data.concat(vFuel);
data.concat("&Temp=");
data.concat(vTemp);
client.println(data);
client.println("Host: www.dweet.io");
client.println("Connection: close");
client.println();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.println("Conn failed");
}
}
freeboard.io ダッシュボード構築手順
- freeboard.io でアカウント作成 → 新規ダッシュボード作成
- Datasources → Add → Type: dweet.io, Thing Name:
mkr1000(スケッチと一致させる) - Widgets → Add Pane → Add Widget → Type: Gauge 等選択
- Value:
datasources["mkr1000"]["RPM"]等を指定 - Min/Max/Units 設定 (RPM: 0-8000, Speed: 0-200, Fuel: 0-100%, Temp: -40-150°C)
- Save → 公開URLでどこからでも監視可能
実車テストでの注意点
- 安全第一: 運転中のデバッグ/操作は絶対に行わない。同乗者が操作するか、停車中に確認。
- 電源: シガーソケット USBアダプター (2A以上推奨) で MKR1000 給電
- Wi-Fi: 車内ではスマホテザリングが最も安定。駐車時は自宅/職場Wi-Fiに自動切替するコード追加推奨
- OBDプロトコル: 車両によって対応PIDが異なる。未対応PIDは「NO DATA」応答 → エラーハンドリング必要
- 電圧レベル: MKR1000は 3.3V 耐圧。**レベルシフター必須**。直結すると破損リスク大。
発展・改良アイデア
- MKR GSM 1400: Wi-Fi不要でLTE-M/NB-IoT直通、エリア拡大
- GPSモジュール追加: 位置情報 + テレメトリー = フリート管理システム
- 加速度センサー (MPU6050等): 急ブレーキ/急ハンドル/衝突検知
- 燃費計算: 速度積分 / 燃料消費量 (インジェクターパルス幅等から推定)
- エッジ処理: 異常検知 (過熱、失火等) をデバイス側で判定し即時アラート
- PCB化: 作者も統合PCB設計中 (シールド形式)
- セキュリティ: TLS/SSL対応 (WiFi101は対応)、デバイス認証、データ暗号化
- OBD-II PID拡張: モード 0x22 (拡張PID) でメーカー固有データ取得
回路図 (Fritzing)
プロジェクトページの Schematics: frizling_schematics_M8kF26dafQ.fzz
製造プロセス
- 車両ナンバープレート総合ガイド:歴史、デザイン、未来
- アンチロックブレーキシステム(ABS):滑りやすい路面での車両安全を守る
- Arduinoと超音波センサーで作るAI障害物回避ロボット 〜Q学習による強化学習実装〜
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- DIYクラウドコスチューム: LEDストリップ、Arduino Nano & SparkFunパーツで光る雲に
- Pixie: Arduino Pro Miniで作るNeoPixel腕時計 - アナログとデジタル融合のDIY光る時計
- 車両シャーシレイアウト:エンジン位置と駆動配置の解説
- 車両配線の基礎知識:信頼性の高い自動車性能のためのキーポイント
- パワートレインを理解する:車両を動かす仕組み
- オルタネーターを理解する:車に電力を供給する仕組みと故障の兆候
- シャシ接地(シャシグラウンド)の目的・機能・重要性を解説