Seri Protokoller
TEKNİK REHBER SERİ PROTOKOLLER OBD-II 2026

OBD-II / ISO 15765
Araç Teşhis Protokolü

PID tabanlı sensör okuma, DTC yönetimi, ISO-TP transport katmanı — Linux SocketCAN ve Python ile araç verisine erişim.

00 OBD-II nedir: yasal zorunluluk ve protokol ailesi

OBD-II (On-Board Diagnostics, ikinci nesil), araçlardaki emisyon sistemlerini izlemek ve arıza kodlarını teşhis etmek amacıyla geliştirilmiş standarttır. ABD'de 1996'dan, AB'de 2001'den itibaren zorunludur.

Yasal zemin

SAE J1962 konnektör16 pinli trapez konnektör, araçta sürücü tarafında ön panelin altında bulunur. Her araçta standardize edilmiştir; adaptör gerekmez. Pin 6: CAN High, Pin 14: CAN Low, Pin 16: +12V, Pin 4/5: GND.
1996+ ABD (CARB)California Air Resources Board zorunluluğuyla tüm ABD pazarındaki yeni araçlarda OBD-II zorunlu hale geldi.
2001+ AB (EOBD)European OBD, benzinli araçlar için 2001'de, dizeller için 2004'te zorunlu hale geldi.

Fiziksel katman protokol ailesi

ProtokolStandartHızDurum
ISO 15765-4 (CAN)ISO 15765250k / 500k bit/sGünümüz standardı (2008+ tüm araçlar)
ISO 9141-2ISO 914110.4 kbaudEski Avrupa ve Asya araçları (K-Line)
ISO 14230 (KWP2000)ISO 1423010.4 kbaudEski Avrupa araçları
SAE J1850 PWMSAE J185041.6 kbaudEski Ford araçları
SAE J1850 VPWSAE J185010.4 kbaudEski GM araçları
NOT

2008 model yılından itibaren ABD'de satılan tüm araçlar ISO 15765-4 (CAN tabanlı OBD-II) kullanmak zorundadır. Eski protokoller ELM327 gibi multi-protokol adaptörlerle desteklenir.

01 ISO 15765-4: CAN tabanlı OBD-II

ISO 15765-4, CAN bus üzerinde OBD-II iletişimini tanımlar. 11-bit CAN ID ile çalışır; özel broadcast ve unicast adresleme şeması kullanır.

CAN ID şeması

CAN IDYönAçıklama
0x7DFScan tool → ECUBroadcast request — tüm ECU'lara
0x7E0Scan tool → ECUEngine ECU'ya hedefli istek
0x7E1Scan tool → ECUTransmission ECU hedefli
0x7E2–0x7E7Scan tool → ECUDiğer ECU'lar
0x7E8ECU → Scan toolEngine ECU yanıtı (0x7E0 + 8)
0x7E9ECU → Scan toolTransmission ECU yanıtı
0x7EA–0x7EFECU → Scan toolDiğer ECU yanıtları

OBD-II istek-yanıt yapısı

  İstek (Scan Tool → 0x7DF):
  [02][01][0C][00][00][00][00][00]
   │   │   │   └── padding (0x00)
   │   │   └────── PID 0x0C (Engine RPM)
   │   └────────── Mode 01 (current data)
   └────────────── PCI byte: Single Frame, length=2

  Yanıt (Engine ECU 0x7E8 →):
  [04][41][0C][1A][F8][00][00][00]
   │   │   │   └──── data (2 byte: 0x1AF8)
   │   │   └──────── PID echo
   │   └──────────── Mode 01 response (0x40 | 0x01 = 0x41)
   └──────────────── PCI byte: Single Frame, length=4

  RPM = (0x1A * 256 + 0xF8) / 4 = (6656 + 248) / 4 = 1726 RPM
    

02 Servis modları

OBD-II servis modları (service/mode byte), farklı teşhis bilgisi kategorilerine erişim sağlar. Mode 01-09 OBD-II standardında tanımlıdır; 0x10+ araç üreticisi tanımlıdır.

Mode (hex)İsimAçıklama
0x01Show current dataGerçek zamanlı sensör verileri (PID ile sorgu)
0x02Show freeze frame dataDTC oluştuğundaki anlık sensör snapshot'ı
0x03Show DTCsAktif arıza kodları (Diagnostic Trouble Codes)
0x04Clear DTCsArıza kodlarını ve freeze frame'leri temizle
0x05O2 sensor monitoringOksijen sensörü test sonuçları (CAN'da kullanılmaz)
0x06On-board monitoringSürekli izleme test sonuçları
0x07Pending DTCsOnaylanmamış (bekleyen) arıza kodları
0x08Control operationAktüatör ve komponent testi
0x09Vehicle informationVIN, kalibrasyon ID, CVN
0x0APermanent DTCsTemizlenemez kalıcı arıza kodları

Mode 03: DTC format

dtc_decode.py
def decode_dtc(b1: int, b2: int) -> str:
    """2 byte DTC'yi P0123 formatına çevir"""
    systems = {0: 'P', 1: 'C', 2: 'B', 3: 'U'}
    system   = systems[(b1 >> 6) & 0x03]
    digit1   = (b1 >> 4) & 0x03   # 0-3
    digit2   = b1 & 0x0F
    digit3   = (b2 >> 4) & 0x0F
    digit4   = b2 & 0x0F
    return ff"{system}{digit1}{digit2:X}{digit3:X}{digit4:X}"

# Örnek: 0x01, 0x13 → P0113 (IAT Sensor High Input)
print(decode_dtc(0x01, 0x13))  # P0113
print(decode_dtc(0x43, 0x02))  # P0302 (Cylinder 2 Misfire)

03 PID sistemi ve formüller

Mode 01 PID'leri, gerçek zamanlı araç verilerine erişim sağlar. Her PID'in veri uzunluğu ve hesaplama formülü standartta belirtilmiştir.

Mode 01 PID 0x00: Desteklenen PID bitmask

PID 0x00 sorgusu, ECU'nun hangi PID'leri desteklediğini 32-bit bitmask olarak döner. Bit 31 = PID 0x01, bit 0 = PID 0x20.

supported_pids.py
def decode_supported_pids(data: bytes, base_pid: int = 0x00) -> list:
    """PID 0x00/0x20/0x40 yanıtından desteklenen PID listesi çıkar"""
    bitmap = int.from_bytes(data[:4], 'big')
    supported = []
    for bit in range(32):
        if bitmap & (1 << (31 - bit)):
            supported.append(base_pid + bit + 1)
    return supported

# Örnek yanıt: 0xBE1FA813
pids = decode_supported_pids(bytes([0xBE, 0x1F, 0xA8, 0x13]))
print(", ".join(ff"0x{p:02X}" for p in pids))

Yaygın PID tablosu ve formüller

PIDİsimByteFormülBirim
0x04Engine LoadAA × 100 / 255%
0x05Coolant TemperatureAA - 40°C
0x0BIntake Manifold PressureAAkPa
0x0CEngine RPMA, B(256A + B) / 4rpm
0x0DVehicle SpeedAAkm/h
0x0ETiming AdvanceAA / 2 - 64° before TDC
0x0FIntake Air TemperatureAA - 40°C
0x10MAF Air Flow RateA, B(256A + B) / 100g/s
0x11Throttle PositionAA × 100 / 255%
0x2FFuel Tank LevelAA × 100 / 255%
0x46Ambient Air TemperatureAA - 40°C
0x5CEngine Oil TemperatureAA - 40°C
pid_decoder.py
PID_DECODERS = {
    0x04: ('Engine Load %',       lambda d: d[0] * 100 / 255),
    0x05: ('Coolant Temp °C',     lambda d: d[0] - 40),
    0x0C: ('Engine RPM',          lambda d: (d[0] * 256 + d[1]) / 4),
    0x0D: ('Vehicle Speed km/h',  lambda d: d[0]),
    0x0F: ('Intake Air Temp °C',  lambda d: d[0] - 40),
    0x10: ('MAF g/s',             lambda d: (d[0] * 256 + d[1]) / 100),
    0x11: ('Throttle %',           lambda d: d[0] * 100 / 255),
    0x2F: ('Fuel Level %',         lambda d: d[0] * 100 / 255),
    0x5C: ('Oil Temp °C',          lambda d: d[0] - 40),
}

def decode_pid(pid: int, raw_data: bytes):
    if pid in PID_DECODERS:
        name, formula = PID_DECODERS[pid]
        value = formula(raw_data)
        print(ff"PID 0x{pid:02X} {name}: {value:.2f}")
    else:
        print(ff"PID 0x{pid:02X}: {raw_data.hex()}")

04 ISO-TP (ISO 15765-2): transport katmanı

ISO-TP (ISO 15765-2), 8 byte CAN limiti üzerinde veri taşımak için segmentasyon ve akış kontrolü sağlar. OBD-II, UDS ve AUTOSAR bu katman üzerine inşa edilir.

ISO-TP frame tipleri

Frame TipiPCI byte (nibble)Açıklama
Single Frame (SF)0x0N (N=0-7)N byte veri, tek CAN frame'e sığıyor
First Frame (FF)0x1L (12-bit len)Çok parçalı mesajın ilk frame'i
Consecutive Frame (CF)0x2N (N=sıra no)Devam frame'leri
Flow Control (FC)0x3XAlıcının akış kontrolü (CTS/WT/OVFLW)

Çok frame'li mesaj akışı

  Gönderen (0x7E0)              Alıcı (0x7E8)
    → [10][0F][41][03]...       First Frame: 15 byte mesaj
                ← [30][00][00]  Flow Control: CTS, BlockSize=0 (sınırsız)
    → [21][xx][xx][xx][xx][xx][xx][xx]  CF #1
    → [22][xx][xx][xx][xx][xx][xx][xx]  CF #2 (son)

  PCI byte açıklaması:
    10 0F = FF, total length = 15
    30 00 00 = FC, ContinueToSend, BlockSize=0, SeparationTime=0ms
    21 = CF sıra no = 1
    22 = CF sıra no = 2
    

Linux isotp socket modülü

bash — isotp modülü
# ISO-TP kernel modülü yükle
sudo modprobe can-isotp

# Modülün yüklendiğini doğrula
lsmod | grep isotp

# isotp socket kullanımı: isotpsend / isotprecv araçları
# isotp-utils paketi (can-utils ile birlikte gelir)

# OBD Mode 01 PID 0x0C isteği gönder (tek frame)
# TX ID: 0x7DF (broadcast), RX ID: 0x7E8 (engine ECU yanıtı)
sudo isotpsend -s 7DF -d 7E8 can0 <<< "01 0C"

# Yanıt al
sudo isotprecv -s 7E8 -d 7DF can0

05 ELM327 ve türevleri

ELM327, OBD-II protokollerini soyutlayan popüler bir mikrodenetleyici chipsettir. AT komutları ile yapılandırılır; çoğu araç teşhis adaptörünün temelini oluşturur.

Temel AT komutları

AT Komutuİşlev
AT ZSıfırla ve ELM327 sürümünü yazdır
AT E0Echo kapalı (temiz çıktı için)
AT L0Satır sonu (linefeed) kapalı
AT H1CAN header (ID) göster
AT SP 6Protokol 6 seç: ISO 15765-4 CAN (11-bit, 500k)
AT SP 0Protokolü otomatik algıla
AT DPAktif protokolü göster
AT RVAraç pil voltajını ölç
01 0CMode 01, PID 0x0C (RPM) sorgula
03Mode 03: DTC listesi oku

ELM327 protokol seçenekleri (AT SP)

SP değeriProtokol
0Otomatik algıla
3ISO 9141-2
4, 5ISO 14230-4 (KWP2000)
6ISO 15765-4 CAN (11-bit, 500 kbps)
7ISO 15765-4 CAN (29-bit, 500 kbps)
8ISO 15765-4 CAN (11-bit, 250 kbps)
9ISO 15765-4 CAN (29-bit, 250 kbps)

Arduino / Bluetooth / WiFi ELM327 dongle

elm327_serial.py — ham AT komutları
import serial, time

class ELM327:
    def __init__(self, port: str, baud: int = 38400):
        self.ser = serial.Serial(port, baud, timeout=2)
        self._init()

    def _init(self):
        self.cmd('AT Z')   # reset
        time.sleep(1)
        self.cmd('AT E0')  # echo off
        self.cmd('AT L0')  # linefeed off
        self.cmd('AT H1')  # headers on
        self.cmd('AT SP 0') # auto protocol

    def cmd(self, command: str) -> str:
        self.ser.write((command + '\r').encode())
        time.sleep(0.1)
        resp = self.ser.read_all().decode('ascii', errors='ignore')
        return resp.strip().rstrip('>').strip()

    def get_rpm(self) -> float:
        resp = self.cmd('01 0C')
        # Yanıt örneği: "7E8 04 41 0C 1A F8"
        parts = resp.split()
        if '41' in parts:
            idx = parts.index('41') + 2  # PID echo sonrası
            return (int(parts[idx], 16) * 256 + int(parts[idx+1], 16)) / 4
        return -1

elm = ELM327('/dev/rfcomm0')  # Bluetooth port
print(ff"RPM: {elm.get_rpm()}")

06 python-obd kütüphanesi

python-obd, ELM327 tabanlı adaptörler için yüksek seviyeli Python API sunar. PID tanımlamaları, birim dönüşümleri ve async izleme desteği içerir.

bash — kurulum
pip install obd
obd_basic.py
import obd

# Otomatik port algılamayla bağlan
connection = obd.OBD()   # veya obd.OBD('/dev/rfcomm0')

if connection.is_connected():
    print("OBD-II bağlantısı kuruldu")

# Anlık sorgu
rpm_resp  = connection.query(obd.commands.RPM)
speed_resp = connection.query(obd.commands.SPEED)
temp_resp  = connection.query(obd.commands.COOLANT_TEMP)

if not rpm_resp.is_null():
    print(ff"RPM: {rpm_resp.value.magnitude:.0f}")

if not speed_resp.is_null():
    print(ff"Hız: {speed_resp.value.to('km/h').magnitude:.1f} km/h")

if not temp_resp.is_null():
    print(ff"Soğutucu Sıcaklığı: {temp_resp.value.magnitude:.1f}°C")

connection.close()

Async mod ve watch()

obd_async.py
import obd, time

# Asenkron bağlantı
connection = obd.Async()

# Callback ile PID izle
def on_rpm(response):
    if not response.is_null():
        print(ff"RPM: {response.value.magnitude:.0f}")

def on_speed(response):
    if not response.is_null():
        print(ff"Hız: {response.value.to('km/h').magnitude:.1f}")

connection.watch(obd.commands.RPM,   callback=on_rpm)
connection.watch(obd.commands.SPEED, callback=on_speed)
connection.start()

time.sleep(30)  # 30 saniye izle
connection.stop()
connection.close()

DTC okuma ve temizleme

obd_dtc.py
import obd

conn = obd.OBD()

# Aktif DTC'leri oku (Mode 03)
dtc_response = conn.query(obd.commands.GET_DTC)
if not dtc_response.is_null():
    for code, desc in dtc_response.value:
        print(ff"DTC: {code}  {desc}")
else:
    print("DTC yok")

# Arıza kodlarını temizle (Mode 04) — DİKKATLİ KULLAN!
# clear_resp = conn.query(obd.commands.CLEAR_DTC)
# if clear_resp.is_null():
#     print("Temizleme başarısız")
# else:
#     print("DTC'ler temizlendi")

conn.close()

Custom command tanımlama

obd_custom.py
import obd
from obd import OBDCommand
from obd.protocols import ECU
from obd.utils import bytes_to_int

def decode_oil_temp(messages):
    d = messages[0].data[2:]
    if len(d) < 1:
        return obd.OBDResponse()
    temp = bytes_to_int(d) - 40
    return obd.Unit.Quantity(temp, obd.Unit.celsius)

# PID 0x5C: Engine Oil Temperature (Mode 01)
OIL_TEMP = OBDCommand(
    name       = "OIL_TEMP",
    desc       = "Engine Oil Temperature",
    command    = b"\x01\x5C",
    _bytes     = 3,
    decoder    = decode_oil_temp,
    ecu        = ECU.ENGINE,
    fast       = True
)

conn = obd.OBD()
resp = conn.query(OIL_TEMP)
if not resp.is_null():
    print(ff"Motor Yağ Sıcaklığı: {resp.value}°C")
conn.close()

07 Linux SocketCAN + isotp ile OBD-II

ELM327 soyutlaması yerine, SocketCAN + ISO-TP socket ile doğrudan CAN katmanından OBD-II sorgusu gönderilebilir. Bu yöntem daha düşük gecikme ve tam kontrol sağlar.

isotp kernel modülü ve araçlar

bash — isotp kurulumu
# ISO-TP modülü yükle
sudo modprobe can-isotp

# Araç bağlantısı (OBD konnektör → PEAK PCAN-USB veya MCP2515)
sudo ip link set can0 type can bitrate 500000 restart-ms 100
sudo ip link set can0 up

# RPM sorgusu: Mode 01, PID 0x0C
# TX: 0x7DF broadcast, RX: 0x7E8 (engine ECU yanıtı)
sudo isotpsend -s 7DF -d 7E8 can0 <<< "01 0C"

# Yanıt alma (ayrı terminal)
sudo isotprecv -s 7E8 -d 7DF can0
# Çıktı: 41 0C 1A F8 (41=Mode 01 yanıt, 0C=PID echo, 1AF8=RPM raw)

# DTC sorgusu (Mode 03 — no PID)
sudo isotpsend -s 7DF -d 7E8 can0 <<< "03"

C ile ham OBD-II sorgusu (ISO-TP socket)

obd2_raw.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/isotp.h>

int main(void) {
    int s = socket(AF_CAN, SOCK_DGRAM, CAN_ISOTP);

    struct sockaddr_can addr;
    memset(&addr, 0, sizeof(addr));
    addr.can_family  = AF_CAN;
    addr.can_addr.tp.tx_id = 0x7DF;  /* OBD broadcast */
    addr.can_addr.tp.rx_id = 0x7E8;  /* Engine ECU yanıtı */
    addr.can_ifindex = if_nametoindex("can0");

    bind(s, (struct sockaddr *)&addr, sizeof(addr));

    /* Mode 01 PID 0x0C isteği */
    uint8_t req[] = {0x01, 0x0C};
    write(s, req, sizeof(req));

    /* Yanıt oku */
    uint8_t resp[8];
    ssize_t len = read(s, resp, sizeof(resp));

    if (len >= 4 && resp[0] == 0x41 && resp[1] == 0x0C) {
        float rpm = ((resp[2] * 256.0f) + resp[3]) / 4.0f;
        printf("Motor Hızı: %.1f RPM\n", rpm);
    }

    close(s);
    return 0;
}

08 Pratik: araç veri loggeri ve dashboard

Raspberry Pi + ELM327 (Bluetooth veya USB) kombinasyonu ile gerçek zamanlı araç verisi kaydedilebilir ve görselleştirilebilir.

Raspberry Pi + ELM327 Bluetooth kurulumu

bash — Bluetooth ELM327 eşleşme
# Bluetooth servis aktif mi?
sudo systemctl status bluetooth

# Bluetooth cihazları tara
sudo bluetoothctl
# power on
# scan on
# pair XX:XX:XX:XX:XX:XX  (ELM327 MAC adresi)
# trust XX:XX:XX:XX:XX:XX

# RFCOMM seri port oluştur
sudo rfcomm bind 0 XX:XX:XX:XX:XX:XX 1
# /dev/rfcomm0 oluştu

# Test
sudo python3 -c "import obd; print(obd.OBD('/dev/rfcomm0').status())"

Gerçek zamanlı dashboard (python-obd + matplotlib)

obd_dashboard.py
import obd, time, collections
import matplotlib.pyplot as plt
import matplotlib.animation as animation

MAX_POINTS = 60
rpm_history   = collections.deque(maxlen=MAX_POINTS)
speed_history = collections.deque(maxlen=MAX_POINTS)

conn = obd.Async()
conn.watch(obd.commands.RPM)
conn.watch(obd.commands.SPEED)
conn.start()

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
fig.suptitle('Araç Verileri — OBD-II')

def update(frame):
    rpm_r   = conn.query(obd.commands.RPM)
    speed_r = conn.query(obd.commands.SPEED)

    if not rpm_r.is_null():
        rpm_history.append(rpm_r.value.magnitude)
    if not speed_r.is_null():
        speed_history.append(speed_r.value.to('km/h').magnitude)

    ax1.clear()
    ax1.plot(list(rpm_history), color='#E74C3C', linewidth=2)
    ax1.set_ylabel('RPM'); ax1.set_ylim(0, 8000)
    ax1.set_title(ff"RPM: {rpm_history[-1]:.0f}" if rpm_history else 'RPM')

    ax2.clear()
    ax2.plot(list(speed_history), color='#2ECC71', linewidth=2)
    ax2.set_ylabel('Hız (km/h)'); ax2.set_ylim(0, 200)
    ax2.set_title(ff"Hız: {speed_history[-1]:.1f} km/h" if speed_history else 'Hız')

ani = animation.FuncAnimation(fig, update, interval=500)
plt.tight_layout()
plt.show()
conn.stop()
conn.close()

VIN decode

obd_vin.py
import obd

conn = obd.OBD()

# Mode 09 PID 0x02: VIN
vin_resp = conn.query(obd.commands.VIN)
if not vin_resp.is_null():
    vin = vin_resp.value
    print(ff"VIN: {vin}")

    # VIN decode (basit)
    countries = {'1': 'ABD', '2': 'Kanada', '3': 'Meksika',
                 '4': 'ABD', 'J': 'Japonya', 'K': 'Güney Kore',
                 'S': 'İngiltere', 'W': 'Almanya', 'V': 'Fransa'}
    if len(vin) == 17:
        wmi = vin[:3]
        model_year_char = vin[9]
        seq_no = vin[11:]
        country = countries.get(vin[0], 'Bilinmiyor')
        print(ff"Ülke: {country}, WMI: {wmi}, Sıra: {seq_no}")

conn.close()

Fleet monitoring giriş

fleet_logger.py — araç filoya veri gönderme
import obd, time, json, requests

FLEET_API = "https://your-fleet-api.example.com/telemetry"

conn = obd.Async()
conn.watch(obd.commands.RPM)
conn.watch(obd.commands.SPEED)
conn.watch(obd.commands.COOLANT_TEMP)
conn.watch(obd.commands.FUEL_STATUS)
conn.start()

def collect_and_send():
    while True:
        data = {
            'timestamp': time.time(),
            'rpm': None, 'speed_kmh': None,
            'coolant_c': None,
        }

        r = conn.query(obd.commands.RPM)
        if not r.is_null(): data['rpm'] = r.value.magnitude

        r = conn.query(obd.commands.SPEED)
        if not r.is_null(): data['speed_kmh'] = r.value.to('km/h').magnitude

        r = conn.query(obd.commands.COOLANT_TEMP)
        if not r.is_null(): data['coolant_c'] = r.value.magnitude

        try:
            requests.post(FLEET_API, json=data, timeout=2)
        except Exception:
            pass  # bağlantı kesintisinde logla, devam et

        time.sleep(5)   # 5 saniyede bir gönder

collect_and_send()
DİKKAT

OBD-II araç verilerine erişim yasal olarak izin verilse de bazı araç üreticileri (özellikle 2022+ modeller) araç sürücüsünün OBD portuna erişimini kısıtlamaktadır. Mode 04 (DTC Temizle) komutu emisyon monitörlerini sıfırlar; araç muayenesi öncesi kullanılmamalıdır.