Configuring ArduinoModbus RS485 on Portenta Machine Control

Daniel Price13 min read
ModbusOther ManufacturerTechnical Reference
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

A Portenta Machine Control (PMC) running ArduinoModbus over RS485 fails in three places: the wiring on the TXP 485 / TXN 485 terminals, a serial-parameter mismatch between the two ends, and the begin() signature of whichever PMC library is installed. This reference traces one request hop by hop, compares the two library generations, and gives a bring-up and verification procedure for the PMC as server (slave) and as client (master).

Which hops does a Modbus RTU request cross between a PC and the PMC?

The test setup in the working guide has a PC master or slave simulator on one end, a USB-to-RS485 adapter, a two-wire A/B pair, and the PMC RS485 terminals. Each hop can drop the frame independently, so test them in this order.

Hop Element What stops the frame here
1 PC application (Modbus master or slave simulator) Wrong COM port, mode not RTU, slave ID mismatch, tool-specific address base
2 USB-to-RS485 adapter and driver Adapter not enumerated, A/B labels swapped by the vendor
3 Two-wire A/B pair plus signal ground A/B reversed, ground not tied, no response and no error on the PMC
4 PMC terminals TXP 485 and TXN 485 Wrong terminal, PMC not powered from the 24 V supply
5 PMC RS485 transceiver, enabled in firmware Transceiver not initialised or not enabled (legacy library: comm_protocols.init() and rs485Enable(true))
6 ArduinoRS485: driver-enable timing via pre/post delay Driver released too early, response truncated; wrong begin() arguments
7 ArduinoModbus: poll() for server, request/response for client Poll starved by a blocking loop; wrong slave ID, address or register count

Physical layer first: hops 1 to 4 are verified with a PC-side tool before any PMC-to-PMC test. A PMC-to-PMC test cannot separate a wiring fault from a library fault, and it cannot tell you whether the two units agree on framing when both may share the same defect.

Which library API should you build on: legacy, current, or pinned?

Two PMC support libraries exist, and their RS485 bring-up code differs. The legacy Arduino_MachineControl.h uses the machinecontrol namespace and comm_protocols. The newer Arduino_PortentaMachineControl.h uses the MachineControl_RS485Comm object. The newer library was reported incomplete, and a later change to its begin() signature left the official documentation example out of date.

Approach RS485 setup calls Evidence of operation Risk
A. Legacy Arduino_MachineControl.h comm_protocols.init(), rs485Enable(true), rs485.setDelays(50, 1600), then ModbusRTUServer.begin(comm_protocols.rs485, ...) or ModbusRTUClient.begin(comm_protocols.rs485, ...) Server and client sketches tested against PC simulators at 9600 baud, 8N1 Superseded library; no new features
B. Current Arduino_PortentaMachineControl.h, three-argument begin MachineControl_RS485Comm.begin(baudrate, predelay, postdelay) Server and client sketches reported working at 115200 baud on the library version current at the time Does not match the later four-argument signature; compile error or wrong argument binding on newer releases
C. Current library, four-argument begin MachineControl_RS485Comm.begin(baudrate, SERIAL_8N1, preDelay, postDelay) Reported to still return Connection timed out on Arduino_PortentaMachineControl@^1.0.4 with ArduinoModbus@^1.0.9 and ArduinoRS485@^1.1.1, in half and full duplex, PMC to PMC An upstream pull request adds missing begin() functions to RS485CommClass for that issue; the fix must be in the release you install
D. Pin an older release of the current library Approach B calls, matched to the pinned release A downgrade was reported to restore operation The exact release number is not given; identify it by trying releases below the one that fails

Recommendation: bring up on approach A at 9600 baud, 8N1, against a PC simulator. It has the most complete tested evidence for both roles and removes the library variable while you validate wiring and addressing. Move to the current library only after the same PC test passes there. Use the release that contains the RS485CommClass begin() fix, or a pinned earlier release (approach D) if that fix is not yet published. Read the installed RS485Comm header to see which begin() overloads exist before choosing between B and C.

What must match on every hop: wiring and serial parameters?

The PC-side simulator and both PMC sketches must agree on every serial parameter. The guide's PC configuration is below.

Parameter PC simulator PMC sketch equivalent
Connection Serial RS485 via library object
Baud rate 9600 (legacy examples); 115200 in the current-library examples baudrate passed to both begin() calls
Parity None SERIAL_8N1
Data bits 8 SERIAL_8N1
Stop bits 1 SERIAL_8N1
Mode RTU ModbusRTUServer / ModbusRTUClient
Byte order 4321 (master software only) n/a
Connection From To
Power 24 V supply PMC
Data USB-to-RS485 adapter A PMC TXP 485
Data USB-to-RS485 adapter B PMC TXN 485
Reference Adapter (-) / GND PMC GND

A/B labelling is not uniform across adapters. If everything else matches and the frame never arrives, swap the A/B pair at the adapter end before touching code; a reversed pair produces silence, not a Modbus exception. The USB serial port used for Serial debug output is a separate path from RS485 and does not need to match the Modbus baud rate, although the examples set both to the same number.

How do you bring up the PMC as a Modbus RTU server?

The server owns a register table and answers requests, and it only answers while ModbusRTUServer.poll() is being called. The legacy-library sketch below sets slave ID 1 and creates holding registers 0 to 15. It writes 500 into address 1 and prints the value locally to prove the table before any master connects.

#include <Arduino_MachineControl.h>
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
using namespace machinecontrol;

constexpr unsigned long baudrate { 9600 };

void setup() {
  Serial.begin(9600);
  delay(10);
  comm_protocols.init();
  comm_protocols.rs485Enable(true);
  comm_protocols.rs485.setDelays(50, 1600);
  ModbusRTUServer.begin(comm_protocols.rs485, 1, baudrate, SERIAL_8N1);
  ModbusRTUServer.configureHoldingRegisters(0, 16); // addresses 0-15
  ModbusRTUServer.holdingRegisterWrite(1, 500);
}

void loop() {
  ModbusRTUServer.poll();
  int regVal = ModbusRTUServer.holdingRegisterRead(1);
  Serial.print("regVal: ");
  Serial.println(regVal);
  Serial.println("-");
  delay(500);
}

The begin() arguments are, in order: the RS485 object, slave ID (1), baud rate, and frame format SERIAL_8N1. The equivalent current-library server uses MachineControl_RS485Comm.begin(baudrate, predelay, postdelay) with #include "mbed.h" first, then ModbusRTUServer.begin(MachineControl_RS485Comm, 1, baudrate, SERIAL_8N1), and writes 153 into address 1. Its configureHoldingRegisters(0, 19) creates 19 registers (addresses 0 to 18); the source comment saying 18 is wrong. On a release with the four-argument signature, insert SERIAL_8N1 as the second begin() argument.

The delay(500) in the diagnostic loop above blocks poll(). Keep that sketch as a bring-up aid only. In production code callpoll() every scan and print on a non-blocking timer.

  1. Upload the sketch and open the serial monitor; confirm regVal: 500.
  2. Start the PC master tool with the serial settings from the table above and the slave ID set to 1.
  3. Connect and read holding register address 1. Expect 500.
  4. If the tool shows an offset, check whether it uses 0-based or 1-based addressing; some tools display address 1 as register 40001 or 40002 depending on base.

How do you bring up the PMC as a Modbus RTU client?

The client initiates requests, so it needs a responding slave on the bus, and the PC simulator plays that role. The legacy-library client below reads slave 1, address 0, and writes 125 to slave 1, address 1.

#include <Arduino_MachineControl.h>
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
using namespace machinecontrol;

constexpr unsigned long baudrate { 9600 };
int readVal;
uint16_t sendVal = 125;

void setup() {
  Serial.begin(9600);
  comm_protocols.init();
  comm_protocols.rs485Enable(true);
  comm_protocols.rs485.setDelays(50, 1600);
  ModbusRTUClient.begin(comm_protocols.rs485, baudrate, SERIAL_8N1);
}

void loop() {
  readVal = ModbusRTUClient.holdingRegisterRead(1, 0);
  ModbusRTUClient.holdingRegisterWrite(1, 1, sendVal);
  Serial.print("Value of holding register 1 in slave: ");
  Serial.println(readVal);
  Serial.println();
}

The call signatures are holdingRegisterRead(slaveId, address) and holdingRegisterWrite(slaveId, address, value). An equivalent two-step form exists: ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0, 1); followed by readVal = ModbusRTUClient.read();. The current-library client example calls holdingRegisterWrite(3, 0, 1), meaning slave ID 3, address 0, value 1. Set the PC slave simulator to ID 3 for that sketch, not 1.

The example loops with no delay, which saturates the bus and blocks on timeouts if the slave is absent. Add a fixed scan interval and check the return value on every call.

  1. Start the PC slave simulator with matching serial settings and the ID the sketch addresses.
  2. Upload the client sketch; the simulator should show 125 at address 1.
  3. Write a non-zero value into address 0 in the simulator. A zero cannot be told apart from an unwritten register.
  4. Open the serial monitor and confirm the printed value equals what you wrote into address 0.

Why does the client return -1 or report Connection timed out?

ArduinoModbus read functions return a long, and -1 is the error return. A valid register value is 0 to 65535, so -1 is unambiguous. The error text Connection timed out reported during a write test means no valid response frame arrived inside the client's timeout. Work through the hops in order.

Observation Mechanism Check
Timeout on every request, PC tool sees no traffic A/B reversed, wrong terminals, or transceiver not enabled Swap A/B; confirm TXP 485 / TXN 485; confirm rs485Enable(true) was called on the legacy library
PC tool sees the request but no response arrives at the PMC Response cut short by driver release, or by the slave answering late Review pre/post delay values against baud rate (next section)
Timeout only with newer library, wiring proven with a PC tool begin() signature or missing begin() overloads in RS485CommClass Compare the installed header with the begin(baudrate, SERIAL_8N1, preDelay, postDelay) form; install the release containing the fix, or pin an earlier release
Timeouts only when the server sketch has a long delay() poll() is not running when the request arrives Remove blocking delays from the server loop; check whether the client exposes setTimeout in your installed ArduinoModbus header
Wrong or unexpected data, not -1 Slave ID mismatch, address base, or a register outside the configured range Match configureHoldingRegisters(start, count) to the address you request
Client returns -1 immediately begin() failed or was never checked Test if (!ModbusRTUClient.begin(...)) and halt with a message, as the kitchen-sink derived example does

Read ModbusRTUClient.lastError() after any -1 return to get the text string; it is the source of messages such as Connection timed out.

The documentation example that calls MachineControl_RS485Comm.begin(baudrate, preDelay, postDelay) predates the signature change. Depending on which overloads your release exposes, the three-argument call either fails to compile or binds preDelay to the config argument. Adding the missing SERIAL_8N1 argument corrected the compile problem but did not clear the timeout on the reported release, which is why the missing begin() overloads in RS485CommClass are the current suspect and the fix is tracked upstream.

How do pre/post delays and loop timing interact with baud rate?

Every example calls the RS485 layer with a pre-delay of 50 and a post-delay of 1600. These values control the driver-enable window around each transmitted frame in the half-duplex transceiver: pre-delay is the wait after enabling the driver and before the first byte; post-delay keeps the driver enabled after the last byte so the final character finishes leaving the shift register. ArduinoRS485 defines the unit for setDelays; confirm it in the library source you installed before scaling these numbers for other baud rates.

The character time sets the minimum sensible post-delay. An 8N1 character is 10 bits on the wire (start, 8 data, stop). Character time in microseconds is 10 / baud * 1,000,000, so at 9600 baud it is about 1042 µs and at 115200 baud about 87 µs. If the post-delay is shorter than the last character's remaining time, the driver releases mid-byte and the master sees a corrupt CRC or a timeout. The examples keep 1600 at both baud rates; that is adequate at 115200 with margin and about 1.5 characters at 9600.

Two other timing constraints apply. First, the server must reach poll() often enough to answer inside the master's timeout. Second, a master that writes continuously with no interval leaves no idle time for slower slaves; give the loop an explicit scan period.

How do you carry floating-point values over 16-bit Modbus registers?

ArduinoModbus registers are unsigned 16-bit integers, and a float read returning -1 is the error path, not a value. Modbus has no native floating-point type. Two methods work.

Method Slave side Master side Constraint
Scaled integer Multiply by a fixed factor and round into one register Divide by the same factor after reading Range limited to 0 to 65535 divided by the factor; use a signed cast for negative values

Scaling is the simpler choice when the resolution is known, for example two decimal places by a factor of 100. Publish the factor in the register map so both ends stay in step.

Can the PMC run RTU beside TCP, and what about the PLC IDE?

The guide's author published a companion guide for Modbus TCP that includes an example running Modbus RTU and TCP on the same PMC simultaneously. In that work, TCP as master was not resolved, so plan for TCP as server if RTU and TCP must coexist. This matters for an OPC server reading over TCP while RS485 talks to a separate device, such as a robot controller.

For the PLC IDE, TCP was reported working while RTU was not, with baud rate, byte configuration and unit ID already checked. No verified RTU configuration exists for the PLC IDE in the reference material. Verify RTU there with the same PC-adapter test as above: if the sketch-based path passes with the PC tool and the PLC IDE path fails, the fault lies in the PLC IDE runtime configuration, not in the wiring.

For simulators, any tool that lets the PC act as either RTU master or RTU slave works. The original download site of the Windows tool named in the guide is offline, so pick another simulator. A free Modbus slave simulator and test tool that runs natively on both Linux and Windows was also reported working. A PC that runs a Modbus master library or test tool can serve as the second endpoint in either direction; the PC is the reference endpoint that lets you separate a PMC problem from a wiring problem.

How do you confirm both directions end to end?

  1. Record library versions: Arduino_PortentaMachineControl (or legacy Arduino_MachineControl), ArduinoRS485, ArduinoModbus. In PlatformIO these come from lib_deps; in the IDE from the library manager.
  2. Read the installed RS485 header and note which begin() overloads exist; match your call to one of them.
  3. Wire A to TXP 485, B to TXN 485, and reference to GND; power the PMC from 24 V.
  4. Set the PC tool to Serial, RTU, matching baud, parity None, 1 stop bit.
  5. Server test: upload the server sketch, connect the PC master, read holding register address 1, and expect 500 (legacy sketch) or 153 (current-library sketch).
  6. Client test: upload the client sketch, connect the PC slave with the ID the sketch addresses (1 for the legacy example, 3 for the current-library example), and confirm address 1 shows 125 for the legacy example or address 0 shows 1 for the current-library example.
  7. Write a non-zero value into address 0 of the PC slave and confirm the same value appears in the serial monitor from holdingRegisterRead(1, 0), with no -1 returns and lastError() empty across at least several consecutive polls.

FAQ

How do I wire a USB-to-RS485 adapter to the Portenta Machine Control for Modbus RTU?

Connect adapter A to TXP 485, adapter B to TXN 485, and adapter (-) / GND to PMC GND, with the PMC powered from a 24 V supply. If no frames arrive, swap A and B at the adapter end, since labelling differs between adapters.

How do I fix Modbus RTU Connection timed out on the Portenta Machine Control?

Prove the wiring and settings with a PC-side simulator at matching baud, 8N1, RTU. Then confirm your MachineControl_RS485Comm.begin() call matches an overload in the installed header, using begin(baudrate, SERIAL_8N1, preDelay, postDelay) on releases with the changed signature. If it still times out, install the release containing the RS485CommClass begin() fix or pin an earlier release.

How do I set the RS485 pre and post delays for ArduinoModbus on the PMC?

The reference examples pass 50 and 1600 to setDelays or begin() at both 9600 and 115200 baud. Keep the post-delay above one character time (about 1042 µs at 9600 baud, about 87 µs at 115200 baud) so the driver stays enabled until the last byte leaves.

How do I read a floating-point value from a Modbus slave with ArduinoModbus?

Registers are unsigned 16-bit integers and -1 is the error return, so send the value as a scaled integer, for example multiplied by 100 on the slave, and divide by the same factor after holdingRegisterRead(). For full 32-bit floats, split the value across two registers and agree the word and byte order on both ends.

Back to blog