Troubleshooting PHP Modbus RTU Responses over RS485

Daniel Price6 min read
ModbusOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

PHP is the Modbus RTU master in this path: it writes a binary request to COM3, the serial adapter converts that stream to RS485 signaling, slave processes the request, and the reply returns through the same hops. In the shown program, the path stops at the first hop because fputs($comport, $atcmd); is commented out. After enabling the write, the receive logic must also treat the reply as a length-delimited binary frame rather than a newline-delimited string.

What bytes must PHP transmit?

The request contains eight binary bytes. It is not an AT command or printable text, so no line ending belongs after it.

Bytes Meaning Value
1 Slave address
2 Function , read input registers
3-4 Starting address
5-6 Quantity , or 73 registers
7-8 Supplied CRC bytes

Remove the comment marker and check the number of bytes written. A partial write must be completed before changing to receive mode.

$request = "\x0A\x04\x00\x00\x00\x49\x30\x87";
$offset = 0;
while ($offset < strlen($request)) {
    $written = fputs($comport, substr($request, $offset));
    if ($written === false || $written === 0) {
        throw new RuntimeException('Serial write failed');
    }
    $offset += $written;
}
fflush($comport);

Before proceeding, record the write count and verify that it reaches eight. If the adapter exposes transmit and receive indicators, the transmit indicator must activate when this block runs.

How should COM3 be configured?

Layer one first. Both ends must use the same baud rate, parity, data bits, and stop bits. The supplied command requests the following format:

Setting PHP host Device requirement Check
Port COM3 Connected adapter port Confirm in the operating system
Baud 9600 Must match Read the device communication setup
Parity n Must match Compare both endpoints
Data bits 8 Must match Compare both endpoints
Stop bits 2 Must match Compare both endpoints

The program does not inspect the return status from exec("mode $device BAUD=9600 PARITY=n DATA=8 STOP=2"). Capture the command result and stop if configuration fails. Opening the handle successfully proves only that PHP obtained a port handle; it does not prove that the serial format changed or that the RS485 bus is electrically usable.

Check polarity, common reference, termination, biasing, and contention. A known-good Modbus application proves that the device and some communication path work, but PHP must use the same physical adapter, port, wiring, and serial format for the comparison to isolate software. Proceed only after the host configuration matches the working setup field for field.

Where can RS485 direction stop the reply?

Two-wire RS485 is normally half duplex. The adapter must drive the bus while the eight request bytes leave, release the transmitter after the final stop bit, and enable reception before the slave replies. USB-to-RS485 adapters commonly manage this direction automatically; other interfaces require driver or control-line support.

Observation Likely stopping point Next check
No transmitted bytes on the bus Commented write, failed write, wrong port, or direction never enabled Confirm eight bytes at the adapter output
Request present but malformed Serial format, wiring, or request CRC Compare the raw frame with the working transaction
Valid request but no reply Slave address, requested range, CRC, or transmit direction not released Check for a reply at the RS485 terminals
Reply at terminals but none in PHP Adapter receive direction, driver, buffering, or read logic Trace bytes at the COM-port boundary

Follow the packet at each boundary. The proof for this stage is a complete eight-byte request followed by the adapter releasing the bus without clipping the last byte.

Why does fgets return no usable response?

fgets($comport, 4017) is the wrong framing operation for Modbus RTU. It reads a line and normally stops at a newline, end of file, or the requested limit. A Modbus frame is arbitrary binary data and does not contain a terminator. Its data can also contain , newline values, or other nonprinting bytes.

For this request, means 73 registers. If the slave returns a normal function-04 response for all requested registers, the data field contains 146 bytes. The derived frame length is:

1 address + 1 function + 1 byte count + (73 × 2 data bytes) + 2 CRC = 151 bytes

An exception response has a different, shorter structure, so the program must inspect the function byte rather than waiting unconditionally for 151 bytes. Read the first three bytes, verify the address and function, then use the returned byte-count field to determine how many data and CRC bytes remain. If the function has its exception bit set, read the exception response structure instead.

Display received data as hexadecimal during commissioning. Printing raw binary with echo $res can look empty even when the variable contains null or nonprinting bytes. The check for this stage is a reported byte count plus a hex dump, not visible terminal text.

How should the request and receive timing be sequenced?

usleep(10)It neither confirms that transmission has finished nor provides a useful response timeout. A fixed delay alone is also a poor frame boundary: operating-system buffering, adapter direction control, device processing, and serial transmission all contribute to elapsed time.

  1. Configure COM3 and verify the configuration command succeeded.
  2. Open the port in binary read/write mode and set the required read timeout through the stream or serial API.
  3. Write all eight request bytes and flush the application buffer.
  4. Allow the adapter or driver to complete transmission and change from transmit to receive. Use adapter-supported direction control rather than guessing a delay where possible.
  5. Read the three-byte response header within the timeout.
  6. If the response is normal, read exactly the byte count plus two CRC bytes. If it is an exception, read the remaining exception bytes.
  7. Reject a timeout, short frame, wrong slave address, unexpected function, inconsistent byte count, or invalid CRC.

Blocking mode without a finite timeout can leave the process waiting forever when no slave answers. Nonblocking mode requires a loop that distinguishes temporary absence of data from the response deadline. The proof before moving on is that a deliberately disconnected device produces a controlled timeout rather than a hung PHP process.

How do you verify the complete Modbus RTU transaction?

Compare the PHP transaction with the working Modbus application at the byte level. Keep the device address, function, register range, serial format, adapter, and wiring unchanged so that only the master software differs.

  1. Confirm PHP writes 0A 04 00 00 00 49 30 87 as eight bytes, without textual conversion or appended line endings.
  2. Capture the returned address, function, byte count, data, and CRC as hexadecimal.
  3. For a normal full-length response, verify address , function , a byte count representing 146 data bytes, and a total derived length of 151 bytes.
  4. Recalculate the response CRC over every byte except the two received CRC bytes and compare the result in Modbus RTU byte order.
  5. Decode each register from two data bytes only after the frame length and CRC pass.

The final acceptance check is one eight-byte transmitted request, one CRC-valid response assigned to the PHP variable, and decoded register values matching the known-good Modbus application.

FAQ

What happens if fputs stays commented out?

PHP opens COM3 but transmits no Modbus request, so the slave has nothing to answer. Enable the write and verify that its accumulated return count equals eight bytes.

What happens if I use fgets for a Modbus RTU reply?

The call waits for line-oriented termination that a binary Modbus frame does not provide, or it returns data that looks blank when echoed. Read the binary header with a timeout, then read the length indicated by the byte-count field.

How do I verify that the final PHP response is valid?

Log the response length and hexadecimal bytes, verify slave and function , then validate the byte count and CRC. For 73 returned registers, the normal response contains 146 data bytes and 151 bytes in total.

Back to blog