Visual Basic > Visual Basic Forums > Visual Basic Language > How to count words received from a Serial Port VB.NET VB2005/2008/2010
Ask a questionAsk a question
 

AnswerHow to count words received from a Serial Port VB.NET VB2005/2008/2010

  • Sunday, October 25, 2009 4:59 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    I'm looking to write a few lines of code that listen to the COM port and count the number of words (as defined by the presence of a start bit) received during a set period of time.

    1.) I want to make sure I don't use a technique that might miss a word (don't want to be sampling the serial port in such a way that data can arrive between samples or can I assume low level buffering handles this transparently anyway)
    2.) I want to do this in a way that is efficient and doesn't consume more system resources than needed (for instance, should I be using interrupts?)

    Any code samples would be greatly appreciated, please provide .NET examples if possible as I know there have been substantial changes after VB6.

    Thanks

Answers

  • Monday, October 26, 2009 4:58 PMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     AnswerHas Code
    Hi ngmvista,

    Acamar, would you be kind enough to provide a snippet or two of code to demonstrate this?

    I'm not Acamar. However, assuming that you have a form into which you dropped - from the toolbox - a SerialPort class, named it SerialPort and setup its connection parameters, then you could do something like this (copy/paste + air-code):

    Private Sub SerialPort_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
       'Read the complete buffer
       dim strCurrentBuffer as string = SerialPort.ReadExisting
       'Retrieve the count of words in the buffer 
       Dim rx As New System.Text.RegularExpressions.Regex("\b\w+\b")
       dim intWordCount as integer = rx.Matches("This is a text-test with eight words.").Count
       '...
    end sub
    
    

    Now in order for that to work, you of course must've set the SP's parameters correctly, opened it and the device that you connected must actually transmit something.

    Cheers,
    Olaf
  • Monday, October 26, 2009 8:42 AMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Hi there,

    Hi Olaf, could you point me to some of the examples you refer to?  I've written some serial port code before, but I think when I did, I would go out and read the port at set intervals rather then listen to it for a period of time (hence my concern about missing data).

    I was just refering to the chat-app sample that is part of the MSDN reference and found in the link I posted. The chat-sample is very popular (which is what I meant by "ubiquitous"), so you'll find tons of them on the web. However, a Google-search for <vb.net serialport example> will yield many results that were created to solve other scenarios.

    Forget about your concern about loosing data. I too remember the times (IIRC that must've been more than 10 yrs back, though) when we utilized timers to poll data in certain intervals. The SerialPort class can cover that for you. I've seen some projects where the interval in which data is coming over the SP was very short, shorter than what regular cards can do, but that's a different story and actually really a hardware topic.

    So, with the DataReceived event that the SerialPort class offers, you'll be able to get informed every time data is being received. Or you could simply call the SerialPort.ReadLine() method in an endless loop to get data. Once you have your data, you can use a little RegEx to perform the counting of the words contained.

    Cheers,
    Olaf
  • Monday, October 26, 2009 8:40 PMDick Grier Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Do you want to count words or characters? 

    Each character (byte) will have a start and stop bit.  Counting is simple, you just use something like SerialPort.Read (or ReadExisting, for text data) to input data into an appropriate buffer.  The BufferName.Length (the name that you've use to DIM the variable used to read the data) property tells you how many characters are in the buffer.

    If you want words, that is, sets of characters that are separated by "white space" you can read data into a String buffer, and parse out each word using whatever "white space" characters might be used.  Simple text might use the Split funtion to separate words into a String array, using a simple " " argument.  For more complex definitions of words, you can use RegularExpressions.

    If I didn't fully understand your question, then I may need more on what you really are trying to do.

    Dick


    Dick Grier, MVP. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.
  • Monday, October 26, 2009 10:54 PMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    It depends.  If you are only interested in counting words (did you actually mean bytes?) then you would have to clear the buffer (DiscardInBuffer).  If you retrieve the data that has been transmitted with ReadExisting, the buffer is cleared for you (or, at least, the buffer is re-set to whatever is still unread - you should still check the buffer count after doing a ReadExisting).

  • Tuesday, October 27, 2009 8:08 AMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Hi ngmvista,

    Olar, thanks for the tips.  I assume it makes sense to flush the buffer after reading the Count?  Such that it will fill with a new count?

    actually, this really depends on the device that you're working with. Most of all devices transfer more or less small amounts of data and will do so once it is available (I'm assuming that your communication is device-to-PC and thus unidirectional).
    Almost all of the devices that I have "interfaced" were signalling some "start of text" (STX) and/or "end of text" (ETX) via <CR> or <CR><LF>. If a complete "chunk" of data is not being received in one shot, you'll have to create your own internal buffer, i.e. a StringBuilder that is declared on the class level and that is emptied when a transmission starts and appended to with each DataReceived event until the ETX-signal is receieved. But that's only necessary if you don't receive all your data in one shot.

    It might be better if you told us what sort of device you have (i.e. what data is being received and at what occasions) and what exactly it is that you're after.
    Regarding the comm-setup, it's out of the question though that you'll have to dig into the device-specifics and the communication-standard, if your device follows one.

    Cheers,
    Olaf

All Replies

  • Sunday, October 25, 2009 5:12 PMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi there,

    you don't really have to dig into the low-level stuff anymore as .Net 2 introduced the SerialPort class. The MSDN is a good starting point and also contains the ubiquitous chat-sample.

    Cheers,
    Olaf
  • Sunday, October 25, 2009 6:55 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi Olaf, could you point me to some of the examples you refer to?  I've written some serial port code before, but I think when I did, I would go out and read the port at set intervals rather then listen to it for a period of time (hence my concern about missing data).
  • Sunday, October 25, 2009 7:51 PMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    If you respond to the serial data received event of the serial port you will not miss data.  The 'words' you refer to will be returned as bytes.  They are buffered as received.  You can get a count of how many were received (ie, the size of the buffer) each time the event occurs.
  • Monday, October 26, 2009 8:42 AMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Hi there,

    Hi Olaf, could you point me to some of the examples you refer to?  I've written some serial port code before, but I think when I did, I would go out and read the port at set intervals rather then listen to it for a period of time (hence my concern about missing data).

    I was just refering to the chat-app sample that is part of the MSDN reference and found in the link I posted. The chat-sample is very popular (which is what I meant by "ubiquitous"), so you'll find tons of them on the web. However, a Google-search for <vb.net serialport example> will yield many results that were created to solve other scenarios.

    Forget about your concern about loosing data. I too remember the times (IIRC that must've been more than 10 yrs back, though) when we utilized timers to poll data in certain intervals. The SerialPort class can cover that for you. I've seen some projects where the interval in which data is coming over the SP was very short, shorter than what regular cards can do, but that's a different story and actually really a hardware topic.

    So, with the DataReceived event that the SerialPort class offers, you'll be able to get informed every time data is being received. Or you could simply call the SerialPort.ReadLine() method in an endless loop to get data. Once you have your data, you can use a little RegEx to perform the counting of the words contained.

    Cheers,
    Olaf
  • Monday, October 26, 2009 4:19 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Acamar, would you be kind enough to provide a snippet or two of code to demonstrate this?
  • Monday, October 26, 2009 4:58 PMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     AnswerHas Code
    Hi ngmvista,

    Acamar, would you be kind enough to provide a snippet or two of code to demonstrate this?

    I'm not Acamar. However, assuming that you have a form into which you dropped - from the toolbox - a SerialPort class, named it SerialPort and setup its connection parameters, then you could do something like this (copy/paste + air-code):

    Private Sub SerialPort_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
       'Read the complete buffer
       dim strCurrentBuffer as string = SerialPort.ReadExisting
       'Retrieve the count of words in the buffer 
       Dim rx As New System.Text.RegularExpressions.Regex("\b\w+\b")
       dim intWordCount as integer = rx.Matches("This is a text-test with eight words.").Count
       '...
    end sub
    
    

    Now in order for that to work, you of course must've set the SP's parameters correctly, opened it and the device that you connected must actually transmit something.

    Cheers,
    Olaf
  • Monday, October 26, 2009 8:40 PMDick Grier Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Do you want to count words or characters? 

    Each character (byte) will have a start and stop bit.  Counting is simple, you just use something like SerialPort.Read (or ReadExisting, for text data) to input data into an appropriate buffer.  The BufferName.Length (the name that you've use to DIM the variable used to read the data) property tells you how many characters are in the buffer.

    If you want words, that is, sets of characters that are separated by "white space" you can read data into a String buffer, and parse out each word using whatever "white space" characters might be used.  Simple text might use the Split funtion to separate words into a String array, using a simple " " argument.  For more complex definitions of words, you can use RegularExpressions.

    If I didn't fully understand your question, then I may need more on what you really are trying to do.

    Dick


    Dick Grier, MVP. Author of Visual Basic Programmer's Guide to Serial Communications 4. See www.hardandsoftware.net.
  • Monday, October 26, 2009 10:18 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Olar, thanks for the tips.  I assume it makes sense to flush the buffer after reading the Count?  Such that it will fill with a new count?
  • Monday, October 26, 2009 10:54 PMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    It depends.  If you are only interested in counting words (did you actually mean bytes?) then you would have to clear the buffer (DiscardInBuffer).  If you retrieve the data that has been transmitted with ReadExisting, the buffer is cleared for you (or, at least, the buffer is re-set to whatever is still unread - you should still check the buffer count after doing a ReadExisting).

  • Tuesday, October 27, 2009 8:08 AMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Hi ngmvista,

    Olar, thanks for the tips.  I assume it makes sense to flush the buffer after reading the Count?  Such that it will fill with a new count?

    actually, this really depends on the device that you're working with. Most of all devices transfer more or less small amounts of data and will do so once it is available (I'm assuming that your communication is device-to-PC and thus unidirectional).
    Almost all of the devices that I have "interfaced" were signalling some "start of text" (STX) and/or "end of text" (ETX) via <CR> or <CR><LF>. If a complete "chunk" of data is not being received in one shot, you'll have to create your own internal buffer, i.e. a StringBuilder that is declared on the class level and that is emptied when a transmission starts and appended to with each DataReceived event until the ETX-signal is receieved. But that's only necessary if you don't receive all your data in one shot.

    It might be better if you told us what sort of device you have (i.e. what data is being received and at what occasions) and what exactly it is that you're after.
    Regarding the comm-setup, it's out of the question though that you'll have to dig into the device-specifics and the communication-standard, if your device follows one.

    Cheers,
    Olaf
  • Tuesday, November 03, 2009 9:36 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    This function expects something to be passed in by Val e when it is called.  Could someone please describe what I should be passing in for the e parameter?  Or can I take that out of the subroutine declaration?

    Thanks

  • Tuesday, November 03, 2009 9:49 PMjinzai Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    No, you are not supposed to call that function...Windows will call it for you...its an Event handler...e is the event arguments that the SerialPort will fill in. This is a type of CALLBACK....do not call it in your code. (You can, but...its not really a good idea.)

    The address of that function is supposed to be provided to the SerialPort class...the Event DataReceived is where you tell it the address of your event handler.

    http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.datareceived.aspx
  • Tuesday, November 03, 2009 9:51 PMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    If you don;t have anything to put in the e parameter, or if the function isn't expecting anything, just pass an empty default argument -  New System.EventArgs

  • Wednesday, November 04, 2009 8:09 AMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi ngmvista,

    as jinzai said - if you refer to DataReceived, you're not supposed to call it as this is the event that is being fired by the SerialPort class when it detects data coming in on the serial port. You should rather write code in this event in order to react to the fact that there is data coming in.


    Cheers,
    Olaf
  • Friday, November 06, 2009 11:03 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    I see so basically when data starts comming into the serial port it creates an interrupt, per say, in the background in essence calling this routine?  I think I've got it.

    Thank you

  • Tuesday, November 17, 2009 3:57 AMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    Sorry to ask what may be obvious to the rest of you, but how would I replace the hard coded string "This is a text-test with eight words." with the strCurrentBuffer variable?

    Can I connect these with:

    dim intWordCount as integer = strCurrentBuffer.Matches.Count
    

    Without passing any parameters into Matches?

  • Tuesday, November 17, 2009 5:07 AMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    Matches requires a parameter.  You can pass a string literal ("This is a text-test with eight words.") or a string variable (the variable that contains the text you want counted).

    Dim intWordCount As Integer = rx.Matches(strCurrentBuffer).Count
    
    

    A similar approach would apply if you used the split function to count your words - you could pass a literal or a variable. Eg:

    Dim intWordCount As Integer = Split("This is a text-test with eight words.", " ").count
    'or
    Dim intWordCount As Integer = Split(strCurrentBuffer, " ").count
    
    

  • Tuesday, November 17, 2009 9:04 AMOlaf Rabbachin Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi ngmvista,

    in addition to what Acamar pointed out - if you intend to work with RegularExpressions, I suggest you download and use Expresso . Not only does it help to ease up constructing, testing and evaluating RegExes, it will also help you understand them better. BTW - Expresso can actually create code for you that, for a given RegEx will contain the various usage-scenarios of your RegEx, i.e. matching, splitting, etc.

    Cheers,
    Olaf
  • Saturday, November 21, 2009 9:29 PMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    And if I wanted to count characters it would simply be:

    Dim intCharCount As Integer = strCurrentBuffer.Length
    

    Correct?

    Well, if so, there it is for anyone else that may be interested.
  • Saturday, November 21, 2009 9:39 PMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    And if I wanted to count characters it would simply be:

    Dim intCharCount As Integer = strCurrentBuffer.Length
    
    

    Correct?

    Well, if so, there it is for anyone else that may be interested.

    Correct.  That count includes spaces, newline etc.
  • Sunday, November 22, 2009 2:04 AMngmvista Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code

    Looks like I've almost achieved my end goal, which I should clarify was to count characters not ASCII words.  I'm looking to have the NumericUpDown1.Value object increment by one everytime another character is received.  I've got it to increment to 1 when the first character is received but it seems to stay there and does not increment further.  I'm trying to think what I'm missing:

    Public Class Form1
    
        Dim intCharCountPublic As Integer
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            SerialPort1.Open()
        End Sub
    
        Private Sub SerialPort_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            'Read the complete buffer
            Dim strCurrentBuffer As String = SerialPort1.ReadExisting
            Dim intCharCount As Integer = strCurrentBuffer.Length
            intCharCountPublic = intCharCount
        End Sub
    
        Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
            NumericUpDown1.Value = intCharCountPublic
        End Sub
    
    End Class
    

    My ReadBufferSize is set to 4096

  • Sunday, November 22, 2009 2:26 AMAcamar Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    ReadExisting clears the buffer, so every time it executes the buffer count is set back to zero.  You should be adding to the count in the numericupdown, not just inserting the most recent count.

    Depending on what you need to do, make ONE of these changes:

    intCharCountPublic += intCharCount
    
    'or
    NumericUpDown1.Value += intCharCountPublic