Link to home
Start Free TrialLog in
Avatar of milindsaraswala
milindsaraswala

asked on

Reading from Serial Port in VB.net

I am trying to read Portal Data Collector through Serial Port. I am success to do it but the problem is that when I try to run in debug mod by press F10 it will work but when I try to run EXE than it stop at some place and don't give any error.

Next problem in this it is reading same Line twice. I think there is problem in thread . Because when I try to write in textbox it will give me thread error.

My code for designer and code behind is as follows
 
Kindly help me to rectify my logical error
Imports System.Threading
Imports System.IO.Ports
Imports System.Text

Public Class frmScanPalReading

  Dim ReadBuffer As String = ""
  Dim _continue As Boolean = False

  Private Sub frmScanPalReading_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
    SerialPort.Close()
  End Sub
  Private Sub frmScanPalReading_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Try
      With SerialPort
        .PortName = "COM1"
        .Handshake = IO.Ports.Handshake.RequestToSend
        .ReceivedBytesThreshold = 1
        .RtsEnable = True
        .BaudRate = 115200
        .DataBits = 8
        .Parity = IO.Ports.Parity.None
        .StopBits = IO.Ports.StopBits.One

        .Open()
      End With
    Catch ex As Exception
      MsgBox(ex.Message)
    End Try
  End Sub

  Private Sub btnExtract_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExtract.Click
    SendCommand("READ")
  End Sub

  Sub SendCommand(ByVal cmd As String)
    SerialPort.Write(cmd + Chr(13))
  End Sub



  Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
    'This event will Receive the data from the selected COM port..
    Dim thRec As Thread
    If e.EventType = SerialData.Chars Then
      thRec = New Thread(AddressOf ReceiveData)
      thRec.IsBackground = True
      thRec.Priority = ThreadPriority.Highest
      thRec.Start()
      Thread.Sleep(2)
    End If
  End Sub

  Private Sub ReceiveData()
    'Sub to Receive Data from the Serial Port, Will Run in a Thread
    Dim bRead, nRead As Integer, crPOS As Integer
    Dim returnStr As String = ""
    Dim ascStr As String = ""
    Dim m As String = ""
    Dim i As Int16 = 1

    bRead = SerialPort.BytesToRead 'Number of Bytes to read
    Dim cData(bRead - 1) As Byte

    SerialPort.Encoding = Encoding.GetEncoding(65001)

    nRead = SerialPort.Read(cData, 0, bRead)  'Reading the Data
    If _continue = False Then crPOS = ByteSearch(cData, "13")

    For Each b As Byte In cData
      If b <> 0 Then
        If crPOS >= i AndAlso _continue = False Then
          ascStr += Chr(b)
          i += 1
        Else
          returnStr += Chr(b)
          _continue = True
        End If
      End If
    Next
    Debug.WriteLine(m)
    If returnStr <> "OVER" AndAlso returnStr <> "" Then
      ReadBuffer += returnStr           'ASCII String

      SendCommand("ACK")
    End If
    Console.Write("==Begin=={0}==End==", ReadBuffer.ToString)
  End Sub

  Function ByteSearch(ByVal bData() As Byte, ByVal SearchString As String) As Int16
    Dim i As Int16 = 1
    For Each b As Byte In bData
      If b = SearchString Then
        Exit For
      End If
      i += 1
    Next
    Return i
  End Function
End Class

Open in new window

<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class frmScanPalReading
  Inherits System.Windows.Forms.Form

  'Form overrides dispose to clean up the component list.
  <System.Diagnostics.DebuggerNonUserCode()> _
  Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    If disposing AndAlso components IsNot Nothing Then
      components.Dispose()
    End If
    MyBase.Dispose(disposing)
  End Sub

  'Required by the Windows Form Designer
  Private components As System.ComponentModel.IContainer

  'NOTE: The following procedure is required by the Windows Form Designer
  'It can be modified using the Windows Form Designer.  
  'Do not modify it using the code editor.
  <System.Diagnostics.DebuggerStepThrough()> _
  Private Sub InitializeComponent()
    Me.components = New System.ComponentModel.Container
    Me.txData = New System.Windows.Forms.RichTextBox
    Me.btnExtract = New System.Windows.Forms.Button
    Me.SerialPort = New System.IO.Ports.SerialPort(Me.components)
    Me.SuspendLayout()
    '
    'txData
    '
    Me.txData.Location = New System.Drawing.Point(12, 29)
    Me.txData.Name = "txData"
    Me.txData.Size = New System.Drawing.Size(684, 345)
    Me.txData.TabIndex = 0
    Me.txData.Text = ""
    '
    'btnExtract
    '
    Me.btnExtract.Location = New System.Drawing.Point(287, 393)
    Me.btnExtract.Name = "btnExtract"
    Me.btnExtract.Size = New System.Drawing.Size(75, 23)
    Me.btnExtract.TabIndex = 1
    Me.btnExtract.Text = "Extract"
    Me.btnExtract.UseVisualStyleBackColor = True
    '
    'frmScanPalReading
    '
    Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
    Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
    Me.ClientSize = New System.Drawing.Size(708, 446)
    Me.Controls.Add(Me.btnExtract)
    Me.Controls.Add(Me.txData)
    Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
    Me.MaximizeBox = False
    Me.MinimizeBox = False
    Me.Name = "frmScanPalReading"
    Me.Text = "Scan Pal Data Reading"
    Me.ResumeLayout(False)

  End Sub
  Friend WithEvents txData As System.Windows.Forms.RichTextBox
  Friend WithEvents btnExtract As System.Windows.Forms.Button
  Friend WithEvents SerialPort As System.IO.Ports.SerialPort

End Class

Open in new window

Avatar of milindsaraswala
milindsaraswala

ASKER

I change my code now but still it is not working. Help me Experts. Sometime it is reading code but some time it is not. When I debug the code it is showing data in the string and it will show in the message box but the run the application string is showing blank and in else part when I try to write in text box it is giving error like

Cross-thread operation not valid
Imports System.Threading
Imports System.IO.Ports
Imports System.Text

Public Class frmScanPalReading

  Dim ReadBuffer As String = ""
  Dim _continue As Boolean = False

  Private Sub frmScanPalReading_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
    SerialPort.Close()
  End Sub
  Private Sub frmScanPalReading_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Try
      With SerialPort
        .PortName = "COM1"
        .Handshake = IO.Ports.Handshake.RequestToSend
        .ReceivedBytesThreshold = 1
        .RtsEnable = True
        .BaudRate = 115200
        .DataBits = 8
        .Parity = IO.Ports.Parity.None
        .StopBits = IO.Ports.StopBits.One

        .Open()
      End With
    Catch ex As Exception
      MsgBox(ex.Message)
    End Try
  End Sub

  Private Sub btnExtract_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExtract.Click
    SendCommand("READ")
  End Sub

  Sub SendCommand(ByVal cmd As String)
    SerialPort.Write(cmd + Chr(13))
  End Sub



  Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
    'This event will Receive the data from the selected COM port..
    ReceiveData()
    'Dim thRec As Thread
    'If e.EventType = SerialData.Chars Then
    '  thRec = New Thread(AddressOf ReceiveData)
    '  thRec.IsBackground = True
    '  thRec.Priority = ThreadPriority.Highest
    '  thRec.Start()
    '  Thread.Sleep(2)
    'End If
  End Sub

  Private Sub ReceiveData()
    Dim cmd As String = "", _buffer = SerialPort.ReadExisting
    Dim crPos As Integer

    crPos = InStr(_buffer, Chr(13))
    If crPos > 1 Then cmd = Microsoft.VisualBasic.Left(_buffer, crPos - 1)

    If cmd = "ACK" AndAlso _continue = False Then
      ReadBuffer = ReadBuffer & Mid(_buffer, crPos + 2)
      _continue = True
      SendCommand(cmd)
    ElseIf cmd <> "OVER" AndAlso _continue = True Then
      ReadBuffer = ReadBuffer & _buffer
      SendCommand("ACK")
      MsgBox(ReadBuffer)
    Else
      txData.Text = ReadBuffer
      'MsgBox(ReadBuffer)
    End If

    'Sub to Receive Data from the Serial Port, Will Run in a Thread
    'Dim bRead, nRead As Integer, crPOS As Integer
    'Dim returnStr As String = ""
    'Dim ascStr As String = ""
    'Dim m As String = ""
    'Dim i As Int16 = 1

    'bRead = SerialPort.BytesToRead 'Number of Bytes to read
    'Dim cData(bRead - 1) As Byte

    'SerialPort.Encoding = Encoding.GetEncoding(65001)

    'nRead = SerialPort.Read(cData, 0, bRead)  'Reading the Data
    'If _continue = False Then crPOS = ByteSearch(cData, "13")

    'For Each b As Byte In cData
    '  If b <> 0 Then
    '    If crPOS >= i AndAlso _continue = False Then
    '      ascStr += Chr(b)
    '      i += 1
    '    Else
    '      returnStr += Chr(b)
    '      _continue = True
    '    End If
    '  End If
    'Next
    'Debug.WriteLine(m)
    'If returnStr <> "OVER" AndAlso returnStr <> "" Then
    '  ReadBuffer += returnStr           'ASCII String

    '  SendCommand("ACK")
    'End If
    'Console.Write("==Begin=={0}==End==", ReadBuffer.ToString)
  End Sub

  Function ByteSearch(ByVal bData() As Byte, ByVal SearchString As String) As Int16
    Dim i As Int16 = 1
    For Each b As Byte In bData
      If b = SearchString Then
        Exit For
      End If
      i += 1
    Next
    Return i
  End Function
End Class

Open in new window

I did this before but it hang my application. if you see my code I have to pass  Read. Then only it will go ahead. Then I have to pass ACK(i.e. Acknowledge) to read another line. So kindly look at my codes try remove bug from it.
There are at least two problems I see with this code:
(1) The thread ReceiveData is run everytime the DataReceived event occurs. This might result in multple threads trying simultaneously to read from SerialPort. The correct implementation would start only one thread when the application starts (or some other initial event), and that thread should read data from SerialPort when the DataReceived event occurs. You might need to signal the read through a Semaphore or similar cross-thread signal from DataReceived event handler to the single thread.
(2) Forms objects like TextBox should be accessed only from the thread on which they were created. The line
     txData.Text = ReadBuffer
in ReceiveData will throw an error. One way to work around this is to call txData.Invoke().
Kindly can u give me some sort of code to it. I will be very helpful for the same
Following is in C#. I hope you can write similar in VB. I did not compile or run this, but it should give you the idea of where to go.

    private class MySerialStream : SerialStream
    {
      private Thread rxThread = null;
      private object RxThreadSync = new object();

      public SerialStream()
      {
        rxThread = new Thread(new ThreadStart(this.RxThread));
        rxThread.IsBackground = true;
        rxThread.Name = "SerialStream RxThread";
        rxThread.Start();
        this.DataReceived += new SerialDataReceivedEventHandler(serialStream_DataReceived);
      }

      private void serialStream_DataReceived(object sender, SerialDataReceivedEventArgs e)
      {
        lock (this.RxThreadSync)
        {
          Monitor.Pulse(this.RxThreadSync);
        }
      }

      private void RxThread()
      {
        while (this.IsOpen)
        {
          try
          {
            lock (this.RxThreadSync)
            {
              int available = this.BytesToRead;
              byte[] tmpBuf = new byte[available];
              this.Read(tmpBuf, 0, available);
            }
          }
          catch (Exception ex)
          {
          }
        }
      }
    }

Open in new window

Sorry I am VB.net coder I don't understand C#  Kindly can you provide me in consent of following code. Please ..............
Imports System.Threading
Imports System.IO.Ports
Imports System.Text

Public Class frmScanPalReading

  Dim ReadBuffer As String = ""

  Private rxThread As Thread = Nothing
  Private RxThreadSync As New Object()



  Private Sub frmScanPalReading_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
    SerialPort.Close()
  End Sub

  Private Sub frmScanPalReading_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Try
      With SerialPort
        .PortName = "COM1"
        .Handshake = IO.Ports.Handshake.RequestToSend
        .ReceivedBytesThreshold = 1
        .RtsEnable = True
        .BaudRate = 115200
        .DataBits = 8
        .Parity = IO.Ports.Parity.None
        .StopBits = IO.Ports.StopBits.One

        .Open()
      End With
    Catch ex As Exception
      MsgBox(ex.Message)
    End Try
  End Sub

  Private Sub btnExtract_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExtract.Click
    SendCommand("READ")
  End Sub

  Sub SendCommand(ByVal cmd As String)
    SerialPort.Write(cmd + Chr(13))
  End Sub



  Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
    'This event will Receive the data from the selected COM port..
    ReceiveData()
  End Sub

  Private Sub ReceiveData()
    Dim cmd As String = "", _buffer = ""
    Dim crPOS As Integer

    _buffer = ByteToStr()

    crPOS = InStr(_buffer, Chr(13))
    If crPOS > 1 Then cmd = Microsoft.VisualBasic.Left(_buffer, crPOS - 1)

    If cmd = "ACK" Then
      While _buffer <> "OVER" '+ Chr(13)
        SendCommand("ACK")
        _buffer = ByteToStr()
        ReadBuffer = ReadBuffer & _buffer
        'MsgBox(ReadBuffer)
      End While
    Else
      MsgBox("Timeout expired!")
    End If
  End Sub

  Public Function ByteToStr() As String
    Dim strData As String = ""
    Dim bRead, nRead As Integer

    bRead = SerialPort.BytesToRead
    Dim cData(bRead - 1) As Byte

    nRead = SerialPort.Read(cData, 0, bRead)  'Reading the Data

    For Each b As Byte In cData
      If b <> 0 AndAlso b <> 3 AndAlso b <> 10 AndAlso b <> 4 AndAlso b <> 2 AndAlso b <> 16 Then
        strData += Chr(b)
      End If
    Next
    MsgBox(strData)
    Return Trim(strData)
  End Function

End Class

Open in new window

Your code does not use threading. I recommend using threading. See sample code below. I have tried to convert this to VB, but there may still be some problems.
Namespace MyNamespace
    Class MySerialStream
      Private rxThread As Thread
      Private RxThreadSync As Object
      Private serialStrea as SerialStream

      Public Sub New()
        Dim str As New MySerialStream()
        Dim rxThread As New Thread(AddressOf str.RxThread)
        rxThread.IsBackground = true;
        rxThread.Name = "SerialStream RxThread";
        rxThread.Start();
        AddHandler serialStream.DataReceived, AddressOf serialStream_DataReceived
      End Sub

      Private Sub MySerialStream::serialStream_DataReceived(object sender, SerialDataReceivedEventArgs e)
         SyncLock RxThreadSync
           Monitor.Pulse(RxThreadSync)
         End SyncLock
      End Sub

      Private Sub MySerialStream::RxThread()
        Dim available As Integer = 0;
        Dim tmpBuf(available) As Byte
        While (serialStream.IsOpen)
            Monitor::Wait( RxThreadSync );
            available = serialStream.BytesToRead;
            serialStream.Read(tmpBuf, 0, available);
        End While
      End Sub
 

Open in new window

Sir,

I used before the same but I am stuck so I again requesting you to change my working code. I will be very much appreciate.

Again I am requesting. From last 1 week I am running behind this.
Please give it a try and post your code when you have integrated the threading. Try runninging it and tell me where you get stuck. I'm not in a position to write all the code for you.
First of all I reading your 2 -3 time but I am getting first line on [b]Private serialStrea as SerialStream [/b] I am not getting what is this SerialStream. And if you see my code Sending some command to the serialPort and then it invoke datarecived event in that I am calling receivedata procedure which I did not find.

If you don't mind than I can send you my codes file through email. So you change the same and send me back the result. Sorry again to bother you.
What is SerialPort from _your_ code? I'm assuming it is of type SerialStream? Where is the declaration? See code below. I replaced serialStream by SerialPort (your name) and I showed you how to call ReceiveData. I hope that is clearer.
Namespace MyNamespace
    Class MySerialStream
      Private rxThread As Thread
      Private RxThreadSync As Object
      Private SerialPort as SerialStream

      Public Sub New()
        Dim str As New MySerialStream()
        Dim rxThread As New Thread(AddressOf str.RxThread)
        rxThread.IsBackground = true;
        rxThread.Name = "SerialStream RxThread";
        rxThread.Start();
        AddHandler SerialPort.DataReceived, AddressOf SerialPort_DataReceived
      End Sub

      Private Sub MySerialStream::SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
         SyncLock RxThreadSync
           Monitor.Pulse(RxThreadSync)
         End SyncLock
      End Sub

      Private Sub MySerialStream::RxThread()
        Dim available As Integer = 0;
        Dim tmpBuf(available) As Byte
        While (SerialPort.IsOpen)
            Monitor::Wait( RxThreadSync );
            available = SerialPort.BytesToRead;
            SerialPort.Read(tmpBuf, 0, available);
            // Call ReceiveData with argument tmpBuf
        End While
      End Sub

  Private Sub ReceiveData()
    Dim cmd As String = ""
    'Don't call ReadExisting here, but use the argument tmpBuf passed from RxThread()
    'Dim _buffer = SerialPort.ReadExisting
    Dim _buffer = tmpBuf
    Dim crPos As Integer

    crPos = InStr(_buffer, Chr(13))
    If crPos > 1 Then cmd = Microsoft.VisualBasic.Left(_buffer, crPos - 1)

    If cmd = "ACK" AndAlso _continue = False Then
      ReadBuffer = ReadBuffer & Mid(_buffer, crPos + 2)
      _continue = True
      SendCommand(cmd)
    ElseIf cmd <> "OVER" AndAlso _continue = True Then
      ReadBuffer = ReadBuffer & _buffer
      SendCommand("ACK")
      MsgBox(ReadBuffer)
    Else
      txData.Text = ReadBuffer
      'MsgBox(ReadBuffer)
    End If

    'Sub to Receive Data from the Serial Port, Will Run in a Thread
    'Dim bRead, nRead As Integer, crPOS As Integer
    'Dim returnStr As String = ""
    'Dim ascStr As String = ""
    'Dim m As String = ""
    'Dim i As Int16 = 1

    'bRead = SerialPort.BytesToRead 'Number of Bytes to read
    'Dim cData(bRead - 1) As Byte

    'SerialPort.Encoding = Encoding.GetEncoding(65001)

    'nRead = SerialPort.Read(cData, 0, bRead)  'Reading the Data
    'If _continue = False Then crPOS = ByteSearch(cData, "13")

    'For Each b As Byte In cData
    '  If b <> 0 Then
    '    If crPOS >= i AndAlso _continue = False Then
    '      ascStr += Chr(b)
    '      i += 1
    '    Else
    '      returnStr += Chr(b)
    '      _continue = True
    '    End If
    '  End If
    'Next
    'Debug.WriteLine(m)
    'If returnStr <> "OVER" AndAlso returnStr <> "" Then
    '  ReadBuffer += returnStr           'ASCII String

    '  SendCommand("ACK")
    'End If
    'Console.Write("==Begin=={0}==End==", ReadBuffer.ToString)
  End Sub

Open in new window

No actually serialport is component from toolbox and In my code I am adding EventHandler already to the sub procedure.

Sorry and please don't get angry if I am wrong
ASKER CERTIFIED SOLUTION
Avatar of dericstone
dericstone
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you very much this solve my problem. I know now about thread I had never worked before with it.
Great he was really patient with me and giving me time and afford to solve my problem.

Though I have irritate him.