Dari register mentah ke nilai proses — menjembatani protokol klasik dengan kebutuhan data modern.

Ditulis oleh Ketut Kumajaya — 17 Oktober 2025

Latar Belakang

Modbus adalah protokol komunikasi klasik yang diperkenalkan sejak akhir 1970‑an dan hingga kini tetap menjadi standar de facto di berbagai sistem industri. Kesederhanaan dan keterbukaannya membuat Modbus terus digunakan secara luas, baik pada perangkat lama maupun perangkat modern, mulai dari PLC hingga flowmeter.

Namun, karena Modbus hanya mendefinisikan pertukaran data dalam bentuk register 16‑bit, interpretasi data bernilai 32‑bit atau 64‑bit memerlukan mekanisme konversi tambahan. Tantangan umum yang muncul meliputi perbedaan endianness, perbedaan addressing, serta variasi hardware layer dan varian protokol.

Endianness

Endianness adalah urutan penyimpanan byte atau word dalam data biner:

  • Big‑endian: byte paling signifikan (MSB) disimpan lebih dulu.
  • Little‑endian: byte paling signifikan disimpan paling akhir.

Contoh pada Modbus 32‑bit

Nilai 1234.56 (FLOAT 32‑bit) dapat dikirim sebagai dua register 16‑bit:

Word Order Register1 Register2 Keterangan
Big‑endian (MSB dulu) 0x449A 0x51EC MSB → LSB
Little‑endian (LSB dulu) 0x51EC 0x449A LSB → MSB

Catatan untuk 64‑bit

Pada data 64‑bit, variasi urutan bisa terjadi baik di level register maupun byte, sehingga interpretasi lebih kompleks.

Praktik Lapangan

Jika hasil konversi tidak sesuai ekspektasi, langkah pertama yang umum dilakukan adalah menukar urutan register.

Pemahaman mengenai endianness ini penting karena menjadi salah satu sumber kesalahan paling sering saat integrasi perangkat Modbus lintas vendor.

Addressing

Selain endianness, Modbus juga memiliki perbedaan sistem penomoran alamat register:

  • Zero‑based addressing: register pertama ditandai sebagai alamat 0.
  • One‑based addressing: register pertama ditandai sebagai alamat 1.

Akibatnya, dapat terjadi perbedaan satu offset antara dokumentasi perangkat dan implementasi di SCADA/DCS. Misalnya, dokumentasi menyebutkan data di register 40001, tetapi pada sistem zero‑based harus diakses pada alamat 40000.

Perbedaan addressing ini sering menjadi sumber kebingungan saat integrasi, sehingga penting untuk selalu memverifikasi konvensi yang digunakan oleh perangkat maupun sistem SCADA/DCS.

Hardware Layer dan Varian Modbus

Modbus hadir dalam beberapa varian:

  • Modbus RTU: berjalan di atas RS‑232/RS‑485 dengan format biner yang efisien, paling umum digunakan di lapangan.
  • Modbus ASCII: juga berbasis serial, data dikirim dalam teks ASCII, lebih mudah dibaca manusia tetapi jarang dipakai di aplikasi modern.
  • Modbus TCP/IP: berjalan di atas Ethernet, menggunakan MBAP header menggantikan CRC, umum pada perangkat generasi baru.
  • Modbus RTU over TCP: frame RTU (lengkap dengan CRC) ditransmisikan melalui TCP tanpa MBAP header. Biasanya digunakan untuk kompatibilitas dengan perangkat lama melalui jaringan IP.

Perbedaan antara Modbus TCP/IP dan Modbus RTU over TCP sangat penting dipahami, karena driver atau library harus sesuai dengan format frame yang digunakan. Pemilihan varian yang tepat memastikan komunikasi berjalan konsisten di berbagai perangkat dan sistem.

Catatan Tambahan

  • Pada Modbus RTU, integritas data dijaga dengan CRC di level frame.
  • Pada Modbus TCP/IP, CRC tidak digunakan karena protokol TCP/IP sudah memiliki mekanisme pemeriksaan integritas bawaan (checksum, sequence number, retransmission). MBAP header menggantikan CRC dalam frame TCP/IP.
  • Frame Modbus RTU juga dapat ditransmisikan melalui media nirkabel seperti LoRa dalam mode transparent. Dalam hal ini, LoRa hanya berfungsi sebagai saluran komunikasi, sementara integritas data tetap dijaga oleh CRC Modbus.

K_WORD2_TO_FLOAT

Function Block ini digunakan untuk menggabungkan dua register UINT menjadi nilai FLOAT. FB ini menjadi baseline parsing untuk data 16‑bit maupun 32‑bit, baik ketika perangkat mengirimkan data dalam format IEEE 754 maupun sebagai akumulator numerik.

%%{init: {'themeVariables': { 'fontSize': '16px', 'primaryColor': '#e8f0fe', 'edgeLabelBackground':'#ffffff'}}}%% flowchart TD A["Start: Baca Register Modbus
IN0 UINT LSB
IN1 UINT MSB"] --> B{Cek Endianness
Hasil sesuai?} B -->|Ya| C["Gabung ke 32-bit
Temp32 = IN0 OR (IN1 << 16)"] B -->|Tidak - Endian salah| D["Tukar Urutan
Swap IN0 ↔ IN1
Ulangi Cek"] D --> B C --> E{"D = 0?"} E -->|Ya| F["RawFloat = 0.0
Proteksi Div-by-Zero"] E -->|Tidak| G{IEE = TRUE?} G -->|Ya| H["Mode IEEE 754
RawFloat = GETFLOAT(Temp32)/D"] G -->|Tidak| I["Mode Numerik
RawFloat = Temp32 as UDINT / D"] H --> K{"IHL = ILL?"} I --> K F --> K K -->|Ya| L["OUT = OLL
(Fallback jika range input nol)"] K -->|Tidak| M["Scaling Linier:
OUT = (OHL-OLL)/(IHL-ILL) * (RawFloat-ILL) + OLL"] L --> N["End: Output FLOAT
(Siap untuk Tampilan/Proses)"] M --> N classDef start fill:#e1f5fe,stroke:#333,stroke-width:2px classDef final fill:#c8e6c9,stroke:#333,stroke-width:2px classDef swap fill:#fff3e0,stroke:#333,stroke-width:2px classDef decision fill:#f3e5f5,stroke:#333,stroke-width:2px classDef process fill:#ffffff,stroke:#333,stroke-width:2px class A start class N final class D swap class B,E,G,K decision class C,F,H,I,L,M process
Gambar 1: Alur Konversi Register Modbus 32-bit ke FLOAT Menggunakan Function Block K_WORD2_TO_FLOAT

Fitur Utama

  • Mendukung data 16‑bit: IN0 diisi, IN1 = 0, IEE = FALSE.
  • Mendukung data 32‑bit: IN0 dan IN1 diisi sesuai register.
  • Mode IEEE 754 (IEE = TRUE) atau mode numerik (IEE = FALSE).
  • Skala pembagi (D) dengan proteksi pembagian nol.
  • Fleksibel terhadap variasi endian antar perangkat.
Klik untuk menampilkan kode K_WORD2_TO_FLOAT
(*
------------------------------------------------------------------------------
 FB Name     : K_WORD2_TO_FLOAT
 Purpose     : Konversi 2 register UINT menjadi FLOAT lalu scaling linier
               ke Engineering Unit (EU) dengan proteksi div/0.
 Author      : Ketut Kumajaya
 Contributor : Copilot (Microsoft AI)
 Version     : 1.1
 Date        : 21/10/2025

 Input       :
    IN0 (UINT) - LSB Modbus
    IN1 (UINT) - MSB Modbus
    D   (LONG) - Skala pembagi (anti-div-zero tahap dasar)
    IEE (BOOL) - TRUE = interpretasi IEEE 754 FLOAT
    ILL (FLOAT)- Input Low Limit
    IHL (FLOAT)- Input High Limit
    OLL (FLOAT)- Output Low Limit
    OHL (FLOAT)- Output High Limit

 Output      :
    OUT (FLOAT) - Nilai hasil scaling ke EU

 Notes       :
    - Proteksi div/0:
        * Jika D=0 maka OUT=0.0
        * Jika IHL=ILL maka OUT=OLL
    - Rumus scaling:
        OUT = (OHL - OLL) / (IHL - ILL) * (Raw - ILL) + OLL
    - Urutan IN0/IN1 dapat ditukar jika perangkat menggunakan endian berbeda
    - Cocok untuk konversi register mentah atau data FLOAT ke engineering unit
    - FB ini tidak mendukung data 64-bit
    - Untuk "tanpa scaling", gunakan ILL=0.0, IHL=1.0, OLL=0.0, OHL=1.0
      -> OUT = Raw (identitas)

 Contoh konfigurasi:
    1. Sensor 4–20 mA -> 0–100 bar
       ILL=4.0, IHL=20.0, OLL=0.0, OHL=100.0
    2. Register 0–32767 -> 0–5000 Nm³/h
       ILL=0.0, IHL=32767.0, OLL=0.0, OHL=5000.0
    3. Tanpa scaling (nilai asli diteruskan)
       ILL=0.0, IHL=1.0, OLL=0.0, OHL=1.0
------------------------------------------------------------------------------
*)
FUNCTION_BLOCK K_WORD2_TO_FLOAT
VAR_INPUT
    IN0 : UINT;
    IN1 : UINT;
    D   : LONG;
    IEE : BOOL;
    ILL : FLOAT;
    IHL : FLOAT;
    OLL : FLOAT;
    OHL : FLOAT;
END_VAR

VAR_OUTPUT
    OUT : FLOAT;
END_VAR

VAR
    Temp32   : DWORD;
    RawFloat : FLOAT;
END_VAR

(* Gabungkan 2 UINT menjadi pola bit 32-bit *)
Temp32 := ULONG_TO_DWORD(UINT_TO_ULONG(IN0));
Temp32 := Temp32 + SHL_DWORD(ULONG_TO_DWORD(UINT_TO_ULONG(IN1)), 16);

(* Hitung nilai dasar *)
IF D <> 0 THEN
    IF IEE THEN
        RawFloat := GETFLOAT(Temp32) / LONG_TO_FLOAT(D);
    ELSE
        RawFloat := LONG_TO_FLOAT(DWORD_TO_LONG(Temp32)) / LONG_TO_FLOAT(D);
    END_IF;
ELSE
    RawFloat := 0.0;
END_IF;

(* Scaling linier ke engineering unit *)
IF (IHL <> ILL) THEN
    OUT := ((OHL - OLL) / (IHL - ILL)) * (RawFloat - ILL) + OLL;
ELSE
    OUT := OLL;  (* fallback jika range input nol *)
END_IF;

END_FUNCTION_BLOCK

K_WORD4_TO_FLOAT

Function Block ini digunakan untuk menggabungkan empat register UINT menjadi nilai 64‑bit, kemudian dikonversi ke FLOAT untuk keperluan tampilan, trending, atau estimasi. FB ini umumnya dipakai untuk perangkat yang mengirimkan akumulator energi, counter besar, atau nilai kumulatif lainnya.

%%{init: {'themeVariables': { 'fontSize': '16px', 'primaryColor': '#e8f0fe', 'edgeLabelBackground':'#ffffff'}}}%% flowchart TD A["Start: Baca Register Modbus
IN0 UINT LSB Low
IN1 UINT MSB Low
IN2 UINT LSB High
IN3 UINT MSB High"] --> B{Cek Endianness
Hasil sesuai?} B -->|Ya| C["Gabung Low32 = IN1 << 16 OR IN0
High32 = IN3 << 16 OR IN2"] B -->|Tidak - Endian salah| D["Tukar Urutan
Swap IN0 ↔ IN1 (Low)
Swap IN2 ↔ IN3 (High)
Ulangi Cek"] D --> B C --> E{"D = 0?"} E -->|Ya| F["Raw64 = 0.0
Proteksi Div-by-Zero"] E -->|Tidak| G["Hitung 64-bit dasar
Raw64 = (High32 * 2^32 + Low32) / D"] F --> K{"IHL = ILL?"} G --> K K -->|Ya| L["OUT = OLL
(Fallback jika range input nol)"] K -->|Tidak| M["Scaling Linier:
OUT = (OHL-OLL)/(IHL-ILL) * (Raw64-ILL) + OLL"] L --> N["End: Output FLOAT
(Untuk Monitoring/Estimasi)"] M --> N classDef start fill:#e1f5fe,stroke:#333,stroke-width:2px classDef final fill:#c8e6c9,stroke:#333,stroke-width:2px classDef swap fill:#fff3e0,stroke:#333,stroke-width:2px classDef decision fill:#f3e5f5,stroke:#333,stroke-width:2px classDef process fill:#ffffff,stroke:#333,stroke-width:2px class A start class N final class D swap class B,E,K decision class C,F,G,L,M process
Gambar 2: Alur Rekonstruksi Data 64-bit dari Empat Register Modbus ke FLOAT

Fitur Utama

  • Menggabungkan Low32 dan High32 secara manual.
  • Rumus: OUT = (High32 × 2^32 + Low32) ÷ D.
  • Proteksi pembagian nol.
  • Catatan presisi: konversi ke FLOAT 32‑bit dilakukan agar nilai dapat diproses di DCS. Hasil ini memadai untuk kebutuhan operasional sehari‑hari, tetapi tidak memenuhi standar akurasi untuk audit atau billing. Untuk keperluan tersebut, gunakan data 64‑bit asli dari perangkat.
Klik untuk menampilkan kode K_WORD4_TO_FLOAT
(*
------------------------------------------------------------------------------
 FB Name     : K_WORD4_TO_FLOAT
 Purpose     : Menggabungkan 4 register UINT menjadi nilai 64-bit lalu scaling
               linier ke Engineering Unit (EU).
 Author      : Ketut Kumajaya
 Contributor : Copilot (Microsoft AI)
 Version     : 1.1
 Date        : 21/10/2025

 Input       :
    IN0 (UINT) - LSB dari Low32
    IN1 (UINT) - MSB dari Low32
    IN2 (UINT) - LSB dari High32
    IN3 (UINT) - MSB dari High32
    D   (LONG) - Skala pembagi (anti-div-zero tahap dasar)
    ILL (FLOAT)- Input Low Limit
    IHL (FLOAT)- Input High Limit
    OLL (FLOAT)- Output Low Limit
    OHL (FLOAT)- Output High Limit

 Output      :
    OUT (FLOAT) - Nilai hasil scaling ke EU

 Notes       :
    - OUT = (High32 * 2^32 + Low32) / D
    - Konstanta 4294967296.0 = 2^32, digunakan untuk menggeser High32
    - Proteksi div/0:
        * Jika D=0 maka OUT=0.0
        * Jika IHL=ILL maka OUT=OLL
    - Scaling linier:
        OUT = (OHL - OLL) / (IHL - ILL) * (Raw - ILL) + OLL
    - Urutan IN0/IN1 dan IN2/IN3 dapat ditukar jika perangkat endian berbeda
    - Presisi terbatas karena hasil disimpan sebagai FLOAT 32-bit
    - Untuk "tanpa scaling", gunakan ILL=0.0, IHL=1.0, OLL=0.0, OHL=1.0
      (hasil OUT = Raw, identitas)
    - Gunakan hanya untuk tampilan/estimasi, bukan akurasi billing

 Contoh konfigurasi:
    1. Energi Wh 64-bit ke kWh
       D=1000, ILL=0.0, IHL=1000000.0, OLL=0.0, OHL=1000.0
    2. Counter 0–1e12 ke 0–100 %
       D=1, ILL=0.0, IHL=1.0e12, OLL=0.0, OHL=100.0
    3. Tanpa scaling
       D=1, ILL=0.0, IHL=1.0, OLL=0.0, OHL=1.0
------------------------------------------------------------------------------
*)
FUNCTION_BLOCK K_WORD4_TO_FLOAT
VAR_INPUT
    IN0 : UINT;
    IN1 : UINT;
    IN2 : UINT;
    IN3 : UINT;
    D   : LONG;
    ILL : FLOAT;
    IHL : FLOAT;
    OLL : FLOAT;
    OHL : FLOAT;
END_VAR

VAR_OUTPUT
    OUT : FLOAT;
END_VAR

VAR
    Low32   : DWORD;
    High32  : DWORD;
    Raw64   : FLOAT;
END_VAR

(* Gabungkan masing-masing pasangan *)
Low32  := SHL_DWORD(ULONG_TO_DWORD(UINT_TO_ULONG(IN1)), 16) 
        + ULONG_TO_DWORD(UINT_TO_ULONG(IN0));
High32 := SHL_DWORD(ULONG_TO_DWORD(UINT_TO_ULONG(IN3)), 16) 
        + ULONG_TO_DWORD(UINT_TO_ULONG(IN2));

(* Hitung nilai dasar sebagai FLOAT *)
IF D <> 0 THEN
    Raw64 := (LONG_TO_FLOAT(DWORD_TO_LONG(High32)) * 4294967296.0  (* 2^32 *)
            + LONG_TO_FLOAT(DWORD_TO_LONG(Low32))) 
            / LONG_TO_FLOAT(D);
ELSE
    Raw64 := 0.0;
END_IF;

(* Scaling linier ke engineering unit *)
IF (IHL <> ILL) THEN
    OUT := ((OHL - OLL) / (IHL - ILL)) * (Raw64 - ILL) + OLL;
ELSE
    OUT := OLL;  (* fallback jika range input nol *)
END_IF;

END_FUNCTION_BLOCK

Panduan Singkat Penggunaan

Kapan menggunakan K_WORD2_TO_FLOAT

  • 16‑bit: isi IN0, set IN1 = 0, gunakan IEE = FALSE.
  • 32‑bit: isi IN0 dan IN1 sesuai register.
    • IEEE 754 → IEE = TRUE.
    • Numerik → IEE = FALSE.
  • Jika hasil tidak sesuai, coba tukar IN0 dan IN1.
  • Setelah nilai dasar diperoleh, FB otomatis melakukan scaling linier dari range input (ILL–IHL) ke range output (OLL–OHL).
  • Untuk tanpa scaling, gunakan konfigurasi identitas: ILL=0.0, IHL=1.0, OLL=0.0, OHL=1.0.

FB ini ideal untuk parsing data register tunggal atau ganda yang umum ditemui pada sensor dan flowmeter.

Kapan menggunakan K_WORD4_TO_FLOAT

  • 64‑bit: isi IN0–IN3 sesuai register.
  • Cocok untuk akumulator besar (misalnya energi, volume kumulatif, atau counter jarak jauh).
  • OUT hanya untuk monitoring, bukan billing.
  • Jika hasil tidak sesuai, coba tukar pasangan register.
  • Setelah nilai dasar diperoleh, FB otomatis melakukan scaling linier dari range input (ILL–IHL) ke range output (OLL–OHL).
  • Untuk tanpa scaling, gunakan konfigurasi identitas: ILL=0.0, IHL=1.0, OLL=0.0, OHL=1.0.

FB ini relevan untuk perangkat yang mengirimkan nilai kumulatif besar, dengan catatan hasil konversi hanya dipakai untuk tampilan dan analisis tren.

Contoh Konfigurasi Uji

FB Input Register Mode D ILL/IHL OLL/OHL OUT (ekspektasi)
K_WORD2_TO_FLOAT IN0=0x51EC, IN1=0x449A IEE=TRUE 1 0.0 / 1.0 0.0 / 1.0 1234.56
K_WORD2_TO_FLOAT IN0=1000, IN1=0 IEE=FALSE 10 0.0 / 1.0 0.0 / 1.0 100.0
K_WORD4_TO_FLOAT IN0=0, IN1=0, IN2=1, IN3=0 Numerik 1 0.0 / 1.0 0.0 / 1.0 4294967296.0

Catatan Umum

  • D = 0 → OUT = 0.0.
  • Addressing dapat berbeda (0‑based vs 1‑based).
  • Endianness antar vendor tidak selalu sama.
  • Pembagi (D) didefinisikan sebagai LONG (32‑bit signed). Rentang ini sudah memadai untuk kebutuhan scaling Modbus, sekaligus lebih sederhana dibanding ULONG yang di Supcon memerlukan konversi tambahan.

Lanjutan

Setelah data akumulator berhasil direkonstruksi menjadi nilai FLOAT yang siap dipakai, langkah berikutnya sering kali adalah menghitung delta energi untuk kebutuhan laporan periodik, rekonsiliasi antar meter, maupun audit energi.

Untuk tujuan tersebut, tersedia Function Block K_ACCDELTA sebagai modul terpisah. FB ini dirancang untuk menghitung delta akumulator secara andal, dengan proteksi nilai negatif dan mekanisme snapshot reset untuk sinkronisasi baseline antar meter. Modul ini menjadi kelanjutan alami dari FB parsing (K_WORD2/4_TO_FLOAT), sehingga alur pengolahan data tetap modular dan konsisten.

K_ACCDELTA: Function Block untuk Perhitungan Delta Akumulator


Penutup

Konversi register Modbus membutuhkan ketelitian terhadap format data, endianness, addressing, dan varian protokol. Kedua Function Block di atas dirancang untuk menyajikan nilai proses secara konsisten dan dapat ditelusuri kembali, sekaligus mengakui batasan presisi ketika menggunakan FLOAT 32‑bit.

Dengan dokumentasi yang jelas dan logika yang eksplisit, konversi bukan sekadar kalkulasi teknis, melainkan sarana untuk memastikan operator, engineer, dan auditor memahami proses dengan cara yang sama. Transparansi inilah yang menjadikan integrasi data lebih andal dan berkelanjutan.