ModbusFB Library Problems

matthew
2022-02-18
2022-02-19
  • matthew - 2022-02-18

    Hi,

    I'm having a few issues with the ModbusFB Library. I'm trying to create some reusable function blocks for devices I use quite often. The below is just a simple Modbus Relay board with 2x Inputs and 2x Relay Outputs.

    My Function blocks works with one SerialClient connection, as soon as I link the second ClientSerial to the second FB I get error 56 which is reply timeout. Both FBs work if I connect each one independantly, just can't have both connected together.

    [-img src=CodeSys ModbusFB Issue.png width=50%: missing =-]

    Here is the ClientSerial ModbusFB

    FUNCTION_BLOCK Modbus_Client_Serial
    VAR_INPUT
        //Serial Client Setup
        ComPort   : INT;
        BaudRate  : DWORD;
        DataBits  : BYTE;
        Parity    : BYTE;
        StopBits  : BYTE;
        RtuAscii  : ModbusFB.RtuAscii;
        xConnect  : BOOL;
        //Auto Reconnect Until Device Found
        AutoReconnect : BOOL;
        //Reset 
        Reset : BOOL;
    
    END_VAR
    
    VAR_IN_OUT  
        //Connect Client Between ModbusFBs
        ClientSerial : ModbusFB.ClientSerial;
    END_VAR 
    
    VAR_OUTPUT
        //Errors from FBs
        Error    : BOOL;
        ErrorID  : INT;
    
    END_VAR
    VAR
        initDone : BOOL;
        ClientConnected : BOOL;
        ClientErrorCnt : UINT;
        CasePos : INT;
    
    END_VAR
    

    Here is the code

    //This FB Uses SysCom please see CodeSys ModbusFB for more information
    
    IF NOT initDone THEN
        //First Scan
        initDone := TRUE;
        CasePos := 0;
    END_IF;
    
    //Call Client 
    ClientSerial();
    //Check Client is Connected
    ClientConnected := ClientSerial.xConnected;
    
    CASE CasePos OF
    
    0: //Step 0 Setup Serial Client
        ClientSerial(   
            iPort:= ComPort, 
            dwBaudrate:= BaudRate, 
            byDataBits:= DataBits, 
            eParity:= Parity, 
            eStopBits:= StopBits,  
            eRtuAscii:= RtuAscii);
        CasePos := 10; //Move to step 10 to setup Client Connect  
    
    10: //Step 10 Connect The Client
        IF xConnect THEN
            ClientSerial.xConnect :=TRUE;
        ELSE 
            ClientSerial.xConnect :=FALSE;
        END_IF
    
    
        IF ClientSerial.xError OR ClientSerial.eErrorID > 0 THEN
            CasePos := 1000; // Process Error
        END_IF
    
    1000: //Step 1000 Process Errors
        //Process Client Connection Errors
        IF ClientSerial.xError OR ClientSerial.eErrorID > 0 THEN
    
            IF AutoReconnect THEN
                //Errors from the Client Functions Reset The Client and repeat until no error ( No Error Flag Active )
                ClientSerial.xConnect := FALSE;
                CasePos := 10; // Try to reconnect the client 
            ELSE 
                IF ClientErrorCnt < 5 THEN
                    //Try to reconnect the client 5x then create error flag
                    ClientSerial.xConnect := FALSE;
                    ClientErrorCnt := ClientErrorCnt + 1;
                    CasePos := 10; // Try to reconnect the client
                ELSE  
                    //Set Error to TRUE and display ErorrID 
                    Error := TRUE;
                    ErrorID := ClientSerial.eErrorID;
                    ClientSerial.xConnect := FALSE;
    
                    IF Reset THEN 
                        Error := FALSE;
                        ErrorID := 0;
                        ClientErrorCnt := 0;
                        CasePos := 0; //Reset Serial Client Parameters incase of changes 
                    END_IF
                END_IF
            END_IF
        END_IF
    
    
    END_CASE
    

    Here is the Relay Board Code

    FUNCTION_BLOCK Modbus_Relay_x2
    VAR_INPUT
    
        //Slave Address ID
        UnitID    : UINT;
        //Auto Reconnect Until Device Found
        AutoReconnect : BOOL;
        //Reset 
        Reset : BOOL;
        //Relay Control
        Relay_1_On : BOOL;
        Relay_2_On : BOOL;
    
    END_VAR
    
    VAR_IN_OUT  
        //Connect Client Between ModbusFBs
        ClientSerial : ModbusFB.ClientSerial;
    END_VAR 
    
    VAR_OUTPUT
        //Errors from FBs
        Error    : BOOL;
        ErrorID  : INT;
        //Optocoupler Inputs
        Input_1_Status  : BOOL;
        Input_2_Status  : BOOL;
    
    END_VAR
    VAR
        initDone : BOOL;
        ClientConnected : BOOL;
        ErrorCnt : UINT;
        FbErrorID : INT;
        DataCoils_Inputs : ARRAY [0..7] OF BOOL;
        Relay1_FC05: ModbusFB.ClientRequestWriteSingleCoil;
        Relay2_FC05: ModbusFB.ClientRequestWriteSingleCoil;
        Inputs_FC02: ModbusFB.ClientRequestReadDiscreteInputs;  
    
        CasePos : INT;
    
    END_VAR
    

    Here is the Code

    //This FB Uses SysCom please see CodeSys ModbusFB for more information
    
    IF NOT initDone THEN
        //First Scan
        initDone := TRUE;
        CasePos := 0;
    END_IF;
    
    ClientConnected := ClientSerial.xConnected;
    
    CASE CasePos OF
    
    0: //Step 0 Initiate Functions
        //Setup Relay 1 ( Address offset 0 )
        Relay1_FC05(  
            rClient:= ClientSerial, 
            uiUnitId:= UnitID,  
            uiItem:= 0);
    
        //Setup Relay 2 ( Address offset 1 )    
        Relay2_FC05(  
            rClient:= ClientSerial, 
            uiUnitId:= UnitID,  
            uiItem:= 1);
    
        //Setup Optocoupler Inputs ( Store Bits in an Array )
        Inputs_FC02(  
            rClient:= ClientSerial, 
            uiUnitId:= UnitID, 
            uiStartItem:= 0, 
            uiQuantity:= 8, 
            pData:= ADR(DataCoils_Inputs[0]));
        CasePos := 20; //Begin Processing Modbus Data
    
    10: //Step 10 Reconnect SerialClient
        ClientSerial.xConnect := TRUE;
    
        IF ClientSerial.xConnected THEN
            CasePos := 20; 
        ELSE
            CasePos := 1000; //Client failed to connect move to errors
        END_IF 
    
    
    20: //Step 20 Process Relay 1 ( Modbus Function Code 05 )
        IF ClientConnected AND NOT Relay1_FC05.xExecute THEN 
            Relay1_FC05.xExecute := TRUE;
    
        END_IF;
    
        IF Relay1_FC05.xExecute THEN
    
            IF Relay1_FC05.xBusy THEN
                Relay1_FC05.xExecute := FALSE;
            END_IF;
    
            IF Relay_1_On THEN
                //Coil Write ( Turn on Relay 1 )
                Relay1_FC05(xValue:= TRUE,
                    rClient := ClientSerial);
            ELSE
                //Coil Write ( Turn off Relay 1 )
                Relay1_FC05(xValue:= FALSE,
                    rClient := ClientSerial);    
            END_IF; 
            //Move to next Data Set 
            IF Relay1_FC05.xDone THEN
                Relay1_FC05.xExecute := FALSE;
                CasePos := 30; // Relay 2
            END_IF; 
            //Process Errors
            IF Relay1_FC05.xError OR Relay1_FC05.eErrorID > 0 THEN
                Relay1_FC05.xExecute := FALSE;
                FbErrorID := Relay1_FC05.eErrorID;
                CasePos := 1000;
            END_IF
        END_IF;
    
    30: //Step 30 Process Relay 2 ( Modbus Function Code 05 )
        IF ClientConnected AND NOT Relay2_FC05.xExecute THEN 
            Relay2_FC05.xExecute := TRUE;
        END_IF;
    
        IF Relay2_FC05.xExecute THEN
    
            IF Relay2_FC05.xBusy THEN
                Relay2_FC05.xExecute := FALSE;
    
            END_IF;
    
            IF Relay_2_On THEN
                //Coil Write ( Turn on Relay 2 )
                Relay2_FC05(xValue:= TRUE,
                    rClient := ClientSerial);
            ELSE
                //Coil Write ( Turn off Relay 2 )
                Relay2_FC05(xValue:= FALSE,
                    rClient := ClientSerial); 
            END_IF; 
            //Move to next Data Set 
            IF Relay2_FC05.xDone THEN
                Relay2_FC05.xExecute := FALSE;
                CasePos := 40; // Opto Inputs
            END_IF; 
            //Process Errors
            IF Relay2_FC05.xError OR Relay2_FC05.eErrorID > 0 THEN
                Relay2_FC05.xExecute := FALSE;
                FbErrorID := Relay2_FC05.eErrorID;
                CasePos := 1000;
            END_IF
        END_IF;
    
    40: //Step 40 Process Inputs ( Modbus Function Code 02 )
        IF ClientConnected AND NOT Inputs_FC02.xExecute THEN 
            Inputs_FC02.xExecute := TRUE;
    
        END_IF;
    
        IF Inputs_FC02.xExecute THEN
    
            IF Inputs_FC02.xBusy THEN
                Inputs_FC02.xExecute := FALSE;
            END_IF;
            //Read Coils from Optocouplers
            Inputs_FC02(rClient := ClientSerial); 
    
            IF Inputs_FC02.xDone THEN
                Inputs_FC02.xExecute := FALSE;
                //Read The Status of the Inputs
                Input_1_Status := DataCoils_Inputs[0];
                Input_2_Status := DataCoils_Inputs[1];
                CasePos := 20; // Return to the start of the stack
            END_IF; 
            //Process Errors
            IF Inputs_FC02.xError OR Inputs_FC02.eErrorID > 0  THEN
                Inputs_FC02.xExecute := FALSE;
                FbErrorID := Inputs_FC02.eErrorID;
                CasePos := 1000;
            END_IF
        END_IF;
    
    
    1000: //Step 1000 Process Errors
    
        IF AutoReconnect THEN
            //Errors from the Modbus Functions Reset The Client and repeat until device found ( No Error Flag Active )
            ClientSerial.xConnect := FALSE;
            CasePos := 10; // Try to reconnect the client 
        ELSE 
            IF ErrorCnt < 5 THEN
                //Try to resolve the Error 5x then create Error Flag
                ClientSerial.xConnect := FALSE;
                ErrorCnt := ErrorCnt + 1;
                CasePos := 10; // Try to reconnect the client
            ELSE 
                //Set Error to TRUE and display ErorrID 
                Error := TRUE;
                ErrorID := FbErrorID;
                ClientSerial.xConnect := FALSE;
    
                IF Reset THEN 
                    Error := FALSE;
                    ErrorID := 0;
                    ErrorCnt := 0;
                    CasePos := 10; //Reconnect Client
                END_IF
            END_IF
        END_IF          
    
    
    
    END_CASE
    

    I have also tested using the original ModbusFB.ClientSerial Function Block and I get the exact same results. I have tried with 2x ClientSerial for each function block and still get the same error. Seems like I can only use 1x ClientSerial with 1x Function Block

     
  • dFx

    dFx - 2022-02-18

    You cannot execute 2 functions at the same time on the same port. You have to sequence each request.

    I would call the read/write inside the MODBUS_CLIENT_SERIAL and then provide a queue for the MODBUS_RELAY FBs. Then just place a request with its parameters in the queue with MODBUS_RELAY, process them in MODBUS_CLIENT_SERIAL, using pointers for in/out data, and also results. This way the asking RELAY FB could be aware his request have been processed.

    For less modifications of your code, you can also add a flag that the line is currently busy, but you will have to manage RELAY FBs concurrency.

     

    Last edit: dFx 2022-02-18
    • matthew - 2022-02-18

      Hi dFx,

      Thanks, that makes perfect sense and I should have picked up on that one. Would you have an example of setting up a queue with the pointers? I haven't used pointers or anything like that before so wouldn't know where to start. I'm trying todo it dynamically so I can easily add more of the same devices or different devices in the future without much modification to the project.

       
      • dFx

        dFx - 2022-02-18

        Pointers are not a big deal. See help
        For instance, you may have a struct, for the queue elements.
        Lets say you need to know the type of work (read/write), the data to be written or read.

        STRUCT ModbusQueueItem
            TypeOfWork : INT;
            pData : Pointer TO BOOL; // Data pointer for boolean data
            szData : INT; // Size of data
        END_STRUCT
        

        So In case of Read, just provide the pData to pData of ClientRequestReadDiscreteInputs, given that your RELAY block did correctly set the item using ADR(YourData).

        This is only a draft, not real code, so there maybe adjustements to do on types.

         
        • matthew - 2022-02-19

          Hi dFx,

          Can you not pass the whole struct as an In/Out and every FB have a connection to that struct?
          What I'm thinking is every slave already has an unique ID/Address and in the Modbus_Client_Serial FB having a FOR loop looking for these IDs may have 8 devices all with different addresses and store them in an array and then use another FOR loop based on the size of that array to trigger the slave devices in sequence based on there address and when that device FB is done it goes on to the next device FB? I haven't dealt with comms stuff in the past and the correct way todo this.

           

Log in to post a comment.