none
Serial Timer/Counter

    Question

  • Can anyone give sample VB.Net code examples of how to capture an on/off signal from a relay and count the seconds of elapsed time between the cycles on a com port? Also, which pins on the com port should be used for this? Any help with this would be greatly appreciated.

    Thank you,


    David
    • Edited by DavidIndy Tuesday, December 27, 2011 6:02 PM
    Tuesday, December 27, 2011 6:01 PM

Answers

  • Here are the correct pins:

    • Ground, Pin5
    • DCD (CarrierDetect), Pin1
    • CTS (ClearToSend), Pin8
    • DSR (DataSetReady), Pin6

    I may have misdentified these in my earlier message.  You do have to connect both an input and ground.  If you need to supply a voltage to the other open contact of the relay, it can come from DTR (DataTerminalReady), Pin4.

    The rate that you need to read is simple.  Also, you can use a Stopwatch object, and the Elapsedtime property.  There would be less arithmetic, but about the same amount of code.  Here is a simple example (the sample increment minutes, but this is easily changed.  Add a SerialPort control and two label controls, one named Counts, the other named Average.  This is a slightly simplified version of code that appears in my book

        'Copyright (c) Richard Grier 2011
        Private Shared Stopwatch As New Stopwatch
        Private Shared m_FormDefInstance As Form1
        Private Shared m_InitializingDefInstance As Boolean
        Public Shared Property DefInstance() As Form1
            Get
                If m_FormDefInstance Is Nothing OrElse _
                            m_FormDefInstance.IsDisposed Then
                    m_InitializingDefInstance = True
                    m_FormDefInstance = New Form1
                    m_InitializingDefInstance = False
                End If
                DefInstance = m_FormDefInstance
            End Get
            Set(ByVal Value As Form1)
                m_FormDefInstance = Value
            End Set
        End Property
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            With SerialPort1
                .PortName = "Com3"
                Try
                    .Open()
                    If .IsOpen Then
                        Me.Text = "Using " & .PortName
                    Else
                        Me.Text = "Unable to open port"
                    End If
                Catch ex As Exception
                End Try
            End With
            m_FormDefInstance = Me
            Counts.Text = "0"
            Average.Text = "0"
        End Sub
        Private Shared Counter As Integer
        Private Shared AveragePerMinute As Double
        Private Sub SerialPort1_PinChanged(sender As Object, e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged
            With Stopwatch
                If e.EventType = IO.Ports.SerialPinChange.CDChanged Then
                    If .IsRunning = False Then .Start()
                    Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                End If
            End With
        End Sub
        Private Delegate Sub ChangeCount()
        Private Shared Sub ChangeCounter()
            With m_FormDefInstance.SerialPort1
                If .CDHolding = True Then
                    m_FormDefInstance.Label1.BackColor = Color.Green
                    Counter += 1
                    If CInt(Stopwatch.Elapsed.TotalMinutes) > 0 Then AveragePerMinute = Counter / Stopwatch.Elapsed.TotalMinutes
                    m_FormDefInstance.Counts.Text = Counter.ToString
                    m_FormDefInstance.Average.Text = AveragePerMinute.ToString
                ElseIf .DsrHolding = False Then
                    m_FormDefInstance.Label1.BackColor = Color.Red
                End If
            End With
        End Sub

    The Input pin used is CarrierDetect (pin1).


    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.
    • Edited by Dick Grier Wednesday, December 28, 2011 7:10 PM
    • Proposed as answer by Mark Liu-lxfModerator Thursday, December 29, 2011 5:15 AM
    • Marked as answer by DavidIndy Thursday, December 29, 2011 1:59 PM
    Wednesday, December 28, 2011 7:08 PM
  • David,

    You have two question, so I hope this code fragment will answer both.

    What you are seeing with your relay is what is called "contact bounce."  That is, when the relay operates, the relay contacts close and then reopen and then close again as the relay operates (closes).  Contact bounce is much less likely when the relay opens (though, technically this can happen, our code will ignore it anyway).  Contact bounce is caused by the mechanics involved -- magnetic force, spring tension, inertia, etc.

    So, what is the solution?  There are two.  First, use a solid-state relay -- this will not bounce.  Second, add some "debounce" code to the PinChanged event code.  For example (I've renamed the Counter variable to CDCounter, to distinguish it from other inputs).

        Private Shared CDCounter As Integer
        Private Shared CTSCounter As Integer
        Private Shared DSRCounter As Integer
        Private Shared AveragePerMinute As Double
        Private Shared LastTime As Double
        Private DebounceTime As Double
        Private Const DebounceInterval As Double = 0.1  'set this constant to some non-zero (positive) number to allow for mechanical contact bounce - such as from a relay or switch
        Private Sub SerialPort1_PinChanged(sender As Object, e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged
            If e.EventType = IO.Ports.SerialPinChange.CDChanged Then
                If Stopwatch.IsRunning = False Then Stopwatch.Start()
            End If
            If e.EventType = IO.Ports.SerialPinChange.CDChanged Then
                If Stopwatch.IsRunning = False Then Stopwatch.Start()
                If SerialPort1.CDHolding = True Then
                    If Stopwatch.Elapsed.TotalSeconds > DebounceTime + DebounceInterval Then
                        CDCounter += 1
                        LastTime = Stopwatch.Elapsed.TotalSeconds
                        DebounceTime = LastTime
                        If UpdateUI.Checked Then Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                    End If
                End If
            End If
            If e.EventType = IO.Ports.SerialPinChange.CtsChanged Then
                If SerialPort1.CtsHolding = True Then
                    CTSCounter += 1
                    'LastTime = Stopwatch.Elapsed.TotalSeconds
                    'If UpdateUI.Checked Then Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                    'code similar to that for Carrier Detect
                End If
            End If
            If e.EventType = IO.Ports.SerialPinChange.DsrChanged Then
                If SerialPort1.DsrHolding = True Then
                    DSRCounter += 1
                    'LastTime = Stopwatch.Elapsed.TotalSeconds
                    'If UpdateUI.Checked Then Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                    'code similar to that for Carrier Detect
                End If
            End If
        End Sub

    So, what I've added is code that says, "don't count a pulse until any bounces have stopped."  This is the Const DebounceInterval value.  I used 0.1 seconds, which is fine for your system, I think, but a little overkill, so a smaller value might be used for shorter counting interval and it might be set to 0 for a purely electronic input, such as from a solid state relay.

    There are three input lines that might be used. My original code showed the CD input, but DSR and CTS also can be used (as sketched out in the block above). Thus, the same code can be used to count up to three inputs simultaneously, with appropriate additions similar to that used for the CD portion. If you need more inputs, you will have to expand the code to use more than one serial port. So, there would be some additional bookkeeping in the Configuration and other code "bits."

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.




    • Edited by Dick Grier Tuesday, June 19, 2012 1:43 AM
    • Marked as answer by DavidIndy Friday, June 22, 2012 4:46 AM
    Tuesday, June 19, 2012 1:39 AM
  • David,

    Cross-talk might be solved by connecting ground.  Go ahead and try this first. You should make sure that whatever cable you are using to connect to the relay also is shielded (the shield should be connected to ground on one side only).  If this doesn't solve the problem, then you might want to go with one of the heavier-duty solutions.  One possibility would be to use a RS-422 adapter, which uses a balanced, differential, input.  RS-232 is designed for very limited distances, and I expect you are exceeding that distance, while RS-422 is designed for up to 4000'. 

    There also are industrial-strength solutions, including connection via fiber optic cable, which would completely elliminate crosstalk and the possbility of ground loops.

    Now, realize that we are doing something here that really isn't part of the basic purpose for which serial communications hardware was designed.

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.

    • Marked as answer by DavidIndy Monday, June 25, 2012 7:04 PM
    Friday, June 22, 2012 4:41 PM
  • As to the relay question, you might look at http://www.bb-elec.com/ for various solutions, or perhaps something like (there are dozens of other possible manufacturers, including some designed for Arduno.  http://microcontrollershop.com/product_info.php?products_id=2196

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.

    • Marked as answer by DavidIndy Monday, June 25, 2012 7:04 PM
    Friday, June 22, 2012 4:46 PM

All replies

  • Hi, David try this one for finding time counting

     

     Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    
            Dim ProcessStartTime As Date = DateTime.Now
            'Do your process here
            '.......
            '.......
            '.......
            Dim ProcessEndTime As Date = DateTime.Now 
            Dim str As String = (ProcessEndTime - ProcessStartTime).ToString
            MsgBox(str)
    
        End Sub
    

    By

    A Pathfinder.

    JoSwa

     


    If you find an answer helpful, click the helpful button. If you find an answer to your question, mark it as the answer.
    Tuesday, December 27, 2011 6:12 PM
  • David,

    This may or may not be practical.  What rate do you expect?  If the "counts" are low speed (say fewer than 10-15 per second), then you can use Now.TimeOfDay.TotalMilliseconds to get the current time, and then do some arithmetic to calculate elapsed time.  The reason for my comment "low speed" is that the Windows serial port device drivers filter inputs from DSR (pin6) via the DSRHolding property, CTS (pin5) via the CTSHolding property and DCD (pin8) via the CDHolding property.  A SerialPort PinChanged event whenever on of these hardware inputs change state.

    You can use any of these three inputs equivalently.

    If the input state changes at rates higher tha 10-15 per second, you will need to use an external counter timer, and use whatever API is appropriate to communicate with that hardware.  These CT devices might be serial or USB, for example.

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.
    Tuesday, December 27, 2011 7:42 PM
  • Thank you very much for your speedy reply. This looks like it would work well, however my bigger problem is figuring out what code I would need to pick up the signal from my relay and which pins on the com port to hook the 2 wires from the relay to. The typical cycle time for this realy would be about 30 seconds between on and off.

    Thanks again,


    David
    Tuesday, December 27, 2011 7:57 PM
  • Thank you for your quick reply. The rate would typically be about 30 seconds between on and off. In the end, I'm attempting to hook 2 wires to a relay on a few machines and count the cycle time on each machine (About 30 seconds normally). Forgive me for my ignorance, but would I have to use 1 of the pins mentioned above plus a signal ground pin to capture the on-off signal and if so, do you know what the code would look like specifically? Also, I will check out your website. Thank you for that as well.

    Thanks again,


    David
    • Edited by DavidIndy Tuesday, December 27, 2011 8:07 PM
    Tuesday, December 27, 2011 8:07 PM
  • Here are the correct pins:

    • Ground, Pin5
    • DCD (CarrierDetect), Pin1
    • CTS (ClearToSend), Pin8
    • DSR (DataSetReady), Pin6

    I may have misdentified these in my earlier message.  You do have to connect both an input and ground.  If you need to supply a voltage to the other open contact of the relay, it can come from DTR (DataTerminalReady), Pin4.

    The rate that you need to read is simple.  Also, you can use a Stopwatch object, and the Elapsedtime property.  There would be less arithmetic, but about the same amount of code.  Here is a simple example (the sample increment minutes, but this is easily changed.  Add a SerialPort control and two label controls, one named Counts, the other named Average.  This is a slightly simplified version of code that appears in my book

        'Copyright (c) Richard Grier 2011
        Private Shared Stopwatch As New Stopwatch
        Private Shared m_FormDefInstance As Form1
        Private Shared m_InitializingDefInstance As Boolean
        Public Shared Property DefInstance() As Form1
            Get
                If m_FormDefInstance Is Nothing OrElse _
                            m_FormDefInstance.IsDisposed Then
                    m_InitializingDefInstance = True
                    m_FormDefInstance = New Form1
                    m_InitializingDefInstance = False
                End If
                DefInstance = m_FormDefInstance
            End Get
            Set(ByVal Value As Form1)
                m_FormDefInstance = Value
            End Set
        End Property
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            With SerialPort1
                .PortName = "Com3"
                Try
                    .Open()
                    If .IsOpen Then
                        Me.Text = "Using " & .PortName
                    Else
                        Me.Text = "Unable to open port"
                    End If
                Catch ex As Exception
                End Try
            End With
            m_FormDefInstance = Me
            Counts.Text = "0"
            Average.Text = "0"
        End Sub
        Private Shared Counter As Integer
        Private Shared AveragePerMinute As Double
        Private Sub SerialPort1_PinChanged(sender As Object, e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged
            With Stopwatch
                If e.EventType = IO.Ports.SerialPinChange.CDChanged Then
                    If .IsRunning = False Then .Start()
                    Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                End If
            End With
        End Sub
        Private Delegate Sub ChangeCount()
        Private Shared Sub ChangeCounter()
            With m_FormDefInstance.SerialPort1
                If .CDHolding = True Then
                    m_FormDefInstance.Label1.BackColor = Color.Green
                    Counter += 1
                    If CInt(Stopwatch.Elapsed.TotalMinutes) > 0 Then AveragePerMinute = Counter / Stopwatch.Elapsed.TotalMinutes
                    m_FormDefInstance.Counts.Text = Counter.ToString
                    m_FormDefInstance.Average.Text = AveragePerMinute.ToString
                ElseIf .DsrHolding = False Then
                    m_FormDefInstance.Label1.BackColor = Color.Red
                End If
            End With
        End Sub

    The Input pin used is CarrierDetect (pin1).


    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.
    • Edited by Dick Grier Wednesday, December 28, 2011 7:10 PM
    • Proposed as answer by Mark Liu-lxfModerator Thursday, December 29, 2011 5:15 AM
    • Marked as answer by DavidIndy Thursday, December 29, 2011 1:59 PM
    Wednesday, December 28, 2011 7:08 PM
  • Dick,

    Tis is great! Thank you very much for taking the time on this. I am going to purchase your book today. I want to understand how you come up with this type of code and hopefully your book will help me to understand VB better.

    Thanks again,

     


    David
    Thursday, December 29, 2011 2:04 PM
  • Dick,

    First, I must say that your programming is Awesome! I am having fun with this one. It did take me a while to figure out how to make this work with a DB25 pin connector because for some reason I have to use pins 4 and 8 to get the signal and cycle times to work, but it works perfectly. If you have the patience for me, my next questions are; if I have multiple signals exactly like your program works  now, how do I seperate them in the same program. In other words, now I can see the cycle of each signal from pins 4 and 8, but how can I use the other pins to get more than one cycle at a time? By the way, I appreciate your speedy response on shipping your book. I'm sure that this will explain much of what I'm trying to accomplish, but your expertise is well beyond what I understand about serial communications and the .net language.

    Thank you,


    David
    Saturday, December 31, 2011 3:38 AM
  • Hello Dick,

    This code works great with the exception of when counter += 1 fires, I get a count of 3 rather than 1. I put a relay on my machine that gives me a "On" signal about every 45 seconds and the contact is made for about 2 seconds. Any idea of how to get the count to 1 rather than 3?

    Thank you,


    David


    • Edited by DavidIndy Friday, June 15, 2012 3:44 PM
    Friday, June 15, 2012 3:23 PM
  • David,

    You have two question, so I hope this code fragment will answer both.

    What you are seeing with your relay is what is called "contact bounce."  That is, when the relay operates, the relay contacts close and then reopen and then close again as the relay operates (closes).  Contact bounce is much less likely when the relay opens (though, technically this can happen, our code will ignore it anyway).  Contact bounce is caused by the mechanics involved -- magnetic force, spring tension, inertia, etc.

    So, what is the solution?  There are two.  First, use a solid-state relay -- this will not bounce.  Second, add some "debounce" code to the PinChanged event code.  For example (I've renamed the Counter variable to CDCounter, to distinguish it from other inputs).

        Private Shared CDCounter As Integer
        Private Shared CTSCounter As Integer
        Private Shared DSRCounter As Integer
        Private Shared AveragePerMinute As Double
        Private Shared LastTime As Double
        Private DebounceTime As Double
        Private Const DebounceInterval As Double = 0.1  'set this constant to some non-zero (positive) number to allow for mechanical contact bounce - such as from a relay or switch
        Private Sub SerialPort1_PinChanged(sender As Object, e As System.IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged
            If e.EventType = IO.Ports.SerialPinChange.CDChanged Then
                If Stopwatch.IsRunning = False Then Stopwatch.Start()
            End If
            If e.EventType = IO.Ports.SerialPinChange.CDChanged Then
                If Stopwatch.IsRunning = False Then Stopwatch.Start()
                If SerialPort1.CDHolding = True Then
                    If Stopwatch.Elapsed.TotalSeconds > DebounceTime + DebounceInterval Then
                        CDCounter += 1
                        LastTime = Stopwatch.Elapsed.TotalSeconds
                        DebounceTime = LastTime
                        If UpdateUI.Checked Then Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                    End If
                End If
            End If
            If e.EventType = IO.Ports.SerialPinChange.CtsChanged Then
                If SerialPort1.CtsHolding = True Then
                    CTSCounter += 1
                    'LastTime = Stopwatch.Elapsed.TotalSeconds
                    'If UpdateUI.Checked Then Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                    'code similar to that for Carrier Detect
                End If
            End If
            If e.EventType = IO.Ports.SerialPinChange.DsrChanged Then
                If SerialPort1.DsrHolding = True Then
                    DSRCounter += 1
                    'LastTime = Stopwatch.Elapsed.TotalSeconds
                    'If UpdateUI.Checked Then Me.BeginInvoke(New ChangeCount(AddressOf ChangeCounter))
                    'code similar to that for Carrier Detect
                End If
            End If
        End Sub

    So, what I've added is code that says, "don't count a pulse until any bounces have stopped."  This is the Const DebounceInterval value.  I used 0.1 seconds, which is fine for your system, I think, but a little overkill, so a smaller value might be used for shorter counting interval and it might be set to 0 for a purely electronic input, such as from a solid state relay.

    There are three input lines that might be used. My original code showed the CD input, but DSR and CTS also can be used (as sketched out in the block above). Thus, the same code can be used to count up to three inputs simultaneously, with appropriate additions similar to that used for the CD portion. If you need more inputs, you will have to expand the code to use more than one serial port. So, there would be some additional bookkeeping in the Configuration and other code "bits."

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.




    • Edited by Dick Grier Tuesday, June 19, 2012 1:43 AM
    • Marked as answer by DavidIndy Friday, June 22, 2012 4:46 AM
    Tuesday, June 19, 2012 1:39 AM
  • Dick,

    It amazes me how you can nail the problem so easily. Your above solution stopped the bounce count perfectly, but now I have an issue with multiple counts when an auxiliary piece of equipment kicks on which has no connection with the output that I'm connected to. I'm wondering if I need to hook up the ground pin of the RS232 to the ground on the machine? What I have now is a USB to RS232, 25 pin with pins 4 and 8 hooked to the mechanical relay and the above code. Do I need to hook up the ground to stop the interference, or am I missing something else? I hope I'm not being annoying, but lastly, on the digital relay, would that need to be like an Arduino, or just a stand alone relay?

    Many thanks,


    David

    Friday, June 22, 2012 4:46 AM
  • David,

    Cross-talk might be solved by connecting ground.  Go ahead and try this first. You should make sure that whatever cable you are using to connect to the relay also is shielded (the shield should be connected to ground on one side only).  If this doesn't solve the problem, then you might want to go with one of the heavier-duty solutions.  One possibility would be to use a RS-422 adapter, which uses a balanced, differential, input.  RS-232 is designed for very limited distances, and I expect you are exceeding that distance, while RS-422 is designed for up to 4000'. 

    There also are industrial-strength solutions, including connection via fiber optic cable, which would completely elliminate crosstalk and the possbility of ground loops.

    Now, realize that we are doing something here that really isn't part of the basic purpose for which serial communications hardware was designed.

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.

    • Marked as answer by DavidIndy Monday, June 25, 2012 7:04 PM
    Friday, June 22, 2012 4:41 PM
  • As to the relay question, you might look at http://www.bb-elec.com/ for various solutions, or perhaps something like (there are dozens of other possible manufacturers, including some designed for Arduno.  http://microcontrollershop.com/product_info.php?products_id=2196

    Dick


    Dick Grier. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.

    • Marked as answer by DavidIndy Monday, June 25, 2012 7:04 PM
    Friday, June 22, 2012 4:46 PM
  • Dick,

    You are a genius! The above block and this one are great information. The cross-talk stopped when I hooked pin 1 protective ground of the DB25 pin connector to the ground on the machine. In the long run I will probably get the output board from your link above, and use the RS 422 for the greater distance to attach everything more permanently after all the testing is done. Once again I really appreciate your help in providing exactly what I need to make this work.

    Thanks so much, 


    David

    Monday, June 25, 2012 7:15 PM