Modbus RTU Master in FX3G\U PLCs
Enhancing Automation with FX3G and FX3U PLCs: Overcoming IDE Limitations and Modbus Challenges The FX3G and FX3U PLCs are excellent solutions for small to medium-sized automation projects. Their affordability and compatibility with budget-friendly, China-made devices further enhance their appeal, making them an attractive choice for many engineers and developers. However, these PLCs come with certain limitations—particularly in the IDE environment. I primarily use GX Works 2, and while it provides Structured Text (ST) programming, its implementation is fairly restricted compared to even older versions like CoDeSys 2.3. That said, the fact that ST is available at all is a significant advantage. Despite its limitations, GX Works 2 remains highly capable of solving most automation tasks. Challenges with Modbus Master Implementation One of the more complex challenges arises when trying to configure the PLC as a Modbus Master. For less experienced developers, this process can be particularly daunting. If you're only communicating with a single device on a single channel, the setup is relatively straightforward. However, as soon as multiple devices and multiple registers enter the picture, things quickly become complicated—especially if two Modbus Masters operate within the same PLC. Another challenge is that Modbus communications cannot be configured through the IDE interface, as is possible in CoDeSys. Instead, all configuration must be handled programmatically within the code. Not only do developers need to issue instructions for each channel, but they must also ensure that these instructions do not overlap, which can become increasingly difficult to manage as the system scales. Introducing the ModbusDriver Library This complexity was the primary barrier preventing me from deploying FX3G and FX3U PLCs in medium-sized automation systems. To resolve this issue once and for all, I developed the ModbusDriver library. Now at version 1.6, it includes all the functionality I envisioned for a seamless Modbus Master implementation. In the following section, I'll provide a quick overview of how to use the library, as its approach to automation may not be immediately intuitive. Core Objective of ModbusDriver The fundamental goal behind ModbusDriver is straightforward: Ensure that only one ADPRW instruction is executed at a time. Automate as much of the Modbus communication process as possible. Note: In my work, I primarily use Coolmay L02 and Coolmay QM3G PLCs. The ModbusDriver library is fully compatible with these models, though minor adjustments may be required for use with PLCs from other manufacturers. Note Library may be downloaded by this link. Initialisation Make sure you have GX Works 2 (version 1.622) installed. Create a structured project and select ST as the programming language. Add the Modbus Driver, Utils, and TimeControl50 libraries to the project. Create a new task named TCO by right-clicking on MAIN in the Program Settings tree. Link it to the TCO_TICKER_50 program from the TimeControl50 library. Set up the task execution conditions by right-clicking on TCO, selecting Properties. Configure the program to trigger via an interrupt every 50ms by entering I750 in the Event field. Program Setup Now that we've completed the initial configuration, we can proceed with writing the program. The first step is to declare several variables: PortSettings – Stores the port settings for initialization. arMB – An array of polling channels. As seen in the diagram, the maximum number of channels is 30. fbMbProcess – A function block responsible for handling polling operations. At the very beginning of the program, we need to add the following line: EI(TRUE) Enable Interupt (EI). This command enables the program to be interrupted whenever the I750 interrupt is triggered. This line must be included in any additional programs you define. Next, we need to specify that our PLC will function as a Modbus Master on one of the two available ports—Port 2 or Port 3—at least for Coolmay PLCs. MOV(M8002, 2, MB_TIMEOUT_COUNT); MOV(M8002, 20, MB_TIMEOUT_TIME); MOV(M8002, 80, MB_SUSPEND_RETRY); PortSettings := MB_PORT_SETTINGS( MB_PARITY_EVEN, MB_STOPBIT_1, MB_BPS_9600 ); M0 := MB_MASTER_INIT_PORT3(M8002 OR M8014, PortSettings); The first three lines define the Modbus driver settings: MB_TIMEOUT_TIME – Timeout duration in polling intervals (each interval is 50ms). A value of 10 corresponds to 500ms. For polling channels exceeding 100 registers, a longer timeout is recommended. I currently use 20, which equals 1 second. MB_TIMEOUT_COUNT – The number of retry attempts before a polling channel enters monitoring mode due to a timeout. In this case, the system will attempt twice before switching to monitoring mode. MB_SUSPEND_RETRY – The frequency at which polling occurs for channels in monitoring mode. Once a

Enhancing Automation with FX3G and FX3U PLCs: Overcoming IDE Limitations and Modbus Challenges
The FX3G and FX3U PLCs are excellent solutions for small to medium-sized automation projects. Their affordability and compatibility with budget-friendly, China-made devices further enhance their appeal, making them an attractive choice for many engineers and developers.
However, these PLCs come with certain limitations—particularly in the IDE environment. I primarily use GX Works 2, and while it provides Structured Text (ST) programming, its implementation is fairly restricted compared to even older versions like CoDeSys 2.3. That said, the fact that ST is available at all is a significant advantage. Despite its limitations, GX Works 2 remains highly capable of solving most automation tasks.
Challenges with Modbus Master Implementation
One of the more complex challenges arises when trying to configure the PLC as a Modbus Master. For less experienced developers, this process can be particularly daunting. If you're only communicating with a single device on a single channel, the setup is relatively straightforward. However, as soon as multiple devices and multiple registers enter the picture, things quickly become complicated—especially if two Modbus Masters operate within the same PLC.
Another challenge is that Modbus communications cannot be configured through the IDE interface, as is possible in CoDeSys. Instead, all configuration must be handled programmatically within the code.
Not only do developers need to issue instructions for each channel, but they must also ensure that these instructions do not overlap, which can become increasingly difficult to manage as the system scales.
Introducing the ModbusDriver Library
This complexity was the primary barrier preventing me from deploying FX3G and FX3U PLCs in medium-sized automation systems. To resolve this issue once and for all, I developed the ModbusDriver library. Now at version 1.6, it includes all the functionality I envisioned for a seamless Modbus Master implementation.
In the following section, I'll provide a quick overview of how to use the library, as its approach to automation may not be immediately intuitive.
Core Objective of ModbusDriver
The fundamental goal behind ModbusDriver is straightforward:
- Ensure that only one ADPRW instruction is executed at a time.
- Automate as much of the Modbus communication process as possible.
Note:
In my work, I primarily use Coolmay L02 and Coolmay QM3G PLCs. The ModbusDriver library is fully compatible with these models, though minor adjustments may be required for use with PLCs from other manufacturers.Note
Library may be downloaded by this link.
Initialisation
- Make sure you have GX Works 2 (version 1.622) installed.
- Create a structured project and select ST as the programming language.
- Add the Modbus Driver, Utils, and TimeControl50 libraries to the project.
- Create a new task named TCO by right-clicking on
MAIN
in the Program Settings tree. Link it to theTCO_TICKER_50
program from the TimeControl50 library.
- Set up the task execution conditions by right-clicking on
TCO
, selecting Properties.
- Configure the program to trigger via an interrupt every 50ms by entering
I750
in the Event field.
Program Setup
Now that we've completed the initial configuration, we can proceed with writing the program.
The first step is to declare several variables:
-
PortSettings
– Stores the port settings for initialization. -
arMB
– An array of polling channels. As seen in the diagram, the maximum number of channels is 30. -
fbMbProcess
– A function block responsible for handling polling operations.
At the very beginning of the program, we need to add the following line:
EI(TRUE)
Enable Interupt (EI). This command enables the program to be interrupted whenever the I750
interrupt is triggered. This line must be included in any additional programs you define.
Next, we need to specify that our PLC will function as a Modbus Master on one of the two available ports—Port 2 or Port 3—at least for Coolmay PLCs.
MOV(M8002, 2, MB_TIMEOUT_COUNT);
MOV(M8002, 20, MB_TIMEOUT_TIME);
MOV(M8002, 80, MB_SUSPEND_RETRY);
PortSettings := MB_PORT_SETTINGS(
MB_PARITY_EVEN,
MB_STOPBIT_1,
MB_BPS_9600
);
M0 := MB_MASTER_INIT_PORT3(M8002 OR M8014, PortSettings);
The first three lines define the Modbus driver settings:
-
MB_TIMEOUT_TIME
– Timeout duration in polling intervals (each interval is 50ms). A value of 10 corresponds to 500ms. For polling channels exceeding 100 registers, a longer timeout is recommended. I currently use 20, which equals 1 second. -
MB_TIMEOUT_COUNT
– The number of retry attempts before a polling channel enters monitoring mode due to a timeout. In this case, the system will attempt twice before switching to monitoring mode. -
MB_SUSPEND_RETRY
– The frequency at which polling occurs for channels in monitoring mode. Once a channel successfully responds, it exits monitoring and resumes normal polling.
Next, we configure the port settings, ensuring they match the device configurations being polled.
Finally, we initialize the Modbus Master on Port 3 using:
M0 := MB_MASTER_INIT_PORT3(M8002 OR M8014, PortSettings);
If needed, Port 2 can be configured by replacing MB_MASTER_INIT_PORT3
with MB_MASTER_INIT_PORT2
.
Important Considerations:
- This initialization process should only run once at PLC startup. It can be placed in a separate program specifically executed on startup.
- The condition
M8002 OR M8014
ensures proper execution of the Modbus Master initialization. I noticed thatM8002
alone might not be sufficient, as the PLC seems to perform additional procedures afterM8002
, potentially resetting the port to default settings. To ensure stability, you can bind initialization to a button trigger, a ticker, or simply useTRUE
as the condition.
Configuring Polling Channels
Polling channels are also part of the PLC initialization and should be defined at startup (M8002
).
For example:
arMB[0].xEnabled := TRUE;
arMB[0].iNum := 3;
arMB[0].iDev := 1;
arMB[0].iPort := MB_PORT_3;
arMB[0].iRDevNum := 100;
arMB[0].iReg := K514;
arMB[0].iRF := H4;
arMB[0].iWF := H10;
arMB[0].iWR := MB_READ_WRITE;
arMB[0].xWriteOnChange := TRUE;
arMB[0].tCycle := 2;
Parameter Breakdown:
-
xEnabled
Bit Activates polling for this channel. IfTRUE
, polling is enabled; ifFALSE
, polling is disabled. Default isTRUE
. -
iReg
Word[Unsigned] Register or coil address. If polling register 514, specify K514 (decimal) or H202 (hexadecimal). -
iNum
Word[Signed] Number of registers or coils to poll. Default is 1. If polling 3 registers, set 3. -
iRDevNum
Word[Signed] Device register number where results are stored. If set to100
, results will be stored starting fromD100
(orM100
for bit registers). -
iRF
Word[Unsigned] Read function code. Default isH4
. -
iWF
Word[Unsigned] Write function code. Default isH6
. -
iDev
Word[Unsigned] Device address. If polling device 1, set 1. -
iWR
Word[Signed] Read/write mode. Default is MB_READ_WRITE, but you can set MB_READ or MB_WRITE. -
iPort
Word[Signed] Specifies the port assigned to this polling channel. If the PLC has two Modbus Masters, assign the correct port. -
tCycle
Word[Signed] Polling frequency in milliseconds (default 0). A value of 40 corresponds to 2 seconds (each unit = 50ms). -
xWriteOnChange
Bit Enables automatic writing upon value change. IfTRUE
, data is immediately written; ifFALSE
, data is written only at the scheduled cycle (tCycle
).
Possible Modbus Functions
-
H1
- Read Coils -
H2
- Read Discrete Inputs -
H3
- Read Holding Register -
H4
- Read Input Register -
H5
- Write Single Coil -
H6
- Write Single Register -
HF
- Write Multiple Coils -
H10
- Write Multiple Registers
Possible Coolmay PLC Posrts
-
MB_PORT_2
- RS485 A,B -
MB_PORT_3
- RS485 A1,B1 -
MB_PORT_CAN
- CAN H,L -
MB_PORT_TCP
- Ethernet
Important Note on iRDevNum
It's crucial to understand that iRDevNum
occupies twice the space. For example, if iNum
(the number of registers to poll) is 3, and iRDevNum
is set to 100, the polling results will be stored in D100
, D101
, and D102
. However, D103
, D104
, and D105
will also be occupied to store previous cycle values, allowing the system to track changes in the program.
This consideration is essential when allocating polling channels, as improper planning could lead to data overlap issues.
Executing the Polling Process
Once the polling channels are configured, the next step is to run the polling process using the fbMbProcess
function block:
fbMbProcess(
mb_arRegs := arMB,
mb_xEnable := TRUE,
mb_Timeout := D200,
mb_iBuffer := 3000
);
Explanation of Key Variables:
-
mb_Timeout
– Stores the channel number that encountered a timeout (active for one cycle). -
mb_iBuffer
– Defines the buffer memory location (D3000
in this case). The buffer size should match the largest polling channel. If a channel polls 50 registers, thenD3000–D3049
will be used as a temporary buffer.
At this stage, everything runs automatically. For example, in our test setup:
-
D100
,D101
, andD102
store the polled values. - If any value changes, they are written as a group (using function
H10
). - If
H6
is used instead ofH10
, only the modified device is written.
Manual Polling and Writing
For manual polling or writing, set tCycle := 0
in the polling channel and use these examples:
Manual Read (Triggering on M1)
IF M1 THEN
arMB[0].xReadOnce := TRUE;
IF arMB[0].xDone = TRUE THEN
M1 := FALSE;
arMB[0].xReadOnce := FALSE;
END_IF;
END_IF;
Manual Write (Triggering on M2)
IF M2 THEN
D100 := 100;
D101 := 100;
D102 := 100;
arMB[0].xWriteOnce := TRUE;
IF arMB[0].xDone = TRUE THEN
M2 := FALSE;
arMB[0].xWriteOnce := FALSE;
END_IF;
END_IF;
When using xWriteOnce, function H10 is automatically applied to registers and HF to coils.
Alternative Method for Verifying Successful Writes
Instead of relying on xDone
, a verification method can be used:
IF M2 THEN
D100 := 200;
D101 := 200;
D102 := 200;
arMB[0].xWriteOnce := TRUE;
IF D100 = D103 AND D101 = D104 AND D102 = D105 THEN
M2 := FALSE;
arMB[0].xWriteOnce := FALSE;
END_IF;
END_IF;
Final Notes
For further examples, check the ModbusDriver.gxw file in the Examples folder in the link provided earlier.
Additionally, the ModbusDriver library can configure any port as a Modbus Slave or use the Mitsubishi protocol. Refer to the library documentation for details.
There are not many articles about PLC in this portal, and this is my first article in English, please make a comment for me to understand if such a content is wanted here.