00 J1939 nedir: ağır araç standart ekosistemi
SAE J1939, kamyon, otobüs, tarım makinesi ve inşaat ekipmanlarında ECU'lar arası haberleşmeyi standardize eden protokol ailesidir. CAN 2.0B (29-bit ID) üzerine inşa edilmiştir.
Uygulama alanları
J1939 standart ailesi
| Standart | Konu |
|---|---|
| J1939/11 | Fiziksel katman: kablo, terminasyon, konnektör |
| J1939/13 | Off-Board Diagnostic konnektörü |
| J1939/15 | Azaltılmış fiziksel katman (izolasyon gerektirmeyen) |
| J1939/21 | Veri katmanı: frame formatı, PGN yapısı |
| J1939/31 | Ağ katmanı: multi-packet transport protocol |
| J1939/71 | Araç uygulama katmanı: PGN ve SPN tanımları |
| J1939/73 | Teşhis uygulamaları: DM mesajları |
| J1939/81 | Adres yönetimi: NAME, address claiming |
ISOBUS, tarım makineleri için J1939 üzerine inşa edilmiş bir protokoldür. Traktör ile çekilen alet (pullType/mounted implements) arasındaki haberleşmeyi standartlaştırır. Temel J1939 bilgisi ISOBUS için doğrudan uygulanabilir.
01 Adres yapısı: PGN, SPN, SA ve DA
J1939'un temel kavramları PGN (parametre grubu numarası) ve SPN (şüpheli parametre numarası) ile tanımlanır. Her mesaj bir kaynak adresten gelir ve bir hedef adrese veya global alıcılara yönelir.
Temel kavramlar
02 Frame anatomy: 29-bit CAN ID detayı
J1939, CAN 2.0B'nin 29-bit extended ID'sini tam olarak kullanır. Her bit grubu protokol anlamı taşır.
29-bit ID yapısı
Bit 28-26: Priority (P) — 3-bit, 0-7 (0=en yüksek)
Bit 25: Reserved (R) — 0 olmalı
Bit 24: Data Page (DP) — PGN seçiminde kullanılır
Bit 23-16: PDU Format (PF) — 8-bit
Bit 15-8: PDU Specific (PS)— 8-bit: PF<240 ise DA, PF≥240 ise Group Extension
Bit 7-0: Source Address (SA) — 8-bit
PGN = (DP << 16) | (PF << 8) | (PS, sadece PDU2/broadcast için)
PDU1 (hedefli, PF < 0xF0):
PS = Destination Address
PGN = (R << 17) | (DP << 16) | (PF << 8) | 0x00
PDU2 (broadcast, PF ≥ 0xF0):
PS = Group Extension
PGN = (R << 17) | (DP << 16) | (PF << 8) | PS
Pratik hesaplama örneği
def decode_j1939_id(can_id_29bit: int) -> dict:
"""29-bit J1939 CAN ID'yi decode eder"""
priority = (can_id_29bit >> 26) & 0x07
reserved = (can_id_29bit >> 25) & 0x01
dp = (can_id_29bit >> 24) & 0x01
pf = (can_id_29bit >> 16) & 0xFF
ps = (can_id_29bit >> 8) & 0xFF
sa = (can_id_29bit >> 0) & 0xFF
if pf < 0xF0: # PDU1: hedefli
pgn = (dp << 16) | (pf << 8)
da = ps
else: # PDU2: broadcast
pgn = (dp << 16) | (pf << 8) | ps
da = 0xFF # global
return {
'priority': priority,
'pgn': pgn,
'sa': sa,
'da': da,
'pf': pf,
'ps': ps,
}
# Örnek: EEC1 mesajı (PGN 61444 = 0xF004, Priority=3, SA=0x00)
# Priority=3 (011b), R=0, DP=0, PF=0xF0, PS=0x04, SA=0x00
# CAN ID = (3<<26)|(0xF0<<16)|(0x04<<8)|0x00 = 0x0CF00400
result = decode_j1939_id(0x0CF00400)
print(ff"PGN: {result['pgn']:05X} ({result['pgn']}) SA: {result['sa']:02X}")
# PGN: F004 (61444) SA: 00
03 PGN örnekleri ve SPN tablosu
J1939/71 yüzlerce PGN tanımlar. En yaygın kullanılan PGN'ler motor yönetimi, araç hızı ve teşhis mesajlarıdır.
Önemli PGN'ler
| PGN (onluk) | PGN (hex) | İsim | Periyot |
|---|---|---|---|
| 61444 | 0xF004 | Electronic Engine Controller 1 (EEC1) | 10 ms |
| 65262 | 0xFEEE | Engine Temperature 1 | 1000 ms |
| 65265 | 0xFEF1 | Cruise Control / Vehicle Speed | 100 ms |
| 65270 | 0xFEF6 | Inlet/Exhaust Conditions 1 | 500 ms |
| 65271 | 0xFEF7 | Vehicle Electrical Power 1 | 1000 ms |
| 65276 | 0xFEFC | Dash Display 1 | 1000 ms |
| 65279 | 0xFEFF | Fuel Economy (Liquid) | 100 ms |
| 60928 | 0xEE00 | Address Claimed | On request / event |
| 59392 | 0xE800 | Acknowledgment (ACK/NACK) | On request |
| 65226 | 0xFECA | DM1 (Active Diagnostic Trouble Codes) | 1000 ms |
EEC1 (PGN 61444) SPN dökümü
| SPN | Bit/Byte | İsim | Birim | Formül |
|---|---|---|---|---|
| 899 | Byte 1, bit 1-4 | Engine Torque Mode | — | Durum kodu |
| 512 | Byte 2 | Driver's Demand Engine Percent Torque | % | x - 125 (%) |
| 513 | Byte 3 | Actual Engine Percent Torque | % | x - 125 (%) |
| 190 | Byte 4-5 | Engine Speed (RPM) | rpm | x * 0.125 rpm/bit |
| 1483 | Byte 6 | Source Address of Controlling Device | — | Kaynak SA |
| 1675 | Byte 7, bit 1-4 | Engine Starter Mode | — | Durum kodu |
import struct
def parse_eec1(data: bytes) -> dict:
"""PGN 61444 EEC1 frame parse (8 byte)"""
if len(data) < 8:
return {}
torque_mode = (data[0] >> 0) & 0x0F # byte1 bit1-4
driver_torque_pct = data[1] - 125 # SPN 512
actual_torque_pct = data[2] - 125 # SPN 513
rpm_raw = struct.unpack_from('<H', data, 3)[0] # byte 4-5 LE
rpm = rpm_raw * 0.125 # SPN 190
src_ctrl_device = data[5] # SPN 1483
return {
'torque_mode': torque_mode,
'driver_demand_torque_pct': driver_torque_pct,
'actual_torque_pct': actual_torque_pct,
'engine_speed_rpm': rpm,
'ctrl_device_sa': src_ctrl_device,
}
# Örnek veri (1500 RPM, 0% torque)
sample = bytes([0x00, 0x7D, 0x7D, 0xC0, 0x2E, 0x00, 0xFF, 0xFF])
print(parse_eec1(sample))
# {'engine_speed_rpm': 1500.0, 'actual_torque_pct': 0, ...}
04 Transport Protocol: BAM ve RTS/CTS
J1939 maksimum 8 byte CAN payload kısıtını aşmak için transport protocol (TP) katmanı tanımlar. 9–1785 byte arası veriler TP ile aktarılır.
BAM — Broadcast Announce Message
BAM, belirli bir hedef olmaksızın tüm node'lara büyük veri aktarmak için kullanılır. Akış kontrolü yoktur; gönderen sabit hızda gönderir.
Gönderen Tüm alıcılar
↓
TP.CM_BAM (PGN 0xEC00): Toplam byte, paket sayısı, PGN bilgisi
↓ (50 ms bekleme)
TP.DT#1 (PGN 0xEB00): 7 byte veri, sekans no = 1
TP.DT#2: 7 byte veri, sekans no = 2
...
TP.DT#N: Son paket (kalan veri, padding 0xFF)
import struct
def build_tp_bam(pgn: int, data: bytes) -> list:
"""BAM paketlerini üret"""
total_bytes = len(data)
num_packets = (total_bytes + 6) // 7 # 7 byte/paket
frames = []
# TP.CM_BAM frame (PGN 0xEC00 + DA=0xFF + SA)
cm = struct.pack('<BBHBB3s',
0x20, # Control Byte = BAM
total_bytes & 0xFF,
(total_bytes >> 8) & 0xFF,
num_packets,
0xFF, # Rezerv
pgn.to_bytes(3, 'little'))
frames.append(('CM_BAM', bytes(cm)))
# TP.DT frame'leri
padded = data + b'\xFF' * (num_packets * 7 - total_bytes)
for i in range(num_packets):
seq_no = i + 1
chunk = padded[i*7:(i+1)*7]
dt = bytes([seq_no]) + chunk
frames.append((ff'DT#{seq_no}', dt))
return frames
RTS/CTS — Request to Send / Clear to Send
RTS/CTS, belirli bir hedefe yönelik büyük veri aktarımında akış kontrolü sağlar. Alıcı ne kadar veri alabileceğini CTS mesajı ile bildirir.
Gönderen Alıcı
→ TP.CM_RTS (toplam veri, paket sayısı, PGN)
TP.CM_CTS ← (kaç paket gönderebilirsin, hangi sekans'tan)
→ TP.DT#1
→ TP.DT#2
...
TP.CM_CTS ← (devam)
→ TP.DT#N
TP.CM_EndOfMsgAck ← (başarı)
05 Address Claiming
J1939/81, ağa yeni katılan her ECU'nun benzersiz adres talep etmesini ve çakışma durumunda çözüm üretmesini tanımlar.
NAME alanı (64-bit)
Bit 63: Arbitrary Address Capable (1=dinamik adres alabilir)
Bit 62-60: Industry Group (0=global, 2=agricultural, 3=construction...)
Bit 59-56: Vehicle System Instance
Bit 55-49: Vehicle System (motor=0, tahrik=1, fren=2...)
Bit 48: Rezerv
Bit 47-42: Function (motor kontrolü=0, fren=5...)
Bit 41-39: Function Instance
Bit 38-35: ECU Instance
Bit 34-21: Manufacturer Code (11-bit)
Bit 20-0: Identity Number (21-bit, üretici seri no)
Address Claiming süreci
1. ECU ağa katılır, tercih ettiği adresi (SA) seçer
2. "Address Claimed" mesajı (PGN 0xEE00) yayınlar:
CAN ID: Priority=6, PGN=0xEE00, DA=0xFF, SA=istenen_adres
Data: kendi NAME (8 byte)
3. Süre: 250 ms bekler
4. Aynı adrese sahip başka bir ECU "Address Claimed" gönderirse:
- Düşük NAME numarası (daha yüksek öncelik) kazanır
- Yüksek NAME numarası: başka adres dener veya SA=0xFE (null) kullanır
5. Başarılıysa o adresle çalışmaya devam eder
Dinamik adres kazanımı örneği
import socket, struct, time
PGN_ADDRESS_CLAIMED = 0xEE00
def build_address_claimed_id(sa: int) -> int:
"""PGN 0xEE00 için CAN ID oluştur (Priority=6, DA=0xFF)"""
priority = 6
dp = 0
pf = 0xEE
ps = 0xFF # global broadcast
return (priority << 26) | (dp << 24) | (pf << 16) | (ps << 8) | sa
def build_name(manufacturer_code: int, identity_number: int,
function: int = 0, industry_group: int = 0,
arbitrary: bool = True) -> bytes:
"""64-bit J1939 NAME oluştur"""
name = (
((int(arbitrary) & 1) << 63) |
((industry_group & 0x7) << 60) |
((function & 0x7F) << 41) |
((manufacturer_code & 0x7FF) << 21) |
(identity_number & 0x1FFFFF)
)
return struct.pack('<Q', name)
# SA=0x80 talep et
desired_sa = 0x80
can_id = build_address_claimed_id(desired_sa) | 0x80000000 # EFF flag
name = build_name(manufacturer_code=0x123, identity_number=0x00001)
print(ff"Address Claimed: CAN_ID=0x{can_id:08X}, NAME={name.hex()}")
06 Linux J1939 socket
Linux 5.4 kernel'inden itibaren J1939 socket desteği dahil edilmiştir. AF_CAN + J1939 protokol ailesi ile PGN bazlı filtreleme ve adres yönetimi kernel tarafından yapılır.
J1939 socket oluşturma
#include <linux/can.h>
#include <linux/can/j1939.h>
/* J1939 socket — CAN_J1939 protokol ailesi */
int s = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
/* J1939 adresi yapısı */
struct sockaddr_can addr = {
.can_family = AF_CAN,
.can_addr.j1939 = {
.name = J1939_NO_NAME, /* NAME kullanmıyoruz */
.pgn = J1939_NO_PGN, /* tüm PGN'leri al */
.addr = 0x80, /* kendi SA = 0x80 */
},
.can_ifindex = if_nametoindex("can0"),
};
bind(s, (struct sockaddr *)&addr, sizeof(addr));
/* Belirli PGN'e filtrele (örn. EEC1 = 61444) */
struct j1939_filter filter = {
.pgn = 61444,
.pgn_mask = 0x3FFFF,
};
setsockopt(s, SOL_CAN_J1939, SO_J1939_FILTER,
&filter, sizeof(filter));
J1939 veri alma
uint8_t buf[8];
struct sockaddr_can src;
socklen_t addrlen = sizeof(src);
ssize_t len = recvfrom(s, buf, sizeof(buf), 0,
(struct sockaddr *)&src, &addrlen);
uint32_t pgn = src.can_addr.j1939.pgn;
uint8_t sa = src.can_addr.j1939.addr;
printf("PGN: %u (0x%05X), SA: 0x%02X, len: %zd\n",
pgn, pgn, sa, len);
/* EEC1: motor hızı decode */
if (pgn == 61444 && len >= 6) {
uint16_t rpm_raw = ((uint16_t)buf[4] << 8) | buf[3];
float rpm = rpm_raw * 0.125f;
printf("Motor hızı: %.1f RPM\n", rpm);
}
J1939 veri gönderme
/* EEC1 benzeri mesaj gönder (simülasyon/test için) */
struct sockaddr_can dst = {
.can_family = AF_CAN,
.can_addr.j1939 = {
.name = J1939_NO_NAME,
.pgn = 61444, /* EEC1 */
.addr = J1939_NO_ADDR, /* broadcast */
},
.can_ifindex = if_nametoindex("can0"),
};
/* 1500 RPM → raw = 1500 / 0.125 = 12000 = 0x2EE0 */
uint8_t data[8] = {0x00, 0x7D, 0x7D,
0xE0, 0x2E, /* RPM = 12000 LE */
0x00, 0xFF, 0xFF};
sendto(s, data, sizeof(data), 0,
(struct sockaddr *)&dst, sizeof(dst));
07 Python ile J1939
python-j1939 kütüphanesi, J1939 ECU simülasyonu ve veri okuma için kapsamlı bir Python API sunar.
pip install j1939 python-can
ECU simülasyonu ve PGN subscribe
import j1939, time, logging
logging.basicConfig(level=logging.INFO)
class EngineMonitor:
def __init__(self):
self.ecu = j1939.ElectronicControlUnit()
self.ecu.connect(bustype='socketcan', channel='can0',
bitrate=250000)
# Tüm J1939 mesajlarını dinle
self.ecu.subscribe(self.on_message)
def on_message(self, priority, pgn, sa, timestamp, data):
if pgn == 0xF004: # EEC1 = 61444 = 0xF004
rpm = ((data[4] << 8) | data[3]) * 0.125
torque = data[2] - 125
print(ff"[EEC1] SA={sa:#04x} RPM={rpm:.1f} Torque={torque}%")
elif pgn == 0xFEEE: # Engine Temperature 1
coolant_temp = data[0] - 40 # SPN 110: offset -40°C
print(ff"[Temp1] Coolant={coolant_temp}°C")
elif pgn == 0xFECA: # DM1 — Active DTCs
self.parse_dm1(sa, data)
def parse_dm1(self, sa, data):
lamp_status = data[0]
if lamp_status == 0x00:
print(ff"[DM1] SA={sa:#04x} DTC yok")
return
# Her DTC 4 byte: SPN(19-bit) + FMI(5-bit) + CM(1-bit) + OC(7-bit)
for i in range(2, len(data), 4):
if i + 3 >= len(data):
break
spn = ((data[i+2] & 0xE0) << 11) | (data[i+1] << 8) | data[i]
fmi = data[i+2] & 0x1F
oc = data[i+3] & 0x7F
print(ff" DTC: SPN={spn} FMI={fmi} OC={oc}")
def run(self, duration=30):
print(ff"J1939 dinleniyor ({duration} saniye)...")
time.sleep(duration)
self.ecu.disconnect()
monitor = EngineMonitor()
monitor.run()
08 Pratik: Raspberry Pi ile J1939 araç veri izleme
Raspberry Pi + MCP2515 SPI-CAN modülü ile kamyon veya iş makinesi J1939 bus'ına bağlanarak motor sıcaklığı ve hız verisi izlenebilir.
Donanım bağlantısı
Raspberry Pi 4 MCP2515 + TJA1050
────────────── ─────────────────
GPIO 8 (CE0) ───────→ CS
GPIO 11 (SCLK) ───────→ SCK
GPIO 10 (MOSI) ───────→ SI
GPIO 9 (MISO) ───────→ SO
GPIO 25 ───────→ INT
3.3V ───────→ VCC (dikkat: bazı modüller 5V)
CAN_H ──→ J1939 bus (pin A — OBD konnektörde)
CAN_L ──→ J1939 bus (pin B — OBD konnektörde)
Device Tree overlay ve kernel yapılandırması
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25
dtparam=spi=on
# J1939 araçlarda çoğunlukla 250 kbit/s kullanılır
sudo ip link set can0 type can bitrate 250000 restart-ms 100
sudo ip link set can0 up
# Linux J1939 modülü yükle
sudo modprobe can-j1939
# Temel test: candump ile J1939 mesajları göster
candump can0 -a
# (1716890123.456) can0 0CF00400 [8] 00 7D 7D E0 2E 00 FF FF
# → J1939: PGN=F004 SA=00 (EEC1, 1500 RPM)
SPN 110 motor sıcaklığı izleme
import socket, struct, time
PGN_ENGINE_TEMP1 = 0xFEEE # 65262
SPN_COOLANT_TEMP = 110 # byte 0, offset -40, 1°C/bit
SPN_OIL_TEMP = 175 # byte 2-3, offset -273, 0.03125°C/bit
def parse_engine_temp1(data: bytes) -> dict:
coolant_raw = data[0]
fuel_temp = data[1] - 40 # SPN 174, °C
oil_temp_raw = struct.unpack_from('<H', data, 2)[0]
return {
'coolant_temp_c': coolant_raw - 40, # SPN 110
'fuel_temp_c': fuel_temp,
'oil_temp_c': oil_temp_raw * 0.03125 - 273, # SPN 175
}
# J1939 socket ile Engine Temp filtrele
s = socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939)
# Bind: SA=0xF9 (araç verisi izleyici olarak)
addr = struct.pack('=HHiQIB',
socket.AF_CAN, 0,
socket.if_nametoindex('can0'),
0, # NAME
PGN_ENGINE_TEMP1, # PGN filtresi
0xF9) # SA
s.bind(addr[:14]) # struct sockaddr_can boyutu
print("J1939 Engine Temp izleniyor...")
while True:
data, _ = s.recvfrom(8)
if len(data) >= 8:
t = parse_engine_temp1(data)
print(ff"Soğutucu: {t['coolant_temp_c']}°C "
f"Motor Yağı: {t['oil_temp_c']:.1f}°C")
ISOBUS (ISO 11783) giriş
ISOBUS, tarım makineleri için J1939'u temel alan üst katman protokolüdür. Temel farklar:
| Özellik | J1939 | ISOBUS (ISO 11783) |
|---|---|---|
| Konnektör | Deutsch 9-pin | AEF ISOBUS konnektör (Deutsch + ek pin) |
| Bitrate | 250 kbit/s | 250 kbit/s |
| Güç hattı | Harici | Konnektörde 12V güç pini |
| Uygulama katmanı | J1939/71 | ISO 11783-7 (traktör-alet iletişimi) |
| VT (Virtual Terminal) | Yok | ISO 11783-6 operatör arayüzü |
| Task Controller | Yok | ISO 11783-10 tarla veri yönetimi |
Araç J1939 bus'ını keşfetmek için ScanTool OBDLink SX veya PEAK PCAN-USB ile candump can0 + j1939acd (J1939 Address Claiming Daemon) kombinasyonu kullanılabilir. j1939acd can-utils içinde yer alır.