Skip to Content

Using Modbus on Arduino Mega with OpenPLC

Map IEC addresses to coils and registers, enable Modbus RTU and test the slave with a master simulator
July 8, 2024 by
Boot & Work Corp. S.L, Arnau Tena

Complete guide: OpenPLC with industrial PLCs

To use Modbus with OpenPLC on an Arduino Mega, enable Modbus RTU (Serial) in the Communications tab of Transfer Program to PLC, set the baud rate and the slave ID, and give your variables IEC addresses. OpenPLC then serves every %QX output as a coil, every %IX input as a discrete input, every %IW as an input register and every %QW as a holding register. The coil number is 8 times the first index plus the second index, so %QX1.1 is coil 9.

In this guide the Arduino Mega is a Modbus RTU slave over USB. The Ladder program makes two signals that blink in opposite phase, a Modbus master on the PC reads them, and the master stops the whole program by writing one coil. You will build the Ladder diagram, map the variables to coils, upload the program with Modbus enabled and test it with a free master simulator.

The steps and screenshots are from the classic OpenPLC Editor. OpenPLC Editor v4 is a new application with a different interface, so the menus can have other names there. It is the second guide of the series: start with First steps with OpenPLC and Arduino Mega if you have not used OpenPLC yet.

What you need

The same steps work on an M-Duino PLC, which is based on the Arduino Mega, once the Industrial Shields boards are in OpenPLC. See Using ESP32 PLC, M-Duino and Ardbox with OpenPLC.

Build the Ladder diagram

Open the Blink example in File > Tutorials and Examples > 2: Blink. Its TON and TOF timers switch the variable blink_led every 500 ms. Then make three changes:

  1. In the variables table, add two variables of type BOOL: blink_led_off and global. Set the initial value of global to 1. global will stop the whole program.
  2. With the variable tool, place blink_led_off as an output and connect it to the right of the negated blink_led contact.
  3. With the contact tool, place global just before the blink_led coil.
OpenPLC variables table with blink_led, blink_led_off and global declared as BOOL, global with initial value 1
Variables table: blink_led, blink_led_off and global are BOOL, and global starts at 1.
Ladder rung where blink_led_off is connected after the negated blink_led contact and the TON timer
blink_led_off is connected after the negated blink_led contact.
Ladder rung with the global contact placed before the blink_led coil after the TOF timer
The global contact is placed before the blink_led coil.

Compile and start the simulation. blink_led_off always takes the opposite value of blink_led. If you set global to 0, both stop.

How OpenPLC maps IEC addresses to Modbus

When Modbus is enabled, OpenPLC adds a Modbus slave to the program it compiles for the board and links each IEC address to a Modbus table. On the Arduino Mega the ranges are these:

IEC addressModbus tableFunction codesArduino Mega rangeMaster can
%IX0.0 to %IX2.7Discrete inputs020 to 23Read
%QX0.0 to %QX2.7Coils01, 05, 150 to 23Read and write
%IW0 to %IW7Input registers040 to 7Read
%QW0 to %QW11Holding registers03, 06, 160 to 11Read and write

For bits, the Modbus address is 8 times the first index plus the second one: %QX1.1 is 8 × 1 + 1 = 9. For words, it is the index itself: %QW11 is holding register 11. Give the three variables these locations:

VariableLocationCoilArduino Mega pin with the default I/O Config
global%QX0.7721
blink_led%QX1.0823
blink_led_off%QX1.1925

A %QX location is a real output: pins 21, 23 and 25 switch with the variables. Leave them unconnected for this test. With Modbus enabled, every pin in the I/O Config also becomes a coil or a register that the master can write, even if the Ladder program does not use it.

Optional: show the state on the built-in LED

The LED of the Arduino Mega is on PWM pin 13, which OpenPLC maps to the analog output %QW11. Add a variable ledMega of type UINT at %QW11 and two MOVE blocks: 65535 (full scale) when blink_led is on, 0 when blink_led_off is on. The master can also read it as holding register 11.

Two MOVE blocks that write 65535 or 0 to the ledMega variable at %QW11
Two MOVE blocks write 65535 or 0 to ledMega (%QW11), the built-in LED.

Enable Modbus RTU and upload the program

Open Transfer Program to PLC, go to the Communications tab and tick Enable Modbus RTU (Serial). This guide uses these values:

SettingValueWhat it does
InterfaceSerialThe USB serial port of the Arduino Mega
Baud115200Must be the same in the master
Slave ID1The address the master asks for (1 to 247)
Tx Pin-1No RS-485 transceiver. With one, it is the pin that enables the transmitter: OpenPLC sets it high while it sends a frame
Enable Modbus TCPOffThe Arduino Mega has no Ethernet or Wi-Fi
OpenPLC Communications tab with Modbus RTU over serial enabled at 115200 baud, slave ID 1 and Tx Pin -1
Communications tab: Modbus RTU over the serial port at 115200 baud, slave ID 1, Tx Pin -1.

OpenPLC opens the serial port with the default Arduino format: 8 data bits, no parity, 1 stop bit (8N1). Many Modbus devices use even parity by default, so set the master to 8N1 here. Go back to the Transfer tab and upload. If you added the LED, it blinks every half second.

Test the slave with a Modbus master simulator

  1. Open Radzio and go to Connection > Settings. Select Modbus RTU, the COM port of the Arduino, 115200 baud, parity None and 1 stop bit, and "Register address (starting from 0)".
  2. In Setup > Data definition, set the scan rate to 100 ms.
  3. Create a page with File > New: Device ID 1, Coil status, address 0, length 10.
  4. Click Connection > Connect. Coils 8 and 9 change every 500 ms and coil 7 stays at 1.
  5. Double click the value of coil 7 and set it to Off. global goes to 0, coils 8 and 9 stop and the LED turns off.
Radzio Modbus Master Simulator connection settings for Modbus RTU at 115200 baud, no parity, 1 stop bit
Radzio connection settings: Modbus RTU, 115200 baud, no parity, 1 stop bit, addresses from 0.
Radzio setup window with a scan rate of 100 ms
Scan rate of 100 ms.
Radzio page set to device ID 1, coil status, address 0 and length 10
New page: device ID 1, coil status, address 0, length 10.
Radzio reading coils from the Arduino Mega with coil 7 at 1 and coil 9 at 1
Connected: coil 7 is at 1 and coils 8 and 9 alternate.

On the wire, the read is the frame 01 01 00 00 00 0A BC 0D (slave 1, function 01, from coil 0, 10 coils, CRC). Writing coil 7 to Off is 01 05 00 07 00 00 7C 0B. The Modbus RTU frame and function codes guide explains each byte.

Disconnect Radzio before you upload a new program. Only one application can use the COM port, and the upload fails while the master holds it.

If the master gets no answer

What you seeLikely causeWhat to do
Timeout on every requestWrong COM port, baud rate, parity or slave IDUse the port of the Arduino, 115200, no parity, 1 stop bit and ID 1
The upload fails in OpenPLCThe master still has the COM port openDisconnect the master, upload, connect again
Values appear one position offThe master numbers coils from 1Select addresses from 0, or add 1 (coil 7 is 00008 in 1 based numbering)
A coil goes back to its value after you write itThe Ladder program writes that variable on every scanWrite only variables the program does not drive, such as global
Coils 8 and 9 never changeglobal is 0Set its initial value to 1, or write coil 7 to On

From the Arduino Mega to an industrial Modbus network

A Modbus slave over USB is good for learning. In a panel, the slave sits on an RS-485 bus or on Ethernet. An M-Duino PLC keeps the Arduino Mega base and adds RS-485, Ethernet and 24 V inputs and outputs, and the Industrial Shields mod adds it to OpenPLC. If the PLC has to be the Modbus master, program it in the Arduino IDE with the Industrial Shields library, as in Modbus RTU with Arduino and ESP32 based PLCs. To compare the families, read how to choose an industrial controller.

See a full Modbus network before you build one. In the Modbus network simulator, an M-Duino 21 polls four Modbus RTU slaves on RS-485 and serves the data on Modbus TCP. Switch a slave off or change the baud rate and watch the frames.

Open the Modbus network simulatorRead the Modbus temperature network case

FAQ about Modbus in OpenPLC on Arduino

Can an Arduino with OpenPLC be a Modbus master?

The firmware that the OpenPLC Editor builds for Arduino boards includes a Modbus slave only. To poll meters, drives or other PLCs, program the PLC in the Arduino IDE with a Modbus master library.

Why parity None if Modbus RTU uses even parity by default?

OpenPLC opens the Arduino serial port in 8N1. The master and every device on the line must use the same format, so set the master to no parity and 1 stop bit.

Does it work over RS-485?

Yes, with an RS-485 transceiver on a serial port of the board. Set Tx Pin to the pin that enables the transmitter of the transceiver. OpenPLC drives it high while it answers and low after.

How do I read analog values over Modbus?

Analog inputs (%IW) are input registers, read with function 04. Analog outputs (%QW) are holding registers, read with function 03 and written with 06 or 16. OpenPLC uses 0 to 65535: on the Arduino Mega, an analog input is the 10 bit reading multiplied by 64.

Why does the Arduino restart when the master connects?

The Arduino Mega resets when a program opens its USB port with DTR active. Leave DTR inactive in the master, as in the Radzio settings above.

​Search in our Blog

Boot & Work Corp. S.L, Arnau Tena July 8, 2024
Share this post
Tags

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.

PLC Comparison