5.3. Modbus Protocol (RS-485)#
Protocol Overview#
The FX200 instrument responds to Modbus RTU requests over the RS-485 data line. Communication parameters such as baud rate, parity, stop bits, and device ID are configured through the PC application or via Modbus itself. The default settings are:
Baud rate: 9600Data bits: 8Parity: EvenStop bits: 1Slave ID: 1
Modbus is a request-response protocol. A master device (such as a data logger or PLC) sends requests, and the slave device (the FX200 instrument) responds. The master can either read the value of one or more holding registers (using Modbus function code 0x03), or write the value of a holding register (function code 0x06), or write more holding registers (function code 0x10). All registers in the FX200 are Holding Registers (40001-range in Modbus convention).
IMPORTANT: Register Address vs. Register Number
The Modbus protocol uses zero-based register addressing in the actual request and response frames transmitted on the wire. This means that the first holding register is addressed as 0, the second as 1, and so on. However, many tools and documentation refer to one-based register numbers.
register number = register address + 1
For example, the first register has address 0 and register number 1. The modpoll utility and most Modbus configuration tools use the 1-based register number.
To further complicate things, certain PLC software packages use 40001-based addressing. In this convention, holding registers are labeled starting from 40001, so that the register number 40001 corresponds to the first holding register at address 0. It is important to understand that the leading “4” in addresses such as 40001 does not form part of the address transmitted over Modbus. Instead, it indicates the register type (4 = Holding Register, not to be confused with function 3 for reading holding registers). The actual address sent in the Modbus frame is zero-based and does not include this prefix.
PLC register number = register address + 40001
Modbus is a very old protocol, so please do not judge its quirks too harshly.
Throughout this document, both the zero-based register address and the corresponding one-based register number are shown for clarity.
Each Modbus register holds a single 16-bit (2-byte) value. The FX200 uses the following data types:
uint16 / int16 (1 register)
A single 16-bit value occupying one register. Unsigned values (uint16) range from 0 to 65535. Signed values (int16) range from -32768 to 32767.
uint32 (2 registers)
A 32-bit unsigned integer spanning two consecutive registers. The low word is in the first (even-addressed) register, and the high word is in the second (odd-addressed) register.
Example: Value 0x00000003 would be transmitted as pair of registers 0x0003 and 0x0000.
float32 (2 registers)
A 32-bit IEEE 754 floating-point value spanning two consecutive registers. The encoding uses little-endian word order (low word first, high word second), which is common in industrial Modbus devices.
Example: The Value 1.5 is encoded per IEEE 754 as 0x3FC00000, and would be transmitted as pair of registers 0x0000 and 0x3FC0.A fixed endianness check register at address 62 (register 63) always returns the value-123.265625. Read this register to verify your decoder handles the byte/word order correctly. The expected raw register values are 0x8800, 0xC2F6.string[n] (n/2 registers)
The string data type allows a Modbus master to read and write textual data on the device. The parameter n (must be an even number) defines the maximum number of characters the string can contain. If the stored text is shorter than n characters, the remaining positions are filled with null characters (ASCII value 0). Each Modbus register is 16 bits (2 bytes) long and stores two characters, so the total number of registers used is n/2. Characters are stored in big-endian order within each register, so that the high byte contains the first (preceding) character and the low byte contains the second character.
Reading Registers with modpoll#
modpoll is a command-line utility for reading and writing Modbus registers from a PC.
To read the first four float32 measurement values (discharge, water level, surface velocity, flow direction) from the FX200, run modpoll with the following arguments:
modpoll -m rtu -a 1 -r 1 -c 8 -t 4:float -e -b 9600 -d 8 -p even -s 1 COM3
To read a single uint16 configuration register (e.g., measurement unit at register 130):
modpoll -m rtu -a 1 -r 130 -c 1 -t 4 -b 9600 -d 8 -p even -s 1 COM3
These examples were tested with modpoll version 3.10.
Flag |
Meaning |
|---|---|
|
Slave address 1 |
|
Start at register number 1 (address 0x00) |
|
Read 8 registers (= 4 float32 values) |
|
Interpret as 32-bit float |
|
Little-endian word order (low word first) |
|
Baud rate 9600 |
|
8 data bits |
|
Even parity |
|
1 stop bit |
|
Serial port (use /dev/ttyUSB0 on Linux) |
Registers Overview#
The register address space is divided into three distinct blocks:
Block 1 provides high-resolution measurement data as 32-bit floats. This is the recommended block for modern data loggers and SCADA systems.
Block 2 provides the same measurement data encoded as 16-bit integers for compatibility with legacy data loggers that cannot decode float32 values. Values are scaled as documented in the register table.
Block 3 contains all user-configurable device parameters. Some addresses are reserved, and will read as zero and must not be written.
Block |
Address Range |
Register Range |
Purpose |
|---|---|---|---|
1 |
0 - 63
0x00 - 0x3F
|
1 - 64
0x01 - 0x40
|
Measurement data (float32, read-only) |
2 |
64 - 127
0x40 - 0x7F
|
65 - 128
0x41 - 0x80
|
Measurement data (int16 compatibility, read-only) |
3 |
128 - 194
0x80 - 0xC2
|
129 - 195
0x81 - 0xC3
|
Configuration parameters (read/write) |
Block 1: Measurement Registers (float32)#
Address(es) |
Reg # |
Data Type |
Description |
|---|---|---|---|
0, 1
0x00, 0x01
|
1, 2
0x01, 0x02
|
float32 |
Flow discharge in the selected measurement unit. The unit is set by configuration register |
2, 3
0x02, 0x03
|
3, 4
0x03, 0x04
|
float32 |
Water level in the selected level measurement unit. The unit is set by configuration register |
4, 5
0x04, 0x05
|
5, 6
0x05, 0x06
|
float32 |
Absolute surface velocity in the selected measurement unit (always >= 0). The unit is set by configuration register |
6, 7
0x06, 0x07
|
7, 8
0x07, 0x08
|
float32 |
Flow direction:
+1.0 = incoming (towards sensor),-1.0 = outgoing (away from sensor),0.0 = no flow detected. |
8, 9
0x08, 0x09
|
9, 10
0x09, 0x0A
|
float32 |
Discharge in m³/s. Not unit-converted, always in m³/s. |
10, 11
0x0A, 0x0B
|
11, 12
0x0B, 0x0C
|
float32 |
Water level in m. Not unit-converted, always in m. |
12, 13
0x0C, 0x0D
|
13, 14
0x0D, 0x0E
|
float32 |
Surface velocity in m/s. The value can be positive or negative. Positive values – incoming flow, negative values – outgoing flow. Not unit-converted, always in m/s. |
14, 15
0x0E, 0x0F
|
15, 16
0x0F, 0x10
|
float32 |
Signal-to-noise ratio (SNR) of the surface velocity radar, in dB. Higher values indicate stronger signal quality. |
16, 17
0x10, 0x11
|
17, 18
0x11, 0x12
|
float32 |
Signal-to-noise ratio (SNR) of the water level radar in dB. Higher values indicate stronger signal quality. |
18, 19
0x12, 0x13
|
19, 20
0x13, 0x14
|
float32 |
Forward tilt angle of the sensor in degrees. Ideally the sensor should be mounted at 0°. |
20, 21
0x14, 0x15
|
21, 22
0x15, 0x16
|
float32 |
Side tilt angle of the sensor in degrees. This angle should be very close to 0°. |
22, 23
0x16, 0x17
|
23, 24
0x17, 0x18
|
float32 |
Device temperature in units of degrees Celsius. |
24, 25
0x18, 0x19
|
25, 26
0x19, 0x1A
|
float32 |
Relative humidity inside the device enclosure in percent (%). |
26, 27
0x1A, 0x1B
|
27, 28
0x1B, 0x1C
|
uint32 |
Status flags (0 = all OK), as error bitmask:
Bit 0: forward tilt angle error (tilt angle outside of expected range).
Bit 1: side tilt angle error (tilt is too big).
Bit 2: radar co-processor update failed.
Bit 3: surface velocity radar co-processor not responding.
Bit 4: radar co-processor needs to be updated.
Bit 5: tilt sensor is not working.
Bit 6: water level radar co-processor not responding.
Bit 7: DFP data corrupt, needs reset.
Bit 8: standby mode is active.
Bit 9: radar is initializing.
|
28
0x1C
|
29
0x1D
|
uint16 |
Firmware upgrade status:
0 = idle,1 = pending,2 = active,3 = done,4 = failed. |
29, 30
0x1D, 0x1E
|
30, 31
0x1E, 0x1F
|
float32 |
Total accumulated volume, in selected units. |
31, 32
0x1F, 0x20
|
32, 33
0x20, 0x21
|
float32 |
Total accumulated volume, in m³. |
33, 34
0x21, 0x22
|
34, 35
0x22, 0x23
|
float32 |
Distance to water, in the selected level measurement unit. The unit is set by configuration register |
35, 36
0x23, 0x24
|
36, 37
0x24, 0x25
|
float32 |
Distance to water in mm. Not unit-converted, always in mm. |
37 - 61
0x25-0x3D
|
38 - 62
0x26-0x3E
|
Reserved. Reads as 0. |
|
62, 63
0x3E, 0x3F
|
63, 64
0x3F, 0x40
|
float32 |
Endianness check: This register will always return constant |
Block 2: Measurement Registers (int16)#
These registers provide the same measurement data as Block 1, but encoded as 16-bit integers for compatibility with data loggers that do not support float32 decoding. All values are read-only.
Address(es) |
Reg # |
Data Type |
Description |
|---|---|---|---|
64
0x40
|
65
0x41
|
uint16 |
Discharge, integer part, in selected measurement unit. Example: if discharge is 12.3456, this register returns 12. |
65
0x41
|
66
0x42
|
uint16 |
Discharge, fractional part (4 decimal digits), in selected measurement units. Example: if discharge is 12.3456 this register returns 3456; if discharge is 12.34 then the register returns 3400. |
66
0x42
|
67
0x43
|
int16 |
Water level, integer part, in selected measurement unit. Example: if the level is 12.3456, this register returns 12. |
67
0x43
|
68
0x44
|
int16 |
Water level, fractional part (4 decimal digits), in selected measurement units. Example: if the level is 12.3456 this register returns 3456; if the level is 12.34 then the register returns 3400. |
68
0x44
|
69
0x45
|
uint16 |
Absolute surface velocity, integer part, in selected measurement unit. Example: if velocity is 12.3456, this register returns 12. |
69
0x45
|
70
0x46
|
int16 |
Absolute surface velocity, fractional part (4 decimal digits), in selected measurement units. Example: if velocity is 12.3456 this register returns 3456; if velocity is 12.34 then the register returns 3400. |
70
0x46
|
71
0x47
|
int16 |
Flow direction:
+1 = incoming,-1 = outgoing,0 = no flow. |
71
0x47
|
72
0x48
|
int16 |
Surface velocity in mm/s, integer part only. Positive values – incoming flow, negative values – outgoing flow. Clamped to range -16000 .. +16000 mm/s. |
72
0x48
|
73
0x49
|
int16 |
Distance to water in mm, integer part only. |
73
0x49
|
74
0x4A
|
int16 |
Signal-to-noise ratio (SNR), of the returned radar signal of the surface velocity radar in dB integer part only. Higher values indicate stronger signal quality. |
74
0x4A
|
75
0x4B
|
int16 |
Signal-to-noise ratio (SNR), of the returned radar signal of the water level radar in dB integer part only. Higher values indicate stronger signal quality. |
75
0x4B
|
76
0x4C
|
int16 |
Forward tilt angle of the sensor in degrees. Ideally the sensor should be mounted at 0°. |
76
0x4C
|
77
0x4D
|
int16 |
Side tilt angle of the sensor in degrees. This angle should be very close to 0°. |
77
0x4D
|
78
0x4E
|
int16 |
Device temperature in degrees Celsius × 100. Example: if temperature is 23.456 °C then this register holds 2345. |
78
0x4E
|
79
0x4F
|
int16 |
Relative humidity inside the device enclosure in percent (%) × 100. Example: if relative humidity is 24.17 % then this register holds 2417. |
79, 80
0x4F, 0x50
|
80, 81
0x50, 0x51
|
uint32 |
Status flags (same encoding as Block 1). |
81
0x51
|
82
0x52
|
uint16 |
Firmware upgrade status (same as Block 1 register |
Block 3: Configuration Registers (R/W)#
Addr. |
Reg # |
Data Type |
Description |
|---|---|---|---|
128
0x80
|
129
0x81
|
uint16 |
Current Firmware Version Number. Read-only. Example: if the firmware version is 1.2.3 this register holds |
129
0x81
|
130
0x82
|
uint16 |
Velocity Measurement Unit. Affects values in registers
0x04, 0x05, 0x44, 0x45. See Surface Velocity Output Unit for the detailed setting description.Available values:
0 = mm/s,1 = m/s,2 = mph,3 = km/h,4 = ft/s,5 = ft/min,6 = cm/s.Default value:
1 (m/s) |
130
0x82
|
131
0x83
|
uint16 |
Water Level Measurement Unit. Affects values in registers
0x02, 0x03, 0x21, 0x22, 0x42, 0x43. See Water Level Output Unit for the detailed setting description.Available values:
0 = mm,1 = cm,2 = m,3 = ft,4 = in.Default value:
2 (m) |
131
0x83
|
132
0x84
|
uint16 |
Discharge Measurement Unit. Affects values in registers
0x00, 0x01, 0x40, 0x41. See Discharge Output Unit for the detailed setting description.Available values:
0 = m³/s,1 = m³/h,2 = l/s,3 = l/h,4 = cfs (ft³/s),5 = gal/min,6 = ML/day (megaliters per day).Default value:
0 (m³/s) |
132
0x84
|
133
0x85
|
uint16 |
Velocity Filter Length. In seconds. Longer values produce smoother but slower-responding readings. See Filter Length for the detailed setting description. Valid range: |
133
0x85
|
134
0x86
|
uint16 |
Direction Filter. See Direction Filter for the detailed setting description.
Available values:
0 = measure both directions,1 = incoming flow only,2 = outgoing flow only.Default value:
0 (both) |
134
0x86
|
135
0x87
|
uint16 |
Radar Beam Width Configuration. See Beam Width for the detailed setting description.
Available values:
0 = wide,1 = narrow.Default value:
0 (wide) |
135
0x87
|
136
0x88
|
uint16 |
Dynamic Flow Profiler (DFP). Enable DFP calculation. See Dynamic Flow Profiler for the detailed setting description.
Available values:
0 = disabled,1 = enabled.Default value:
0 (off) |
136, 137
0x88, 0x89
|
137, 138
0x89, 0x8A
|
float32 |
Maximum Velocity Threshold. Velocity readings above this threshold are rejected. See Maximum Velocity for the detailed setting description. Default value: |
138
0x8A
|
139
0x8B
|
uint16 |
Maximum Velocity Unit. See Maximum Velocity for the detailed setting description.
Available values:
0 = mm/s,1 = m/s,2 = mph,3 = km/h,4 = ft/s,5 = ft/min,6 = cm/s.Default value:
1 (m/s) |
139
0x8B
|
140
0x8C
|
uint16 |
Radar Beam Offset Configuration. See Beam Position for the detailed setting description.
Available values:
0 = near,1 = far.Default value:
0 (near) |
140, 141
0x8C, 0x8D
|
141, 142
0x8D, 0x8E
|
float32 |
Minimum Valid Distance. Reflections detected closer than this distance are ignored. Use this parameter to exclude near-field returns e.g., mounting hardware, bridge elements, or other objects close to the sensor. See Minimum Distance for the detailed setting description. Default value: |
142
0x8E
|
143
0x8F
|
uint16 |
Minimum Valid Distance Unit. See Minimum Distance for the detailed setting description.
Available values:
0 = mm,1 = cm,2 = m,3 = ft,4 = in.Default value:
2 (m) |
143
0x8F
|
144
0x90
|
uint16 |
Radar Receiver Mode. Selects how the signals from the radar receive antennas are combined.
Available values:
0 = coherent,1 = sum.Default value:
0 (coherent) |
144, 145
0x90, 0x91
|
145, 146
0x91, 0x92
|
float32 |
Maximum Valid Distance. Reflections detected beyond this distance are ignored. Use this parameter to exclude far-field returns outside the intended measurement zone e.g., opposite bank, structures, or background targets. See Maximum Distance for the detailed setting description. Default value: |
146
0x92
|
147
0x93
|
uint16 |
Maximum Valid Distance Unit. See Maximum Distance for the detailed setting description.
Available values:
0 = mm,1 = cm,2 = m,3 = ft,4 = in.Default value:
2 (m) |
147
0x93
|
148
0x94
|
uint16 |
SNR Threshold in dB. Reflections with a signal-to-noise ratio below this threshold are ignored. See SNR Threshold for the detailed setting description. Valid range: |
148
0x94
|
149
0x95
|
uint16 |
RS-232 Serial Port Baud Rate. See RS-232 Baud Rate for the detailed setting description.
Available values:
0 = 4800,1 = 9600,2 = 14400,3 = 19200,4 = 38400,5 = 57600,6 = 115200.Default value:
6 (115200) |
149
0x95
|
150
0x96
|
uint16 |
Modbus RS-485 Baud Rate. Caution: changing this will require the master to reconnect at the new baud rate. See RS-485 Baud Rate for the detailed setting description.
Available values:
0 = 4800,1 = 9600,2 = 14400,3 = 19200,4 = 38400,5 = 57600,6 = 115200.Default value:
1 (9600) |
150
0x96
|
151
0x97
|
uint16 |
Modbus Parity Setting. See Modbus Parity for the detailed setting description.
Available values:
0 = none,1 = odd,2 = even.Default value:
2 (even) |
151
0x97
|
152
0x98
|
uint16 |
Modbus Stop Bits. See RS-485 Stop Bits for the detailed setting description.
Available values:
0 = one stop bit,1 = two stop bits.Default value:
0 (one) |
152
0x98
|
153
0x99
|
uint16 |
Modbus Slave Address. Must be unique on the RS-485 bus. See Modbus ID for the detailed setting description. Valid range: |
153
0x99
|
154
0x9A
|
uint16 |
SDI-12 Sensor Address. The value is stored as a numeric integer (0 - 61) in the register. SDI-12 addressing is character-based, so the numeric value from the register is converted to the corresponding SDI-12 address character according to the following mapping: |
154
0x9A
|
155
0x9B
|
uint16 |
Power Management Mode. Gets / sets the automatic SDI-12 sleep mode parameter. When SDI-12 sleep is enabled, the device enters low-power sleep between SDI-12 transactions. When disabled, the sensor runs continuously and is ready at all times. See Power Management for the detailed setting description.
Available values:
0 = sleep enabled (low power),1 = sleep disabled (continuous scanning).Default value:
1 (off) |
155
0x9B
|
156
0x9C
|
uint16 |
Analog output selection. Gets / sets the type of the measurement that will be sent to the analog output. See 4 - 20 mA Output Type for the detailed setting description.
Available values:
0 = none,1 = discharge,2 = water level,3 = distance to water,4 = surface velocity,5 = total volume.Default value:
1 (discharge)Note: writing this register resets both the minimum value unit (register
0x9E) and the maximum value unit (register 0xA2) to 0, because the set of valid units depends on the selected output type. When writing several of these registers in a single request, write this register first. |
156, 157
0x9C, 0x9D
|
157, 158
0x9D, 0x9E
|
float32 |
Minimum Value for Analog Output. Measurements at or below this value produce minimum current. See 4 - 20 mA Minimum Value for the detailed setting description. Default value: |
158
0x9E
|
159
0x9F
|
uint16 |
Minimum Value Unit for Analog Output. See 4 - 20 mA Minimum Value for the detailed setting description.
Available values, if analog output is set to discharge:
0 = m³/s,1 = m³/h,2 = l/s,3 = l/h,4 = cfs (ft³/s),5 = gal/min,6 = ML/day.Available values, if analog output is set to water level or distance to water:
0 = mm,1 = cm,2 = m,3 = ft,4 = in.Available values, if analog output is set to surface velocity:
0 = mm/s,1 = m/s,2 = mph,3 = km/h,4 = ft/s,5 = ft/min,6 = cm/s.Available values, if analog output is set to total volume:
0 = m³,1 = l,2 = ft³,3 = gal,4 = ML.Default value:
1 (with the factory default analog output of discharge, this is m³/h) |
159
0x9F
|
160
0xA0
|
Reserved. |
|
160, 161
0xA0, 0xA1
|
161, 162
0xA1, 0xA2
|
float32 |
Maximum Value for Analog Output. Measurements at or above this value produce maximum current. See 4 - 20 mA Maximum Value for the detailed setting description. Default value: |
162
0xA2
|
163
0xA3
|
uint16 |
Maximum Value Unit for Analog Output. See 4 - 20 mA Maximum Value for the detailed setting description.
Available values, if analog output is set to discharge:
0 = m³/s,1 = m³/h,2 = l/s,3 = l/h,4 = cfs (ft³/s),5 = gal/min,6 = ML/day.Available values, if analog output is set to water level or distance to water:
0 = mm,1 = cm,2 = m,3 = ft,4 = in.Available values, if analog output is set to surface velocity:
0 = mm/s,1 = m/s,2 = mph,3 = km/h,4 = ft/s,5 = ft/min,6 = cm/s.Available values, if analog output is set to total volume:
0 = m³,1 = l,2 = ft³,3 = gal,4 = ML.Default value:
1 (with the factory default analog output of discharge, this is m³/h) |
163
0xA3
|
164
0xA4
|
uint16 |
Total Volume Measurement Unit. Selects the unit used for the total accumulated volume reported in registers
0x1D, 0x1E.Available values:
0 = m³,1 = l,2 = ft³,3 = gal,4 = ML (megaliters).Default value:
0 (m³) |
164
0xA4
|
165
0xA5
|
uint16 |
Operation Mode. When the operation mode is set to standby, the device will communicate on all communication interfaces, but the radar transmitters will be turned off to reduce the power, and the instrument will not be making any measurements. See Operation Mode for the detailed setting description.
Available values:
0 = off (normal operation),1 = on (standby operation).Default value:
0 (off) |
165
0xA5
|
166
0xA6
|
uint16 |
Disable NMEA Sentence Output. On RS-232.
Available values:
0 = NMEA enabled,1 = NMEA disabled.Default value:
0 (off) |
166
0xA6
|
167
0xA7
|
uint16 |
Show Data on Error. Continue reporting velocity data even when angle errors are detected.
Available values:
0 = clear velocity on error (send 0 as velocity instead of measured value),1 = keep velocity.Default value:
0 (off) |
167
0xA7
|
168
0xA8
|
uint16 |
Fixed Mounting Angle. Override measured forward angle with a fixed value (degrees). Changing this parameter will affect the reported velocity as cosine correction behaviour will be changed. Use this parameter only during tow-tank testing. |
168, 169
0xA8, 0xA9
|
169, 170
0xA9, 0xAA
|
float32 |
Radar Vertical Position. Defines the vertical position of the radar sensor above the hydrological zero (gauge zero). This value is used to calculate the water level from the measured distance to the water surface and to determine the sensor position within the channel cross-section for discharge calculations. The unit is set by configuration register |
170
0xAA
|
171
0xAB
|
Reserved. |
|
171
0xAB
|
172
0xAC
|
uint16 |
Unit for radar vertical position.
0 = mm,1 = cm,2 = m,3 = ft,4 = in.Default value:
2 (m) |
172
0xAC
|
173
0xAD
|
uint16 |
Enable Wi-Fi.
0 = off,1 = on.Default value:
0 (off) |
173 - 180
0xAD - 0xB4
|
174 - 181
0xAE - 0xB5
|
string[16] |
Wi-Fi password, up to 16 characters. Shorter passwords are padded with null characters. |
181
0xB5
|
182
0xB6
|
Reserved. Reads as 0, writes are ignored. |
|
182
0xB6
|
183
0xB7
|
uint16 |
LiDAR Mode. Enables automatic scanning of the channel cross-section with the built-in LiDAR sensor. See LiDAR Mode for the detailed setting description.
Available values:
0 = off (automatic scanning disabled),1 = automatic.Default value:
0 (off) |
183
0xB7
|
184
0xB8
|
uint16 |
LiDAR Left Limit Angle. Defines how far the LiDAR sweep extends towards the left side of the channel, in degrees measured from the vertical axis of the instrument. See Left Limit Angle for the detailed setting description. Valid range: |
184
0xB8
|
185
0xB9
|
uint16 |
LiDAR Right Limit Angle. Defines how far the LiDAR sweep extends towards the right side of the channel, in degrees measured from the vertical axis of the instrument. See Right Limit Angle for the detailed setting description. Valid range: |
185
0xB9
|
186
0xBA
|
uint16 |
Command register. Write value |
186
0xBA
|
187
0xBB
|
uint16 |
Command register. Write value |
187
0xBB
|
188
0xBC
|
uint16 |
Command register. Write value |
188
0xBC
|
189
0xBD
|
Reserved. |
|
189, 190
0xBD, 0xBE
|
190, 191
0xBE, 0xBF
|
float |
Command register. Write the value of the current staff gauge reading to automatically adjust the vertical position of the radar. The units of the measurement are defined in register 171 (172). Writing to this register will automatically adjust the value in register 168-169 (169-170). |
191
0xBF
|
192
0xC0
|
uint16 |
Command register. Write value |
192, 193
0xC0, 0xC1
|
193, 194
0xC1, 0xC2
|
Reserved. |
|
194
0xC2
|
195
0xC3
|
uint16 |
Command register. Write value |
Reading LiDAR Scan#
The channel cross-section produced by the most recent manual LiDAR scan (the same data shown on the LiDAR page of the configurator, see 6.3. Using LiDAR) can be read out over Modbus.
The scan is a relatively large data set (up to 1000 measured points) and does not fit into the holding-register map. It is therefore transferred using a set of custom Modbus function codes that move an arbitrary-length byte stream (a “file”) between the master and the device, rather than through registers. The same mechanism is used for firmware upgrades and echo-curve download.
The LiDAR scan is exposed as file number 40.
Custom Function Codes#
Three custom function codes are used. In all of them the first byte of the frame is the slave address and the last two bytes are the standard Modbus RTU CRC (CRC-16/MODBUS, low byte first). All multi-byte fields inside the frames are big-endian.
Code |
Meaning |
|---|---|
|
File operation: open for read, open for write, or close. |
|
Read a block of bytes from the currently open file. |
|
Write a block of bytes to the currently open file (not used when reading a scan). |
File operation (0x46) – request:
Field |
Size |
Value |
|---|---|---|
Address |
1 |
Slave address |
Function |
1 |
|
Operation |
1 |
|
File number |
1 |
|
File size |
4 |
Ignored when opening the LiDAR scan for read; send |
CRC |
2 |
Modbus RTU CRC |
File operation (0x46) – response to open read: the device prepares the scan and returns its total size in bytes:
Field |
Size |
Value |
|---|---|---|
Address |
1 |
Slave address |
Function |
1 |
|
File size |
4 |
Total size of the scan payload, in bytes |
CRC |
2 |
Modbus RTU CRC |
File read (0x47) – request:
Field |
Size |
Value |
|---|---|---|
Address |
1 |
Slave address |
Function |
1 |
|
Start address |
4 |
Byte offset within the file to start reading from |
Byte count |
2 |
Number of bytes to read (keep within a single Modbus frame, e.g. 240) |
CRC |
2 |
Modbus RTU CRC |
File read (0x47) – response:
Field |
Size |
Value |
|---|---|---|
Address |
1 |
Slave address |
Function |
1 |
|
Byte count |
2 |
Number of data bytes that follow |
Data |
n |
The requested block of file bytes |
CRC |
2 |
Modbus RTU CRC |
If the requested offset is at or beyond the end of the file, the device returns a
byte count of 0. This marks the end of the transfer.
An exception response (function code with the high bit set, e.g. 0xC6) with
exception code 6 (busy) is returned if the device cannot prepare the scan at
that moment; retry after a short delay.
Transfer sequence#
To read the scan:
Send an open read request for file 40 and read the returned file size.
Repeatedly send read requests, advancing the start address by the number of bytes returned each time, until the returned byte count is
0or the accumulated length reaches the file size.Send a close request (operation
0xFF) to release the buffer on the device.
Example frames for slave address 1:
open read : 01 46 01 28 00 00 00 00 C7 D4
read : 01 47 00 00 00 00 00 F0 B6 87 (240 bytes from offset 0)
close : 01 46 FF 28 00 00 00 00 D2 0A
Payload format#
The bytes returned for file 40 are the scanned profile serialized as a CBOR (RFC 8949) map and then compressed with Zstandard (zstd). Unlike the servicing protocol, the Modbus transfer carries the raw compressed bytes directly: there is no byte-stuffing and no extra application CRC, since the Modbus frame CRC already protects each block.
After concatenating all the blocks, decompress the payload with zstd and parse the resulting CBOR map. The relevant keys are:
Key |
Type |
Description |
|---|---|---|
|
text string |
Unit of the coordinates. Always |
|
array of float32 |
X coordinates of the profile points, left to right. |
|
array of float32 |
Y coordinates of the profile points. Values are negative, measured downward from the sensor. |
The x and y arrays have the same length and together define the
cross-section points.
Python example#
The following example reads the scan over an RS-485 adapter and saves the profile
as a CSV file with x and y columns in millimeters. Because the transfer
uses custom function codes that standard Modbus libraries do not implement, the
frames are built directly on top of a serial port. It requires the pyserial,
zstandard, and cbor2 packages
(pip install pyserial zstandard cbor2).
import csv
import struct
import time
import serial
import zstandard
import cbor2
PORT = "COM3" # RS-485 adapter port (e.g. "/dev/ttyUSB0")
BAUD = 9600
SLAVE = 1
LIDAR_FILE = 40
CHUNK = 240 # bytes per read request
OUTPUT_CSV = "lidar_profile.csv"
def crc16_modbus(frame: bytes) -> int:
crc = 0xFFFF
for byte in frame:
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc
def send(port: serial.Serial, pdu: bytes) -> bytes:
frame = bytes([SLAVE]) + pdu
frame += struct.pack("<H", crc16_modbus(frame)) # CRC low byte first
port.write(frame)
time.sleep(0.05)
resp = port.read(512)
if len(resp) < 4 or crc16_modbus(resp[:-2]) != struct.unpack("<H", resp[-2:])[0]:
raise IOError("bad or missing response: " + resp.hex(" "))
if resp[1] & 0x80:
raise IOError(f"Modbus exception {resp[2]}")
return resp
with serial.Serial(PORT, BAUD, parity=serial.PARITY_EVEN, timeout=1) as port:
# 1. Open file 40 for read; response carries the total size (4 bytes).
resp = send(port, bytes([0x46, 0x01, LIDAR_FILE]) + struct.pack(">I", 0))
file_size = struct.unpack(">I", resp[2:6])[0]
# 2. Read the file in chunks until the whole payload is received.
payload = bytearray()
while len(payload) < file_size:
pdu = bytes([0x47]) + struct.pack(">I", len(payload)) + struct.pack(">H", CHUNK)
resp = send(port, pdu)
count = struct.unpack(">H", resp[2:4])[0]
if count == 0:
break
payload += resp[4:4 + count]
# 3. Close the file to release the device buffer.
send(port, bytes([0x46, 0xFF, LIDAR_FILE]) + struct.pack(">I", 0))
cbor_bytes = zstandard.ZstdDecompressor().decompress(bytes(payload))
profile = cbor2.loads(cbor_bytes)
with open(OUTPUT_CSV, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["x_mm", "y_mm"])
for x, y in zip(profile["x"], profile["y"]):
writer.writerow([x, y])
print(f"Saved {len(profile['x'])} points to {OUTPUT_CSV}")