Bu yazı, FSoE (Functional Safety over EtherCAT) CRC-16 sağlaması için iki adet kullanıma hazır C++ uygulamasını sunar. Her ikisi de özdeş sonuçlar üretir ve orijinal FSoE referans koduna karşı doğrulanmıştır.
Algoritmanın nasıl çalıştığına dair arka plan için, bkz:
- FSoE CRC: Hangi polinomu kullanır? — polinom $P(x) = x^{16} + x^{13} + x^{12} + x^{11} + x^{8} + x^{7} + x^{5} + x^{4} + x^{2} + x + 1$ (0x39B7)
- FSoE CRC tabloları nasıl oluşturulur? — iki arama tablosunun polinomdan nasıl türetildiği
- FSoE: CRC kalıtımı nasıl çalışır? — bayt başına güncelleme adımı, çerçeveler arası kalıtım ve çerçeve içi
crc_commonmekanizmasının ayrıntılı açıklaması
Algoritma kısaca
FSoE CRC her baytı şununla işler:
$$ \text{crc}_{\text{new}} = (\text{crc}_{\text{lo}} \ll 8) \;\oplus\; T_0[\text{crc}_{\text{hi}}] \;\oplus\; T_3[\text{input}] $$burada $T_0[i] = (i \cdot x^{16}) \bmod P$ ve $T_3[i] = (i \cdot x^{40}) \bmod P$. CRC başta 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[0], [Data[1], ...]10 bayttan büyük PDU’lar için, birden fazla CRC hesaplanır — her segment CRC’si paylaşılan bir crc_common tabanından (ilk 7 başlık baytını işledikten sonraki durum) yeniden başlar ve bir indeks bayt çifti ile 2 veri baytı ekler.
İki uygulama varyantı
Varyant A: Tablo tabanlı (FsoeCrcTable)
İki önceden hesaplanmış 256 girişli arama tablosunu (CRC16_TABLE ve CRC16_TABLE2) kullanır. Bu daha hızlıdır (bayt başına iki dizi araması) ancak tabloların üretilmesini ve bağlanmasını gerektirir. Bunları nasıl üreteceğiniz için bkz. FSoE CRC tabloları nasıl oluşturulur?.
Varyant B: Anında (FsoeCrcOnTheFly)
Her tablo girişini bit-by-bit polinom uzun bölme kullanılarak talep üzerine hesaplar. Arama tablosu gerekmez — yalnızca polinom sabiti 0x39B7. Daha yavaş (bayt başına 8 veya 32 kaydırma-XOR döngüsü) ancak kendi kendine yeten ve belleği kısıtlı ortamlar veya 1 KB tablo verisi depolamaktan kaçınmak istediğinizde uygundur.
Her iki varyant da aynı API’yi paylaşır:
// Low-level
void reset(); // CRC = 0
void update(uint8_t byte); // FSoE byte update step
uint16_t value() const; // Get current CRC
// High-level (FSoE-specific)
uint16_t computeCrc0(...); // CRC0: oldCRC + ConnID + SeqNo + Cmd + Data[0..1]
uint16_t computeCrcCommon(...); // Shared base: oldCRC + ConnID + SeqNo + Cmd
uint16_t computeCrcI(...); // CRCi: crc_common + Index + Data[2i,2i+1]
std::vector<uint16_t> computeAllCrcs(...); // All CRCs for a PDU
Kullanım örneği
#include "FsoeCrcTable.hpp" // or "FsoeCrcOnTheFly.hpp"
#include <cstdio>
int main() {
FsoeCrcTable crc; // or FsoeCrcOnTheFly
uint8_t data[] = {0xAB, 0xCD};
uint16_t startCrc = 0x0000; // first frame: startCrc = 0
uint16_t connId = 0x1234;
uint16_t seqNo = 0x0001;
uint8_t command = 0x01;
int pduSize = 7; // 2 data bytes
// Compute all CRCs for this PDU
auto crcs = crc.computeAllCrcs(startCrc, connId, seqNo, command,
data, 2, pduSize);
printf("CRC0 = 0x%04X\n", crcs[0]);
// For the next frame, use crcs[0] as startCrc (CRC inheritance)
return 0;
}CRC0 = 0xDD27Varyant A: Tablo tabanlı uygulama
Lisans: CC0 1.0 Universal — kamu malı ilanı
Bu varyant, Tables.c‘den arama tablolarını gerektirir (bkz. FSoE CRC tabloları nasıl oluşturulur?) ve buna karşılık gelen Tables.h başlığı:
/*
* Tables.h
* ========
*
* Tables.c'de tanımlanan iki FSoE CRC-16 arama tablosu için başlık
* (GenTables.c tarafından otomatik üretildi).
*
* SPDX-License-Identifier: CC0-1.0
*/
#ifndef TABLES_H
#define TABLES_H
#include <stdint.h>
extern const uint16_t CRC16_TABLE[256];
extern const uint16_t CRC16_TABLE2[256];
#endif /* TABLES_H *//*
* FsoeCrcTable.hpp
* =================
*
* Tablo tabanlı FSoE CRC-16 hesaplaması.
*
* Tables.c'deki önceden hesaplanmış arama tablolarını kullanır:
* CRC16_TABLE (k=0): T0[i] = (i * x^16) mod P
* CRC16_TABLE2 (k=3): T3[i] = (i * x^40) mod P
*
* Bayt başına güncelleme adımı:
* new_crc = (crc_lo << 8) ^ T0[crc_hi] ^ T3[input]
*
* Derleme: FsoeCrcTable.cpp'yi Tables.c ile bağlayın
*
* SPDX-License-Identifier: CC0-1.0
*/
#ifndef FSOE_CRC_TABLE_HPP
#define FSOE_CRC_TABLE_HPP
#include <cstdint>
#include <vector>
class FsoeCrcTable {
public:
FsoeCrcTable();
/* ---- Düşük seviyeli CRC işlemleri ---- */
/* CRC durumunu 0'a sıfırla */
void reset();
/* Bir bayt işle (FSoE güncelleme adımı) */
void update(uint8_t byte);
/* Mevcut CRC değerini al */
uint16_t value() const;
/* ---- Yüksek seviyeli FSoE işlemleri ---- */
/* Verilen PDU parametreleri için CRC0 hesapla.
İşler: startCrc(2), ConnID(2), SeqNo(2), Command(1), Data[0..dataLen-1]
dataLen, size<=6 için 1, size>6 için 2'dir. */
uint16_t computeCrc0(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command,
const uint8_t* data, int dataLen);
/* crc_common hesapla (multi-CRC segmentleri için paylaşılan taban).
İşler: startCrc(2), ConnID(2), SeqNo(2), Command(1) */
uint16_t computeCrcCommon(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command);
/* crc_common'den CRCi (i >= 1) hesapla.
İşler: Index(2), Data[2i], Data[2i+1] */
uint16_t computeCrcI(uint16_t crcCommon, uint16_t index,
uint8_t data0, uint8_t data1);
/* Bir PDU için tüm CRC'leri hesapla ve bir vector olarak döndür.
İlk eleman CRC0'dır, sonraki elemanlar CRC1, CRC2, ...
pduSize, PDU'nun bayt cinsinden toplam boyutudur.
numData, PDU'daki veri baytlarının sayısıdır. */
std::vector<uint16_t> computeAllCrcs(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command,
const uint8_t* data, int numData,
int pduSize);
private:
uint16_t crc_;
/* Yardımcı: 16-bit değeri little-endian bayt sırasında işle */
void update16(uint16_t word);
};
#endif /* FSOE_CRC_TABLE_HPP *//*
* FsoeCrcTable.cpp
* =================
*
* Tablo tabanlı FSoE CRC-16 uygulaması.
*
* Derleme: g++ -std=c++17 -c FsoeCrcTable.cpp -o FsoeCrcTable.o
* (Tables.c ile bağlayın)
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "FsoeCrcTable.hpp"
#include "Tables.h"
FsoeCrcTable::FsoeCrcTable() : crc_(0) {}
void FsoeCrcTable::reset() { crc_ = 0; }
void FsoeCrcTable::update(uint8_t byte)
{
/* FSoE güncelleme adımı:
* new_crc = (crc_lo << 8) ^ T0[crc_hi] ^ T3[input]
*
* T0 = CRC16_TABLE (i * x^16 mod P)
* T3 = CRC16_TABLE2 (i * x^40 mod P)
*/
uint16_t t0 = CRC16_TABLE[crc_ >> 8];
uint16_t t3 = CRC16_TABLE2[byte];
crc_ = ((crc_ & 0xFF) << 8) ^ t0 ^ t3;
}
uint16_t FsoeCrcTable::value() const { return crc_; }
void FsoeCrcTable::update16(uint16_t word)
{
update(static_cast<uint8_t>(word & 0xFF)); /* Lo bayt */
update(static_cast<uint8_t>((word >> 8) & 0xFF)); /* Hi bayt */
}
uint16_t FsoeCrcTable::computeCrc0(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command,
const uint8_t* data, int dataLen)
{
reset();
update16(startCrc); /* oldCRC-Lo, oldCRC-Hi */
update16(connId); /* ConnID-Lo, ConnID-Hi */
update16(seqNo); /* SeqNo-Lo, SeqNo-Hi */
update(command); /* Command */
for (int i = 0; i < dataLen; i++)
update(data[i]); /* Veri baytları */
return crc_;
}
uint16_t FsoeCrcTable::computeCrcCommon(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command)
{
reset();
update16(startCrc);
update16(connId);
update16(seqNo);
update(command);
return crc_;
}
uint16_t FsoeCrcTable::computeCrcI(uint16_t crcCommon, uint16_t index,
uint8_t data0, uint8_t data1)
{
crc_ = crcCommon; /* paylaşılan tabandan yeniden başlat */
update16(index); /* Index-Lo, Index-Hi */
update(data0); /* Data[2i] */
update(data1); /* Data[2i+1] */
return crc_;
}
std::vector<uint16_t> FsoeCrcTable::computeAllCrcs(uint16_t startCrc,
uint16_t connId,
uint16_t seqNo,
uint8_t command,
const uint8_t* data,
int /*numData*/,
int pduSize)
{
std::vector<uint16_t> crcs;
/* İlk 1-2 veri baytı (CRC0'dan önce) */
int firstData = (pduSize > 6) ? 2 : 1;
/* CRC0 */
crcs.push_back(computeCrc0(startCrc, connId, seqNo, command,
data, firstData));
/* Ek segment CRC'leri (size > 10 ise) */
if (pduSize > 10) {
uint16_t crcCommon = computeCrcCommon(startCrc, connId, seqNo, command);
int numExtra = (pduSize - 7 + 3) / 4;
int dataPos = firstData;
for (int i = 1; i <= numExtra; i++) {
crcs.push_back(computeCrcI(crcCommon, static_cast<uint16_t>(i),
data[dataPos], data[dataPos + 1]));
dataPos += 2;
}
}
return crcs;
}Derleme (tablo tabanlı):
# Generate the lookup tables (see the linked post for GenTables.c)
cc -std=c99 -O2 -o GenTables GenTables.c
./GenTables > Tables.c
# Compile the table-based variant
g++ -std=c++17 -O2 -c FsoeCrcTable.cpp -o FsoeCrcTable.o
gcc -std=c99 -c Tables.c -o Tables.o
# Link FsoeCrcTable.o + Tables.o into your projectVaryant B: Anında uygulama (tablosuz)
Lisans: CC0 1.0 Universal — kamu malı ilanı
Bu varyant tamamen kendi kendine yeten — harici tablo dosyası gerekmez. Her bayt güncellemesinde polinom bölmesini değerlendirerek aynı CRC’yi hesaplar.
/*
* FsoeCrcOnTheFly.hpp
* ====================
*
* Anında FSoE CRC-16 hesaplaması (arama tablosu yok).
*
* Tablo tabanlı sürümle aynı CRC'yi hesaplar, ancak her tablo
* girişini bit-by-bit polinom uzun bölme kullanarak talep
* üzerine P(x) = 0x39B7 polinomuyla hesaplar.
*
* Bayt başına güncelleme adımı:
* new_crc = (crc_lo << 8) ^ T0[crc_hi] ^ T3[input]
*
* burada T0 ve T3 anında hesaplanır:
* T0[byte] = polyMod(byte, 8) = (byte * x^16) mod P
* T3[byte] = polyMod(byte, 32) = (byte * x^40) mod P
*
* Derleme: g++ -std=c++17 -c FsoeCrcOnTheFly.cpp -o FsoeCrcOnTheFly.o
* (harici tablo bağımlılığı yok)
*
* SPDX-License-Identifier: CC0-1.0
*/
#ifndef FSOE_CRC_ONTHEFLY_HPP
#define FSOE_CRC_ONTHEFLY_HPP
#include <cstdint>
#include <vector>
class FsoeCrcOnTheFly {
public:
FsoeCrcOnTheFly();
/* ---- Düşük seviyeli CRC işlemleri ---- */
void reset();
void update(uint8_t byte);
uint16_t value() const;
/* ---- Yüksek seviyeli FSoE işlemleri (FsoeCrcTable ile aynı API) ---- */
uint16_t computeCrc0(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command,
const uint8_t* data, int dataLen);
uint16_t computeCrcCommon(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command);
uint16_t computeCrcI(uint16_t crcCommon, uint16_t index,
uint8_t data0, uint8_t data1);
std::vector<uint16_t> computeAllCrcs(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command,
const uint8_t* data, int numData,
int pduSize);
private:
uint16_t crc_;
/* FSoE CRC polinomu: x^16 + x^13 + x^12 + x^11 + x^8 + x^7
* + x^5 + x^4 + x^2 + x + 1
* Düşük 16 bit (x^16 terimi örtük): 0x39B7 */
static constexpr uint16_t POLY = 0x39B7;
/* (byte * x^(8+shifts)) mod P'yi bit-by-bit polinom bölmesiyle hesapla.
* byte'ı 16-bit bir yazmacın yüksek baytına yükler ve sola 'shifts'
* kez kaydırır, bit 15 set olduğunda POLY'yi XOR'lar. */
static uint16_t polyMod(uint8_t byte, int shifts);
/* Yardımcı: 16-bit değeri little-endian bayt sırasında işle */
void update16(uint16_t word);
};
#endif /* FSOE_CRC_ONTHEFLY_HPP *//*
* FsoeCrcOnTheFly.cpp
* ====================
*
* Anında FSoE CRC-16 uygulaması (arama tablosu yok).
*
* Derleme: g++ -std=c++17 -c FsoeCrcOnTheFly.cpp -o FsoeCrcOnTheFly.o
* (harici tablo bağımlılığı yok)
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "FsoeCrcOnTheFly.hpp"
constexpr uint16_t FsoeCrcOnTheFly::POLY;
FsoeCrcOnTheFly::FsoeCrcOnTheFly() : crc_(0) {}
void FsoeCrcOnTheFly::reset() { crc_ = 0; }
uint16_t FsoeCrcOnTheFly::polyMod(uint8_t byte, int shifts)
{
/* byte'ı 16-bit yazmacın yüksek baytına yükle, ardından sola
* 'shifts' kez kaydır, bit 15 set olduğunda polinomu XOR'la.
* Bu (byte * x^(8+shifts)) mod P'yi hesaplar. */
uint16_t r = static_cast<uint16_t>(byte) << 8;
for (int i = 0; i < shifts; i++)
r = (r & 0x8000U) ? static_cast<uint16_t>((r << 1) ^ POLY)
: static_cast<uint16_t>(r << 1);
return r;
}
void FsoeCrcOnTheFly::update(uint8_t byte)
{
/* FSoE güncelleme adımı (tablo tabanlıyla aynı, ancak T0/T3 anında hesaplanır):
* new_crc = (crc_lo << 8) ^ T0[crc_hi] ^ T3[input]
*
* T0[crc_hi] = polyMod(crc_hi, 8) = (crc_hi * x^16) mod P
* T3[input] = polyMod(input, 32) = (input * x^40) mod P
*/
uint16_t t0 = polyMod(static_cast<uint8_t>(crc_ >> 8), 8);
uint16_t t3 = polyMod(byte, 32);
crc_ = ((crc_ & 0xFF) << 8) ^ t0 ^ t3;
}
uint16_t FsoeCrcOnTheFly::value() const { return crc_; }
void FsoeCrcOnTheFly::update16(uint16_t word)
{
update(static_cast<uint8_t>(word & 0xFF));
update(static_cast<uint8_t>((word >> 8) & 0xFF));
}
uint16_t FsoeCrcOnTheFly::computeCrc0(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command,
const uint8_t* data, int dataLen)
{
reset();
update16(startCrc);
update16(connId);
update16(seqNo);
update(command);
for (int i = 0; i < dataLen; i++)
update(data[i]);
return crc_;
}
uint16_t FsoeCrcOnTheFly::computeCrcCommon(uint16_t startCrc, uint16_t connId,
uint16_t seqNo, uint8_t command)
{
reset();
update16(startCrc);
update16(connId);
update16(seqNo);
update(command);
return crc_;
}
uint16_t FsoeCrcOnTheFly::computeCrcI(uint16_t crcCommon, uint16_t index,
uint8_t data0, uint8_t data1)
{
crc_ = crcCommon;
update16(index);
update(data0);
update(data1);
return crc_;
}
std::vector<uint16_t> FsoeCrcOnTheFly::computeAllCrcs(uint16_t startCrc,
uint16_t connId,
uint16_t seqNo,
uint8_t command,
const uint8_t* data,
int /*numData*/,
int pduSize)
{
std::vector<uint16_t> crcs;
int firstData = (pduSize > 6) ? 2 : 1;
crcs.push_back(computeCrc0(startCrc, connId, seqNo, command,
data, firstData));
if (pduSize > 10) {
uint16_t crcCommon = computeCrcCommon(startCrc, connId, seqNo, command);
int numExtra = (pduSize - 7 + 3) / 4;
int dataPos = firstData;
for (int i = 1; i <= numExtra; i++) {
crcs.push_back(computeCrcI(crcCommon, static_cast<uint16_t>(i),
data[dataPos], data[dataPos + 1]));
dataPos += 2;
}
}
return crcs;
}Derleme (anında):
# No table generation needed — fully self-contained
g++ -std=c++17 -O2 -c FsoeCrcOnTheFly.cpp -o FsoeCrcOnTheFly.o
# Link FsoeCrcOnTheFly.o into your projectHangi varyantı kullanmalıyım?
| Kriter | Tablo tabanlı | Anında |
|---|---|---|
| Hız | Hızlı (bayt başına 2 arama) | Daha yavaş (bayt başına 8+32 kaydırma) |
| Bellek | Tablolar için ~1 KB | ~0 bayt (sadece polinom sabiti) |
| Bağımlılıklar | Tables.c gerektirir | Kendi kendine yeten |
| Kullanım durumu | Performans kritik, tablolar zaten mevcut | Bellek kısıtlı, hızlı prototipleme veya tablo üretiminden kaçınma |
Her iki varyant da özdeş CRC değerleri üretir — bunlar, tek CRC’li PDU’ları, multi-CRC PDU’ları (4 CRC’ye kadar), çerçeveler arası CRC kalıtımını ve sınır durumlarını (tam sıfır, tam 0xFF, sıfır olmayan startCrc) kapsayan 10 test durumu boyunca orijinal FSoE CalcCrc referans uygulamasına karşı doğrulanmıştır.
Referans CRC değerleri
Aşağıdaki değerler uygulamanızı doğrulamak için kullanılabilir. Aksi belirtilmedikçe tümü startCrc = 0x0000, seqNo = 0x0001 kullanır:
| size | startCrc | ConnID | Cmd | Data | CRC₀ | CRC₁ | CRC₂ | CRC₃ |
|---|---|---|---|---|---|---|---|---|
| 6 | 0x0000 | 0x1234 | 0x01 | AB | 0x2C82 | — | — | — |
| 7 | 0x0000 | 0x1234 | 0x01 | AB CD | 0xDD27 | — | — | — |
| 11 | 0x0000 | 0x1234 | 0x01 | AB CD EF 12 | 0xDD27 | 0x05A5 | — | — |
| 15 | 0x0000 | 0x1234 | 0x01 | AB CD EF 12 34 56 | 0xDD27 | 0x05A5 | 0x93AA | — |
| 7 | 0xDD27 | 0x1234 | 0x01 | AB CD | 0x41F5 | — | — | — |
| 19 | 0x0000 | 0xCAFE | 0x02 | 10 20 30 40 50 60 70 80 | 0xE6BD | 0x687A | 0xB265 | 0x657C |
- satır (
startCrc = 0xDD27) CRC kalıtımını gösterir: 2. satırdaki CRC₀’ıstartCrcolarak kullanır ve aynı veri için farklı bir CRC üretir.