Talk to Modbus devices over RS485
Many industrial peripherals — power meters, PLCs, sensors, HVAC controllers — speak Modbus RTU over RS485. This guide covers reaching them from a KTM board that has an RS485 or UART serial port.
The device API covers power, display, GPIO, network and kiosk
control — not UART I/O. Serial communication uses the standard Android serial
interface against the device node listed on your board page. This guide is about
that path, not ItempApiPub.
First: find your serial port
Each board page lists its serial connectors and the device node for each — for
example, KTM-S68 maps RS485 to a specific node, and its
RS232 ports to /dev/ttyS0, /dev/ttyS6, /dev/ttyS9. Open the node that
matches the physical RS485 connector you wired.
If a board's datasheet did not specify the node for a given port, confirm it on the device:
ls /dev/ttyS*
RS485 basics that bite
RS485 is half duplex — one pair of wires carries both directions, so only one device transmits at a time. Two things follow:
- Direction control. The transceiver must be switched between transmit and receive around each frame. On KTM boards with a dedicated RS485 port this is handled by the port hardware — you do not toggle a GPIO by hand. (Manually toggling a line from Android userspace is too slow at 115200 baud and truncates the end of the frame — a classic intermittent fault.)
- Termination. Only the two physical ends of the bus get a 120 Ω terminator, set by jumper/DIP on the board — not by software. Terminating in the middle, or at every node, corrupts the bus.
Modbus RTU frame shape
A Modbus RTU request is compact and CRC-checked:
[ slave addr ][ function ][ data ... ][ CRC lo ][ CRC hi ]
Example — read 2 holding registers starting at 0 from slave 1 (function 0x03):
01 03 00 00 00 02 C4 0B
└reg─┘ └cnt┘ └CRC┘
The last two bytes are a CRC-16 (Modbus polynomial) over everything before them, sent low byte first.
The read/response cycle
- Build the request bytes, append the CRC.
- Write to the serial port; flush.
- Read the reply within a timeout (Modbus slaves answer in tens of ms).
- Validate the CRC on the reply before trusting it.
- If it times out or the CRC fails, retry — noisy buses drop frames.
// Pseudocode against the standard Android serial interface.
// Open the RS485 device node for your board, 8N1 at the device's baud.
SerialPort port = openSerial("/dev/ttyS3", 9600); // node + baud per your device
port.setReadTimeout(300);
byte[] req = withModbusCrc(new byte[]{0x01, 0x03, 0x00, 0x00, 0x00, 0x02});
port.write(req);
port.flush();
byte[] reply = new byte[256];
int n = port.read(reply);
if (n > 0 && modbusCrcOk(reply, n)) {
int[] regs = parseHoldingRegisters(reply, n);
// use regs...
} else {
// timeout or bad CRC — retry with backoff
}
Use a library, don't hand-roll the stack
CRC, framing, exception responses and retry logic are easy to get subtly wrong. For a real product, use an established Java/Kotlin Modbus RTU library over your serial stream rather than writing the protocol from scratch — hand-rolled Modbus is a reliable source of field bugs. Reserve the byte-level view above for understanding what the library is doing and for debugging on a scope.
Debugging a silent bus
| Symptom | Likely cause |
|---|---|
| No reply at all | Wrong node, wrong baud, or A/B swapped |
| Garbage bytes | Baud/parity mismatch, or missing termination |
| Intermittent, worse under load | Termination wrong, or manual direction toggling |
| First byte lost | Direction turnaround timing |
Swapping A/B and re-checking baud fixes the majority of "it just won't talk" cases. Confirm the wiring against the RS485 pinout on your board page.
Related
- Board pages — serial connectors and device nodes per board
- GPIO API — for non-Modbus digital I/O on the expansion header