Skip to content

[PC-1660] Portenta Machine Control: User Manual Modbus RTU w/ two PMCs #2029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 19, 2024
Merged
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -1232,10 +1232,227 @@ To use the Modbus protocol with your Portenta Machine Control, you will need the

#### Modbus RTU

Modbus RTU, generally operating in half-duplex mode, with its capability to handle noisy and long-distance transmission lines, makes it an excellent choice for industrial environments. Modbus RTU communication is supported using Portenta's Machine Control RS-485 physical interface.
Modbus RTU, generally operating in half-duplex mode, has the capability to handle noisy and long-distance transmission lines, which makes it an excellent choice for industrial environments. Modbus RTU communication is supported using Portenta Machine Control's RS-485 physical interface.

***The Portenta Machine Control has onboard termination resistors; its RS-485 interface can be configured as a half or full duplex.***

#### Using Two Portenta Machine Controls

The following example shows how to establish Modbus RTU communication between **two Portenta Machine Control devices**. We will use four LEDs from the Digital Output port of the Portenta Machine Control as the visual indicator to confirm the communication is working as intended. Since Portenta Machine Control supports half-duplex and full-duplex modes, each mode requires a different wiring setup.

We will begin showing the **Full-Duplex mode** example following the connection diagram below between two Portenta Machine Control devices:

![Modbus RTU (Full-Duplex) between two Portenta Machine Control](assets/modbus-full-rtu-pmcs.png)

The following script assigns a Portenta Machine Control as a Client device, which sends four coil values to the Portenta Machine Control assigned as a Server.

```arduino
// Include the necessary libraries
#include <Arduino_PortentaMachineControl.h>
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>

// Define the baud rate for Modbus communication
constexpr auto baudrate{ 38400 };

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// Modbus over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 Modbus Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
constexpr auto bitduration{ 1.f / baudrate };
constexpr auto preDelay{ bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelay{ bitduration * 9.6f * 3.5f * 1e6 };

// Counter variable to demonstrate coil control logic
int counter = 0;

void setup() {
// Begin serial communication at 9600 baud for debug messages
Serial.begin(9600);

// Wait for serial port to connect (necessary for boards with native USB)
//while (!Serial);
// Initialize RS-485 communication with specified baud rate and delays
MachineControl_RS485Comm.begin(baudrate, preDelay, postDelay);

MachineControl_RS485Comm.setFullDuplex(true);

// Short delay to ensure RS-485 communication is stable
delay(2500);

// Indicate start of Modbus RTU client operation
Serial.println("- Modbus RTU Coils control");

// Start the Modbus RTU client with the RS-485 communication settings
if (!ModbusRTUClient.begin(MachineControl_RS485Comm, baudrate, SERIAL_8N1)) {
Serial.println("- Failed to start Modbus RTU Client!");
// Halt execution if unable to start
while (1)
;
}
}
void loop() {
// Increment counter to change coil values on each iteration
counter++;

// Determine coil value based on the counter's parity
byte coilValue = ((counter % 2) == 0) ? 0x00 : 0x01;

// Attempt to write coil values to a Modbus RTU server
Serial.print("- Writing coil values ... ");

// Begin transmission to Modbus server (slave ID 1) to write coil values at address 0x00
ModbusRTUClient.beginTransmission(1, COILS, 0x00, 4);

for (int i = 0; i < 4; i++) {
// Write the same value to all 4 coils
ModbusRTUClient.write(coilValue);
}

// Check for successful transmission and report errors if any
// Print error code if transmission failed
// Or confirm successful coil value writing
if (!ModbusRTUClient.endTransmission()) {
Serial.print("- Failed! Error code: ");
Serial.println(ModbusRTUClient.lastError());
} else {
Serial.println("- Success!");
}

// Delay before next operation to simulate periodic control
delay(1000);
}
```

Because the Portenta Machine Control is operating in **Full-Duplex mode**, the following line of code is essential and must be included within the code to enable Full-Duplex mode:

```arduino
MachineControl_RS485Comm.setFullDuplex(true);
```

The Server Portenta Machine Control uses the script below, which translates received coil values into corresponding Digital Outputs. It will blink four Digital Outputs accordingly in a timely manner.

```arduino
// Include the necessary libraries
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <Arduino_PortentaMachineControl.h>

// Define the number of coils to control LEDs
const int numCoils = 4;

// Define the baud rate for Modbus communication
constexpr auto baudrate{ 38400 };

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// Modbus over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 Modbus Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
constexpr auto bitduration{ 1.f / baudrate };
constexpr auto preDelay{ bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelay{ bitduration * 9.6f * 3.5f * 1e6 };

void setup() {
// Begin serial communication at a baud rate of 9600 for debug messages
Serial.begin(9600);

// Print a startup message
Serial.println("- Modbus RTU Server");

// Set RS485 transmission delays as per Modbus specification
MachineControl_RS485Comm.begin(baudrate, preDelay, postDelay);

// Enable full duplex mode and 120 Ohm termination resistors
MachineControl_RS485Comm.setFullDuplex(true);

// Set the RS-485 interface in receive mode initially
MachineControl_RS485Comm.receive();

// Start the Modbus RTU server with a specific slave ID and baud rate
// Halt execution if the server fails to start
if (!ModbusRTUServer.begin(MachineControl_RS485Comm, 1, baudrate, SERIAL_8N1)) {
Serial.println("- Failed to start Modbus RTU Server!");
while (1)
;
}

//Set over current behavior of all channels to latch mode (true)
MachineControl_DigitalOutputs.begin(true);

//At startup set all channels to OPEN
MachineControl_DigitalOutputs.writeAll(0);

// Set 7th Digital Output channel ON to show the port has been correctly configured
MachineControl_DigitalOutputs.write(7, HIGH);

// Configure coils for controlling the onboard LEDs
ModbusRTUServer.configureCoils(0x00, numCoils);
}

void loop() {
// Poll for Modbus RTU requests and process them
int packetReceived = ModbusRTUServer.poll();
Serial.println(packetReceived);
if (packetReceived) {
// Process each coil's state and control LEDs accordingly
for (int i = 0; i < numCoils; i++) {
// Read coil value
// Update discrete input with the coil's state
int coilValue = ModbusRTUServer.coilRead(i);
ModbusRTUServer.discreteInputWrite(i, coilValue);

// Debug output to the IDE's serial monitor
Serial.print("LED ");
Serial.print(i);
Serial.print(" = ");
Serial.println(coilValue);

// Control the onboard LEDs based on the coil values
switch (i) {
case 0:
MachineControl_DigitalOutputs.write(0, coilValue ? HIGH : LOW);
break;
case 1:
MachineControl_DigitalOutputs.write(1, coilValue ? HIGH : LOW);
break;
case 2:
MachineControl_DigitalOutputs.write(2, coilValue ? HIGH : LOW);
break;
case 3:
MachineControl_DigitalOutputs.write(3, coilValue ? HIGH : LOW);
// New line for better readability
Serial.println();
break;
default:
// Error handling for unexpected coil addresses
Serial.println("- Output out of scope!");
break;
}
}
}
}
```

With this, we have two Portenta Machine Control devices, each assigned as a Client and Server correspondingly, communicating with Modbus RTU in full-duplex mode.

![Modbus RTU (Full-Duplex) Demo](assets/rtu-full-ani.gif)

Alternatively, to establish communication between two Portenta Machine Control with Modbus RTU in **Half-Duplex mode**, the following wiring setup is required:

![Modbus RTU (Half-Duplex) between two Portenta Machine Control](assets/modbus-half-rtu-pmcs.png)

The previous example can be used in half-duplex mode, but it will require one minor change in the following line of code:

```arduino
MachineControl_RS485Comm.setFullDuplex(false);
```

By modifying this line, *Full-Duplex mode* is deactivated. This minor code tweak, plus the appropriate wiring, enable the two units to communicate using Modbus RTU in half-duplex mode.

![Modbus RTU (Half-Duplex) Demo](assets/rtu-half-ani.gif)

#### Using a Portenta Machine Control & Opta™

The example below shows how to enable Modbus RTU communication between a Portenta Machine Control device and an Opta™ device. For wiring both devices, follow the diagram below:

![Portenta Machine Control and Opta™ Modbus RTU wiring](assets/modbus-rtu.png)
Expand Down
Loading