Skip to content

Interfacing serial comm port directly from a C++ PCWLX

Few months ago, I red a post about interfacing the Phoenix Serial Comm of  AXL F RS UNI 1H module directly from a C++ library (through GDS I guess).

I cannot find this post again.

Can you point out any documentation and cpp example on how to do it properly ?

Thanks a lot.

 

Comments

  • Hello,

    The basic process would be:

    1. Create one Input Port and one Output Port in your C++ program. The data types of these ports should match the GDS data types of the Output Process Data and Input Process Data arrays from the RS UNI module.
    2. Write your C++ code to read the Input Process Data array and to set the Output Process Data array on every scan cycle. The communication protocol that uses these two arrays is described in the User Manual for the RS UNI.
    3. In PLCnext Engineer, connect the ports on your C++ program with the Process Data ports on the RS UNI in the usual way.

    Below is some sample code written in C# that performs basic read and write operations with an RS UNI, using the protocol described in the User Manual. It goes without saying that there is no warranty provided with this code  :)

    Martin.

            public void __Process()
            {
                // Initialise transient variables
                xSendDone = false;
                xRcvNDR = false;
                uiRcvDataLength = 0;
    
                // Make a copy of InputPD for ease of handling, since Input "arrays":
                // 1. don't derive from System.Array, and
                // 2. are not zero-based   :-\
                int LB = EV_PROCESS_DATA.LB;
                for (int i = 0; i < PROCESS_DATA_LENGTH; i++) InputPD[i] = arrInputPD[i + LB];
    
                // While full duplex communication is possible between the AXL F RS UNI 1H and the
                // EV Charging device, only half-duplex communication is possible between the AXC module
                // and the AXL controller, so we manage this with a State Machine.
    
                switch (CurrentState)
                {
                    case State.Idle:
    
                        // Watch for rising edge on Sending and Receiving inputs.
                        if (xReceive && !PreviousReceive)
                        {
                            // Check that there is data in the receive buffer.
                            if (BytesInReceiveBuffer <= 0)
                            {
                                uiRcvDataLength = 0;
                                xRcvNDR = true;
                            }
                            else
                            {
                                // The complete message will be read in 17 byte chunks
                                // before being passed to the user.
    
                                // Initialise loop variable.
                                BytesRemaining = BytesInReceiveBuffer;
    
                                // Set the correct command.
                                OutputPD[0] = (CMD_RECEIVE);
    
                                // Set the Command Toggle bit; it will be reset below for the first transmission.
                                OutputPD[0] ^= CMD_TOGGLE;
    
                                // Set the time that this command started processing.
                                StartTime = DateTime.Now;
    
                                // Set the correct state, and start processing immediately.
                                CurrentState = State.RequestingReceive;
                                goto case State.RequestingReceive;
                            }
                        }
                        else if (xSend && !PreviousSend)
                        {
                            // Check that the message length is OK.
                            if (uiSendLength <= 0) xSendDone = true;
                            else
                            {
                                // Make a copy of Send data, for two reasons:
                                // 1. Freeze all values in the Send data array, and
                                // 2. Ease of handling.
                                SendLength = uiSendLength;
    
                                // Check value of SendLength, and limit it to the maximum allowed for the current protocol.
                                // TODO: Implement protocol type check.
                                if (SendLength > MAX_SEND_DATA_LENGTH)
                                    SendLength = MAX_SEND_DATA_LENGTH;
    
                                // Now copy SendData.
                                LB = EV_SEND_DATA.LB;
                                for (int i = 0; i < SendLength; i++) SendData[i] = arrSendData[i + LB];
    
                                // Initialise loop variable.
                                BytesRemaining = SendLength;
    
                                // For messages between 18 and 340 bytes in length,
                                // store in the temporary buffer before sending the complete message.
                                // Otherwise send the message directly, in 17 byte chunks.
                                // This is done only to demonstrate the two methods of sending data.
                                // Depending on the specific protocol it might not be necessary to
                                // use the temporay buffer at all - but if it is, then the maximum message
                                // length must be limited to the size of the temporary memory area.
    
                                // The following flag indicates which method will be used.
                                SendViaBuffer = BytesRemaining > PROCESS_USER_DATA_LENGTH && BytesRemaining <= TEMPORARY_BUFFER_SIZE;
    
                                // Set the correct command
                                OutputPD[0] = (SendViaBuffer ? CMD_STORE : CMD_SEND);
    
                                // Set the Command Toggle bit; it will be reset below for the first transmission.
                                OutputPD[0] ^= CMD_TOGGLE;
    
                                // Set the time that this command started processing.
                                StartTime = DateTime.Now;
    
                                // Set the correct state, and start processing immediately.
                                CurrentState = State.Sending;
                                goto case State.Sending;
                            }
                        }
    
                        // If we reach here, we're neither receiving nor sending.
                        // Set the default command.
                        OutputPD[0] = CMD_READY_TO_RECEIVE;
    
                        // If the input data indicates Ready To Receive,
                        // then we can read the status of the Receive buffer.
                        if (InputPD[0] == CMD_READY_TO_RECEIVE)
                        {
                            // Check if there are any unread characters in the receive buffer,
                            // and signal this to the user.
                            // Note that we don't give the user the "Fill Level" value,
                            // which could be used to read from the receive buffer less frequently.
                            BytesInReceiveBuffer = (ushort)((InputPD[2] << 8) | InputPD[3]);
                            RcvBufferNotEmpty = (BytesInReceiveBuffer > 0);
                        }
                        break;
    
                    case State.RequestingReceive:
    
                        // Toggle the command
                        OutputPD[0] ^= CMD_TOGGLE;
    
                        // Wait for confirmation that the command has been processed.
                        CurrentState = State.Receiving;
                        break;
    
                    case State.Receiving:
    
                        // Wait for confirmation that the command has been processed.
                        if (InputPD[0] == OutputPD[0])
                        {
                            // Copy the Input Process Data into the next part of the received message.
                            // Note that the first 3 bytes of Process Data are reserved for metadata.
                            int RxIndex = BytesInReceiveBuffer - BytesRemaining;  // Index into the ReceiveData array.
                            ushort CharsToReceive = (ushort)Math.Min(BytesRemaining, PROCESS_USER_DATA_LENGTH);
                            Array.Copy(InputPD, (int)(PROCESS_DATA_LENGTH - PROCESS_USER_DATA_LENGTH), ReceiveData, RxIndex, (int)CharsToReceive);
                            // TODO: Set the data length
                            BytesRemaining -= CharsToReceive;
    
                            // Check if the complete message has been received.
                            if (BytesRemaining > 0)
                            {
                                // The complete message has not been received,
                                // so go back to the previous step immediately.
                                CurrentState = State.RequestingReceive;
                                goto case State.RequestingReceive;
                            }
                            else
                            {
                                // Copy ReceiveData to the output array.
                                LB = EV_RCV_DATA.LB;
                                for (int i = 0; i < BytesInReceiveBuffer; i++) arrRcvData[i + LB] = ReceiveData[i];
    
                                // Notify the user that we're ready for the next command.
                                uiRcvDataLength = BytesInReceiveBuffer;
                                xRcvNDR = true;
    
                                // Reset the Buffer data until we know better.
                                RcvBufferNotEmpty = false;
                                BytesInReceiveBuffer = 0;
    
                                // Move to the next state.
                                OutputPD[0] = CMD_READY_TO_RECEIVE;
                                CurrentState = State.Idle;
                            }
                        }
                        break;
    
                    case State.Sending:
    
                        // Copy the next part of the message into the Output Process Data.
                        // Note that the first 3 bytes of Process Data are reserved for metadata.
                        int TxIndex = SendLength - BytesRemaining;  // Index into the SendData array.
                        ushort CharsToSend = (ushort)Math.Min(BytesRemaining, PROCESS_USER_DATA_LENGTH);
                        Array.Copy(SendData, TxIndex, OutputPD, (int)(PROCESS_DATA_LENGTH - PROCESS_USER_DATA_LENGTH), (int)CharsToSend);
                        BytesRemaining -= CharsToSend;
    
                        // Set the data length in the output data.
                        OutputPD[2] = (byte)CharsToSend;
    
                        // Toggle the command byte to continue sending.
                        OutputPD[0] ^= CMD_TOGGLE;
    
                        // Check if this is the last piece of the message.
                        if (BytesRemaining <= 0)
                        {
                            // If sending via buffer, and this is the last chunk of data, then set the SEND command.
                            // This should (!) send the contents of the buffer, followed by the contents of the Process Data array.
                            // TODO: The documentation is unclear on this behaviour, so check this!
                            if (SendViaBuffer) OutputPD[0] = CMD_SEND;
                        }
    
                        // Wait for confirmation that the command has been processed.
                        CurrentState = State.ConfirmingSend;
                        break;
    
                    case State.ConfirmingSend:
    
                        // Wait for confirmation that the command has been processed.
                        if (InputPD[0] == OutputPD[0])
                        {
                            // Check if the complete message has been sent.
                            if (BytesRemaining > 0)
                            {
                                // The complete message has not been sent,
                                // so go back to the previous step immediately.
                                CurrentState = State.Sending;
                                goto case State.Sending;
                            }
                            else
                            {
                                // Notify the user that we're ready for the next command.
                                xSendDone = true;
                                OutputPD[0] = CMD_READY_TO_RECEIVE;
                                CurrentState = State.Idle;
                            }
                        }
    
                        // TODO: Set a timeout, otherwise we could be waiting here forever ...
    
                        break;
    
                    default:
                        // We should never reach here, but just in case ... reset the sequence.
                        CurrentState = State.Idle;
                        break;
                }
    
                // Check how long the current process has been running, and cancel it if necessary.
                if ((tTimeout > 0) && (CurrentState != State.Idle) && (DateTime.Now.Subtract(StartTime).TotalMilliseconds >= tTimeout))
                {
                    if ((CurrentState == State.RequestingReceive) || (CurrentState == State.Receiving))
                    {
                        // Reset the Receive Buffer data until we know better.
                        RcvBufferNotEmpty = false;
                        BytesInReceiveBuffer = 0;
                    }
    
                    // Wait for the next action.
                    CurrentState = State.Idle;
                }
    
                // Set outputs
                xBusy = CurrentState != State.Idle;
                xRcvBufferNotEmpty = RcvBufferNotEmpty;
    
                OutputPD[1] = 0x00;  // "Control bits".
    
                // Write the complete Output Process Data array.
                LB = EV_PROCESS_DATA.LB;
                for (int i = 0; i < PROCESS_DATA_LENGTH; i++) arrOutputPD[i + LB] = OutputPD[i];
    
                // Remember the current value of the trigger inputs for next time.
                PreviousSend = xSend;
                PreviousReceive = xReceive;
            }
    

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.