locked
How to wait for a Serial Port to respond RRS feed

  • 問題

  • Hi

    Is it to possible to wait for a serialPort component to complete it's response? 

    I have a device connected to the serial port of my PC. I use the serialPort component in order to communicate with the device. I have to send 2 different strings of data to the serial port but the trick is that after sending the first string of data I have to wait for the serial port to respond. Then I have to check the response and decide whether I must send the second string or not. Also after sending the second string, I have to check also the response of the serial port.

    I tried to use an arbitrary amount of wait time between the sending of the two strings. Something like

    SerialPort.Write(...) ; Thread.Sleep(2000); SerialPort.Write(...);

    But the software failed when I tried it in a slower PC. So is there a way to replace the Thread.Sleep(2000) with a command that will wait until the serial port complete its response?

    Although, the response of the serial port is different between the two sending strings, I do know that the first response is only one byte and I know the ending byte of the second response. Also, the second response is no more than 30 bytes in length.

    Regards

    Dimitris

    2010年8月31日 上午 09:09

所有回覆

  • Use WriteLine and ReadLine.  Using ReadLine in the DataReceived event will keep the UI responsive.

    2010年8月31日 上午 10:23
  • gnout

    This is one of those situations, which from a first glance seems simple, but is actually fairly difficult to handle in .Net and Windows in an efficient and "clean" way. It is simple if you do it all from the UI thread, but it will block the UI thread for other tasks while the communication takes place, which may be unacceptable.

    You can also choose to do it from a background thread, but this is of course much more complicated and requires a lot of inter thread communication, which is difficult because you cannot send messages to a background thread unless it runs a message pump. Besides it is difficult to handle the read timeout. Note that read timeout does not work if you use the DataReceived event.

    1) How fast is your communication and how fast does your device respond? In other words, is the communication so fast that the user will not notice if the UI thread is dead while the poll's take place?

    2) Do you use ASCII or binary? ReadLine and WriteLine is only usable in an ASCII protocol where all telegrams are terminated with a given character and of course you cannot use ReadLine to receive a single byte - the first response.

     


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    2010年8月31日 上午 11:57
  • Carsten Kanstrup

    There is no UI. Just an empty screen that displays a couple of messages. The UI has nothing to do with the operation of the software. The device that I'm talking about is a mechanical device. It performs a very limited range of actions but every action takes a different amount of time to complete. In order to perform an action, first I have to send a command to the device (with SerialPort.Write(...)) in order to get the status of the device then wait for a response from the device. Second, depending on the response, I can continue by sending the second command (with SerialPort.Write(...)) or abort the operation.

    In order to send data to the device, I use the type "byte" and I'm assigning hexadecimal numbers like 0xA6 (VS 2005 and C#)

    Regards

    Dimitris

    2010年8月31日 下午 05:24
  • There is no UI. Just an empty screen that displays a couple of messages. The UI has nothing to do with the operation of the software.

    UI means user interface.  For example, a screen that displays messages.

    Use the DataReceived event to read your bytes into a buffer.  You'll actually have 3 buffers.  Parse your buffer to extract the messages.  You read all of the bytes you receive and extract the messages from the buffer.  The process is similar to parsing an image file.  You look for escape bytes to find the beginning or the end of a message and extract each message.

    2010年8月31日 下午 05:39
  • JohnWein

    I know what UI means. By answering to Carsten Kanstrup that there is no UI, I wanted to tell him that the computer screen has no importance to the whole process. I could remove the computer screen from the whole system. The "user" press a mechanical button and a barrier opens before him and let him enter to a restricted area. The computer screen displays the message "Please Enter"! The whole thing is not about the user interface or the "user" interacting with it or how fast is the UI or if the UI hangs the software or something else. Please, forget the UI. The question is not about the UI

    Now, regarding the rest of your answer. The DataReceived event of the SerialPort component occurs in a separate thread and independently of the flow of the rest of the program. Please, read the answer in the following post (http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/e36193cd-a708-42b3-86b7-adff82b19e5e/).

    So, when I call the first SerialPort.Write(...); the computer does NOT wait for the DataReceived event to complete or even "to be fired". The computer continues to the second SerialPort.Write(...) regardless of the DataReceived event. I think that there is the possibility that the DataReceived event can be fired once after the two calls to SerialPort.Write(...)! So, I don't think that the code inside the DataReceived event will solve my problem. As I said in my first post, I need a method to inform me that the DataReceived event has been completed after the first call to SerialPort.Write(...). By inserting the Thread.Sleep(2000) between the two calls to SerialPort.Write() I stopped the current process and give the "opportunity" the serial port (and the DataReceived event) to finish its operation. But the number of milliseconds in the Thread.Sleep is arbitrary and this method can fail in slow computer (like an embedded pc). So I'm looking for another approach to solve the problem.

    There is a very good article from Kim Hamilton (the guy who was responsible for the development of the .net SerialPort component) that clarifies some parts of the DataReceived event (http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx).

    Regards

    Dimitris

    2010年8月31日 下午 07:30
  • If you write to the port without waiting for a response, you can send as many messages as you wish.  The DataReceived event is only fired when a byte is received.  Typically one would send a message and wait for a returned valid message and then send another message.

    "As I said in my first post, I need a method to inform me that the DataReceived event has been completed after the first call to SerialPort.Write(...). 

    What do you mean by "the DataReceived event has been completed"  The event fires to advise that there is data in the buffer.  It continues to fire whenever bytes are received unless you have disabled the event.  It's up to you to read and parse the data to determine if you received a valid message somewhere within the buffer.

    2010年8月31日 下午 08:00
  • JohnWein

    "...Typically one would send a message and wait for a returned valid message and then send another message..."

    That's I was asking in the first place!!! How to wait for a returned valid message before sending another message!!! Please read my first post!

    "...What do you mean by ... within the buffer..."

    This is not accurate at all. Please read the Tip 1 from the article of Kim Hamilton. You assume that the data buffer remains the same during the execution of the DataReceived event. The data buffer can actually change during the execution of the DataReceived event! Please read the links that I send you. Consider the following code fragment.

     

    void OnDataReceived(object sender, SerialDataReceivedEventArgs e)

    {

        int StartBufferSize = SerialPort1.BytesToRead;

        Thread.Sleep(2000);

     

        int EndBufferSize = SerialPort1.BytesToRead;

    }

     

    The two variables (StartBufferSize and EndBufferSize) may differ!
    By the "the DataReceived event has been completed" I meant that since the OnDataReceived is running in separate thread, I wanted to know when that thread is finished.

     

    2010年8月31日 下午 08:39
  • A byte arrives, the DataReceived event is fired.  if you don't do anything, the event continues to be fired when bytes arrive.  You can't count on the event being fired for a certain threshold value.  And you can't rely on a certain number of bytes being available.  To maintain any kind of synchronization, you have to read the byte(s) within the event.  Use

    BytesRead = SerailPort.Read(MyBuffer,MyBufferOffset,BytesToRead)  //This statement is critical.  You can't count on expected values of the variables. 

    After the Read, MyBuffer will contain MyBufferOffset + BytesRead bytes.

    If it contains enough bytes, process for a message.  If not exit the event and wait for it to fire again.

    If you do your processing within the DataReceived event, it won't fire again until you exit the event.

    You have to find your received message,  there isn't any mechanism other than lines to parse data in the SerialPort.  (Reliably).  The timeouts and threshold could help.  But it usually best to leave them at their default values.

     

    There are actually three buffers:  The SerialStream, the Recieve buffer and MyBuffer.  MyBuffer is the only one you know what it contains at all times.
    2010年8月31日 下午 09:14
  • JohnWein

    Thank you for your effort but your answers doesn't seem to be very helpful. Please, understand that the problem isn't in the DataReceived event. The problem is outside the DataReceived event. The DataReceived event runs on a separate thread. "... The call to your DataReceived event may well be delayed, depending on system load..." as nobugz states in the first link that I send you. That means that there is a possibility that the DataReceived event fires only once after the execution of the two SerialPort.Write(...) calls. Your answers does not address this problem and that's exactly my issue.

    Please, view the following link (http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx) which is the description of the SerialPort class. Observe the example and how it uses the Thread.Start() and Thread.Join() methods in order to enclose inside this thread the DataReceived event of the SerialPort. Also please take a look at the following link (http://stackoverflow.com/questions/1584062/how-to-wait-for-thread-to-finish-with-net) for practices for waiting threads to finish.

    Again, thank you for your effort but maybe we should wait for another member to try and answer the question.

    Regards

    Dimitris

    2010年8月31日 下午 10:06
  • I have to agree.  Using the SerialPort seems to be a real challenge for most coders.  I have an engineering-technician background and it seems straight forward to me, but it seems to be quite a challenge for coders.  Carsten will advise you to read his tutorial and Dick Grier will encourage you to read his book.  You seem to be quite confused so retreating to an introductory level might help.  For your purposes, the MSDN SerialPort Class PortChat example should be adequate.
    2010年8月31日 下午 10:37
  • gnout

    The DataReceived event fires completely asynchronous with the transmitter as soon as the threshold level is reached or a ctrl-z character is received - as simple as that - see chapter "SerialPort Events" of my tutorial for a complete description.

    When I talk about the UI thread I mean the thread that runs the UI and therefore has a message pump. It really doesn't matter if you utilize the UI. It is the thread that is important.

    The problem is neither the DataReceived event nor the UI, but the mechanical delay. Since the user starts the sequence by pressing a button, you need to run your code on a thread, which has a message pump - see chapter "Events and Messages". You can choose to use the UI thread, but the problem is that the mechanical delay will block the UI thread while the system waits for the response.

    This is what we want to do:

    1) Send the first command.

    2) Call Read to wait for the single byte response or a timeout.

    3) Send the second command.

    4) Call ReadLine or call ReadByte in a loop to get the multi byte response or a timeout.

    This is easy to do if it doesn't matter if you block the thread or not. However, if you execute this sequence from the UI thread, other user actions will be blocked while the code waits for the responses. Note that we really don't want to use the DataReceived event! If we do, we have to make the timeout supervision ourselves and we need some inter thread communication since the DataReceived event is executed on a thread pool thread.

    Point 1 may be executed on the UI thread. It doesn't block the UI thread since the command is just transferred to the transmitter buffer. The tricky part is to execute point 2-4 without blocking all other actions. To do this in .Net and Windows, we need a new thread. You can for example use a BackgroundWorker and execute point 1-2 or just point 2 on that. When the job is done, you can use a new BackgroundWorker to exeute point 3-4 or just point 4. A more elegant way is to make a second UI thread - you can have as many as you like - and use that for the communication.

    You can of course also choose to use the DataReceived event, but since the event will not fire if no bytes are received, you need to implement a timeout function yourselves. The DataReceived event is great for asynchronous operation, but not quite as useful for synchronous operations like a poll.

    The most advanced way, which I have used before for these kind of operations (on embedded systems), is to program an event driven PLC (Programmable Logic Controller) interpreter. It is a great job, but then you just need to specify the relationship between signals with for example AND and OR functions and timers and don't need to worry about blocking anything due to delays. If I had made Windows, I would have made it that way. This could replace all threads with just 2-3 priority levels and in this way save a huge number of resources and turn Windows into a RTOS (Real Time Operating System).

     


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    2010年9月1日 上午 05:37
  • Carsten Kanstrup

    Thank you for the reply. Now, we 're getting somewhere! It seems that your proposition - solution is that what I wanted. I have a few questions.

    My first question is: in Point 2 you suggest that I should use the SerialPort.Read(...) method not inside the DataReceived event but after sending the first command. Something like SerialPort.Write(...); SerialPort.Read(...); My question is: after the last command the program will stop and wait until it receives at least 1 byte (or the number in the ReceivedBytesThreshold) from the serial port? Because that's exactly what I want! As you very well understood, the computer controls a mechanical system. The computer screen is optional! The "real" UI is the mechanical barrier that opens and closes. So, I do want the computer to stop program execution until it receives a byte to the serial port. That's why in my first version I use the Thread.Sleep(2000) between sending message to the serial port, to force the computer (or the current thread) to stop executing and wait to receive a byte to the serial port.

    The second question is for Point 4. There an example (http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx) on how to read data from a serial port. The example uses a loop to read data from the serial port inside a Thread.Start() and a Thread.Join() methods. Do you suggest that I should do something like that in order to get the multi byte response from the serial port?

    Thank you again for the reply. It was very helpful

    Regards

    Dimitris

    2010年9月1日 上午 08:20
  • If it doesn't matter that the UI thread is dead while waiting for the response, it is very simple - just make the above sequence 1-4 and call each Read in a try-catch statement so that you can catch the timeout in case your device is not responding to the poll (remember to set timeout time).

    The last response consists of a number of bytes where the last byte has a known value so it is fairly easy to catch the full telegram. Just put ReadByte into a Do - Loop Until where you call ReadByte until you have received the termination character. In case the response is ASCII, you can do this in a single statement using ReadLine, but you should be aware that all data are converted to 16-bit Unicode so this method should only be used in you really do receive Text. Something like this simplified code should do it:

    YourSerialPort.ReadTimeout = TimeInMilliseconds   ' Set the receiver timeout

    YourSerialPort.Write(ByteArray1, Offset, Count)  ' ByteArray1 must contain the first command.

        ' This is the only byte related send method there is. Don't use a Char based method unless you use ASCII.

    Try

        ReceivedByte = YourSerialPort.ReadByte

    Catch ex As Exception

        ' Do something in case of a timeout.

    End Try

    ' Make your decision according to the response you received (ReceivedByte)

    YourSerialPort.Write(ByteArray2, Offset, Count)  ' Send the second command using a new array or an offset in ByteArray1

    Dim ReceivedArray(Size) As Byte   ' Make an array to receive the second response

    Dim I As Integer = -1

    Do

        I = I + 1    ' First time I := 0

        Try

            ReceivedArray(I) =  YourSerialPort.ReadByte

        Catch ex As Exception

            ' Exit the loop and do something in case of a timeout

        End Try

     Loop Until ReceivedArray(I) = YourTerminationByte

     


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    • 已標示為解答 gnout 2010年9月1日 上午 09:08
    • 已取消標示為解答 gnout 2012年12月26日 下午 07:42
    2010年9月1日 上午 09:02
  • My experience has taught me whenever I write code for any communications between computers or between computers and devices is there is a critical starting point. What is the message protocol? It sounds like you are buried in the details of how to write code for this, when you do not understand the messages you must send and the expected response messages. Once you learn the messages to send and the messages to be received. Then learn how to send and receive and then learn how to fit the messages into a sequence and conditions that meet your system design.

    Code to Joy - aka Ludwig


    • 已編輯 NK3 2016年10月3日 下午 05:47
    2016年10月3日 下午 05:45
  • Hi dimitris ,

    i am working in a project and i am having the same problem . Did you finally manged to find a solution of your problem . If you have one can you please help me?

    2020年7月11日 上午 11:56
  • Hi dimitris ,

    i am working in a project and i am having the same problem . Did you finally manged to find a solution of your problem . If you have one can you please help me?

    Hi ATsiri

    It's been 10 years since I had this problem and a lot of things changed since then. I think that the answer from Carsten Kanstrup helped me to solve the issue.

    Maybe you can take a look at this particular answer.

    Dimitris

    2020年7月11日 下午 01:11