I'm currently working on a project which requires me to communicate with an MBED through serial ports.
I've read the msdn site but I don't understand.
Can anyone teach me or show examples of how to use SerialPort.Handshake (XonXoff or RequestToSend)?
My main request how to set: If C# reads a certain string, then send data.
First of all i think that Handshaking is not what you need. Handshaking is a low level communication, to control when to send data or not. more about handshaking
My main request how to set: If C# reads a certain string, then send
data.
What you need is a continuous data reading or an event, and after certain string is read - to send some data.
Let me find some examples.
void connect(){
_port = new SerialPort();
// set port parameters
_port.DataReceived += portDataReceived; //set the data received event
_port.Open();
}
void portDataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = _port.ReadExisting();
if(data.Contains("data-start")) //check for the start data
{
CreateReply(); // your reply
}
}
You can read the data in a while loop, but take a note that an infinite loop executed in the main thread will block your program from responding.
void connectAndRead(){
_port = new SerialPort();
// set port parameters
_port.Open();
_stopReading = false;
var dataBuffer = "";
while(!_stopReading)
{
dataBuffer += _serialPort.ReadLine();
if(!dataBuffer.Contains("data-start")) //check for the start data
continue;
CreateReply(); // your reply
_stopReading = true; // exit loop
}
}
Related
In my scenario there are one or two serial port. If only one port is configured, that port can be dedicated to a PLC or to a Scale.
The PLC communicate only when user push some button on the machine, the Scale instead constantly poll the measured that has weighted. If only one port is configured there are no issue.
When two port are configured the communication protocol change a bit, if user push the PLC's weight button PLC send a weight request. If that request is listened on DataReceived event the Scale port is opened and the polled value is readen.
Now i have to admit that the legacy code that i will paste here is not the best code possible, and that is bad that it use the same DataReceived event for both the ports; but that code had work for quite a lot (about six years). Recently the Scale has been changed and the program had stoped working properly (only in the two port configuration) raising a sistematic timeout exception. I have sniffed the traffic and nothing change with protocol or data format.
Here the problematic piece of code :
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
lock (_myLockSerialPort)
{
string source = "No Data" ;
try
{
if (sender == null)
return;
SerialPort genericPort = (SerialPort)sender;
if (genericPort.Equals(ScalePort))
{
if (genericPort.BytesToRead > 0)
{
source = "Scale" ;
string datas = string.Empty;
datas = genericPort.ReadTo(_currentConfiguration.ReadToForPese);
ReadDataFromScale(genericPort, datas);
}
}
else
{
if (genericPort.BytesToRead > 0)
{
source = "PLC" ;
string datas = string.Empty;
datas = genericPort.ReadTo(_currentConfiguration.ReadToForPlc);
ReadDataFromPlc(genericPort, datas);
}
}
}
catch (TimeoutException timeoutException)
{
//Handle Timeout
}
catch (Exception err)
{
//Handle Other Errors
}
}
}
The question is if there is something in the ReadTo method or in the polling interval that can explain that behaviour.
I would underline that nothing in the data exchange format has changed, so the character used to terminate the communication are still in place (and present in the sniffed traffic).
NO, NO, NO!!! If you had a text file normally you would use ReadLine() method to read one line at a time. With ports or sockets data is coming at you at real time and the program is probably running faster than the data so you will not get a full line at one time. The serial uart in the PC has only a small read buffer (could be as small as 8 bytes). Windows is using timers to move the serial port data into a stream. Your program is reading the windows stream. So the actual data received is a combination of how fast the sender is sending data, the latency of the UART, the latency of Windows, and the latency of the Net Library. It is not a configuration issue. It is a real time issue.
I would recommend using two SerialPort classes, one for your scale and one for the PLC in which they would have their own DataReceived events. As others have suggested, you should append characters until you see your end character, then parse the string with your methods you wrote. Below is an example where Line Feed (LF) is my end character I'm looking for.
StringBuilder sbScale = new StringBuilder();
StringBuilder sbPLC = new StringBuilder();
char LF = (char)10;
private void serialPortScale_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string Data = serialPortScale.ReadExisting();
foreach (char c in Data)
{
if (c == LF)
{
sbScale.Append(c);
CurrentLine = sbScale.ToString();
sbScale.Clear();
ReadDataFromScale(CurrentLine);
}
else
{
sbScale.Append(c);
}
}
}
private void serialPortPLC_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string Data = serialPortPLC.ReadExisting();
foreach (char c in Data)
{
if (c == LF)
{
sbPLC.Append(c);
CurrentLine = sbPLC.ToString();
sbPLC.Clear();
ReadDataFromPlc(CurrentLine);
}
else
{
sbPLC.Append(c);
}
}
}
You could try to implement that into one DataReceived event, but it can be messy. I think have dedicated classes to each device is much cleaner and easier to read, especially if someone has to take over the code some day after you. Also I clear the string builder immediately, because the DataReceived event is threaded, so it can fire while you're already in there.
Goal:
Trying to write a string to a serial port, read it, then print it to console
Code:
// for waiting until event is detected
private static ManualResetEvent waitHandle = new ManualResetEvent(false);
public Driver()
{
// create new serial port
comPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
// add event handler
comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);
// configure port
comPort.DtrEnable = true;
comPort.RtsEnable = true;
comPort.ReadTimeout = 3000;
// open port
comPort.Open();
// send string through port
string command = "test \n";
byte[] MyMessage = System.Text.Encoding.UTF8.GetBytes(command);
comPort.Write(MyMessage, 0, MyMessage.Length);
// wait until event is detected
waitHandle.WaitOne();
}
private void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// Write data to buffer and stop wait
Console.WriteLine(comPort.ReadExisting());
waitHandle.Set();
}
Issue:
Write to serial seems to work fine (confirmed by using Serial Port Monitor) but "comPort_DataReceived" never gets called
If I change my code and add
while(true)
{
Console.WriteLine(comPort.ReadExisting());
}
Right after the "comPort.Write(MyMessage, 0, MyMessage.Length);" line so that I'm polling instead of waiting for the event handler then a whole lot of nothing gets written
If I try polling this way
while (true)
{
Byte[] buf = new Byte[2048];
comPort.Read(buf, 0, 2048);
Console.WriteLine(buf.ToString());
}
It just times out (System.TimeoutException: 'The operation has timed out.'
).
I'm not sure where I am going wrong/why I am unable to read from the serial port
Ok, from what I see it looks like there is no device listening on serial port. Then, if you write something to serial port it does not mean that the same data will occur as a received data. This data is outgoing data. If you want to receive data there must be another device connected to that serial port and sending data as a response to your data written.
Turns out it was a hardware issue (no device was writing to serial port) coupled with a misunderstanding (thinking I could write to a serial port and then read what I wrote from within the same program)
I'm issuing a NModBus call (setting a register to 1) to a sensor that then triggers a transmission of binary data from the sensor via the serial port.
As long as I only read/write registers via NModBus, everything works just fine but when I want to read this returning byte flow, it just doesn't work.
I've tried a few different approaches:
Using the same SerialPort
Using two different SerialPort objects (on the same port of course) in case there were trailing buffers
Reading "synchronously" immediately after the NModBus call
Reading using SerialPortDataReceived
Adding timeouts here and there (including adding waiting times on the sensor itself before starting transmission)
Etc.
Depending on the approach, I may read 4 (four) bytes but I think they are the ModBus answer to the write register call. No way to retrieve the rest of the flow (a few hundred bytes). And yes, in case you were wondering, I have verified via a separate tool that such flow is indeed transmitted. :-P
I have exchanged information via serial port in other programs with no particular problems so I was wondering if the fact that I'm using NModBus then standard serial port operations on the same port is generating concerns?
The code - that is supposed to be pretty basic - looks like:
using (SerialPort serialForModBus = new SerialPort(Port))
{
// Basic serial port settings
Open(serialForModBus);
// Create ModBus RTU master
IModbusSerialMaster master = CreateMaster(serialForModBus);
// Write register to trigger reading...
master.WriteMultipleRegisters(SLAVE_ID, SensorModBusRegisters.COMMAND_SEND_LOGS, new ushort[] { (ushort)1 });
}
// Now read...
using (SerialPort serialToReadLogs = new SerialPort(Port))
{
Open(serialToReadLogs);
serialToReadLogs.ReadTimeout = 10000;
// Externalize reading to separate class (that will also do the parsing..)
SensorLogSerialReader logReader = new SensorLogSerialReader();
serialToReadLogs.DataReceived += logReader.SerialPortDataReceived;
}
// In SensorLogSerialReader
public void SerialPortDataReceived(object senderSerialPort, SerialDataReceivedEventArgs e)
{
SerialPort sender = (SerialPort)senderSerialPort;
List<byte> bytes = new List<byte>();
try
{
bool moreToRead = (sender.BytesToRead != 0);
int cur;
while (moreToRead)
{
cur = sender.ReadByte();
bytes.Add((byte)cur);
moreToRead = (sender.BytesToRead != 0);
}
Done = true;
}
catch (...)
}
Any idea? Am I missing something "evident"?
Thank you!
Note: I've seen an post that may be similar but remained unanswered
I am trying to read 3 temperature devices using WinForms and the Modbus 485 protocol.
Basically I have to periodically write a command to each device, wait for response and when I get the response, process it. Each device has a unique communication adress. In order to periodically send the command I am using a timer.Timer1.interval=100;
This is how I am sending the command and where I am processing the response:
private void ProcessTimer_Tick(object sender, EventArgs e)
{
switch (tempState)
{
case TempTimerState.sendCommDevice1:
if (!tempSerial.IsOpen)
{
tempSerial.Open();
}
tempSerial.DiscardInBuffer();
communication.tempCommand[0] = 0x01; //device adress
communication.tempCommand[6] = 0xA5; //CRC
communication.tempCommand[7] = 0xC2; //CRC
tempSerial.Write(communication.tempCommand, 0, 8);
tempState = TempTimerState.recievedDevice1;
communication.waitTime = 0; //time to wait before throw a timeout exception
communication.dataRecievedTemp = false; //flag for response recieved
break;
case TempTimerState.recievedDevice1:
communication.waitTime++;
if (communication.dataRecievedTemp)
{
communication.waitTime = 0;
if(CheckCRC(communication.tempResponse)) //CRC checking
{
//process response
}
else
{
//handle CRC Failure error
}
}
if(commcommunication.waitTime>=maxWaitTime)
{
//handle Timeout exception
}
tempState=TempTimerState.sendCommDevice2;
break;
}
}
and so on for each device. This is my serialport data recieved event:
private void tempSerial_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
sp.Read(communication.tempResponse, 0, sp.BytesToRead);
communication.dataRecievedTemp = true; //flag for data recieved
}
So my communication should be :
send command device1
recieve response device1
send command device2
recieve command device2
send command device3
recieve command device3
and then send command device1 again. The problem is that I get sometimes get communication timeout error and I know for sure that all the devices are responding very quick and every time. Since I had preset the sp.ReceivedBytesThreshold=8I started to get CRC errors too. My response should always be 8 bytes long.
I think the problem is in the serial port data recieved event, but I can't see what's the problem.
P.S. I have also tried to set the timer interval to 1000 miliseconds, but that didn't solve my problem
Relying on ReceivedBytesThreshold is very brittle, the show is over when you get out of sync once. Your code is also very vulnerable to other reasons that DataReceived may fire, you are not checking the e.EventType property. Which certainly can be SerialData.Eof for a binary protocol.
Just write robust code that doesn't depend on the EventType nor the number of available bytes. Like this:
private byte[] rcveBuf = new byte[8];
private int rcveLen;
private void tempSerial_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
rcveLen += sp.Read(rcvebuf, rcveLen, rcveBuf.Length - rcveLen);
if (rcveLen == rcveBuf.Length) {
Array.Copy(rcveBuf, communication.tempResponse, rcveBuf.Length);
communication.dataRecievedTemp = true;
rcveLen = 0;
}
}
And reset rcveLen back to zero on a timeout. And make sure that the timeout isn't too low, you can lose many seconds if your program got swapped out, use 10 seconds to be safe.
Im trying to read the output of a device on a COM port on my PC.
Im wrote a C# program to do so.
Using PuTTY, I can see the output Im expecting from my device.
The problem is that the function SerialPort.ReadExisting(); in my DataReceived function gives my a completely different string.
What is the proper way to read from a COM port using SerialPort?
Also, the strings I get from SerialPort.ReadExisting(); are fragments of the string I sent to the device.
Below is the code that initializes the SerialPort.
SerialPort port;
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = port.ReadExisting();
}
void init_serialport(object sender, EventArgs e)
{
if (port != null)
{
port.Close();
}
port = new SerialPort( /* ... */ );
port.BaudRate = 9600;
port.Parity = Parity.None;
port.DataBits = 8;
port.StopBits = StopBits.One;
port.Handshake = Handshake.RequestToSend;
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
try
{
port.Open();
}
catch (Exception ex)
{
// ...
}
}
the strings I get from SerialPort.ReadExisting(); are fragments of the string I sent to the device.
I'd have a look at SerialPort.ReceivedBytesThreshold.
"Gets or sets the number of bytes in the internal input buffer before a DataReceived event occurs."
I would first take a look at the Read method of the port object, look at the raw bytes and verify that they match your expectations, which would then narrow down the problem to the encoding on the conversion to string.
More information on this is provided here.
You received fragments because SerialPort.Existing() executes and completes in less time then it takes for your sending device to send the entire string.
You need to repeat the call continuously or until you received the end of string character if the string has one.