Ditulis oleh Ketut Kumajaya | 25 September 2025

Latar Belakang

Dalam sistem industri berbasis DCS/SCADA, akumulasi data flow adalah fondasi penting untuk perhitungan energi, OEE, hour meter, maupun audit produksi. Supcon menyediakan function block bawaan TOTAL_ACCUM untuk fungsi ini. Namun, ketika block tersebut dijalankan di Modbus controller, muncul masalah serius: drift akumulasi akibat keterbatasan clock internal dan jitter eksekusi.

Selain itu, terdapat keterbatasan komunikasi: hanya tersedia 128 alamat 32‑bit untuk transfer data dari main controller ke Modbus controller. Sinyal analog SFLOAT 12‑bit hanya menempati 16‑bit, sehingga satu alamat 32‑bit dapat menampung dua sinyal. Sebaliknya, satu akumulator penuh menempati seluruh 32‑bit, sehingga konsumsi alamat menjadi dua kali lebih besar. Jika seluruh akumulasi dipusatkan di main controller, alokasi alamat akan cepat habis. Oleh karena itu, diperlukan solusi yang tidak hanya hemat alamat, tetapi juga tetap menjaga presisi.


Tantangan Teknis

  • Drift akumulasi: TOTAL_ACCUM bawaan bergantung pada clock internal Modbus controller yang tidak sinkron dengan main controller.
  • Keterbatasan alamat komunikasi: hanya tersedia 128 alamat 32‑bit untuk transfer data.
  • Perbedaan kebutuhan ruang data: sinyal flow 12‑bit disimpan dalam 16‑bit word, sehingga satu alamat 32‑bit dapat menampung dua sinyal. Sebaliknya, satu akumulator penuh menempati seluruh 32‑bit, membuat konsumsi alamat dua kali lebih besar.
  • Kebutuhan auditabilitas: operator membutuhkan hasil akumulasi yang transparan, dapat ditelusuri, dan mudah dipahami lintas shift.

Analisis Akar Masalah

  • Drift akumulasi terjadi karena clock internal Modbus controller berbasis 16‑bit tidak disiplin dan tidak sinkron dengan main controller, sehingga perhitungan delta waktu rawan melompat atau melambat.
  • Alamat cepat habis disebabkan oleh perbedaan kebutuhan ruang: sinyal analog bisa dipadatkan (2 sinyal per alamat 32‑bit), sedangkan akumulator selalu menghabiskan satu alamat penuh.
  • Kurangnya visibilitas audit muncul karena block bawaan TOTAL_ACCUM tidak mengekspos delta waktu. Operator hanya melihat nilai total, tanpa jejak perhitungan detail.

Solusi: K_ACCUM

Saya merancang function block custom bernama K_ACCUM, dengan fitur utama:

  • Clock referensi dari main controller → bebas drift, sinkron lintas device.
  • Integrasi wrap‑aware → rollover 16‑bit ditangani aman.
  • Proteksi delta & RATE → delta negatif atau terlalu besar diabaikan, RATE anomali difilter.
  • Kompatibilitas penuh → menyediakan output FLOAT yang bisa dikonversi ke structAccum menggunakan function block CONVERT_TO_ACCUM
  • Audit‑friendly → komentar inline, reserved field dapat digunakan untuk logging delta atau flag anomali.

Diagram Alur (Mermaid)

flowchart LR A["Input"] --> B["Proteksi"] B --> C["Akumulasi"] C --> D["Output"] C --> E["Persistensi"] subgraph Input A1["ENABLE (BOOL)"] A2["RATE (SFLOAT)"] A3["DELTA (UINT)"] A4["ACC_IN (structKAccum)"] end subgraph Proteksi B1["Wrap-aware Timer 16-bit"] B2["Guard DeltaSec > 60 (Clamp/Discard)"] B3["Guard RATE anomali"] end subgraph Akumulasi C1["AccReal := AccReal + (RATE * DeltaSec)"] C2["Preserve remainder (SFLOAT)"] C3["ENABLE = FALSE → Freeze AccReal"] end subgraph Output D1["ACC_OUT (structKAcuum)"] D2["ALT_OUT (FLOAT)"] end subgraph Persistensi E1["ACC_OUT := incremented ACC_IN"] end A1 --> B A2 --> B A3 --> B A4 --> B B --> B1 --> C B --> B2 --> C B --> B3 --> C C --> D C --> E

Detail Teknis

  • Rekonstruksi nilai total: akumulasi accum dan remainder.
  • Perhitungan delta waktu: wrap‑aware 16‑bit, dengan proteksi >60 detik.
  • Integrasi: AccReal := AccReal + (RATE * DeltaSec) dilakukan dalam FLOAT penuh.
  • Fragmentasi kembali: meskipun struktur data berbeda dengan structAccum, konversi bisa dilakukan menggunakan block bawaan.
  • Konversi ke SFLOAT di tahap akhir: akumulasi dijaga dalam FLOAT untuk presisi maksimum, lalu dipadatkan ke SFLOAT pada bagian akhir.

Catatan Teknis: structKAccum dan Horizon Overflow

Selain presisi integrasi, salah satu aspek penting dari kompatibilitas K_ACCUM adalah bisa dikonversi ke structAccum bawaan Supcon, sehingga tidak ada masalah jika hasilnya ditampilkan ke HMI. Struktur data terdiri dari 2 field:

  • accum → long (32‑bit)
  • remainder → pecahan (SFLOAT)

Sedangkan struktur structAccum terdiri dari empat field:

  • accum1 → low word (16‑bit)
  • accum2 → high word (16‑bit)
  • remainder → pecahan (SFLOAT)
  • reserved → dapat digunakan untuk logging atau flag anomali

Dengan keduanya adalah kombinasi integer 32‑bit dan pecahan SFLOAT, baik structKAccum maupun structAccum memiliki karakteristik unik:

  • Horizon overflow ±136 tahun
    Integer 32‑bit mampu menampung hingga 4,29 miliar detik. Overflow baru terjadi setelah lebih dari satu abad operasi kontinu. Praktis, operator tidak perlu melakukan reset manual untuk menghindari overflow.

  • Independen dari skala input
    Berapapun range engineering unit (misalnya 0–10.000 Nm³/h), sinyal tetap dipadatkan ke SFLOAT 0–1 sebelum diakumulasi, lalu di-back scaling ke unit aslinya.

  • Audit‑friendly
    Nilai total dapat direkonstruksi dengan cara yang sama lintas block, sehingga konsistensi audit tetap terjaga.


Beralih dari K_ACCUMULATOR ke K_ACCUM

Salah satu alasan utama transisi dari K_ACCUMULATOR ke K_ACCUM adalah karena struktur structAccum bawaan Supcon menggunakan dua word 16‑bit (accum1 dan accum2). Urutan bit/word ini ternyata tidak selalu konsisten (tergantung endianness, yaitu cara sistem menyusun byte dalam memori), sehingga hasil akumulasi bisa mendadak jatuh atau menurun. Untuk aplikasi audit jangka panjang, kondisi ini tidak bisa diterima karena membuat hasil akumulasi sulit diprediksi, dan kesalahan akumulasi tidak dapat ditoleransi.

Sebagai solusi, K_ACCUM dirancang menggunakan LONG 32‑bit tunggal yang bebas dari ambiguitas bit order. Struktur ini tetap dilengkapi dengan remainder (SFLOAT) untuk menjaga presisi pecahan.

Dengan desain ini, K_ACCUM lebih stabil, tetap presisi, dan audit‑friendly.
Konversi ke structAccum masih dimungkinkan jika diperlukan untuk kompatibilitas HMI, tetapi logika inti tetap aman di structKAccum.


Dependensi dan Ekstensi Modular K_ACCUM

  • K_DELTA (Dependensi Waktu)
    Menyediakan delta waktu wrap‑aware dari timer utama 16‑bit.
    → Digunakan oleh K_ACCUM dan seluruh block turunan agar konsisten, presisi, dan audit‑friendly.

  • K_ADD_ACCUM (Aggregator)
    Menjumlahkan dua accumulator structKAccum menjadi satu total konsolidasi.
    Cocok untuk agregasi OEE lintas line, penggabungan energi dari beberapa sumber, atau chaining modular (shift → harian → bulanan).

  • K_SUB_ACCUM (Differentiator)
    Mengurangkan dua accumulator structKAccum untuk menyingkap selisih.
    Cocok untuk audit energi (supply–demand), neraca massa (inlet–outlet), atau analisis delta OEE. Jika dua accumulator yang sama dikurangkan, block ini berfungsi sebagai reset.

Karakteristik Bersama

  • Presisi terjaga dengan kombinasi LONG + SFLOAT.
  • Output utama tetap structKAccum, dengan ALT_OUT (FLOAT) sebagai alternatif cepat.
  • Audit‑friendly: hasil dapat direkonstruksi dengan cara yang sama seperti K_ACCUM.
  • Modular: operator cukup drag‑and‑drop block tanpa scripting manual.

Dengan K_DELTA sebagai time backbone dan K_ADD_ACCUM/K_SUB_ACCUM sebagai ekstensi, keluarga K_ACCUM membentuk arsitektur akumulasi modular yang presisi, efisien, dan siap audit lintas plant.


Implikasi Operasional

  • Tidak perlu reset → operator terbebas dari prosedur reset akumulator yang berisiko menghapus histori.
  • Data kontinu → histori akumulasi dapat berjalan tanpa interupsi, memudahkan analisis jangka panjang.
  • Sederhana untuk transfer knowledge → operator baru cukup memahami bahwa akumulator akan terus bertambah, tanpa perlu menghafal SOP reset.

Dengan horizon overflow ±136 tahun, structKAccum praktis tidak memerlukan reset manual. Hal ini menghilangkan risiko kehilangan data akibat reset, sekaligus menyederhanakan SOP operator. Akumulator dapat berjalan kontinu, audit tetap konsisten, dan transfer knowledge lintas shift menjadi lebih mudah.


Potensi Ekstensi Fungsi

Aplikasi Konfigurasi RATE Hasil
Hour Meter RATE = 1.0 saat equipment ON Akumulasi detik operasi → jam
Runtime Counter RATE = 1 saat ON, 0 saat OFF Total waktu ON untuk PM
Event Duration Logger RATE = 1 saat kondisi tertentu Total durasi kondisi aktif
Audit Energi RATE = konsumsi energi per detik Total energi (kWh)

Validasi Independen dengan Rapid SCADA

Untuk memastikan hasil tidak bias, pengujian dilakukan menggunakan Rapid SCADA sebagai sistem eksternal yang independen dari DCS.

  • Metode: flow statis 2500 diberikan secara identik ke K_ACCUM dan TOTAL_ACCUM dari variabel yang sama, lalu hasil ditrend di Rapid SCADA.
  • Hasil:
    • K_ACCUM: 2499,3 (deviasi −0,03%)
    • TOTAL_ACCUM: 2450 (deviasi −2%)
  • Makna: bahkan dengan pengujian eksternal, in‑house FB terbukti lebih presisi dibanding block bawaan vendor.

Dampak dan Nilai Tambah

  • Efisiensi komunikasi → alamat 128 tidak cepat habis.
  • Presisi akumulasi → hasil konsisten, bebas drift jangka panjang.
  • Auditabilitas → operator dapat menelusuri delta waktu.
  • Fleksibilitas → block dapat digunakan sebagai accumulator, hour meter, runtime counter, maupun logger.
  • Kredibilitas eksternal → hasil diverifikasi oleh sistem independen (Rapid SCADA).

Kesimpulan

K_ACCUM berhasil menurunkan deviasi dibanding block bawaan Supcon. Hasilnya memang tidak harus sempurna—karena faktor eksternal seperti jitter komunikasi dan keterbatasan representasi data tetap ada—tetapi penurunan deviasi dari 2% menjadi 0,03% adalah pencapaian nyata.

Solusi ini bukan sekadar “membuat block baru”, melainkan membangun arsitektur yang efisien, presisi, audit‑friendly, dan terbukti lebih baik bahkan saat diuji dengan sistem independen. Pencapaian ini menjadi basis kuat untuk benchmark jangka panjang, di mana performa K_ACCUM dapat diuji lebih lama (jam, hari, hingga minggu) untuk membuktikan stabilitasnya dalam kondisi operasi nyata.


Catatan kontributor: Artikel ini disusun oleh Ketut Kumajaya dengan dukungan editorial dari Copilot (Microsoft AI) dalam penyusunan narasi, struktur, dan dokumentasi teknis.


Appendix A — Listing Kode Lengkap

Klik untuk membuka kode Function Block K_ACCUM

(*==============================================================================
FB Name     : K_ACCUM
Purpose     : Akumulator modular berbasis LONG + SFLOAT, menerima DELTA dari K_DELTA
Author      : Ketut Kumajaya
Contributor : Copilot (Microsoft AI)
Version     : 1.1 (tanpa RESET, dependensi ke K_DELTA)
Date        : 01/10/2025
Input       : ENABLE (BOOL)   - aktivasi akumulasi
              RATE   (SFLOAT) - laju akumulasi per detik
              DELTA  (UINT)   - selisih waktu antar siklus dari K_DELTA
              ACC_IN (structKAccum) - nilai akumulasi sebelumnya
Output      : ACC_OUT (structKAccum) - hasil akumulasi modular
              ALT_OUT (FLOAT)        - total nilai akumulasi sebagai alternatif
Notes       : - Input waktu TIMER telah digantikan oleh DELTA dari FB K_DELTA
              - K_ACCUM tidak menyimpan LAST_IN/OUT, sehingga lebih ringan
              - RATE dijaga dalam domain ±16 untuk stabilitas dan audit modular
              - Cocok untuk runtime, hour meter, OEE timer, dan akumulasi energi
              - Pairing waktu dan nilai dilakukan di artefak audit terpisah
Use-case    : Digunakan bersama K_DELTA untuk runtime modular lintas FB dan plant
==============================================================================*)

FUNCTION_BLOCK K_ACCUM
VAR_INPUT
    ENABLE : BOOL;
    RATE   : SFLOAT;
    DELTA  : UINT;
    ACC_IN : structKAccum;
END_VAR
VAR_OUTPUT
    ACC_OUT : structKAccum;
    ALT_OUT : FLOAT;
END_VAR
VAR
    AccReal : FLOAT;
    RateF   : FLOAT;
END_VAR

(* STEP 1: Rekonstruksi total dari input *)
AccReal := LONG_TO_FLOAT(ACC_IN.accum) + SFLOAT_TO_FLOAT(ACC_IN.remainder);

(* STEP 2: Integrasi normal *)
RateF := SFLOAT_TO_FLOAT(RATE);
IF ENABLE THEN
    IF (RateF > -16.0) AND (RateF < 16.0) THEN
        IF DELTA > 0 THEN
            AccReal := AccReal + (RateF * UINT_TO_FLOAT(DELTA));
        END_IF;
    END_IF;
END_IF;

(* STEP 3: Pisahkan kembali ke integer + pecahan *)
ACC_OUT.accum     := FLOAT_TO_LONG(AccReal);
ACC_OUT.remainder := FLOAT_TO_SFLOAT(AccReal - LONG_TO_FLOAT(ACC_OUT.accum));

(* STEP 4: Output alternatif total *)
ALT_OUT := AccReal;

END_FUNCTION_BLOCK

Klik untuk membuka kode Function Block K_DELTA
(*==============================================================================
FB Name     : K_DELTA
Purpose     : Menghitung delta waktu wrap-aware dari timer utama (UINT 16-bit)
Author      : Ketut Kumajaya
Version     : 1.2 (IF-THEN-ELSE, no MOD)
Date        : 07/10/2025
Input       : TIMER_IN (UINT), LAST_IN (UINT)
Output      : DELTA_OUT (UINT), LAST_OUT (UINT), ALARM_OUT (BOOL)
Notes       : - IF-THEN-ELSE untuk hindari loncatan MOD
              - Cutoff 60 detik; LONG untuk Supcon-safe
              - Persisten global LAST_IN/OUT
==============================================================================*)

FUNCTION_BLOCK K_DELTA
VAR_INPUT
    TIMER_IN : UINT;
    LAST_IN  : UINT;
END_VAR
VAR_OUTPUT
    DELTA_OUT : UINT;
    LAST_OUT  : UINT;
    ALARM_OUT : BOOL;
END_VAR
VAR
    Delta       : LONG;
    StartupFlag : BOOL;
END_VAR

StartupFlag := g_bColdStartup OR g_bHotStartup OR g_bDownUsrPrgFlag OR g_bDownCfgFlag;

IF StartupFlag THEN
    Delta := 0;
    ALARM_OUT := FALSE;
    g_bColdStartup := FALSE;
    g_bHotStartup := FALSE;
    g_bDownUsrPrgFlag := FALSE;
    g_bDownCfgFlag := FALSE;
ELSE
    IF TIMER_IN >= LAST_IN THEN
        Delta := ULONG_TO_LONG(UINT_TO_ULONG(TIMER_IN)) - ULONG_TO_LONG(UINT_TO_ULONG(LAST_IN));
    ELSE
        Delta := ULONG_TO_LONG(UINT_TO_ULONG(65535 - LAST_IN)) + ULONG_TO_LONG(UINT_TO_ULONG(TIMER_IN)) + 1;
    END_IF;

    IF Delta > 60 THEN
        Delta := 0;
        ALARM_OUT := TRUE;
    ELSE
        ALARM_OUT := FALSE;
    END_IF;
END_IF;

DELTA_OUT := ULONG_TO_UINT(LONG_TO_ULONG(Delta));
LAST_OUT  := TIMER_IN;

END_FUNCTION_BLOCK

Klik untuk membuka kode Function Block K_ADD_ACCUM
(*==============================================================================
 FB Name     : K_ADD_ACCUM
 Purpose     : Menjumlahkan dua accumulator (structKAccum) secara modular
 Author      : Ketut Kumajaya
 Contributor : Copilot (Microsoft AI)
 Version     : 1.0 (initial release)
 Date        : 28/09/2025
 Input       : ACC1 (structKAccum), ACC2 (structKAccum)
 Output      : ACC_OUT (structKAccum), ALT_OUT (FLOAT total)
 Notes       : - Presisi dijaga dengan LONG + SFLOAT
               - ALT_OUT = total float alternatif
               - Cocok untuk merge runtime, energy, atau counter modular
 Use-case    : Penjumlahan antar accumulator, agregasi OEE, chaining modular
==============================================================================*)

FUNCTION_BLOCK K_ADD_ACCUM
VAR_INPUT
    ACC1 : structKAccum;
    ACC2 : structKAccum;
END_VAR
VAR_OUTPUT
    ACC_OUT : structKAccum;
    ALT_OUT : FLOAT;
END_VAR
VAR
    SumReal : FLOAT;
END_VAR

(* STEP 1: Rekonstruksi total dari masing-masing input *)
SumReal := LONG_TO_FLOAT(ACC1.accum) + SFLOAT_TO_FLOAT(ACC1.remainder)
         + LONG_TO_FLOAT(ACC2.accum) + SFLOAT_TO_FLOAT(ACC2.remainder);

(* STEP 2: Pisahkan kembali ke integer + pecahan *)
ACC_OUT.accum     := FLOAT_TO_LONG(SumReal);
ACC_OUT.remainder := FLOAT_TO_SFLOAT(SumReal - LONG_TO_FLOAT(ACC_OUT.accum));

(* STEP 3: Output alternatif total *)
ALT_OUT := SumReal;

END_FUNCTION_BLOCK

Klik untuk membuka kode Function Block K_SUB_ACCUM
(*==============================================================================
 FB Name     : K_SUB_ACCUM
 Purpose     : Mengurangkan dua accumulator (structKAccum) secara modular
 Author      : Ketut Kumajaya
 Contributor : Copilot (Microsoft AI)
 Version     : 1.0 (initial release)
 Date        : 28/09/2025
 Input       : ACC1 (structKAccum), ACC2 (structKAccum)
 Output      : ACC_OUT (structKAccum), ALT_OUT (FLOAT total)
 Notes       : - Presisi dijaga dengan LONG + SFLOAT
               - ALT_OUT = total float alternatif
               - Cocok untuk selisih runtime, energy, atau counter modular
 Use-case    : Pengurangan antar accumulator, analisis delta OEE, chaining modular
==============================================================================*)

FUNCTION_BLOCK K_SUB_ACCUM
VAR_INPUT
    ACC1 : structKAccum;
    ACC2 : structKAccum;
END_VAR
VAR_OUTPUT
    ACC_OUT : structKAccum;
    ALT_OUT : FLOAT;
END_VAR
VAR
    DiffReal : FLOAT;
END_VAR

(* STEP 1: Rekonstruksi total dari masing-masing input *)
DiffReal := (LONG_TO_FLOAT(ACC1.accum) + SFLOAT_TO_FLOAT(ACC1.remainder))
          - (LONG_TO_FLOAT(ACC2.accum) + SFLOAT_TO_FLOAT(ACC2.remainder));

(* STEP 2: Pisahkan kembali ke integer + pecahan *)
ACC_OUT.accum     := FLOAT_TO_LONG(DiffReal);
ACC_OUT.remainder := FLOAT_TO_SFLOAT(DiffReal - LONG_TO_FLOAT(ACC_OUT.accum));

(* STEP 3: Output alternatif total *)
ALT_OUT := DiffReal;

END_FUNCTION_BLOCK

Klik untuk membuka kode Function Block legacy K_ACCUMULATOR

(*
==============================================================================
 Function Block : K_ACCUMULATOR
 Deskripsi      : Integrator berbasis laju per detik (SFLOAT) dan free-running
                  timer 16-bit, kompatibel Supcon structAccum
 Penulis        : Ketut Kumajaya
 Kontributor    : Copilot (Microsoft AI)
 Versi          : 1.1
 Tanggal        : 25/09/2025
==============================================================================

Fungsi:
- Mengakumulasi nilai = RATE * DeltaSec setiap scan.
- Menggunakan external clock T_IN (0..65535 detik, wrap-aware).
- Reset penuh ditangani oleh built-in FB Supcon melalui structAccum.
- Proteksi delta waktu: delta negatif atau terlalu besar (>60 detik) diabaikan.
- Guard RATE: nilai anomali (-16 > RATE > 16) diabaikan.
- Struktur kompatibel dengan accumulator built-in Supcon.

Versi 1.1 (26/09/2025)
- AccInt diganti ke tipe LONG → konversi lebih sederhana dan mendukung nilai negatif
- Validasi remainder selalu dinormalisasi ke [0,1)
- Patch encoding low word → dipaksa unsigned (0..65535) agar kompatibel penuh dengan structAccum
- Eliminasi bug “accum1 = –32768” yang menyebabkan akumulasi tampak menurun
==============================================================================
*)

FUNCTION_BLOCK K_ACCUMULATOR
VAR_INPUT
    RATE     : SFLOAT;       (* Laju per detik (unit/s) *)
    T_IN     : UINT;         (* Free-running timer (0..65535 detik) *)
    ACC_IN   : structAccum;  (* Akumulator persisten input *)
    LAST_IN  : UINT;         (* Snapshot timer sebelumnya *)
END_VAR
VAR_OUTPUT
    ACC_OUT  : structAccum;  (* Akumulator hasil update *)
    LAST_OUT : UINT;         (* Snapshot timer berikutnya *)
END_VAR
VAR
    AccReal   : FLOAT;       (* Rekonstruksi total accumulator *)
    AccInt    : LONG;        (* Bagian integer dari AccReal *)
    AccFrac   : FLOAT;       (* Bagian pecahan dari AccReal *)
    DeltaSec  : UINT;        (* Selisih detik antar scan *)
    RateF     : FLOAT;       (* RATE dalam FLOAT *)
    LowWord   : WORD;
    HighWord  : INT;
END_VAR

(* NOTE: accum1 = low word, accum2 = high word, remainder = fractional part *)
(* EVENT: Rekonstruksi nilai total dari ACC_IN *)
AccInt := INT_TO_LONG(WORD_TO_INT(INT_TO_WORD(ACC_IN.accum1)))
        + DWORD_TO_LONG(
            SHL_DWORD(LONG_TO_DWORD(INT_TO_LONG(ACC_IN.accum2)),
                16));                         (* shift high word 16 bit *)
AccFrac := SFLOAT_TO_FLOAT(ACC_IN.remainder); (* fractional part *)
AccReal := LONG_TO_FLOAT(AccInt) + AccFrac;   (* total accumulator *)

(* PROTEKSI: hitung delta dengan wrap 16-bit (modular, no-branch) *)
DeltaSec := (T_IN + 65536 - LAST_IN) MOD 65536;

(* PROTEKSI: guard delta anomali *)
IF DeltaSec > 60 THEN
    DeltaSec := 0;
END_IF;

(* PROTEKSI: guard RATE anomali sesuai domain SFLOAT 12-bit: -16..+16 *)
RateF := SFLOAT_TO_FLOAT(RATE);
IF (RateF < 16) AND (RateF > -16) THEN
    IF DeltaSec > 0 THEN
        AccReal := AccReal + (RateF * UINT_TO_FLOAT(DeltaSec));
    END_IF;
END_IF;

(* EVENT: Pisahkan kembali ke structAccum *)
AccInt  := FLOAT_TO_LONG(AccReal);
AccFrac := AccReal - LONG_TO_FLOAT(AccInt);

(* Normalisasi remainder ke [0,1) *)
IF AccFrac < 0.0 THEN
    AccInt  := AccInt - 1;
    AccFrac := AccFrac + 1.0;
ELSE
    IF AccFrac >= 1.0 THEN
        AccInt  := AccInt + 1;
        AccFrac := AccFrac - 1.0;
    END_IF;
END_IF;

(* NOTE: LowWord dipaksa unsigned (0..65535) agar kompatibel dengan structAccum.
   HighWord tetap signed. Total AccInt = HighWord*65536 + LowWord. *)
LowWord  := INT_TO_WORD(LONG_TO_INT(AccInt MOD 65536)); (* selalu 0..65535 *)
HighWord := LONG_TO_INT(AccInt / 65536);                (* high word bisa signed *)

ACC_OUT.accum1    := WORD_TO_INT(LowWord); (* low word aman, tidak pernah -32768 *)
ACC_OUT.accum2    := HighWord;             (* high word signed *)
ACC_OUT.remainder := FLOAT_TO_SFLOAT(AccFrac);
ACC_OUT.reserved  := 0.0;

(* Persist snapshot untuk scan berikutnya *)
LAST_OUT := T_IN;

END_FUNCTION_BLOCK

Appendix B — Extended Validation Runtime (1–6 Jam)

Pengujian runtime lebih panjang dilakukan dengan flow statis 2500 untuk melihat pola drift akumulasi secara progresif.

Hasil Pengujian Runtime

Runtime Ideal K_ACCUM Deviasi Absolut Deviasi Relatif TOTAL_ACCUM Deviasi Absolut Deviasi Relatif
1 jam 2500 2499.31 −0.694 −0.028% 2450.69 −49.306 −1.972%
2 jam 5000 5002.08 +2.083 +0.042% 4905.56 −94.444 −1.898%
3 jam 7500 7500.69 +0.694 +0.009% 7355.56 −144.445 −1.926%
4 jam 10000 10000.69 +0.694 +0.007% 9806.94 −193.056 −1.931%
5 jam 12500 12500.00 +0.000 +0.000% 12258.33 −241.667 −1.933%
6 jam 15000 14998.61 −1.389 −0.009% 14708.33 −291.666 −1.944%

Interpretasi

  • K_ACCUM: deviasi relatif tetap <0.05% bahkan hingga 6 jam, menunjukkan presisi tinggi dan stabilitas jangka panjang.
  • TOTAL_ACCUM: drift konsisten di sekitar −1.9% sejak awal hingga 6 jam, menegaskan bias bawaan block vendor.
  • Makna audit: tren progresif ini memperkuat klaim bahwa K_ACCUM bukan hanya presisi sesaat, tetapi juga tahan drift dalam runtime panjang.