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

May 8, 2025 - 08:40
 0
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.

Fig. 1 – About window

  • Create a structured project and select ST as the programming language.

Fig. 2 – Creating an FX3G project

  • Add the Modbus Driver, Utils, and TimeControl50 libraries to the project.

Fig. 3 – Adding libraries

  • 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.

Fig. 4 – Adding a new task

  • Set up the task execution conditions by right-clicking on TCO, selecting Properties.

Fig. 5 – Task properties

  • Configure the program to trigger via an interrupt every 50ms by entering I750 in the Event field.

Fig. 6 – TCO settings

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:

Fig. 7 – Program 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 that M8002 alone might not be sufficient, as the PLC seems to perform additional procedures after M8002, potentially resetting the port to default settings. To ensure stability, you can bind initialization to a button trigger, a ticker, or simply use TRUE 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. If TRUE, polling is enabled; if FALSE, polling is disabled. Default is TRUE.
  • 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 to 100, results will be stored starting from D100 (or M100 for bit registers).
  • iRF Word[Unsigned] Read function code. Default is H4.
  • iWF Word[Unsigned] Write function code. Default is H6.
  • 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. If TRUE, data is immediately written; if FALSE, 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, then D3000–D3049 will be used as a temporary buffer.

At this stage, everything runs automatically. For example, in our test setup:

  • D100, D101, and D102 store the polled values.
  • If any value changes, they are written as a group (using function H10).
  • If H6 is used instead of H10, 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.