Tüm eğitimler
TEKNİK REHBERGÖMÜLÜ LİNUXARAÇ OTOMASYON2026

J1939
Ağır Taşıt CAN Ağ Protokolü

SAE J1939 protokolünün Linux SocketCAN uygulaması, PGN/SPN veri modeli, adres talebi (address claiming) ve ağır taşıt gömülü sistemleri.

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ı

ÖzellikJ1939OBD-II / UDS
Hedef araç sınıfıAğır taşıt (kamyon, otobüs, traktör)Hafif taşıt (otomobil)
CAN ID uzunluğu29-bit (Extended)11-bit (Standard)
Baud hızı250 kbit/s (standart)500 kbit/s (standart)
Adres yönetimiDinamik Address ClaimingSabit (0x7DF / 0x7Ex)
Veri modeliPGN (mesaj grubu) + SPN (veri öğesi)DID (Data Identifier)
Büyük veri transferiJ1939 Transport Protocol (1785 bayt)ISO-TP (4095 bayt)
Mesaj tipiAğırlıklı periyodik yayın (broadcast)İstek-yanıt tanılama

PGN/SPN Veri Modeli

PGN (Parameter Group Number)Belirli bir mesaj grubunu tanımlayan 18-bit numara. Örn: PGN 61444 (0xF004) = Electronic Engine Controller 1 (EEC1).
SPN (Suspect Parameter Number)Bir PGN içindeki bireysel veri öğesi. Örn: SPN 190 = Engine Speed (rpm), SPN 110 = Engine Coolant Temperature.
29-bit ID YapısıBit 28-26: Öncelik (0-7), Bit 25: Rezerve, Bit 24: Veri sayfası (DP), Bit 23-16: PDU Format (PF), Bit 15-8: PDU Specific (PS/GE veya DA), Bit 7-0: Kaynak adres (SA).

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örUygulamaTipik ECU Sayısı
Uzun yol kamyonuMotor (ECM), şanzıman (TCM), ABS, telematik10–30
Şehir içi otobüsMotor, şanzıman, kapı kontrolörü, HVAC20–50
İş makinesiMotor, hidrolik pompa, kontrol kolları5–20
Tarım traktörüMotor, PTO, 3 nokta askı, ISOBUS (ISO 11783)5–15
Deniz motoruNMEA 2000 (J1939 tabanlı) — navigasyon entegrasyonu3–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:

BitAlanAçıklama
63Arbitrary Address Capable1 = dinamik adres talep edebilir
62-60Industry Group0=Global, 1=OnHighway, 2=AgrConstr, 5=Marine
59-56Vehicle System InstanceAynı türden kaçıncı araç sistemi
55-49Vehicle SystemAraç sistemi kodu (motor, şanzıman, fren vb.)
47-42FunctionECU işlev kodu (engine=128, transmission=130)
41-39Function InstanceAynı işlevden kaçıncı örnek
38-36ECU InstanceAynı ECU'dan kaçıncı örnek
35-25Manufacturer CodeSAE tarafından atanmış 11-bit üretici kodu
24-0Identity 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çenekSeviyeAçıklama
SO_BROADCASTSOL_SOCKETYayın mesajı göndermek için etkinleştirilmeli
SO_J1939_FILTERSOL_CAN_J1939PGN/adres/NAME bazlı alım filtresi
SO_J1939_PROMISCSOL_CAN_J1939Tüm J1939 mesajlarını al (pasif izleme)
SO_J1939_SEND_PRIOSOL_CAN_J1939Gönderilen mesajın öncelik değeri (0-7)
SO_J1939_ERRQUEUESOL_CAN_J1939Hata 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

J1939 TP maks. 1785 bayt255 paket x 7 bayt/paket. Daha büyük veriler için ETP (Extended Transport Protocol) gereklidir.
J1939 ETP maks. ~117 MBSAE J1939-21 ETP ile dev firmware güncellemeleri dahi taşınabilir.
T1 = 750 msCTS veya EndofMsgAck bekleme süresi. Bu süre aşılırsa bağlantı iptal edilir.
T3 = 1250 msTüm TP.DT paketlerinin alınma bekleme süresi (BAM modu).

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

ÖncelikKullanımÖrnek PGN
0 (en yüksek)Kritik kontrol — fren, direksiyonPGN 61441 (EBC1 — Fren)
2Yüksek öncelikli parametrelerPGN 61444 (EEC1 — Motor hızı)
3Kontrol 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şhisPGN 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

SorunSemptomÇözüm
Veri yokcandump'ta çerçeve görünmüyorBaud hızı 250 kbit/s mi? Bus terminasyonu (120Ω x 2) var mı?
J1939 soket hatasısocket() EPROTONOSUPPORTmodprobe can_j1939; kernel 5.4+ gerekli
Address claiming başarısızgetsockname() 0xFE döndürüyorNAME çakışması — farklı NAME veya adres aralığı dene
RPM değeri None0xFFFF raw değerECU aktif mi? SPN 190 byte 3-4'ü 0xFFFF = veri yok
TP mesajı yarıdaKısmi veri alınıyorT1/T3 zaman aşımı — bus yükünü kontrol et; filtre PGN doğru mu?
Broadcast başarısızsendto() EACCESSO_BROADCAST etkinleştir: setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
Yanlış PGNAnlamsız değerlerPF < 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")

Faydalı Kaynaklar

Linux kernel J1939 belgesilinux/Documentation/networking/j1939.rst — soket API'si, address claiming ve TP tam referans belgesi.
SAE J1939 belge ailesiJ1939-21 (veri bağı ve TP), J1939-71 (uygulama katmanı PGN/SPN), J1939-81 (adres yönetimi). Resmi SAE üyeliği gereklidir.
can-utils j1939acd ve j1939cathttps://github.com/linux-can/can-utils — address claiming daemon ve komut satırı J1939 araçları.
python-j1939 kütüphanesipip install j1939 — Python tabanlı J1939 yığını; SocketCAN üzerinde kolay PGN gönderme/alma.