Skip to content

Modbus-RTU protocol

The frames the driver sends, the registers behind each call, and how values are scaled, from the operation manual's register tables.

Function codes

The BASIS-2 implements three Modbus functions, and the driver uses all three.

Code Name Driver Reply
0x03 Read holding registers ReadHoldingRegisters(), every read call Address, 0x03, byte count, data, CRC
0x06 Write single register WriteSingleRegister(), every 16-bit write An echo of the request
0x10 Write multiple registers WriteMultipleRegisters(), the 32-bit writes Address, 0x10, start register, count, CRC

A rejected request gets an exception reply: the function code with bit 7 set (0x83, 0x86, 0x90), an exception code and the CRC, 5 bytes in all (Modbus Application Protocol V1.1b3, section 7). The driver reads the first 3 bytes of every reply; when bit 7 is set it reads the 2 CRC bytes, checks the CRC and returns ModbusException without waiting for the timeout. GetLastException() returns the exception code:

Code ModbusExceptionCode Typical cause on a BASIS-2
0x01 IllegalFunction A function other than 03, 06, 16
0x02 IllegalDataAddress The manual's "Bad Address": a register this model or firmware does not have
0x03 IllegalDataValue A value or register count the instrument does not accept
0x04 ServerDeviceFailure An error inside the instrument

The write-single echo carries the value the host sent, not the value the instrument stored: an out-of-range setpoint is adjusted inside the instrument but echoed unchanged. Read the register back when you need the stored value. Reading an undefined register may return 0 or an exception.

Frame format

A Modbus-RTU read-holding request and reply, byte by byte: slave address, function code, register address and
count, then the CRC-16 low byte first

Field Size (bytes) Notes
Address 1 1 to 247; 0 is broadcast (writes only, no reply)
Function 1 0x03, 0x06, 0x10; bit 7 set in an exception reply
Data 1 to 69 Big-endian register addresses, counts and values
CRC-16 2 Polynomial 0xA001 (reflected 0x8005), initial value 0xFFFF, low byte first

The driver sends the data addresses of the manual's tables unchanged: register 2100 is 0x0834 in the request. The driver reads at most 32 registers and writes at most 32 in one request (its buffer sizes).

Instantaneous data (2053 to 2109)

Register Contents Scaling Field
2053-2054 (R/W) Commanded setpoint, signed 32-bit; takes effect when 2054 is written value / 1000 SetSetpoint()
2100 (R/W) Selected gas number Gas gas, SetGas()
2101 (R) Status: +1 MOV, +2 TOV, +4 OVR, +8 HLD, +16 VTM bits *_overrange, valve_held, valve_thermal_management
2102 (R) Temperature, signed value / 100 = °C temperature_c
2103 (R) Flow, signed 16-bit factory-set decimals flow
2104-2105 (R) Total volume since power-up or reset factory-set decimals total_volume
2106 (R) Current setpoint (may lag the command while ramping) setpoint
2107 (R) Valve drive value / 100 = % valve_drive_pct
2108-2109 (R) Batch volume remaining as 2104-2105 batch_remaining

ReadInstantaneous(decimals) reads 2100 to 2109 in one request and divides flow, total, setpoint and batch by 10decimals. The manual says these registers carry a "factory-set number of decimal places" but names no register that reports it; take it from the instrument's calibration sheet or compare with the ASCII data frame. The setpoint write is always value / 1000, whatever the decimals.

Measurement window (4200 to 4214)

Register Contents
4200 (R/W) Trigger (controllers): +1 on a digital setpoint change, +2 on a valve-hold change, +4 when the low register of the previous average flow (4215) is read
4201 (R/W) Samples to take, about 2.5 ms each; a write starts a new window
4202, 4203 (R) Minimum and maximum temperature, value / 100 = °C
4204, 4205 (R) Minimum and maximum flow, signed 16-bit, factory-set decimals
4206 (R) Samples averaged so far
4207, 4208 (R) Average temperature and average flow
4209 (R) Valve drive, value / 100 = %
4210 to 4213 (R) Previous window: minimum and maximum flow, samples, average temperature
4214-4215 (R) Previous window's average flow, signed 32-bit, high register first

StartMeasurementSamples() writes 4201, ConfigureMeasurementTrigger() writes 4200 and ReadMeasurement() reads 4201 to 4215 in one request. The manual gives the previous average flow as a 4-byte integer at 4214, so it fills 4214 and 4215 like every other 32-bit value; prev_avg_flow_raw is decoded from both. num_samples_collected is 4206 (samples averaged so far), num_samples_requested is 4201.

Configuration and function registers

Register Contents Driver
21 (R/W) Baud rate code: 0 = 4800, 1 = 9600, 2 = 19200, 3 = 38400, 4 = 57600, 5 = 115200 SetBaudRate()
25 (R) Firmware version: Va.b.c = 256a + 16b + c ReadIdentity()
26-31 (R) Serial number, two characters per register ReadIdentity()
35-36 (R) Full scale in SCCM, unsigned 32-bit reg::FullScaleFlowSccm
39 (W) Tare: write 0xAA55 Tare()
45 (R/W) Modbus address, 1 to 247 SetModbusAddress()
46 (R/W) ASCII unit ID, A to Z SetAsciiUnitId()
47-48 (R) Full scale in the flow units, value / 1000 ReadIdentity()
49 (R) Flow units code, 0 = SCCM to 19 = SCFD FlowUnits
51 (R/W) Samples per tare, 1 to 65535 reg::TareSampleCount
52 (R/W) Reference temperature, 0 to 3000 = 0 °C to 30 °C SetReferenceTemperatureC()
53 (W) Reset the totalizer (and the batch): write 0xAA55 ResetTotalizer()
54 (R/W) Totalizer limit mode, 0 to 3 SetTotalizerLimitMode()
55 (R/W) Flow averaging time constant, 0 to 2500 ms SetFlowAveragingMs()
56 (R/W) Command protocol: 0 = BASIS 2, 1 = BASIS 1 reg::CommandProtocol
80 (W) Factory restore: write 0x5214 FactoryRestore()
81-140 (R) Gas table: 12 entries of number, k factor (4096 = 1.0), label reg::GasInfoTableStart

Control registers (controllers only)

Register Contents Driver
514 (R/W) Communication watchdog, 0 to 5000 ms, 0 = off SetCommWatchdogMs()
515 (R/W) Autotare: 1 on, 0 off SetAutotareEnabled()
516 (R/W) Setpoint source: 0 analog, 1 digital saved, 2 digital unsaved SetSetpointSource()
519, 520 (R/W) P and I gain of the PDF loop, 0 to 65535 reg::PgainPdf, reg::IgainPdf
521-522 (R/W) Batch volume, in the total-volume units and scaling SetTotalizerBatch()
524-525 (R/W) Maximum setpoint ramp, % of full scale per ms × 107; 0 = no ramp SetMaxSetpointRamp()

Several registers were added in later firmware (2053 in V2.1.0, address and unit ID in V2.4.0, most data registers in V3.0.0, the gas table in V3.0.5). Read register 25 first when an older instrument answers with an exception.

Magic values

Value Register Effect
0xAA55 (43605) 39 Tare; interrupts a running measurement
0xAA55 (43605) 53 Totalizer and batch reset
0x5214 (21012) 80 Factory restore; power-cycle afterwards

The ASCII protocol

The instrument also accepts Alicat's ASCII commands (A for the data frame, AS 15.44 for a setpoint), addressed by the unit ID. The driver does not implement them; the manual lists them. Switching register 56 to BASIS 1 changes the command set and works only with SCCM, SLPM, NCCM and NLPM.