00 CAN bus ve SocketCAN
CAN (Controller Area Network), Bosch tarafından 1980'lerde geliştirilen, otomotiv, endüstriyel otomasyon ve tıbbi cihazlarda yaygın olarak kullanılan dayanıklı bir seri haberleşme protokolüdür.
CAN, bir otomobilin farklı ECU'larının (Engine Control Unit, ABS, airbag, klima) birbirleriyle güvenilir biçimde haberleşmesini sağlamak amacıyla tasarlandı. O günden bu yana uygulama alanı fabrika otomasyonuna, robot sistemlerine, tıbbi cihazlara ve havacılık uygulamalarına kadar genişledi.
CAN özellikleri
CAN frame türleri
| Frame türü | Açıklama | Kullanım |
|---|---|---|
| Data frame | 0-8 byte veri taşır; en yaygın kullanılan tür | Sensör verisi, komut gönderimi |
| Remote frame (RTR) | Veri içermez; belirli ID'den veri ister | İstek-cevap protokolleri (nadiren) |
| Error frame | Hata tespitinde node tarafından otomatik üretilir | Bus hata bildirimi |
| Overload frame | Alıcı node yavaşlama talep eder | Receiver kalabalık; modern devrelerde nadiren |
SocketCAN: Linux'ta CAN ağ arayüzü
SocketCAN, CAN donanımını Linux ağ yığınına entegre eden bir kernel altyapısıdır. Temel yenilik, CAN'ı bir ağ arayüzü (network interface) olarak modellemesidir: can0, can1 gibi arayüzler eth0 gibi davranır.
Kullanıcı alanı (uygulama)
↕ BSD socket API: socket() / bind() / read() / write()
AF_CAN / PF_CAN socket layer
↕
CAN network device layer (can_dev)
↕
SoC/USB CAN controller driver (mcp251x, flexcan, gs_usb ...)
↕
CAN transceiver (TJA1050, SN65HVD230 ...)
↕
CAN bus (diferansiyel kablo)
SocketCAN öncesinde her donanım üreticisinin kendi özel API'si vardı: bir karttan diğerine geçiş, uygulama kodunun baştan yazılmasını gerektiriyordu. SocketCAN bu parçalanmışlığı ortadan kaldırır.
SocketCAN, CAN uygulama protokollerini (CANopen, SAE J1939, AUTOSAR COM) kapsamamaktadır. Bu protokoller kullanıcı alanında ayrı kütüphaneler (lely-core, libopencan, canlib) tarafından implemente edilir ve SocketCAN'ı transport katmanı olarak kullanır.
01 CAN frame yapısı
Linux'ta CAN frame'leri struct can_frame ve CAN FD için struct canfd_frame yapılarıyla temsil edilir; ID alanındaki flag bitleri frame türünü belirler.
Standard vs Extended frame
| Format | ID uzunluğu | ID aralığı | Flag |
|---|---|---|---|
| Standard Frame Format (SFF) | 11 bit | 0x000 – 0x7FF | — |
| Extended Frame Format (EFF) | 29 bit | 0x00000000 – 0x1FFFFFFF | CAN_EFF_FLAG |
struct can_frame
typedef __u32 canid_t;
/* Flag bitleri — can_id'nin üst bitleri */
#define CAN_EFF_FLAG 0x80000000U /* 29-bit extended ID */
#define CAN_RTR_FLAG 0x40000000U /* Remote Transmission Request */
#define CAN_ERR_FLAG 0x20000000U /* Error frame */
#define CAN_SFF_MASK 0x000007FFU /* 11-bit ID maskesi */
#define CAN_EFF_MASK 0x1FFFFFFFU /* 29-bit ID maskesi */
struct can_frame {
canid_t can_id; /* ID + EFF/RTR/ERR flag'ları */
__u8 can_dlc; /* Data Length Code: 0-8 byte */
__u8 __pad;
__u8 __res0;
__u8 __res1;
__u8 data[8] __attribute__((aligned(8)));
};
/* CAN FD frame — 64 byte payload, hız anahtarı */
struct canfd_frame {
canid_t can_id;
__u8 len; /* 0-64 byte */
__u8 flags; /* CANFD_BRS: bit rate switch, CANFD_ESI */
__u8 __res0;
__u8 __res1;
__u8 data[64] __attribute__((aligned(8)));
};
CAN ID ve flag kullanımı
#include <linux/can.h>
/* Standard frame, ID = 0x123 */
canid_t id_std = 0x123;
/* Extended frame, ID = 0x12345678 */
canid_t id_ext = 0x12345678 | CAN_EFF_FLAG;
/* Standard Remote frame, ID = 0x123 */
canid_t id_rtr = 0x123 | CAN_RTR_FLAG;
/* ID'yi çıkarmak için mask uygula */
uint32_t raw_id = id_ext & CAN_EFF_MASK; /* 0x12345678 */
/* Extended mi kontrol et */
if (frame.can_id & CAN_EFF_FLAG)
printf("Extended frame: 0x%08X\n", frame.can_id & CAN_EFF_MASK);
else
printf("Standard frame: 0x%03X\n", frame.can_id & CAN_SFF_MASK);
/* Hata frame'i mi? */
if (frame.can_id & CAN_ERR_FLAG)
printf("Error frame, error class: 0x%08X\n",
frame.can_id & CAN_ERR_MASK);
Arbitration — öncelik mekanizması
CAN bus'ta iki node aynı anda göndermeye başlarsa arbitration devreye girer. Her node kendi gönderdiği biti okur; dominant bit (0) recessive bit'i (1) bastırır. Daha düşük ID değerine sahip node (daha fazla dominant bit) arbitration'ı kazanır ve göndermeye devam eder. Kaybeden node gönderimi durdurup alıcı konumuna geçer.
Node A: ID = 0x100 → 0 0 0 1 0 0 0 0 0 0 0
Node B: ID = 0x200 → 0 1 0 0 0 0 0 0 0 0 0
↑
Bit 9'da çakışma:
A=0 (dominant), B=1 (recessive)
→ A kazanır, B alıcı olur
02 Kernel modülleri ve donanım
SocketCAN, katmanlı bir kernel modül mimarisi üzerine inşa edilmiştir; core modüller tüm CAN uygulamaları için ortak altyapıyı sağlar.
Temel modüller
SPI bağlantılı CAN: mcp251x
MCP2515, yaygın kullanılan SPI bağlantılı CAN controller'dır. Raspberry Pi ve benzeri SBC'lerde sıkça kullanılır. Device Tree binding'i aşağıdaki gibidir:
&spi0 {
mcp2515: can@0 {
compatible = "microchip,mcp2515";
reg = <0>; /* SPI CS0 */
spi-max-frequency = <10000000>; /* 10 MHz */
clocks = <&mcp2515_clk>;
interrupt-parent = <&gpio>;
interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
};
};
Yerleşik CAN controller'lar
| Driver modülü | Donanım | Kullanılan platform |
|---|---|---|
flexcan | FlexCAN IP bloğu | NXP i.MX6, i.MX8, LS1021A |
c_can_platform | C_CAN/D_CAN IP bloğu | TI AM335x (BeagleBone), AM437x |
sun4i_can | Allwinner CAN | Allwinner A10, A20 SoC |
rcar_canfd | Renesas CAN FD | Renesas R-Car H3, M3 |
stm32_fdcan | STM32 FDCAN | STM32H7, STM32G0 |
USB CAN adaptörler
| Driver | Donanım | Notlar |
|---|---|---|
gs_usb | Canable, Candlelight, GVRET | Açık kaynak firmware, uygun fiyatlı |
kvaser_usb | Kvaser Leaf, USBcan | Endüstriyel sınıf |
peak_usb | PEAK PCAN-USB | Yaygın, geniş yazılım desteği |
ems_usb | EMS CPC-USB | ISO 15765-4 donanım desteği |
Virtual CAN: vcan
Gerçek donanım olmadan CAN uygulamalarını test etmek için vcan modülü kullanılır. vcan arayüzü, gönderilen frame'i hemen aynı socket'e geri yansıtır; loopback gibi çalışır.
# Modülleri yükle
sudo modprobe can
sudo modprobe can_raw
sudo modprobe vcan
# Virtual arayüz oluştur ve aç
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 up
# Arayüzü doğrula
ip link show vcan0
# 4: vcan0: <NOARP,UP,LOWER_UP> mtu 72 qdisc noqueue state UNKNOWN
03 ip link ile CAN yapılandırması
CAN arayüzü bitrate, sample-point ve diğer parametreler ip link komutu ile yapılandırılır; arayüz açılmadan önce tüm parametrelerin ayarlanmış olması gerekir.
Temel CAN arayüzü açma
# Arayüzü kapat (yapılandırma yapmadan önce)
sudo ip link set can0 down
# 500 kbit/s bitrate ile aç
sudo ip link set can0 up type can bitrate 500000
# Bitrate + sample-point (0.875 = %87.5) birlikte
sudo ip link set can0 up type can bitrate 500000 sample-point 0.875
# Triple sampling ile (gürültülü ortamlar)
sudo ip link set can0 up type can bitrate 250000 triple-sampling on
CAN FD yapılandırması
# CAN FD: nominal 500 kbit/s, data fazı 2 Mbit/s
sudo ip link set can0 up type can \
bitrate 500000 \
dbitrate 2000000 \
fd on
# CAN FD + sample-point her iki faz için
sudo ip link set can0 up type can \
bitrate 500000 sample-point 0.875 \
dbitrate 2000000 dsample-point 0.75 \
fd on
Arayüz bilgisi ve istatistik
# Detaylı arayüz bilgisi — bitrate, sample-point, durum
ip -details link show can0
# Örnek çıktı:
# 3: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP
# link/can
# can state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
# bitrate 500000 sample-point 0.875
# tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
# mcp251x: tseg1 3..16 tseg2 2..8 sjw 1..4 brp 1..64 brp-inc 1
# TX/RX/error sayaçları
ip -s link show can0
Bus-off recovery
TX hata sayacı 255'i geçtiğinde CAN controller Bus-Off durumuna girer ve bus'tan ayrılır. Otomatik recovery için restart-ms parametresi kullanılır:
# 100ms sonra otomatik recovery dene
sudo ip link set can0 up type can bitrate 500000 restart-ms 100
# Manuel recovery (otomatik yoksa)
sudo ip link set can0 type can restart
# Durumu kontrol et
ip -details link show can0 | grep "can state"
# can state ERROR-ACTIVE ← normal çalışma
# can state BUS-OFF ← hata durumu
Loopback ve listen-only modları
# Loopback: gönderilen frame'ler kendi socket'lerinde de görünür
sudo ip link set can0 up type can bitrate 500000 loopback on
# Listen-only: hiçbir şey göndermez, ACK bile vermez — pasif izleme
sudo ip link set can0 up type can bitrate 500000 listen-only on
listen-only modunda CAN controller ACK biti göndermediğinden, bus'taki diğer node'lar gönderilen mesajlara ACK alamaz ve ACK error üretir. Bu mod yalnızca başka bir dinleyicinin olduğu test ortamlarında veya pasif loglama senaryolarında kullanılmalıdır.
04 Raw socket ile gönderme ve alma
CAN frame'leri gönderip almak için standart POSIX socket API kullanılır; AF_CAN adres ailesi ve CAN_RAW protokolü ile düşük seviyeli erişim sağlanır.
Tam gönderici örneği
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main(void)
{
int sock;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
int nbytes;
/* 1. Raw CAN socket aç */
sock = socket(AF_CAN, SOCK_RAW, CAN_RAW);
if (sock < 0) {
perror("socket");
return 1;
}
/* 2. Arayüz index'ini al */
strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl SIOCGIFINDEX");
close(sock);
return 1;
}
/* 3. Socket'i can0 arayüzüne bağla */
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return 1;
}
/* 4. Frame hazırla: ID=0x123, 8 byte veri */
memset(&frame, 0, sizeof(frame));
frame.can_id = 0x123;
frame.can_dlc = 8;
frame.data[0] = 0xDE;
frame.data[1] = 0xAD;
frame.data[2] = 0xBE;
frame.data[3] = 0xEF;
frame.data[4] = 0x01;
frame.data[5] = 0x02;
frame.data[6] = 0x03;
frame.data[7] = 0x04;
/* 5. Frame gönder */
nbytes = write(sock, &frame, sizeof(frame));
if (nbytes != sizeof(frame)) {
perror("write");
close(sock);
return 1;
}
printf("Frame gönderildi: can_id=0x%03X, dlc=%d\n",
frame.can_id, frame.can_dlc);
close(sock);
return 0;
}
Alıcı ve select() ile non-blocking
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main(void)
{
int sock, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
fd_set rdfs;
struct timeval tv;
sock = socket(AF_CAN, SOCK_RAW, CAN_RAW);
if (sock < 0) { perror("socket"); return 1; }
strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
ioctl(sock, SIOCGIFINDEX, &ifr);
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
printf("can0 dinleniyor...\n");
while (1) {
/* select() ile 1 saniyelik timeout */
FD_ZERO(&rdfs);
FD_SET(sock, &rdfs);
tv.tv_sec = 1;
tv.tv_usec = 0;
int ret = select(sock + 1, &rdfs, NULL, NULL, &tv);
if (ret < 0) { perror("select"); break; }
if (ret == 0) { printf("timeout — frame yok\n"); continue; }
nbytes = read(sock, &frame, sizeof(frame));
if (nbytes < 0) { perror("read"); break; }
if (nbytes < (int)sizeof(struct can_frame)) {
fprintf(stderr, "eksik frame\n"); continue;
}
printf("[0x%03X] dlc=%d data:", frame.can_id & CAN_SFF_MASK,
frame.can_dlc);
for (int i = 0; i < frame.can_dlc; i++)
printf(" %02X", frame.data[i]);
printf("\n");
}
close(sock);
return 0;
}
gcc -Wall -o can_send can_send.c
gcc -Wall -o can_recv can_recv.c
# Bir terminalde alıcıyı başlat
./can_recv
# Başka terminalde gönder
./can_send
05 Filtreleme
Raw socket'e filtre listesi atanarak yalnızca belirli ID veya ID aralıklarından gelen frame'ler uygulamaya iletilir; kernel katmanında yapılan bu filtreleme CPU yükünü azaltır.
struct can_filter ve setsockopt
struct can_filter {
canid_t can_id; /* aranan değer */
canid_t can_mask; /* hangi bitler kontrol edilecek */
};
/* Eşleşme koşulu: (frame.can_id & mask) == (can_id & mask) */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main(void)
{
int sock;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
sock = socket(AF_CAN, SOCK_RAW, CAN_RAW);
strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
ioctl(sock, SIOCGIFINDEX, &ifr);
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
/* Filtre 1: tam eşleşme — yalnızca ID=0x123 */
/* Filtre 2: aralık — 0x200-0x2FF arası */
struct can_filter filters[2] = {
{ .can_id = 0x123, .can_mask = CAN_SFF_MASK },
{ .can_id = 0x200, .can_mask = 0x700 },
};
setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER,
filters, sizeof(filters));
/* Error frame'leri de al */
can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF
| CAN_ERR_CRTL;
setsockopt(sock, SOL_CAN_RAW, CAN_RAW_ERR_FILTER,
&err_mask, sizeof(err_mask));
while (1) {
int nbytes = read(sock, &frame, sizeof(frame));
if (nbytes < 0) { perror("read"); break; }
if (frame.can_id & CAN_ERR_FLAG) {
printf("HATA frame: 0x%08X\n", frame.can_id);
continue;
}
printf("[0x%03X] dlc=%d\n", frame.can_id, frame.can_dlc);
}
close(sock);
return 0;
}
CAN FD frame etkinleştirme
#include <linux/can.h>
#include <linux/can/raw.h>
/* CAN FD frame'leri etkinleştir */
int enable_fd = 1;
setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
&enable_fd, sizeof(enable_fd));
/* FD socket'ten okuma */
struct canfd_frame fdframe;
int nbytes = read(sock, &fdframe, sizeof(fdframe));
if (nbytes == sizeof(struct can_frame)) {
/* Klasik CAN frame */
} else if (nbytes == sizeof(struct canfd_frame)) {
/* CAN FD frame — fdframe.len: 0-64 */
printf("FD frame: ID=0x%X len=%d flags=0x%02X\n",
fdframe.can_id, fdframe.len, fdframe.flags);
}
Filtre maskesi, hangi bitlerin karşılaştırılacağını belirler. can_mask = 0x7FF 11-bit ID'nin tüm bitlerini karşılaştırır (tam eşleşme). can_mask = 0x700 yalnızca üst 3 biti karşılaştırır; bu sayede bir 256 ID bloğu tek filtre ile yakalanabilir. can_mask = 0x000 ise tüm frame'leri geçirir.
06 BCM — Broadcast Manager
BCM (Broadcast Manager), periyodik CAN frame gönderimi ve Rx multiplexing için kernel içinde bir zamanlayıcı altyapısı sunar; SOCK_DGRAM ve özel mesaj yapısı ile kullanılır.
struct bcm_msg_head
struct bcm_msg_head {
__u32 opcode; /* TX_SETUP, TX_DELETE, RX_SETUP, RX_READ ... */
__u32 flags; /* SETTIMER, STARTTIMER, TX_COUNTEVT ... */
__u32 count; /* kaç kez gönderilecek (0=sonsuz) */
struct timeval ival1; /* count adet gönderim aralığı */
struct timeval ival2; /* sonraki periyodik gönderim aralığı */
canid_t can_id; /* mesaj ID */
__u32 nframes; /* kaç frame var */
struct can_frame frames[0]; /* esnek dizi — nframes adet frame */
};
TX_SETUP: periyodik gönderim
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/bcm.h>
struct bcm_tx_msg {
struct bcm_msg_head head;
struct can_frame frame;
};
int main(void)
{
int sock;
struct sockaddr_can addr;
struct ifreq ifr;
struct bcm_tx_msg msg;
/* BCM için SOCK_DGRAM kullanılır */
sock = socket(AF_CAN, SOCK_DGRAM, CAN_BCM);
if (sock < 0) { perror("socket"); return 1; }
strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
ioctl(sock, SIOCGIFINDEX, &ifr);
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
/* BCM: connect() kullanılır, bind() değil */
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect"); close(sock); return 1;
}
memset(&msg, 0, sizeof(msg));
msg.head.opcode = TX_SETUP;
msg.head.flags = SETTIMER | STARTTIMER;
msg.head.count = 0;
msg.head.ival1.tv_sec = 0;
msg.head.ival1.tv_usec = 0;
msg.head.ival2.tv_sec = 0;
msg.head.ival2.tv_usec = 100000; /* 100ms */
msg.head.can_id = 0x701;
msg.head.nframes = 1;
msg.frame.can_id = 0x701;
msg.frame.can_dlc = 1;
msg.frame.data[0] = 0x05; /* CANopen NMT: operational */
if (write(sock, &msg, sizeof(msg)) < 0) {
perror("write"); close(sock); return 1;
}
printf("0x701 heartbeat başlatıldı — 100ms periyot\n");
pause();
close(sock);
return 0;
}
RX_SETUP: değişim yakalama ve timeout tespiti
struct bcm_rx_msg {
struct bcm_msg_head head;
struct can_frame frame;
};
/* RX_SETUP: 0x123 ID'li mesajı izle
- içerik değişince bildir (RX_CHANGED)
- 500ms gelmezse timeout bildir (RX_TIMEOUT) */
struct bcm_rx_msg rxmsg;
memset(&rxmsg, 0, sizeof(rxmsg));
rxmsg.head.opcode = RX_SETUP;
rxmsg.head.flags = SETTIMER | RX_FILTER_ID;
rxmsg.head.ival1.tv_sec = 0;
rxmsg.head.ival1.tv_usec = 0;
rxmsg.head.ival2.tv_sec = 0;
rxmsg.head.ival2.tv_usec = 500000; /* 500ms timeout */
rxmsg.head.can_id = 0x123;
rxmsg.head.nframes = 1;
rxmsg.frame.can_id = 0x123;
rxmsg.frame.can_dlc = 8;
memset(rxmsg.frame.data, 0xFF, 8);
write(sock, &rxmsg, sizeof(rxmsg));
struct bcm_rx_msg event;
while (1) {
int n = read(sock, &event, sizeof(event));
if (n < 0) { perror("read"); break; }
switch (event.head.opcode) {
case RX_CHANGED:
printf("0x123 değişti: %02X %02X...\n",
event.frame.data[0], event.frame.data[1]);
break;
case RX_TIMEOUT:
printf("0x123 timeout — cihaz cevap vermiyor!\n");
break;
}
}
07 candump ve cansend araçları
can-utils paketi, CAN bus'unu izlemek, test etmek ve kayıt altına almak için kullanılan temel komut satırı araçlarını içerir.
Kurulum
# Debian/Ubuntu/Raspberry Pi OS
sudo apt-get install can-utils
# Yocto Project — local.conf veya recipe'ye ekle
IMAGE_INSTALL:append = " can-utils"
# Buildroot
make menuconfig
# Target packages → Networking applications → can-utils
candump
# Tüm trafiği ekrana yaz
candump can0
# Örnek çıktı:
# (1703074800.123456) can0 123#DEADBEEF01020304
# (1703074800.234567) can0 456#0102030405060708
# Log dosyasına yaz
candump -l can0
# Oluşan dosya: candump-2026-04-12_134523.log
# Belirli ID filtresiyle — sadece 0x123 ve 0x456
candump can0 123:7FF 456:7FF
# Kaç frame alındığında dur
candump -n 100 can0
# Çoklu arayüz aynı anda
candump can0 can1 vcan0
canplayer — log tekrar oynatma
# candump log'unu tekrar oynat
canplayer -I candump-2026-04-12_134523.log
# Farklı interface'e oynat (can0 yerine vcan0)
canplayer -I candump.log vcan0=can0
cansend ve cangen
# Tek frame gönder: ID#DATA (hex)
cansend can0 123#DEADBEEF
# Extended ID (8 hex karakter)
cansend can0 12345678#01020304AABBCCDD
# Remote frame (RTR)
cansend can0 123#R
# CAN FD frame (## ayırıcı, flags byte)
cansend can0 123##100112233445566778899AABBCCDDEEFF
# cangen: yük testi için rastgele frame
cangen can0
# Belirli parametrelerle
cangen -g 5 -I 0x100 -L 8 can0
# -g: gecikme (ms), -I: base ID, -L: DLC
Diğer can-utils araçları
# can0 üzerinde 500 kbit/s için bus yükünü 1s aralıkla ölç
canbusload can0@500000 -r 1 -e -b
# Örnek çıktı:
# can0@500000 57 frames/s 13.680 kbit/s 2.7% bus load
08 Python ile SocketCAN
python-can kütüphanesi, SocketCAN dahil pek çok CAN arayüzünü soyutlayan Pythonic bir API sunar; hızlı prototip ve test için idealdir.
Kurulum ve temel kullanım
# pip ile kur
pip install python-can
# Geliştirme ortamı için virtual env
python3 -m venv venv
source venv/bin/activate
pip install python-can
import can
import time
# can0 arayüzüne bağlan
bus = can.interface.Bus(channel='can0', bustype='socketcan')
# Frame gönder
msg = can.Message(
arbitration_id=0x123,
data=[0xDE, 0xAD, 0xBE, 0xEF],
is_extended_id=False
)
bus.send(msg)
print(f"Gönderildi: {msg}")
# Tek frame al — 1 saniye timeout
received = bus.recv(timeout=1.0)
if received:
print(f"Alındı: ID=0x{received.arbitration_id:03X} "
f"data={received.data.hex()}")
else:
print("Timeout — frame gelmedi")
bus.shutdown()
can.Message yapısı
OBD-II PID sorgusu
import can
bus = can.interface.Bus(channel='can0', bustype='socketcan')
# OBD-II sorgu frame'i:
# 0x7DF = broadcast functional address
# [02] = 2 byte veri uzunluğu
# [01] = Mode 01: current data
# [0C] = PID 0x0C: engine RPM
query = can.Message(
arbitration_id=0x7DF,
data=[0x02, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00],
is_extended_id=False
)
bus.send(query)
# Cevap 0x7E8 adresinden beklenir
while True:
resp = bus.recv(timeout=0.5)
if resp is None:
print("Cevap yok")
break
if resp.arbitration_id == 0x7E8:
if resp.data[1] == 0x41 and resp.data[2] == 0x0C:
rpm = ((resp.data[3] * 256) + resp.data[4]) / 4
print(f"Motor Devri: {rpm:.0f} RPM")
break
bus.shutdown()
Asenkron alım: Notifier ve BufferedReader
import can
import time
bus = can.interface.Bus(channel='can0', bustype='socketcan')
reader = can.BufferedReader()
notifier = can.Notifier(bus, [reader])
print("5 saniye CAN trafiği yakalanıyor...")
time.sleep(5)
notifier.stop()
count = 0
while True:
msg = reader.get_message(timeout=0)
if msg is None:
break
print(f"[{msg.timestamp:.6f}] 0x{msg.arbitration_id:03X}: {msg.data.hex()}")
count += 1
print(f"Toplam {count} frame yakalandı")
bus.shutdown()
CSV Logger
import can
bus = can.interface.Bus(channel='can0', bustype='socketcan')
logger = can.Logger('can_log.csv')
notifier = can.Notifier(bus, [logger])
try:
import time
time.sleep(60)
finally:
notifier.stop()
logger.stop()
bus.shutdown()
print("Kayıt tamamlandı: can_log.csv")
09 Ağ istatistikleri ve hata yönetimi
CAN bus hataları katmanlı bir mekanizma ile yönetilir; istatistikler kernel tarafından tutulur ve SocketCAN üzerinden izlenebilir.
ip -s link show ile istatistikler
ip -s link show can0
# Örnek çıktı:
# 3: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP
# link/can
# RX: bytes packets errors dropped missed mcast
# 12345 1543 0 0 0 0
# TX: bytes packets errors dropped carrier collsns
# 9876 1234 0 0 0 0
# Kernel CAN protokol istatistikleri
cat /proc/net/can/stats
# Per-socket istatistikleri
cat /proc/net/can/rcvlist_all
CAN hata durumu makinesi
Her CAN controller, TX Error Counter (TEC) ve RX Error Counter (REC) tutarak hata durumunu yönetir. Bu sayaçlar iletişim kalitesini gösterir ve node'un bus'a katılım yetkisini belirler.
Error Active (TEC < 128, REC < 128)
↓ TEC ≥ 128 ya da REC ≥ 128
Error Passive (hata flag gönderiminde farklı format)
↓ TEC ≥ 256
Bus-Off (bus'tan tamamen ayrılır)
↓ 128 × 11 recessive bit görünce
Error Active (kurtarıldı)
Error frame türleri ve decode
#include <linux/can/error.h>
void decode_error_frame(const struct can_frame *frame)
{
canid_t eid = frame->can_id & CAN_ERR_MASK;
if (!(frame->can_id & CAN_ERR_FLAG))
return;
if (eid & CAN_ERR_TX_TIMEOUT)
printf("TX timeout\n");
if (eid & CAN_ERR_LOSTARB)
printf("Arbitration kaybedildi: bit %d\n", frame->data[0]);
if (eid & CAN_ERR_CRTL) {
printf("Controller hatası:");
if (frame->data[1] & CAN_ERR_CRTL_RX_OVERFLOW)
printf(" RX overflow");
if (frame->data[1] & CAN_ERR_CRTL_TX_PASSIVE)
printf(" TX passive");
if (frame->data[1] & CAN_ERR_CRTL_RX_PASSIVE)
printf(" RX passive");
printf("\n");
}
if (eid & CAN_ERR_PROT) {
printf("Protokol hatası:");
if (frame->data[2] & CAN_ERR_PROT_BIT)
printf(" bit error");
if (frame->data[2] & CAN_ERR_PROT_STUFF)
printf(" stuff error");
if (frame->data[2] & CAN_ERR_PROT_FORM)
printf(" form error");
if (frame->data[2] & CAN_ERR_PROT_ACK)
printf(" ACK error");
printf("\n");
}
if (eid & CAN_ERR_BUSOFF)
printf("BUS-OFF — arayüzü yeniden başlat\n");
printf("TEC=%d, REC=%d\n", frame->data[6], frame->data[7]);
}
Üretim ipuçları
| Konu | Öneri |
|---|---|
| Termination | CAN bus'un her iki ucuna 120 Ω direnç bağlanmalıdır. Eksik termination refleksiyona yol açar; yüksek hızlarda özellikle kritiktir. |
| Baud rate toleransı | CAN, ±2.5% saat toleransını kaldırır. Farklı üreticilerin node'ları ile çalışırken sample-point değerlerinin uyumlu olduğundan emin olun. |
| Kablo uzunluğu | 1 Mbit/s için maksimum 40 m. Hız düştükçe uzunluk artar: 125 kbit/s → 500 m. Kablo kapasitansı kritik parametredir. |
| EMI | CAN_H ve CAN_L hatları bükümlü çift (twisted pair) olmalıdır. Yüksek akım devrelerinden (motor sürücüleri) ayrı tutulmalıdır. |
| Bus-off recovery | Üretim sistemlerinde restart-ms sonsuz döngüyü önlemek için backoff algoritması ile kullanılmalıdır. Kök neden çözülmeden recovery döngüsü zararlıdır. |
| Timestamp | SO_TIMESTAMP veya SO_TIMESTAMPING ile hardware timestamp kullanın. Software timestamp ±ms sapma üretebilir; log analizinde yanıltıcı olur. |
Hardware timestamp
#include <linux/net_tstamp.h>
/* Hardware timestamp talep et */
const int ts_flags = SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING,
&ts_flags, sizeof(ts_flags));
/* recvmsg() ile timestamp'i ancillary data olarak al */
struct msghdr mhdr;
struct iovec iov;
struct can_frame frame;
char ctrlbuf[128];
iov.iov_base = &frame;
iov.iov_len = sizeof(frame);
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = ctrlbuf;
mhdr.msg_controllen = sizeof(ctrlbuf);
recvmsg(sock, &mhdr, 0);
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&mhdr); cmsg; cmsg = CMSG_NXTHDR(&mhdr, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SO_TIMESTAMPING) {
struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
/* ts[2] = hardware timestamp */
printf("HW TS: %ld.%09ld\n", ts[2].tv_sec, ts[2].tv_nsec);
}
}
Hardware timestamp yalnızca destekleyen CAN controller driver'larında mevcuttur (flexcan, c_can, bazı USB adaptörleri). ethtool -T can0 komutu ile hangi timestamp modlarının desteklendiği kontrol edilebilir. Desteklenmeyen modlar için kernel otomatik olarak software timestamp'e geri döner.