Functional Safety over EtherCAT (FSoE), her Safety PDU’sunu (Protocol Data Unit) korumak için özel bir 16-bit CRC kullanır. İlk bakışta algoritma bir arama tablosuyla standart bir CRC-16 gibi görünür, ancak iki tasarım tercihi onu olağan dışı yapar:
- Her veri baytı, standart bayt bazlı CRC güncellemesine kıyasla ek bir $x^{24}$ çarpanıyla çarpılır.
- Önceki çerçevenin CRC’si bir sonraki çerçevenin CRC’sine katılır, çerçeveler arasında bir zincir oluşturur (“CRC kalıtımı”).
Bu makale, her iki mekanizmayı da orijinal FSoE kaynak koduna ve Tables.c içindeki otomatik olarak üretilen arama tablolarına dayanarak ayrıntılı olarak açıklar.
Polinom ve iki tablo
FSoE CRC, 17-bit polinomu kullanır
$$ P(x) = x^{16} + x^{13} + x^{12} + x^{11} + x^{8} + x^{7} + x^{5} + x^{4} + x^{2} + x + 1 $$0x39B7 olarak paketlenmiş ($x^{16}$ terimi örtüktür). CRC MSB-first (“normal”, yansıtılmamış yön) olarak işlenir.
Bu polinomdan iki adet 256 girişli arama tablosu önceden hesaplanır:
| Tablo | Formül | Kaydırmalar | Anlamı |
|---|---|---|---|
CRC16_TABLE ($T_0$) | $T_0[i] = (i \cdot x^{16}) \bmod P$ | 8 | Standart bayt bazlı tablo |
CRC16_TABLE2 ($T_3$) | $T_3[i] = (i \cdot x^{40}) \bmod P$ | 32 | Slice-by-4 tablosu = $T_0$ 4× uygulanmış (bayt $i$ + 3 sıfır bayt) |
Her iki tablo da $i$‘yi 16-bit bir yazmacın yüksek baytına yükleyerek ve sola kaydırarak, 15. bit her set olduğinde polinomu XOR’layarak üretilir:
for (n = 0; n < 8 + 8*k; n++)
crc = (crc & 0x8000) ? ((crc << 1) ^ 0x39B7) : (crc << 1);Bayt başına güncelleme adımı
Her bayt bu örüntüyle CRC’ye katılır (hesaplama içindeki her bayt için tekrarlanır):
w1 = aCRCTab1[crc >> 8]; /* T0[crc_hi] -- CRC yazmacını 1 bayt kaydır */
w2 = aCRCTab2[input_byte]; /* T3[input] -- giriş baytını kat */
w = w1 ^ w2;
crc = (w & 0xFF) | ((w >> 8 ^ crc_lo) << 8);Basitleştirilirse (bu, bayt işlemleri açılarak doğrulanabilir):
$$ \text{crc}_{\text{new}} = (\text{crc}_{\text{lo}} \ll 8) \;\oplus\; T_0[\text{crc}_{\text{hi}}] \;\oplus\; T_3[\text{input}] $$$(\text{crc}{\text{lo}} \ll 8) \oplus T_0[\text{crc}{\text{hi}}]$ terimi tam olarak standart MSB-first CRC yazmaç kaydırması bir bayt için (bir sıfır bayt işlemeye eşdeğer). Ardından $T_3[\text{input}]$ XOR’lanır.
Bu, standart CRC-16’dan nasıl farklıdır
Standart bir bayt bazlı CRC-16 güncellemesi şöyle olurdu:
$$ \text{crc}_{\text{std}} = (\text{crc}_{\text{lo}} \ll 8) \;\oplus\; T_0[\text{crc}_{\text{hi}} \oplus \text{input}] $$bu da açılır:
$$ \text{crc}_{\text{std}} = (\text{crc}_{\text{lo}} \ll 8) \;\oplus\; T_0[\text{crc}_{\text{hi}}] \;\oplus\; T_0[\text{input}] $$FSoE kodu $T_0[\text{input}]$‘u $T_3[\text{input}]$ ile değiştirir. CRC aritmetiği GF(2) üzerinde doğrusal olduğundan:
- $T_0[\text{input}] = (\text{input} \cdot x^{16}) \bmod P$ — giriş baytı doğal konumunda
- $T_3[\text{input}] = (\text{input} \cdot x^{40}) \bmod P$ — giriş baytı 24 bit (3 bayt) daha yüksek kaydırılmış
Böylece her giriş baytı, standart CRC’ye kıyasla ek bir $x^{24}$ çarpanı alır. $\text{startCrc}$ başlangıç değerinden (ki bu kendisi ilk 2 bayt olarak katılır) $n$ bayt $b_0 \dots b_{n-1}$ işledikten sonra:
$$ \text{FSoE CRC} = \left( \text{startCrc} \cdot x^{8n} + \sum_{k=0}^{n-1} b_k \cdot x^{40 + 8(n-1-k)} \right) \bmod P $$standart olan ise:
$$ \text{Standard CRC} = \left( \text{startCrc} \cdot x^{8n} + \sum_{k=0}^{n-1} b_k \cdot x^{16 + 8(n-1-k)} \right) \bmod P $$Tek fark her veri baytı için $x^{40}$ ile $x^{16}$ arasındadır — bayt başına ek bir $x^{24}$. Eşdeğer olarak, FSoE CRC’si, mesajın her baytı 3 sıfır baytla takip ediliyormuş gibi CRC’sidir, ancak başlangıç değeri yalnızca gerçek bayt sayısı kadar (doldurulmuş sayı kadar değil) kaydırılır. Bu, FSoE’ye özgü özel bir varyanttır ve hiçbir standart doldurulmuş-CRC kuralına eşdeğer değildir.
Bayt işleme sırası
CRC her yinelemenin başında 0’a sıfırlanır, ardından baytlar bu sırayla işlenir:
oldCRC-Lo, oldCRC-Hi, ConnID-Lo, ConnID-Hi, SeqNo-Lo, SeqNo-Hi, Command, [Data...]- oldCRC,
startCrcparametresinden gelir (önceki çerçevenin CRC’si — aşağıda Kalıtım’a bakın) - ConnID, PDU’nun sonundan okunur:
au8Data[size-2](Lo) veau8Data[size-1](Hi) - SeqNo,
seqNoişaretçisinden gelir - Command,
au8Data[OFFS_COMMAND]konumundadır - Data,
au8Data[OFFS_DATA]konumundan başlar
CRC kalıtımı — iki seviye
Seviye 1: Çerçeveler arası kalıtım (startCrc / oldCRC zinciri)
Her CRC hesaplamasına katılan ilk iki bayt önceki çerçevenin CRC’sidir (startCrc). Bu, çerçeveler arasında bir zincir oluşturur:
Her çerçevenin CRC’si tüm önceki çerçevelerin CRC’lerine bağlıdır. Bir saldırgan eski bir çerçeveyi tekrar oynatamaz çünkü CRC zinciri kırılır.
oldCrc parametresi do…while döngüsünde çakışma tespiti için kullanılır:
} while (crc == oldCrc && (bRcvDir & NEW_CRC) != 0);Yeni hesaplanan CRC tesadüfen önceki çerçevenin CRC’sine eşitse, sıra numarası artırılır (seqNo[0]++, sıfır atlanır) ve CRC yeniden hesaplanır. Bu, CRC oldCrc‘den farklı olana kadar devam eder ve ardışık iki çerçevenin asla aynı CRC’yi paylaşmamasını sağlar — bu, CRC çakışmalarını tespit edilebilir kılan bir güvenlik gereksinimidir.
Seviye 2: Çerçeve içi kalıtım (crc_common tabanı)
Tek bir PDU içinde, yük 10 baytı aştığında, birden fazla CRC (CRC₀, CRC₁, CRC₂, …) gömülür — her biri 2 baytlık bir veri segmentini korur. Anahtar mekanizma crc_common‘dur.
Adım 1 — Ortak tabanı hesapla (7 bayt işlenir):
crc = 0;
/* process: oldCRC-Lo, oldCRC-Hi, ConnID-Lo, ConnID-Hi,
SeqNo-Lo, SeqNo-Hi, Command */
crc_common = crc; /* burada kaydedilir */Bu taban tüm “başlık” alanlarını kapsar: kalıtılan eski CRC, Bağlantı Kimliği, Sıra Numarası ve Komut baytı.
Adım 2 — CRC₀ (ilk segment, crc_common‘dan devam ederek):
/* crc_common'dan devam: Data[0], Data[1] işle */
/* sonuç = CRC₀ (pCrc'de saklanır/doğrulanır) */Adım 3 — i ≥ 1 için CRCᵢ (sonraki segmentler, her biri crc_common‘dan yeniden başlayarak):
crc = crc_common; /* paylaşılan tabandan YENİDEN BAŞLAT */
/* process: Index-Lo (i & 0xFF), Index-Hi (i >> 8),
Data[2i], Data[2i+1] */
/* sonuç = CRCᵢ (sonraki pCrc konumunda saklanır/doğrulanır) */Döngü yapısı:
while (size >= 4) { /* her yineleme = 2 veri baytı + 2 CRC baytı */
crc = crc_common; /* paylaşılan tabandan yeniden başlat */
/* kat: i_lo, i_hi, data[2i], data[2i+1] */
/* pCrc[0..1] konumunda CRC doğrula/ekle */
size -= 4;
pSafeData += 4;
pCrc += 4;
i++;
}İndeks baytı neden önemlidir
Her segment CRCᵢ, Command ile Data arasına indeks $i$‘yi (1-tabanlı, 2 baytlık little-endian değer olarak) dahil eder. Bu, iki segment özdeş veri baytları içerse bile, indeks farklı olduğundan CRC’lerinin farklı olmasını sağlar. İndeks olmadan, iki veri segmentinin yer değiştirmesi tespit edilmeden geçerdi.
Görselleştirme
Aşağıdaki diyagram, CRC kalıtımının çerçeveler arasında ve tek bir multi-CRC PDU içinde nasıl çalıştığını gösterir. Her segment CRC’si crc_common‘dan (paylaşılan taban) yeniden başlar ve her çerçevenin CRC’si bir sonraki çerçeve için startCrc olur.
CRCᵢ = f( crc_common, Index=i, Data[2i], Data[2i+1] ) for i ≥ 1
crc_common = f( oldCRC, ConnID, SeqNo, Command ) /* kaydedildi, iletilmedi */
nextFrame.startCrc = thisFrame.CRC₀
PDU düzeni
PDU bayt düzeni (CalcCrc‘deki alan erişimlerinden yeniden yapılandırılmıştır) toplam boyuta bağlıdır:
size = 6 (1 data byte, 1 CRC):
[Command, Data0, CRC0Lo, CRC0Hi, ConnIDLo, ConnIDHi]
size = 7..10 (2 data bytes, 1 CRC):
[Command, Data0, Data1, CRC0Lo, CRC0Hi, ConnIDLo, ConnIDHi]
size > 10 (multi-CRC, 2+2k data bytes):
[Command, Data0, Data1, CRC0(2), Data2, Data3, CRC1(2),
Data4, Data5, CRC2(2), ..., ConnIDLo, ConnIDHi]- ConnID her zaman son 2 bayttadır (
au8Data[size-2..size-1]) - CRC₀ ofset 2’de (size ≤ 6) veya ofset 3’te (size > 6)
- Her ek segment 4 bayttır: 2 veri + 2 CRC
size -= 7ilk parçayı hesaba katar (ConnID 2 + CRC₀ 2 + Command 1 + Data 2), ardından her ek segment içinsize -= 4
Referans CRC değerleri
Aşağıdaki referans değerler, orijinal CalcCrc fonksiyonunun sadık bir transkripsiyonu tarafından üretilmiş ve bit-by-bit polinom uzun bölme kullanılarak bağımsız olarak doğrulanmıştır (arama tablosu yok). Aksi belirtilmedikçe tüm girişler startCrc = 0x0000, seqNo = 0x0001, oldCrc = 0x0000 kullanır.
| Test | size | startCrc | ConnID | Cmd | Data | CRC₀ | CRC₁ | CRC₂ | CRC₃ |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 6 | 0x0000 | 0x1234 | 0x01 | AB | 0x2C82 | — | — | — |
| 2 | 7 | 0x0000 | 0x1234 | 0x01 | AB CD | 0xDD27 | — | — | — |
| 3 | 11 | 0x0000 | 0x1234 | 0x01 | AB CD EF 12 | 0xDD27 | 0x05A5 | — | — |
| 4 | 15 | 0x0000 | 0x1234 | 0x01 | AB CD EF 12 34 56 | 0xDD27 | 0x05A5 | 0x93AA | — |
| 5 | 7 | 0x0000 | 0x5678 | 0x05 | 01 02 | 0x82DC | — | — | — |
| 6b | 7 | 0xDD27 | 0x1234 | 0x01 | AB CD | 0x41F5 | — | — | — |
| 7 | 7 | 0xBEEF | 0x4242 | 0x03 | DE AD | 0x945E | — | — | — |
| 8 | 11 | 0x0000 | 0x0000 | 0x00 | 00 00 00 00 | 0xC864 | 0x7BC5 | — | — |
| 9 | 15 | 0x0000 | 0xFFFF | 0xFF | FF FF FF FF FF FF | 0x2669 | 0xDB40 | 0xCD65 | — |
| 10 | 19 | 0x0000 | 0xCAFE | 0x02 | 10 20 30 40 50 60 70 80 | 0xE6BD | 0x687A | 0xB265 | 0x657C |
Test 6b, çerçeveler arası kalıtımı gösterir: Test 2’den CRC₀’ı (0xDD27) startCrc olarak kullanır ve aynı veri için farklı bir CRC (0x41F5) üretir — bu, yük özdeş olsa bile kalıtım zincirinin CRC’yi nasıl değiştirdiğini gösterir.
Özet
FSoE CRC mekanizmasının iki kalıtım katmanı vardır:
Çerçeveler arası: Önceki çerçevenin CRC₀’ı bir sonraki çerçevenin CRC’sine ilk iki bayt olarak katılır, tekrar oynatma saldırılarını önleyen bir zincir oluşturur. Bir
do…whiledöngüsü, ardışık iki çerçevenin aynı CRC’yi paylaşmamasını sağlar.Çerçeve içi: Multi-segment PDU’lar için, paylaşılan bir
crc_commontabanı (oldCRC, ConnID, SeqNo, Command içeren) bir kez hesaplanır ve her segment CRC’si için başlangıç noktası olarak yeniden kullanılır. Her segment indeksini ve 2 veri baytını ekler, segment yeniden sıralanmasına karşı koruyan bağımsız bir CRC üretir.
Güncelleme adımının kendisi standart bir MSB-first CRC-16 yazmaç kaydırmasıdır, ancak giriş baytı $T_0$ ($x^{16}$ tablosu) yerine $T_3$ ($x^{40}$ tablosu) üzerinden katılır ve her veri baytına ek bir $x^{24}$ çarpanı verir. Bu, FSoE CRC’sini özel bir varyant yapar ve hiçbir standart CRC-16 kuralına eşdeğer değildir.
İlgili yazılar
Genel bakış ve temeller
- FSoE kısaltması gerçekte ne anlama gelir? — FSoE kısaltması ne anlama gelir
- FSoE: çerçeve yapısı örneklerle açıklanıyor — genel Safety PDU düzeni
- FSoE durum makinesinin tüm durumları — FSoE’nin beş durumu ve geçişleri
- FSoE: Safety PDU komut tablosu — FSoE komutlarının tam listesi
Duruma göre PDU yapıları
- FSoE Reset State PDU: Master ve Slave Yapısı — Reset durumu için PDU düzeni ve hata kodları
- FSoE Session PDU: Master ve Slave Yapısı — Session durumu için PDU düzeni
- FSoE Connection PDU: Master ve Slave Yapısı — Connection durumu için PDU düzeni
- FSoE Parameter PDU: Master ve Slave Yapısı — Parameter durumu için PDU düzeni
- FSoE Data PDU: Master ve Slave Yapısı — Data durumu için PDU düzeni
- FSoE Reset PDU: Master ve Slave Yapısı — tüm safety-data uzunlukları için byte düzenleri
CRC
- FSoE CRC: hangi polinomu kullanır? — FSoE CRC’sinin arkasındaki 17-bitlik polinom
- FSoE CRC tabloları nasıl oluşturulur? — CRC lookup tabloları nasıl üretilir
- FSoE PDU’ları için CRC sağlaması nasıl hesaplanır? — byte-by-byte CRC hesaplama algoritması
Hata kodları ve veri formatı
- FSoE: Tüm iletişim hata kodlarının listesi — tüm FSoE iletişim hata kodları
- FSoE: veri little-endian mı big-endian mı olarak iletilir? — çok baytlu alanların byte sırası