The RS232 module frequently drops one or more characters in a received packet of data. I tried both 9600 baud and 38400 baud. I am sending a byte string to a Programmable Logic Controller and waiting for the response packet.
Here is an example trace log:
16:13:47.973 PLC SendData(): 01010C0000083E9C : >
16:13:48.040 PLC Data received: 88 :
16:13:58.081 PLC SendData() Error: Timeout waiting for response from PLC
16:13:59.083 PLC SendData(): 01010C0000083E9C : >
16:13:59.145 PLC Data received: 010101 :
16:13:59.149 PLC Data received: 005188 : Q
16:13:59.152 PLC data received: 010101005188 : Q
The first received byte 88 hex, is the last byte of a packet, most of the bytes are missing. The second try shows the full packet expected to be received.
This is the code:
/// <summary>
/// Read the input coil status byte from the PLC, and return an error code on failure
/// </summary>
/// <param name="CoilStatusBits"></param>
/// <param name="ErrorCode"></param>
/// <returns></returns>
public bool ReadCoilStatus(out byte CoilStatusBits, out byte ErrorCode )
{
CoilStatusBits = 0;
bool rv;
/*
EXAMPLE - Read Coil Status command
0 Slave Address 11
1 Function 01
2 Starting Address Hi 00
3 Starting Address Lo 13
4 No. of Points Hi 00
5 No. of Points Lo 25
6-7 Error Check (LRC or CRC) --
Response:
0 Slave Address 11
1 Function 01
2 Byte Count 05
3 Data (Coils 27-20) CD
4 Data (Coils 35-28) 6B
5 Data (Coils 43-36) B2
6 Data (Coils 51-44) 0E
7 Data (Coils 56-52) 1B
*/
rv = SendData(10000, 3, true, out ErrorCode, MakeReadCoilStatusPacket(PLCAddress, READ_COIL_STATUS, InputCoilsAddress, 8));
if ( rv == true)
{
CoilStatusBits = RxDataBuf[3];
}
return rv;
}
/// <summary>
/// Build the Modbus command packet for reading input coil status bytge
/// </summary>
/// <param name="SLAVEADDR"></param>
/// <param name="FUNCTIONCODE"></param>
/// <param name="STARTINGCOIL"></param>
/// <param name="NUMCOILS"></param>
/// <returns></returns>
private byte[] MakeReadCoilStatusPacket(byte SLAVEADDR, byte FUNCTIONCODE, ushort STARTINGCOIL, ushort NUMCOILS)
{
byte[] Packet = new byte[8];
byte CRC_Hi, CRC_Lo;
Packet[0] = SLAVEADDR;
Packet[1] = FUNCTIONCODE;
Packet[2] = (byte)(STARTINGCOIL >> 8);
Packet[3] = (byte)(STARTINGCOIL & 0x00FF);
Packet[4] = (byte)(NUMCOILS >> 8);
Packet[5] = (byte)(NUMCOILS & 0x00FF);
CalcCRC(Packet, 6, out CRC_Hi, out CRC_Lo);
Packet[6] = CRC_Hi;
Packet[7] = CRC_Lo;
return Packet;
}
/// <summary>
///Send a variable length byte packet to the rs232 port for the PLC
///Wait for a byte string response from the PLC
///After sending a command to the PLC, the program must wait since the PLC
///will not accept any other commands while executing the previous command.
/// </summary>
/// <param name="ms_time">time in milliseconds. Used for the AutoResetEvent object wait</param>
/// <param name="ErrorCode">Error code received from PLC in case of exception</param>
/// <param name="sendData">variable length packet of bytes to transmit</param>
/// <returns>true if packet sent without error</returns>
private bool SendData(int ms_time, int _RxDataQueueSizeExpected, bool _ExpectByteCount, out byte ErrorCode, params byte[] sendData)
{
bool rv = false;
ErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
try
{
RxDataCount = 0;
if (rs232PLC.serialPort.IsOpen)
{
//Enter a critical section - only one thread at a time. Don't release until transaction is finished
lock (SendDataLock)
{
lock (RxDataQueue.SyncRoot)
{
//thread-safe initialize the Queue of data received from the PLC reader
RxDataQueue.Clear();
RxDataEvent.Reset();
RxDataQueueSizeExpected = _RxDataQueueSizeExpected;
ExpectByteCount = _ExpectByteCount;
}
if (LoggingLevel > 0)
{
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " SendData(): "
+ KioskUtilities.BytesToHexString(sendData, sendData.Length)
+ " : " + KioskUtilities.BytesToAsciiString(sendData, sendData.Length));
}
//Send the command to the PLC
if (IdleTimer.IsRunning)
{
/// Wait for the end of the serial port idle time between sending packets required by ModBus
IdleTimerEvent.WaitOne(PacketIdleTimeMs + 20, false);
}
rs232PLC.serialPort.Write(sendData);
rs232PLC.serialPort.Flush();
IdleTimer.Start(); //Mark the start of the serial port idle time between sending packets required by ModBus
//wait for a certain number of bytes to appear in the PLC reader data Queue
rv = RxDataEvent.WaitOne(ms_time + 100, false);
if (rv)
{
lock (RxDataQueue.SyncRoot)
{
//thread-safe capture of the received byte array from the PLC reader receive Queue
RxDataQueue.CopyTo(RxDataBuf, 0);
RxDataCount = RxDataQueue.Count;
ErrorCode = PLCExceptionCodes.RECEIVE_SUCCESS;
}
if (LoggingLevel > 0)
{
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " data received: "
+ KioskUtilities.BytesToHexString(RxDataBuf, RxDataCount)
+ " : " + KioskUtilities.BytesToAsciiString(RxDataBuf, RxDataCount));
}
// Test for exception response
//Byte Contents Example
//0 Slave Address 0A
//1 Function Code 81 - Most significant bit (7) of byte is true if error happened
//2 Exception Code 02
//3 CRC HI
//4 CRC LO
if ( ( RxDataCount >= 3 ) && ( (RxDataBuf[1] & 0x80) == 0x80) )
{
rv = false;
ErrorCode = RxDataBuf[2];
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code
+ " SendData() Error: PLC reported exception " + GetExceptionCodeString(ErrorCode));
}
}
else
{
ErrorCode = PLCExceptionCodes.RECEIVE_TIMEOUT;
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " SendData() Error: Timeout waiting for response from PLC");
}
}
}
else
{
Debug.Print( code + " SendData() serial port is not open");
rs232PLC.serialPort.Open();
rv = false;
}
}
catch (GTI.Serial.PortNotOpenException ex)
{
KioskUtilities.LogException( code + " SendData() serial port not open exception ", ex);
rs232PLC.serialPort.Open();
rv = false;
}
return rv;
}
/// <summary>
/// This is the function that will be called by the rs232 module when data is available on the serial port from the PLC.
/// </summary>
/// <param name="sender">Object that sent called this function</param>
/// <param name="data">serial data received</param>
private void PLC_DataReceived(GT.Interfaces.Serial sender, System.IO.Ports.SerialData data)
{
try
{
lock (RxDataQueue.SyncRoot)
{
// the BytesToRead property tells us how many bytes are waiting in a buffer.
rs232Count = rs232PLC.serialPort.BytesToRead;
if (rs232Count > MaxRxDataQueueSize)
{
rs232Count = MaxRxDataQueueSize; //Don't overflow the allocated rs232Data byte array size
if (LoggingLevel > 0)
{
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " PLC_DataReceived() error - rs232Data array is full, bytes discarded");
}
}
// Now read the serial port data into the rs232Data buffer, starting at the first byte.
rs232PLC.serialPort.Read(rs232Data, 0, rs232Count);
if (LoggingLevel > 1)
{
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " Data received: "
+ KioskUtilities.BytesToHexString(rs232Data, rs232Count)
+ " : " + KioskUtilities.BytesToAsciiString(rs232Data, rs232Count));
}
OnDL05PLCDataReceived(new DL05PLCEventArgs(rs232Data, rs232Count));
// Accumulate received characters in the RxDataQueue, but prevent the Queue size from expanding forever
if (RxDataQueue.Count < MaxRxDataQueueSize)
{
for (int i = 0; i < rs232Count; i++)
{
if (RxDataQueue.Count < MaxRxDataQueueSize)
{
RxDataQueue.Enqueue(rs232Data[i]);
}
else
{
if (LoggingLevel > 0)
{
Debug.Print(DateTime.Now.ToString("HH:mm:ss.fff") + " " + code + " RxDataQueue.Count = " + RxDataQueue.Count.ToString() + " RxDataQueue is full");
}
break;
}
}
}
//compute and wait for the expected number of bytes
if (RxDataQueue.Count >= 3)
{
// Test for exception response
RxDataQueue.CopyTo(RxQDataBuf, 0);
if ((RxQDataBuf[1] & 0x80) == 0x80)
{
//PLC reports an exception in its response
//Byte Contents Example
//0 Slave Address 0A
//1 Function 81
//2 Exception Code 02
//3 CRC HI
//4 CRC LO
RxDataQueueSizeExpected = 5;
}
else
{
//PLC reports no error
if (ExpectByteCount)
{
//Expect to receive: header size (3 bytes) plus data length (variable) plus CRC size (2 bytes)
RxDataQueueSizeExpected = 3 + RxQDataBuf[2] + 2;
}
}
}
if (RxDataQueue.Count >= RxDataQueueSizeExpected)
{
RxDataEvent.Set();
}
}
}
catch (GTI.Serial.PortNotOpenException ex2)
{
KioskUtilities.LogException(code + " PLC_DataReceived() port not open exception ", ex2);
}
catch (Exception ex1)
{
KioskUtilities.LogException(code + " PLC_DataReceived() exception ", ex1);
KioskUtilities.Reboot();
}
}