FSoE PDU'ları için CRC sağlama nasıl hesaplanır?

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:

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:

example.txt
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:

example.cpp
// 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

example.cpp
#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;
}
output.txt
CRC0 = 0xDD27

Varyant 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.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
/*
 * 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
/*
 * 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ı):

build-table-based.sh
# 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 project

Varyant 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
/*
 * 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
/*
 * 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):

build-on-the-fly.sh
# No table generation needed — fully self-contained
g++ -std=c++17 -O2 -c FsoeCrcOnTheFly.cpp -o FsoeCrcOnTheFly.o
# Link FsoeCrcOnTheFly.o into your project

Hangi varyantı kullanmalıyım?

KriterTablo tabanlıAnında
HızHızlı (bayt başına 2 arama)Daha yavaş (bayt başına 8+32 kaydırma)
BellekTablolar için ~1 KB~0 bayt (sadece polinom sabiti)
BağımlılıklarTables.c gerektirirKendi kendine yeten
Kullanım durumuPerformans kritik, tablolar zaten mevcutBellek 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:

sizestartCrcConnIDCmdDataCRC₀CRC₁CRC₂CRC₃
60x00000x12340x01AB0x2C82
70x00000x12340x01AB CD0xDD27
110x00000x12340x01AB CD EF 120xDD270x05A5
150x00000x12340x01AB CD EF 12 34 560xDD270x05A50x93AA
70xDD270x12340x01AB CD0x41F5
190x00000xCAFE0x0210 20 30 40 50 60 70 800xE6BD0x687A0xB2650x657C
  1. satır (startCrc = 0xDD27) CRC kalıtımını gösterir: 2. satırdaki CRC₀’ı startCrc olarak kullanır ve aynı veri için farklı bir CRC üretir.

Ayrıca bkz.


Check out similar posts by category: Functional Safety EtherCAT C++ CRC