00 J1939 Neden?
SAE J1939, ticari araçlar, tarım makineleri, iş makineleri ve deniz araçlarında kullanılan endüstriyel seviyede bir CAN uygulama katmanı protokolüdür. Araç ağı standartlaştırmasında otomotiv OBD-II'nin ağır taşıt karşılığı olarak düşünülebilir; ancak çok daha kapsamlıdır.
J1939 ve CAN Katmanlaması
+-----------------------------------------------+ | Uygulama Katmani | | J1939 PGN Mesajlari (SAE J1939-71) | | Tanim: Motor hizi, yakit, frenleme vb. | +-----------------------------------------------+ | Ag Yonetimi / Tasima Katmani | | J1939 Address Claiming (SAE J1939-81) | | Transport Protocol (SAE J1939-21) | +-----------------------------------------------+ | Veri Bagli Katmani | | CAN 2.0B 29-bit Extended ID | +-----------------------------------------------+ | Fiziksel Katman | | CAN bus 250 kbit/s (ISO 11898) | +-----------------------------------------------+
J1939 ve OBD-II / UDS Karşılaştırması
| Özellik | J1939 | OBD-II / UDS |
|---|---|---|
| Hedef araç sınıfı | Ağır taşıt (kamyon, otobüs, traktör) | Hafif taşıt (otomobil) |
| CAN ID uzunluğu | 29-bit (Extended) | 11-bit (Standard) |
| Baud hızı | 250 kbit/s (standart) | 500 kbit/s (standart) |
| Adres yönetimi | Dinamik Address Claiming | Sabit (0x7DF / 0x7Ex) |
| Veri modeli | PGN (mesaj grubu) + SPN (veri öğesi) | DID (Data Identifier) |
| Büyük veri transferi | J1939 Transport Protocol (1785 bayt) | ISO-TP (4095 bayt) |
| Mesaj tipi | Ağırlıklı periyodik yayın (broadcast) | İstek-yanıt tanılama |
PGN/SPN Veri Modeli
29-bit CAN ID Çözümleme Örneği
/* J1939 29-bit CAN ID: 0x18F00400 (EEC1, Motor ECU) */
/* Bit 28-26 : Oncelik = 0b110 = 6 */
/* Bit 25 : Rezerve = 0 */
/* Bit 24 : Veri sayfasi (DP) = 0 */
/* Bit 23-16 : PDU Format (PF) = 0xF0 = 240 */
/* Bit 15-8 : PDU Specific (PS) = 0x04 */
/* Bit 7-0 : Kaynak adres (SA) = 0x00 (Motor ECU) */
/* PF >= 240 oldugu icin PDU2 formati (broadcast): */
/* PGN = (DP << 17) | (PF << 8) | PS */
/* PGN = (0 << 17) | (0xF0 << 8) | 0x04 = 0xF004 */
/* PGN = 61444 (EEC1 — Motor Hizi) */
Kullanım Alanları
| Sektör | Uygulama | Tipik ECU Sayısı |
|---|---|---|
| Uzun yol kamyonu | Motor (ECM), şanzıman (TCM), ABS, telematik | 10–30 |
| Şehir içi otobüs | Motor, şanzıman, kapı kontrolörü, HVAC | 20–50 |
| İş makinesi | Motor, hidrolik pompa, kontrol kolları | 5–20 |
| Tarım traktörü | Motor, PTO, 3 nokta askı, ISOBUS (ISO 11783) | 5–15 |
| Deniz motoru | NMEA 2000 (J1939 tabanlı) — navigasyon entegrasyonu | 3–10 |
01 J1939 Adres Modeli
J1939, CAN ID içindeki 8-bit adres alanını kullanarak ağdaki her ECU'ya benzersiz bir adres atar. Bu atama işlemi dinamik bir protokol olan "Address Claiming" ile gerçekleştirilir.
Adres Alanı Dağılımı
| Adres Aralığı | Kullanım |
|---|---|
| 0x00 (0) | Motor ECU (Engine #1) — öncelikli ayrılmış |
| 0x01 (1) | Şanzıman ECU (Transmission #1) |
| 0x00–0x7F (0–127) | Sabit atanmış adresler — SAE J1939-81'de tanımlı |
| 0x80–0xF7 (128–247) | Dinamik atanabilir adresler — Address Claiming ile |
| 0xF8–0xFD (248–253) | Üretici tanımlı adresler |
| 0xFE (254) | NULL adres — Address Claiming tamamlanmadan önce |
| 0xFF (255) | Yayın adresi (Global Broadcast) |
NAME — 64-Bit Kimlik Yapısı
Her J1939 düğümünün benzersiz bir 64-bit NAME değeri vardır. Address Claiming sürecinde çakışan adresleri çözmek için kullanılır — düşük NAME değeri daha yüksek önceliğe sahiptir:
| Bit | Alan | Açıklama |
|---|---|---|
| 63 | Arbitrary Address Capable | 1 = dinamik adres talep edebilir |
| 62-60 | Industry Group | 0=Global, 1=OnHighway, 2=AgrConstr, 5=Marine |
| 59-56 | Vehicle System Instance | Aynı türden kaçıncı araç sistemi |
| 55-49 | Vehicle System | Araç sistemi kodu (motor, şanzıman, fren vb.) |
| 47-42 | Function | ECU işlev kodu (engine=128, transmission=130) |
| 41-39 | Function Instance | Aynı işlevden kaçıncı örnek |
| 38-36 | ECU Instance | Aynı ECU'dan kaçıncı örnek |
| 35-25 | Manufacturer Code | SAE tarafından atanmış 11-bit üretici kodu |
| 24-0 | Identity Number | Üreticiye özgü 21-bit seri numarası |
Address Claiming Prosedürü
Dugum baslar
|
v
"Address Claimed" mesaji yayinlar
(PGN 60928 / 0xEE00, kendi NAME ile, istenen adres SA alaninda)
|
v
250 ms bekle
|
+--- Cakisma var mi? (daha dusuk NAME'li dugum ayni adresi talep etti)
| |
| v
| NAME daha kucuk mu?
| Evet: Adres birakilir, yeni adres aranir (arbitrary=1 ise)
| Hayir: Cakisma bildirimi gonderilir, adres korunur
|
v
Adres basariyla alindi -- normal iletisim baslar
NAME C'de Oluşturma
#include <stdint.h>
uint64_t j1939_build_name(
int arbitrary_address,
int industry_group,
int vehicle_system,
int function,
int function_instance,
int ecu_instance,
int manufacturer_code,
int identity_number)
{
uint64_t name = 0;
name |= (uint64_t)(arbitrary_address & 0x01) << 63;
name |= (uint64_t)(industry_group & 0x07) << 60;
name |= (uint64_t)(vehicle_system & 0x7F) << 49;
name |= (uint64_t)(function & 0xFF) << 40;
name |= (uint64_t)(function_instance & 0x1F) << 35;
name |= (uint64_t)(ecu_instance & 0x07) << 32;
name |= (uint64_t)(manufacturer_code & 0x7FF)<< 21;
name |= (uint64_t)(identity_number & 0x1FFFFF);
return name;
}
/* Motor ECU icin NAME olustur */
uint64_t engine_name = j1939_build_name(
1, /* arbitrary address capable */
1, /* on-highway vehicle */
0, /* vehicle system: non-specific */
128, /* function: engine */
0, /* function instance: 0 */
0, /* ECU instance: 0 */
0x17B, /* manufacturer code */
0x1234 /* identity number */
);
02 Linux J1939 Soketi
Linux 5.4 ve sonrasında çekirdek içi J1939 soket desteği mevcuttur. AF_CAN / SOCK_DGRAM /
CAN_J1939 üçlüsüyle adres yönetimi ve PGN gönderim/alım işlemleri kernel tarafından
yönetilir.
Kernel Modülü ve Arayüz Hazırlığı
# J1939 modülünü yükle
modprobe can
modprobe can_raw
modprobe can_j1939
# Yüklü mü?
lsmod | grep j1939
# can_j1939 65536 0
# Kernel config kontrolü
zcat /proc/config.gz | grep CONFIG_CAN_J1939
# CONFIG_CAN_J1939=m
# CAN arayüzünü J1939 için hazırla (250 kbit/s standart)
ip link set can0 type can bitrate 250000
ip link set can0 up
# Durum kontrol
ip -details link show can0
sockaddr_can — J1939 Alanları
/* linux/can/j1939.h — J1939 adres yapisi */
struct sockaddr_can {
__kernel_sa_family_t can_family; /* AF_CAN */
int can_ifindex; /* can0 arayuz indeksi */
struct {
__u64 name; /* 64-bit J1939 NAME (0 = NAME kullanma) */
__u32 pgn; /* Parameter Group Number */
/* J1939_NO_PGN (0x40000) = PGN filtresi yok */
__u8 addr; /* 8-bit kaynak/hedef adres (0-253) */
/* J1939_NO_ADDR (0xFF) = tum adresler */
} can_addr.j1939;
};
Soket Oluşturma ve Bağlama
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/j1939.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
int j1939_open(const char *iface, __u8 my_addr, __u64 my_name)
{
int sock;
struct sockaddr_can addr;
struct ifreq ifr;
/* SOCK_DGRAM + CAN_J1939 soketi */
sock = socket(AF_CAN, SOCK_DGRAM, CAN_J1939);
if (sock < 0) { perror("socket"); return -1; }
/* Arayuz indeksi */
strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl");
close(sock);
return -1;
}
/* Adres yapisi */
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
addr.can_addr.j1939.name = my_name;
addr.can_addr.j1939.addr = my_addr;
addr.can_addr.j1939.pgn = J1939_NO_PGN;
/* Bind — address claiming tetiklenir (name != 0 ise) */
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return -1;
}
return sock;
}
Soket Seçenekleri
| Seçenek | Seviye | Açıklama |
|---|---|---|
SO_BROADCAST | SOL_SOCKET | Yayın mesajı göndermek için etkinleştirilmeli |
SO_J1939_FILTER | SOL_CAN_J1939 | PGN/adres/NAME bazlı alım filtresi |
SO_J1939_PROMISC | SOL_CAN_J1939 | Tüm J1939 mesajlarını al (pasif izleme) |
SO_J1939_SEND_PRIO | SOL_CAN_J1939 | Gönderilen mesajın öncelik değeri (0-7) |
SO_J1939_ERRQUEUE | SOL_CAN_J1939 | Hata mesajı kuyruğu (address claiming sonuçları) |
03 Temel PGN Gönderme ve Alma (C)
Linux J1939 soket API'si üzerinden PGN gönderme ve alma işlemleri. Hem PDU1 (peer-to-peer) hem de PDU2 (broadcast) mesaj biçimleri desteklenir.
PGN Yayını (Broadcast) Gönderme
#define PGN_EEC1 0xF004U /* Electronic Engine Controller 1 */
#define PGN_ETC1 0xF005U /* Electronic Transmission Controller 1 */
int j1939_send_pgn(int sock, __u32 pgn,
const void *data, size_t len)
{
struct sockaddr_can dest = {0};
dest.can_family = AF_CAN;
dest.can_addr.j1939.pgn = pgn;
dest.can_addr.j1939.addr = J1939_NO_ADDR; /* yayin = 0xFF */
dest.can_addr.j1939.name = J1939_NO_NAME;
ssize_t n = sendto(sock, data, len, 0,
(struct sockaddr *)&dest, sizeof(dest));
if (n < 0) { perror("sendto"); return -1; }
return 0;
}
/* EEC1 mesaj gonderimi: 1600 rpm, %50 tork */
unsigned char eec1[8] = {
0xFF, /* SPN 899 Torque Mode */
0xFF, /* SPN 512 Driver Demand Torque */
0x82, /* SPN 513 Actual Torque: 0x82-125 = 5% */
0x00, 0x32, /* SPN 190 Engine Speed: 0x3200 * 0.125 = 1600 rpm */
0x00, /* SPN 1483 Source Address */
0xFF, /* SPN 1675 Starter Mode */
0xFF /* SPN 2432 Engine Demand */
};
int broadcast = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
j1939_send_pgn(sock, PGN_EEC1, eec1, sizeof(eec1));
PGN Alma
int j1939_recv(int sock, void *buf, size_t len,
__u8 *src_addr_out, __u32 *pgn_out)
{
struct sockaddr_can src = {0};
socklen_t src_len = sizeof(src);
ssize_t n = recvfrom(sock, buf, len, 0,
(struct sockaddr *)&src, &src_len);
if (n < 0) { perror("recvfrom"); return -1; }
if (src_addr_out) *src_addr_out = src.can_addr.j1939.addr;
if (pgn_out) *pgn_out = src.can_addr.j1939.pgn;
return (int)n;
}
/* Kullanim: Tüm PGN'leri dinle */
int main(void)
{
uint64_t name = j1939_build_name(1, 1, 0, 0xE0, 0, 0, 0x17B, 0x5678);
int sock = j1939_open("can0", 0x23, name);
/* Promiscuous mod etkinlestir */
int promisc = 1;
setsockopt(sock, SOL_CAN_J1939, SO_J1939_PROMISC,
&promisc, sizeof(promisc));
unsigned char buf[1785];
__u8 src; __u32 pgn;
while (1) {
int n = j1939_recv(sock, buf, sizeof(buf), &src, &pgn);
if (n > 0)
printf("PGN=0x%05X SA=0x%02X len=%d\n", pgn, src, n);
}
}
Peer-to-Peer (PDU1) Mesajı
/* Belirli bir adrese mesaj gonder (PDU1 formati) */
int j1939_send_p2p(int sock, __u8 dest_addr, __u32 pgn,
const void *data, size_t len)
{
struct sockaddr_can dest = {0};
dest.can_family = AF_CAN;
dest.can_addr.j1939.pgn = pgn;
dest.can_addr.j1939.addr = dest_addr;
dest.can_addr.j1939.name = J1939_NO_NAME;
return (int)sendto(sock, data, len, 0,
(struct sockaddr *)&dest, sizeof(dest));
}
/* Ornek: Sanziman ECU'ya (0x01) ozel mesaj gonder */
unsigned char msg[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
j1939_send_p2p(sock, 0x01, 0xEF00, msg, sizeof(msg));
SPN 190 — Motor Hızı Çözümleme
float decode_engine_speed(const unsigned char *eec1_data)
{
/* SPN 190: Byte 3 (index 3) = LSB, Byte 4 (index 4) = MSB */
/* Birim: 0.125 RPM/bit, aralık: 0-8031.875 RPM */
/* 0xFFFF = veri mevcut degil */
unsigned int raw = eec1_data[3] | ((unsigned int)eec1_data[4] << 8);
if (raw == 0xFFFF) return -1.0f;
return raw * 0.125f;
}
/* Kullanim */
unsigned char rxbuf[8];
__u8 sa; __u32 pgn;
int n = j1939_recv(sock, rxbuf, sizeof(rxbuf), &sa, &pgn);
if (n == 8 && pgn == PGN_EEC1) {
float rpm = decode_engine_speed(rxbuf);
printf("Motor hizi: %.1f RPM (SA=0x%02X)\n", rpm, sa);
}
04 Transport Protocol — Çok Paketli Mesajlar
J1939 Transport Protocol (TP), 8 baytlık CAN çerçevesi sınırını aşan mesajları 1785 bayta kadar taşıyabilir. BAM (Broadcast Announce Message) ve CMDT (Connection Mode Data Transfer) olmak üzere iki alt protokol tanımlar.
TP.CM_BAM — Yayın Modu
Gonderen Ag (Tum Dugumler)
| |
|--- TP.CM_BAM (PGN 0xEC00) ----> | Boyut + hedef PGN bildirir
| |
|--- TP.DT paket 1 --------------->| 7 bayt veri
|--- TP.DT paket 2 --------------->|
|--- TP.DT paket N --------------->|
| |
(baglanti kapatilir — onayi beklenmez)
TP.CM_RTS/CTS — Bağlantı Modu (CMDT)
Gonderen Alici
| |
|--- TP.CM_RTS --------------->| Boyut + paket sayisi
| |
|<-- TP.CM_CTS ----------------| Kac paket gonderilecek
| |
|--- TP.DT paket 1 ----------> |
|--- TP.DT paket 2 ----------> |
|--- TP.DT paket N ----------> |
| |
|<-- TP.CM_EndofMsgAck --------| Transfer tamamlandi
Linux Kernel'de TP Otomatik Yönetim
Linux kernel J1939 modülü, TP çerçevelemesini ve birleştirmeyi tamamen otomatik olarak yönetir.
Kullanıcı alanı uygulamaları büyük payload'ları doğrudan write() ile gönderir;
kernel uygun TP paketlerine böler:
/* 200 bayt ETP mesajı gonder — kernel TP'yi halleder */
unsigned char big_data[200];
memset(big_data, 0xAA, sizeof(big_data));
ssize_t n = write(sock, big_data, sizeof(big_data));
if (n == (ssize_t)sizeof(big_data))
printf("TP mesaji gonderildi: %zu bayt\n", sizeof(big_data));
/* Alma tarafinda tek bir read() ile birlestirilmis veriyi al */
unsigned char rxbuf[1785];
ssize_t rxlen = read(sock, rxbuf, sizeof(rxbuf));
printf("TP mesaji alindi: %zd bayt\n", rxlen);
TP Boyut Sınırları ve Zamanlayıcılar
05 Address Claiming
J1939 Address Claiming (SAE J1939-81), ağdaki her düğümün benzersiz bir adres edinmesini sağlayan dinamik protokoldür. Linux kernel J1939 modülü bu süreci otomatik olarak yönetir.
Address Claiming ile Soket Bağlama
/* name != 0 ile bind yapilirsa kernel address claiming baslatir */
uint64_t engine_name = j1939_build_name(1, 1, 0, 128, 0, 0, 0x17B, 0x1234);
struct sockaddr_can addr = {0};
addr.can_family = AF_CAN;
addr.can_ifindex = ifindex;
addr.can_addr.j1939.name = engine_name;
addr.can_addr.j1939.addr = 0x00; /* tercih edilen adres */
addr.can_addr.j1939.pgn = J1939_NO_PGN;
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
/* Kernel "Address Claimed" (PGN 0xEE00) mesajini otomatik yayinlar */
Tahsis Edilen Adresi Öğrenme
/* Bind sonrasi tahsis edilen adresi oku */
struct sockaddr_can cur = {0};
socklen_t cur_len = sizeof(cur);
getsockname(sock, (struct sockaddr *)&cur, &cur_len);
__u8 my_addr = cur.can_addr.j1939.addr;
if (my_addr == 0xFE) {
printf("Address claiming devam ediyor veya basarisiz...\n");
} else {
printf("Tahsis edilen adres: 0x%02X\n", my_addr);
}
Çakışma Bildirimi
/* SO_J1939_ERRQUEUE ile cakisma bildirimlerini al */
int enable = 1;
setsockopt(sock, SOL_CAN_J1939, SO_J1939_ERRQUEUE, &enable, sizeof(enable));
/* Hata kuyruğunu oku — cakisma olursa EADDRNOTAVAIL alirsiniz */
char ctrl[1024];
struct msghdr msg = {0};
struct iovec iov = { .iov_base = NULL, .iov_len = 0 };
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
int ret = recvmsg(sock, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (ret < 0 && errno == EAGAIN)
printf("Hata yok — claiming muhtemelen basarili\n");
j1939acd — Address Claiming Daemon
# j1939acd: kullanici alani address claiming daemon (can-utils)
apt-get install can-utils
# Daemon baslatma: NAME ve adres araligini belirt
j1939acd --name 0x0000000100000001 -r 0x80-0xEF can0 &
# Adres durumunu izle
j1939acd --list
# can0: NAME=0x0000000100000001 addr=0x80 status=claimed
06 Filtre ve Öncelik
J1939 soket filtreleri, alınacak mesajları PGN, kaynak adres ve NAME bazında sınırlandırır. Mesaj önceliği ise 29-bit CAN ID'nin en üst 3 bitiyle belirlenir.
j1939_filter Yapısı
/* linux/can/j1939.h */
struct j1939_filter {
__u8 addr; /* kaynak adres filtresi */
__u8 addr_mask;
__u64 name; /* NAME filtresi */
__u64 name_mask;
__u32 pgn; /* PGN filtresi */
__u32 pgn_mask;
};
Filtre Örnekleri
/* Yalnizca motor ECU (SA=0x00) mesajlarini al */
struct j1939_filter filt_sa = {
.addr = 0x00,
.addr_mask = 0xFF,
.name = J1939_NO_NAME,
.name_mask = 0,
.pgn = J1939_NO_PGN,
.pgn_mask = 0,
};
setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER,
&filt_sa, sizeof(filt_sa));
/* Yalnizca EEC1 (PGN=0xF004) mesajlarini al */
struct j1939_filter filt_pgn = {
.addr = J1939_NO_ADDR,
.addr_mask = 0,
.name = J1939_NO_NAME,
.name_mask = 0,
.pgn = 0xF004,
.pgn_mask = 0x3FFFF, /* tam PGN eslesme */
};
setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER,
&filt_pgn, sizeof(filt_pgn));
/* Birden fazla filtre: dizi gonder */
struct j1939_filter filters[2] = {filt_sa, filt_pgn};
setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER,
filters, sizeof(filters));
/* Tum filtreleri temizle */
setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, NULL, 0);
Mesaj Önceliği Ayarlama
/* Gonderim onceligi ayarla (0=en yuksek, 7=en dusuk) */
int prio = 3;
setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &prio, sizeof(prio));
/* Mesaj gonder — 29-bit ID'nin bit 28-26'si bu degerle ayarlanir */
j1939_send_pgn(sock, PGN_EEC1, data, 8);
Yaygın Öncelik Seviyeleri
| Öncelik | Kullanım | Örnek PGN |
|---|---|---|
| 0 (en yüksek) | Kritik kontrol — fren, direksiyon | PGN 61441 (EBC1 — Fren) |
| 2 | Yüksek öncelikli parametreler | PGN 61444 (EEC1 — Motor hızı) |
| 3 | Kontrol mesajları | PGN 61184 (TSC1 — Tork set komutu) |
| 6 (varsayılan) | Periyodik bilgi mesajları | PGN 65262 (ET1 — Sıcaklık) |
| 7 (en düşük) | Tanılama ve teşhis | PGN 59392 (ACK) |
PDU2 PGN Grubu Filtresi
/* PDU2 PGN grubu: 0xF000–0xFFFF arasindaki tum PGN'ler */
struct j1939_filter pdu2_filter = {
.pgn = 0xF000,
.pgn_mask = 0x3FF00, /* ust 10 bit eslessin */
.addr = J1939_NO_ADDR,
.addr_mask = 0,
.name = J1939_NO_NAME,
.name_mask = 0,
};
setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER,
&pdu2_filter, sizeof(pdu2_filter));
07 Araç Ağı Entegrasyon Senaryosu
Gerçek bir ağır taşıt ağı senaryosu: motor ECU, şanzıman ECU ve gösterge panosu (cluster) birbirleriyle J1939 üzerinden periyodik veri alışverişi yapar.
Ağ Topolojisi
[Motor ECU] [Sanziman ECU] [Gosterge Panosu]
Adres: 0x00 Adres: 0x01 Adres: 0x23
| | |
+------------------+-------------------+
CAN bus 250 kbit/s
Periyodik mesajlar:
Motor ECU --(EEC1 PGN 0xF004, 20 ms)---> Tum
Motor ECU --(ET1 PGN 0xFEEE, 1 s)---> Tum
Sanziman ECU --(ETC1 PGN 0xF005, 20 ms)---> Tum
Gosterge --(REQ PGN 0xEA00, talep)---> Motor ECU
Motor ECU Simülatörü
#define PGN_EEC1 0xF004 /* Electronic Engine Controller 1 */
#define PGN_ET1 0xFEEE /* Engine Temperature 1 */
void encode_eec1(unsigned char *buf, unsigned int rpm, int torque_pct)
{
unsigned int speed_raw = (unsigned int)(rpm / 0.125f);
buf[0] = 0xFF;
buf[1] = 0xFF;
buf[2] = (unsigned char)(torque_pct + 125);
buf[3] = (unsigned char)(speed_raw & 0xFF);
buf[4] = (unsigned char)((speed_raw >> 8) & 0xFF);
buf[5] = 0x00;
buf[6] = 0xFF;
buf[7] = 0xFF;
}
void encode_et1(unsigned char *buf, int coolant_c, int oil_c)
{
buf[0] = (unsigned char)(coolant_c + 40);
buf[1] = 0xFF;
buf[2] = 0xFF;
buf[3] = (unsigned char)(oil_c + 40);
buf[4] = 0xFF; buf[5] = 0xFF; buf[6] = 0xFF; buf[7] = 0xFF;
}
int main(void)
{
uint64_t name = j1939_build_name(1, 1, 0, 128, 0, 0, 0x17B, 0x1234);
int sock = j1939_open("can0", 0x00, name);
int bc = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
unsigned char eec1[8], et1[8];
int tick = 0;
while (1) {
encode_eec1(eec1, 1600, 50);
j1939_send_pgn(sock, PGN_EEC1, eec1, 8);
if (tick % 50 == 0) { /* her 50x20ms = 1 saniye */
encode_et1(et1, 85, 95);
j1939_send_pgn(sock, PGN_ET1, et1, 8);
}
tick++;
usleep(20000); /* 20 ms (EEC1 gonderim araligi) */
}
}
Gösterge Panosu Alıcısı
void display_eec1(const unsigned char *buf, int len, __u8 sa)
{
if (len < 5) return;
unsigned int raw_rpm = buf[3] | ((unsigned int)buf[4] << 8);
if (raw_rpm == 0xFFFF) return;
float rpm = raw_rpm * 0.125f;
int torque = (int)buf[2] - 125;
printf("[EEC1 SA=0x%02X] Motor hizi: %.1f rpm Tork: %d%%\n",
sa, rpm, torque);
}
void display_et1(const unsigned char *buf, int len, __u8 sa)
{
if (len < 4) return;
int coolant = (int)buf[0] - 40;
int oil = (int)buf[3] - 40;
printf("[ET1 SA=0x%02X] Sogutma: %d C Yag: %d C\n",
sa, coolant, oil);
}
int main(void)
{
uint64_t name = j1939_build_name(1, 1, 0, 0xE0, 0, 0, 0x17B, 0x5678);
int sock = j1939_open("can0", 0x23, name);
unsigned char buf[1785];
__u8 sa; __u32 pgn;
while (1) {
int n = j1939_recv(sock, buf, sizeof(buf), &sa, &pgn);
if (n > 0) {
if (pgn == PGN_EEC1) display_eec1(buf, n, sa);
else if (pgn == 0xFEEE) display_et1 (buf, n, sa);
}
}
}
DM1 — Aktif DTC Okuma ve Çözümleme
#define PGN_DM1 0xFECA /* Active Diagnostic Trouble Codes */
void decode_dm1(const unsigned char *data, int len)
{
if (len < 2) return;
/* Byte 0-1: Lamp durumu (MIL, RSL, AWL, PL) */
int mil_on = (data[0] >> 6) & 0x03; /* Malfunction Indicator Lamp */
printf("MIL: %s\n", mil_on == 1 ? "ACIK" : "KAPALI");
/* Her DTC: 4 bayt */
int dtc_count = (len - 2) / 4;
for (int i = 0; i < dtc_count; i++) {
int offset = 2 + i * 4;
unsigned int spn = data[offset]
| ((unsigned int)data[offset+1] << 8)
| (((unsigned int)data[offset+2] & 0xE0) << 11);
int fmi = data[offset+2] & 0x1F;
int oc = data[offset+3] & 0x7F;
printf(" DTC %d: SPN=%u FMI=%d OC=%d\n",
i+1, spn, fmi, oc);
}
}
08 Hata Ayıklama Araçları
J1939 trafiğini izlemek, hata ayıklamak ve SPN değerlerini çözümlemek için candump filtreleri, j1939acd izleme ve Python tabanlı araçlar kullanılır.
candump ile J1939 Trafiğini İzleme
# Tum CAN (29-bit) trafiklerini izle
candump can0
# EEC1 PGN filtresi (SA=wildcard):
# CAN ID = priority(6) | DP(0) | PF(0xF0) | PS(0x04) | SA
# 0x18F00400 & maske 0x1FFFFF00 = tum SA degerlerini kapsar
candump can0 18F00400:1FFFFF00
# Zaman damgali ve ASCII dump
candump -t a -a can0
# Log dosyasina kaydet
candump -L can0 > j1939_session.log
CAN ID'den J1939 Alanları Çözümleme
python3 - <<'EOF'
def parse_j1939_id(cid):
p = (cid >> 26) & 0x7
dp = (cid >> 24) & 0x1
pf = (cid >> 16) & 0xFF
ps = (cid >> 8) & 0xFF
sa = cid & 0xFF
pgn = (dp << 17) | (pf << 8) | (ps if pf >= 240 else 0)
da = 0xFF if pf >= 240 else ps
print(f"ID=0x{cid:08X} P={p} PGN=0x{pgn:05X}({pgn}) SA=0x{sa:02X} DA=0x{da:02X}")
parse_j1939_id(0x18F00400) # EEC1, Motor ECU
parse_j1939_id(0x0CF00400) # EEC1, oncelik=3
parse_j1939_id(0x18FECA00) # DM1
EOF
j1939acd ile Address Claiming İzleme
# Address Claimed mesajlarini izle (PGN 0xEE00)
candump can0 18EE0000:1FFFFF00
# Ornek cikti:
# (0.001) can0 18EE00EE [8] 01 00 00 A8 00 00 00 80
# = SA=0xEE (NULL), NAME ile 0x00 adresini talep ediyor
# j1939acd ile canli adres tablosu
j1939acd --monitor can0
# Ornek:
# can0: addr=0x00 NAME=0x0000800000000001 status=claimed
# can0: addr=0x01 NAME=0x0000800000000002 status=claimed
Sık Karşılaşılan Sorunlar ve Çözümleri
| Sorun | Semptom | Çözüm |
|---|---|---|
| Veri yok | candump'ta çerçeve görünmüyor | Baud hızı 250 kbit/s mi? Bus terminasyonu (120Ω x 2) var mı? |
| J1939 soket hatası | socket() EPROTONOSUPPORT | modprobe can_j1939; kernel 5.4+ gerekli |
| Address claiming başarısız | getsockname() 0xFE döndürüyor | NAME çakışması — farklı NAME veya adres aralığı dene |
| RPM değeri None | 0xFFFF raw değer | ECU aktif mi? SPN 190 byte 3-4'ü 0xFFFF = veri yok |
| TP mesajı yarıda | Kısmi veri alınıyor | T1/T3 zaman aşımı — bus yükünü kontrol et; filtre PGN doğru mu? |
| Broadcast başarısız | sendto() EACCES | SO_BROADCAST etkinleştir: setsockopt(SOL_SOCKET, SO_BROADCAST, 1) |
| Yanlış PGN | Anlamsız değerler | PF < 240 (PDU1): PS = DA; PF >= 240 (PDU2): PS = GE ayrımına dikkat |
python-can ile J1939 Veri Kaydı
#!/usr/bin/env python3
"""J1939 trafiğini JSON formatinda kaydet"""
import socket, time, json
AF_CAN, SOCK_DGRAM, CAN_J1939 = 29, 2, 7
J1939_NO_PGN, J1939_NO_NAME = 0x40000000, 0
def log_j1939(iface, duration, outfile):
sock = socket.socket(AF_CAN, SOCK_DGRAM, CAN_J1939)
sock.bind((iface, J1939_NO_PGN, J1939_NO_NAME, 0xF9))
sock.settimeout(0.1)
records = []
t0 = time.time()
while time.time() - t0 < duration:
try:
data, _, _, addr = sock.recvmsg(1785, 0)
iface_rx, pgn, name, sa = addr
records.append({
"ts": round(time.time() - t0, 6),
"pgn": f"0x{pgn:05X}",
"sa": f"0x{sa:02X}",
"data": data.hex(),
})
except TimeoutError:
pass
sock.close()
with open(outfile, "w") as f:
json.dump(records, f, indent=2)
print(f"{len(records)} mesaj kaydedildi -> {outfile}")
# 30 saniye kayit
log_j1939("can0", duration=30.0, outfile="/tmp/j1939_capture.json")