none
Multiple execution of PinChanged Event of SerialPort

    Question

  • Hi,

     

    I am developing a Windows based application using Vb.Net 2.0.  I have used the SerialPort control to communicate with a FootPedal device connected to COM1 port. I have made all the settings required for the serial port control and also made DTREnable = true. I am handling the PinChanged event of the SerialPort control.

     

    The problem is that the PinChanged event executes more than once when I interact with the FootPedal device. I mean if I press a button on the FootPedal device only once, then also the PinChanged event executes more than once.

     

    Here is the configuration of the SerialPort control

    BaudRate = 9600

    DataBits = 8

    DtrEnable = True

    HandShake = None

    Parity = None

    ParityReplace = 63

    PortName = "COM1"

    StopBits = One

    ReadTimeOut = -1

    WriteTimeOut = -1

    RtsEnable = False

     

    The other Settings of SerialPort are as per their default values. The code written in the PinChanged event is as follows

     

    Private Sub SerialPort1_PinChanged(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged

    MsgBox(e.EventType.ToString())

    End Sub

     

    Can anyone tell me why the PinChanged event is getting fired more than once. I am missing some property. Please Help.... Urgent

     

    Thanks & Regards

     

    Vineed

     

    Thursday, March 06, 2008 7:52 AM

Answers

  • Oscilloscopes and Logic Analysers are very expensive, but very useful instruments used to make electrical signals visible on a screen. If you don't know what they are, you have probably not access to such instruments.

     

    To call Sleep() from the UI thread is not a good solution because it will block your UI. Generally, it is considered bad programming except for very special cases where there are no other resonable solutions. For example, Sleep(0) and Sleep(1) may be used for busy wait loops in case of bad drivers, which cannot raise an event, and it is necessary with e.g. a Sleep(200) statement before a serial port is reopened (between Close and Open). There has been a very long thread about this in the VB forum http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1939393&SiteID=1.

     

    By means of a flag, it is very easy to block your If statements in case of multiple PinChanged event. Just clear the flag when you are ready to receive and set it the first time the PinChanged event occur, but why do you want to block it? What do you try to achive? If a pin changes state more times, why don't you want to show this? Don't you confuse PinChanged with the DataReceived event you use to receive?

     

     

    Thursday, March 06, 2008 1:05 PM
  • So you are only using the modem control signals - not the serial communication? It is the serial communication, which fires the DataReceived event - not the device! I think that you confuse things. Read my totorial (previous link). It describes everything from multithreading to events and the UART. I have used more weeks to write it. If you (and many other beginners) would just use a few days to read it and try to understand it, it can bring you very far!

     

    "I haven't used the main thread for any reason, hence I am quite unaware as to the effects it might cause." Yes you have, but maybe you are not aware of this. The main thread is the place your program starts and it usually takes care of the user interface (UI). This is also described in details in the totorial. I agree with you that blocking the UI thread for 200 mS is not a big deal, but with a proper software structure, I don't think that it is necessary.

     

    Thursday, March 06, 2008 6:44 PM

All replies

  • The PinChanged event fires if one of the following pins changes state: CTS, DSR, RLSD and Break (break only when it goes high). See the chapter: "SerialPort Events" of this description http://www.innovatic.dk/knowledg/SerialCOM/SerialCOM.htm for a detailed description.

    Thursday, March 06, 2008 8:20 AM
  • Hi,

     

    Thanks for your time and effort. The article you provided me was of good help and cleared some of my concepts. The example provided was also worth a try. Now I am using The DSRHolding, CTSHolding and CDHolding properties to identify the EventType. However that has not solved my problem. The event still fires multiple times.

     

    The current code I have written is as follows -

     

    Code Snippet

    Private Sub SerialPort1_PinChanged(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged

    If (SerialPort1.DsrHolding) Then

    Label1.Text = Label1.Text & " DSR"

    End If

     

    If (SerialPort1.CdHolding) Then

    Label2.Text = Label2.Text & " CD"

    End If

     

    If (SerialPort1.CtsHolding) Then

    Label3.Text = Label3.Text & " CTS"

    End If

     

    End Sub

     

     

    If I still press a button on the device once, the event still fires more than once. One interesting fact is that if I put a msgbox or a MessageBox function inside any of the if conditions then the event is executed only once. Is it because the messagebox causes some sort of delay or some thing? I am still not able to identify the reason why the event is getting executed more than once and how to stop it.

     

    Please help. Thanks again

     

    Regards

     

    Vineed

     

    Thursday, March 06, 2008 10:01 AM
  • There is a mutual blocking between all events and SerialPort has no memory on the various events - it just reads the present status. This means that if you put a MessageBox inside one of the If statements, it will course a delay so that e.g. CtsHolding may no longer be true when the MessageBox is acknowledged.

     

    SerialPort is (also) rather foolish made when it comes to events. Because of the lacking buffer, an event may be long gone when the event handler gets up and running. It is like ringing a door bell and then get away quickly. When the door is opened, there is nobody outside. This may be what you see when you add the MessageBox, but even without a MessageBox the lacking buffer may create problems. I have the same problems with Break's used to separate high-speed (up to 921.6 kbit/s) telegrams. When the event handler for the PinChanged event gets up and running, the Break state is long gone. "Of course" Microsoft takes 11 bits from the UART, throws the three vital bits Break, Framing Error and Parity Error away and then stores the result in a 16-bit stream! How foolish can you be? SerialPort is stone age (and not even functional in .Net 3.5).

     

    I think that the reason why you receive more events is simply because the modem signal(s) change state more times. Have you tried to watch the signals with an oscilloscope or logic analyser?

    Thursday, March 06, 2008 11:00 AM
  • Hi,

     

    Thanks for that bit of information and yes I think Microsoft needs to rethink about this control (atleast its event handling). Can you tell me where I can get the oscilloscope or a logic analyser? I am very new in this SerialPort region and just have some basic understanding of how a COM port works.

     

    For the time being I have found a solution (atleast sort of). I have done a very simple trick. After the PinChanged event is first executed, I make the Thread to sleep for about 200 milliseconds. Since the thread is in a sleep mode the next   signal(s) sent by the device is not handled by the Thread and it works fine.

     

    However I would still like to learn if I can stop this device from sending multiple signals at one go or something similar. Please do tell me if this technique is a good way to handle the problem or will I end up in some other trouble?

     

    Thanks again

     

    Regards

     

    Vineed

    Thursday, March 06, 2008 11:51 AM
  • Oscilloscopes and Logic Analysers are very expensive, but very useful instruments used to make electrical signals visible on a screen. If you don't know what they are, you have probably not access to such instruments.

     

    To call Sleep() from the UI thread is not a good solution because it will block your UI. Generally, it is considered bad programming except for very special cases where there are no other resonable solutions. For example, Sleep(0) and Sleep(1) may be used for busy wait loops in case of bad drivers, which cannot raise an event, and it is necessary with e.g. a Sleep(200) statement before a serial port is reopened (between Close and Open). There has been a very long thread about this in the VB forum http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1939393&SiteID=1.

     

    By means of a flag, it is very easy to block your If statements in case of multiple PinChanged event. Just clear the flag when you are ready to receive and set it the first time the PinChanged event occur, but why do you want to block it? What do you try to achive? If a pin changes state more times, why don't you want to show this? Don't you confuse PinChanged with the DataReceived event you use to receive?

     

     

    Thursday, March 06, 2008 1:05 PM
  • Hi,

     

    Thanks again for your time and effort. I really appreciate the time you have taken to help me solve this problem

     

    Now, the FootPedal (the device which is connected to COM1 port) is basically a device used with Audio Players, etc. It has buttons specially for Play, Rewind and Forward. The problem I am facing is that the device does not fire the DataRecieved event. I checked several properties and found that it does return any sort of data for the data recieved event to fire. So the only event that work is the PinChanged event.

     

    Now, Since the event is getting executed more than once as described above, lets say the user hits the Rewind button The PinChanged event gets executed more than once and each time it rewinds the audio. So lets say the user wanted to rewind the audio for 5 seconds and the event got fired 3 times. In such a case the audio is rewinded for 15 secs (5 secs * 3 times). This is something that will surely annoy any user. The same scenario happens with the other buttons as well.

     

    So, when this event gets fired I want to stop further execution of the event. The logic I am using is that when the form gets loaded I open the COM Port. I only close the Port only when the user exits the application. To interact with the device I use the PinChanged event and I have written the following code.

     

    Code Snippet

    Private Sub SerialPort1_PinChanged(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged

    If (SerialPort1.DsrHolding) Then

    fnPlayPause() 'Function to Play or Pause

    Threading.Thread.Sleep(200) ' Making the Thread Sleep for 200 milliseconds

    ElseIf (SerialPort1.CDHolding) Then

    fnRewind() 'Function to Rewind

    Threading.Thread.Sleep(200) ' Making the Thread Sleep for 200 milliseconds

    ElseIf (SerialPort1.CtsHolding) Then

    fnForward() 'Function to Forward

    Threading.Thread.Sleep(200) ' Making the Thread Sleep for 200 milliseconds

    End If

    End Sub

     

     

     

    I agree with you on the fact the making the main thread go to sleep is not a good option, but does a pause for 200 milliseconds cause a major overhead to the application? I haven't used the main thread for any reason, hence I am quite unaware as to the effects it might cause. Please guide me.

     

    Thanks and Regards

     

    Vineed

    Thursday, March 06, 2008 1:37 PM
  • So you are only using the modem control signals - not the serial communication? It is the serial communication, which fires the DataReceived event - not the device! I think that you confuse things. Read my totorial (previous link). It describes everything from multithreading to events and the UART. I have used more weeks to write it. If you (and many other beginners) would just use a few days to read it and try to understand it, it can bring you very far!

     

    "I haven't used the main thread for any reason, hence I am quite unaware as to the effects it might cause." Yes you have, but maybe you are not aware of this. The main thread is the place your program starts and it usually takes care of the user interface (UI). This is also described in details in the totorial. I agree with you that blocking the UI thread for 200 mS is not a big deal, but with a proper software structure, I don't think that it is necessary.

     

    Thursday, March 06, 2008 6:44 PM
  • Hi,

     

    Thank you very much for your time and effort. I would surely go through the entire article. Currently, since the requirement was quite urgent I did not spend much time reading it.

     

    "I haven't used the main thread for any reason, hence I am quite unaware as to the effects it might cause." - Yes I do know about the main thread execution, what I actually meant was I have not played around with the main Thread like I was doing here, so just needed your guidance.

     

    Currently I am making the application work around with the Thread.Sleep method and once I read and understand your article completely I will modify it as per requirement. It will take a few days time and I hope I can change it. If I have any more queries regarding the article I will surely place a post here.

     

    Thanks for all your help.

     

    Regards

     

    Vineed

    Friday, March 07, 2008 4:37 AM
  • It looks to me that your major problem is the fact that this event fires continuously.

    I had to overcome the same problem because I am not interested in the pin state, just when it changes.

    So effectivley I have altered the logic of the event to notify me when a pin state changes, not every time it fires

     

    This way go can get rid of the sleeps you were thinking of using while your other funtions fire

     

    NOTE all I am doing is finding out which pin fired and it's state and putting the result in a textbox eg CTS0CTS-1DSR0

    Becuase I am putting the result in a textbox and serial port events are in a different thread to that of form controls I have to use the whole delegate thing to get around the cross threaded calls

     

    Heres the code I'm using to accomplish this;

    I have a couple of modular level variables defined

    Private CD_High As Boolean = False

    Private CTS_High As Boolean = False

    Private DSR_High As Boolean = False

    Private Sub SerialPort1_PinChanged(ByVal sender As Object, ByVal e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged

    Dim Fired As String = String.Empty

    If e.EventType = IO.Ports.SerialPinChange.CDChanged Then

    ' if the state of the reported pin is different to thta of my holding variable, do something about it

    If Not Me.CD_High = Me.SerialPort1.CDHolding Then

    Me.CD_High = Me.SerialPort1.CDHolding

    Fired = "CD" & Me.SerialPort1.CDHolding.ToString

    DisplayPinState(Fired)

    End If

    End If

    If e.EventType = IO.Ports.SerialPinChange.CtsChanged Then

    If Not Me.CTS_High = Me.SerialPort1.CtsHolding Then

    Me.CTS_High = Me.SerialPort1.CtsHolding

    Fired = "CTS" & Me.SerialPort1.CtsHolding.ToString

    DisplayPinState(Fired)

    End If

    End If

    If e.EventType = IO.Ports.SerialPinChange.DsrChanged Then

    If Not Me.DSR_High = Me.SerialPort1.DsrHolding Then

    Me.DSR_High = Me.SerialPort1.DsrHolding

    Fired = "DSR" & Me.SerialPort1.DsrHolding.ToString

    DisplayPinState(Fired)

    End If

    End If

     

    If e.EventType = IO.Ports.SerialPinChange.Break Then

    Fired = "Break"

    DisplayPinState(Fired)

    End If

    If e.EventType = IO.Ports.SerialPinChange.Ring Then

    Fired = "Ring"

    DisplayPinState(Fired)

    End If

    End Sub

     

    delegate routine for putting the value derived from one thread into a control of another thread.

     

    Delegate Sub DisplayPinStateDelegate(ByVal [text] As String)

    Private Sub DisplayPinState(ByVal [text] As String)

    If Me.txtPinState.InvokeRequired Then

    Dim d As New DisplayPinStateDelegate(AddressOf DisplayPinState)

    Me.Invoke(d, New Object() {[text]})

    Else

    Me.txtPinState.Text &= [text]

    End If

    End Sub

     

    Hope this helps

    Marcus

    Tuesday, April 22, 2008 10:06 PM
  • Two things i see.... one is electrical switch bounce, and two, those events fire on a change of state, and you must check the state after it fires.  For example, if you push a switch, the switch state would change twice.  The switch may be low to start, but pushing it cause it to go high, and releasing it causes it to go low, therefore, two state changes.  When the event fires, check the state of the CTS, DSR, etc line to see if it is high or low....

    If you are using a mechanical switch, then it is probably "bouncing"... see this..

    http://www.all-electric.com/schematic/debounce.htm


    Saturday, October 04, 2008 8:51 PM