none
VB2008 SerialPort.DataReceived 事件的應用 RRS feed

  • 問題

  • 請問各位大大
    我在利用SerialPort元件監控PLC的功能下,有個問題想請教各位大大
    目前測試只抓一個信號,放置在timer元件(interval=200)中輪詢:

     SXDLOT = "@00" + "RR" + "0030" + "0002"
     SXDLOT = SXDLOT + fcs(SXDLOT) + "*" + Chr(13)
     SerialPort1.Write(SXDLOT)

    在SerialPort.DataReceived 事件中

    Dim RXDLOT As String = SerialPort1.ReadExisting()
    Label1.Text = Label1.Text & RXDLOT

    輸出的值是正確的!
    但是我抓2個以上的信號時..要怎麼切呢??
    回應字串過長時,除了字串會被切到下一個cycle才顯示外,也可能發生執行緒已停止的狀況
    請問這樣的話延遲時間該放在哪呢?
    新手上路
    2009年4月1日 上午 02:19

解答

  • eblue:
             我照著自己想法寫了一個Demo,不過裡面有幾個問題
             1.因為我沒有PLC可測,所以結果是否正確就要靠你告訴我了
             2.你有用到一個自訂的fcs函式,我不知道那內容,所以就空下來了
            Imports System.IO
    Imports System.IO.Ports
    Imports System.Threading
    Imports Microsoft.VisualBasic
    Imports System.Windows.Forms
    Public Class Form1
        Dim myResetEvent As New AutoResetEvent(False)

        Delegate Sub SetMsg1Callback(ByVal InputString As String)
        Private Sub DisplayMsg1(ByVal strReceive As String)
            If Me.Label1.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg1)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label1.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg2Callback(ByVal InputString As String)
        Private Sub DisplayMsg2(ByVal strReceive As String)
            If Me.Label2.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg2)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label2.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg3Callback(ByVal InputString As String)
        Private Sub DisplayMsg3(ByVal strReceive As String)
            If Me.Label3.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg3)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label3.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg4Callback(ByVal InputString As String)
        Private Sub DisplayMsg4(ByVal strReceive As String)
            If Me.Label4.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg4)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label4.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg5Callback(ByVal InputString As String)
        Private Sub DisplayMsg5(ByVal strReceive As String)
            If Me.Label5.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg5)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label5.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg6Callback(ByVal InputString As String)
        Private Sub DisplayMsg6(ByVal strReceive As String)
            If Me.Label6.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg6)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label6.Text = strReceive
            End If
        End Sub

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            SerialPort1.Open()

        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest))


        End Sub

      
        Private Sub PLCtest(ByVal state As Object)
            Do
                Application.DoEvents()
                Dim SXDLOT1 As String
                ' CheckForIllegalCrossThreadCalls = False
                SerialPort1.DiscardOutBuffer()
                SerialPort1.DiscardInBuffer()
                SXDLOT1 = "@00" + "RC" + "0300" + "0030"
                SXDLOT1 = SXDLOT1 + fcs(SXDLOT1) + "*" + Chr(13)
                SerialPort1.Write(SXDLOT1)
                myResetEvent.WaitOne(5000, False) '如果五秒內沒有引發SerialPort1_DataReceived就繼續

            Loop
        End Sub
        Private Function fcs(ByVal str As String) As String

        End Function
        Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            RemoveHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
            '  CheckForIllegalCrossThreadCalls = False
            Dim RXD As String = ""
            Dim i As Integer = 0
            Try
                While Microsoft.VisualBasic.Right(RXD, 1) <> Chr(13)
                    System.Threading.Thread.Sleep(200)
                    RXD &= SerialPort1.ReadExisting
                    i += 1
                    If i > 5 Then '進入回圈超過五次就跳離,不管有沒有讀完
                        Exit While
                    End If
                End While
                If i <= 5 Then
                    TXT(RXD)
                Else
                    DisplayMsg5("讀取超過次數")
                End If
            Catch ex As Exception
                DisplayMsg6(ex.Message)
            Finally
                Application.DoEvents()
                AddHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
                myResetEvent.Set()
            End Try

        End Sub
        Public Sub TXT(ByVal P As String)  '判斷字元長度顯示
            Dim strLen As Integer = Len(P)
            Select Case Len(P)
                Case 15
                    DisplayMsg1(P)
                Case 31
                    DisplayMsg3(P)
                Case 131
                    DisplayMsg2(P)
                Case 135
                    DisplayMsg4(P)
                Case Else
                    DisplayMsg5("讀取不完全")
            End Select
         
        End Sub


    End Class

    • 已標示為解答 eblue 2009年4月3日 上午 04:38
    2009年4月2日 下午 03:56
    版主

所有回覆

  • Visual Basic 2005與自動化系統監控-RS232串列通訊篇
    http://www.kingsinfo.com.tw/item_detail.asp?pro_id=6487


    http://220.130.0.150/pp/kingsinfo/p/P6207.zip

    所以您是ReceivedBytesThreshold屬性設為1?觸發DataReceived事件,再來處理數值?您的信號內容是什麼呢?
    • 已編輯 Joe Hung 2009年6月2日 下午 02:25
    2009年4月1日 上午 02:40
  • PLC回覆一定也有起始和結束字元
    所以可以先把回傳的RXDLOT先拆成一個字元一個字元丟到另一個用以暫存的字串變數
    而使用條件判斷當此字元為結束時則將暫存字串變數的內容存起來(我猜你不是存到資料表就是文字檔吧)然後把暫存變數清成空的,
    以便繼續接後面的字元
    2009年4月1日 上午 03:43
    版主
  • 不好意思...大大是我說的不清楚
    我在timer中放置.多段PLC監控通信的程式碼做為即時監控用
    所以必須每隔一段時間去執行一次..來確認PLC狀態

    ReceivedBytesThreshold=1的確會只出現一筆....
    Label1.Text = Label1.Text & RXDLOT..拿掉這段
    RXDLOT會變成短收字串


    新手上路
    2009年4月1日 上午 03:50
  • 這是我原本在timer元件的程式碼
            Dim DeadLine As DateTime = Now.AddSeconds(0.1)
            Label1.Text = ""
            CheckForIllegalCrossThreadCalls = False
            SerialPort1.DiscardOutBuffer()
            SerialPort1.DiscardInBuffer()
            RXDLOT = ""
            SXDLOT = "@00" + "RR" + "0030" + "0001"
            SXDLOT = SXDLOT + fcs(SXDLOT) + "*" + Chr(13)
            SerialPort1.Write(SXDLOT)     ' 下達通訊指令
            System.Threading.Thread.Sleep(20)
            '接收PLC回傳資料
            Do
            RXDLOT = RXDLOT + SerialPort1.ReadExisting
            Loop Until Len(RXDLOT) > 14 Or Now > DeadLine

    我想利用DataReceived事件來做接收的動作


    回應內容"@00RR00002547*"


    新手上路
    2009年4月1日 上午 05:58
  • '        Do
    '        RXDLOT = RXDLOT + SerialPort1.ReadExisting
    '        Loop Until Len(RXDLOT) > 14 Or Now > DeadLine
    
        Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            RXDLOT =  SerialPort1.ReadExisting
        End Sub


    把timer裡的接收註解,改成在DataReceived事件裡接收,這樣回傳資料不會是"@00RR00002547*"嗎?

    2009年4月1日 上午 06:09
  • 不會..收到的會是"@00R00"
    若是改成這樣
    Label1.Text = Label1.Text & RXDLOT
    才會在label1顯示出"@00RR00002547*"
    就因如此小弟才在思考...

    新手上路
    2009年4月1日 上午 06:22
  • Hi eblue

            你要用DataReceived事件並無不可,只要把ReadExisting方法用在事件的程序中即可
            Imports System.IO
            Imports System.IO.ports
            Public Class Form2
              Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
                 SerialPort1.Open()
                 SerialPort1.ReceivedBytesThreshold = 1
              End Sub

              Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As  System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

            CType(sender, SerialPort).ReadExisting()

             End Sub

        其實比較大的問題會出在幾點
        (1)這個Event屬於多執行緒的做法,所以如果你要把收到的字串傳到Form上的控制項(例如 TextBox 或 Label),需使用委派才能夠傳送
        (2)Forms.Timer元件有個毛病,它的最大間隔約在64.8秒左右,而且不太準時
            MSDN文件庫中有特別提到這一點
            Windows Form Timer 元件的 Interval 屬性限制
            http://msdn.microsoft.com/zh-tw/library/xy0zeach(VS.80).aspx
        (3)另外一個Timer的問題是,它有可能因為要執行的動作太長,以致於造成你的程序還沒完就跳了一個新的Timer Tick進來,這樣可能會造成一團混亂

         所以建議你將 SerialPort1.Write(SXDLOT)  的這些程序使用多執行緒的方法來做,並使用ManualResetEvent(或AutoResetEvent)搭配WaitOne來確保必需完成SerialPort1.DataReceived事件所執行的內容後才又發送下一次的Write.
         最簡易的多執行緒可使用BackGroundWorker元件來達成

          希望以上的說明對你有幫助

    2009年4月1日 上午 06:30
    版主
  • timer的interval可以設大於200嗎?
    'System.Threading.Thread.Sleep(20),調小或註解

    您的serial port是從電腦接出來的嗎?還是經過USB轉換呢?

    2009年4月1日 上午 06:45
  • 我是直接與PC連線的
    'System.Threading.Thread.Sleep(20)---我有測試使用與不使用結果相同
    timer我調到500 

        Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            Dim RXD As String
            Dim DeadLine As DateTime = Now.AddSeconds(0.1)
            Do
                RXD = RXD + SerialPort1.ReadExisting
            Loop Until   Now > DeadLine
        End Sub
    這樣取出單筆的值就OK..
    現在再測試切割多筆


    新手上路
    2009年4月1日 上午 08:23
  • 感謝大大你告知我程式上的風險...
    多執行緒的動作...
    waitout()這個部分我有查一下線上說明..目前還摸不著頭緒!!這部分我會再努力!!
    新手上路
    2009年4月1日 上午 08:25
  • eblue:

              關於WaitOne這玩意兒,你可以參考一下之前的討論[AutoResetEvent,ManualResetEvent]
              http://social.msdn.microsoft.com/Forums/zh-TW/233/thread/03a6e821-7660-4c53-a07b-3c1a8db91eef

              另一件事 'System.Threading.Thread.Sleep(20) 關於這個的時間,說真的 20/1000秒大概也測不出啥結果
              我看起碼也要大於 150/1000才會有點效果,你可試著調整數值,然後看接回來的字串是否完整,
              因為PC-->PLC--->PC恐怕也要一點時間

    2009年4月1日 下午 01:10
    版主
  • 多謝大大您提供這個簡易的sample跟說明,目前應用上還有點疑問如下
    我單獨做一個測試抓PLC的輸出
    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick  'Timer  interval=500
            Dim myResetEvent As New AutoResetEvent(False)
            Dim j As Integer
            Label1.Text = Nothing
            Label2.Text = Nothing
            Label3.Text = Nothing
            Label4.Text = Nothing
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest), myResetEvent)
            myResetEvent.WaitOne()
            System.Threading.Thread.Sleep(50)
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest1), myResetEvent)
            myResetEvent.WaitOne()
            System.Threading.Thread.Sleep(50)
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest2), myResetEvent)
            myResetEvent.WaitOne()
            System.Threading.Thread.Sleep(50)
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest3), myResetEvent)
            myResetEvent.WaitOne()
        End Sub

    Private Sub PLCtest(ByVal PLC As Object)     'PLCtest我一共寫了4個 這個的回應碼為131個字元
            Dim RXDLOT1 As String
            Dim SXDLOT1 As String
            CheckForIllegalCrossThreadCalls = False
            SerialPort1.DiscardOutBuffer()
            SerialPort1.DiscardInBuffer()
            SXDLOT1 = "@00" + "RC" + "0300" + "0030"
            SXDLOT1 = SXDLOT1 + fcs(SXDLOT1) + "*" + Chr(13)
            SerialPort1.Write(SXDLOT1)
        End Sub

    Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            Dim RXD As String
            Dim DeadLine As DateTime = Now.AddSeconds(0.1)
            System.Threading.Thread.Sleep(200)
            RXD = Nothing
            Do
                RXD = RXD + SerialPort1.ReadExisting
            Loop Until Now > DeadLine

            TXT(RXD)
        End Sub
      Public Function TXT(ByVal P As String)  '判斷字元長度顯示
            Dim strLen As Integer = Len(P)
            If Len(P) = 15 Then
                Label1.Text = P
            End If

            If Len(P) = 131 Then
                Label2.Text = P
            End If

            If Len(P) = 31 Then
                Label3.Text = P
            End If

            If Len(P) = 135 Then
                Label4.Text = P
            End If
        End Function

    以上是小弟的測試碼
    發生的問題如下:
    1.PLC回傳碼偶爾會有收碼不全的狀況發生 (只抓取PLCtest,PLCtest1,PLCtest2 .3各執行緒的情況下)
    2.當我新增PLCtest3這個執行緒時變成全部收碼會不全..

    小弟想請問當我執行 SerialPort1.Write 後 延遲時間應該放置在 SerialPort1.Write 後 還是 DataReceived中,因為2者的情況都一樣..收碼都會發生短收


    新手上路
    2009年4月2日 上午 04:40
  • 哇,你學的還真快!
    其實我會比較建議只引發一個次執緒然後做Loop,這樣講不太具體..來個範例說明
    1.用多執行緒的目的是為了替代Timer.
    2.所以假設我有一個Button,用來表示要啟動送資料到PLC的作業,會像這樣
        把  Dim myResetEvent As New AutoResetEvent(False) 設為全域
       在Button1的click事件區塊中撰寫
      ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest))
       '所以在這邊它會呼叫 PLCTest囉
    3.PlcTest的內容
       PLCtest(ByVal state As Object)
            Do
               Applications.DoEvent()
               Dim RXDLOT1 As String
               Dim SXDLOT1 As String
               CheckForIllegalCrossThreadCalls = False
               SerialPort1.DiscardOutBuffer()
               SerialPort1.DiscardInBuffer()
               SXDLOT1 = "@00" + "RC" + "0300" + "0030"
               SXDLOT1 = SXDLOT1 + fcs(SXDLOT1) + "*" + Chr(13)
               SerialPort1.Write(SXDLOT1)
               myResetEvent.WaitOne
            Loop
        End Sub
        Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            '其實這一段的內容有個大問題,因為你還是用時間去判斷你是否要跳出loop,如果時間到了還是沒收完,就又短收了,我覺得應該判斷結尾字元才對,看起來PLC回復給你的字串結尾也應該是Chr(13).這樣需要好幾個字串變數去當緩衝,以便可以把字串切來切去.提供這個建議給你參考.
            Dim RXD As String
            Dim DeadLine As DateTime = Now.AddSeconds(0.1)   
            System.Threading.Thread.Sleep(200)
            RXD = Nothing
            Do
                RXD = RXD + SerialPort1.ReadExisting
           Loop Until Now > DeadLine

           TXT(RXD)
           myResetEvent.Set
     End Sub
      Public Function TXT(ByVal P As String)  '判斷字元長度顯示
            Dim strLen As Integer = Len(P)
            If Len(P) = 15 Then
                Label1.Text = P
            End If

            If Len(P) = 131 Then
                Label2.Text = P
            End If

            If Len(P) = 31 Then
                Label3.Text = P
            End If

            If Len(P) = 135 Then
                Label4.Text = P
            End If
        End Function

    2009年4月2日 上午 09:17
    版主
  • 再次感謝大大!!不是我學的快!!是大大您給的例子相當的適合我們這種程式苦手當作參考
    依據大大的建議
    小弟在收集字串做了些改變

     Dim RXD As String
            Dim x As Integer
            Dim CHK As String
            Dim DeadLine As DateTime = Now.AddSeconds(0.3)
            System.Threading.Thread.Sleep(100)
            RXD = Nothing
            Do
             RXD = RXD + SerialPort1.ReadExisting
                x = Len(RXD)
                CHK = Mid(RXD, x - 1, 1)
                If CHK <> "*" Then
                    Exit  Do
                End If
            Loop Until CHK = "*" Or Now > DeadLine  
          TXT(RXD)
            myResetEvent.Set()
    加上時間判斷是小弟怕迴圈會跳不出
    以目前的狀態來看
    收碼不全的原因..小弟判斷是..字串長度>128 就會造成 字串被切掉
    目前我一共用了5個執行緒測試 長度分別為15,19,123,119,31,目前測試抓取的回傳值相較之前字串大於128的時候,穩定許多
    現在測試中~


    新手上路
    2009年4月2日 下午 12:38
  • eblue:
             我照著自己想法寫了一個Demo,不過裡面有幾個問題
             1.因為我沒有PLC可測,所以結果是否正確就要靠你告訴我了
             2.你有用到一個自訂的fcs函式,我不知道那內容,所以就空下來了
            Imports System.IO
    Imports System.IO.Ports
    Imports System.Threading
    Imports Microsoft.VisualBasic
    Imports System.Windows.Forms
    Public Class Form1
        Dim myResetEvent As New AutoResetEvent(False)

        Delegate Sub SetMsg1Callback(ByVal InputString As String)
        Private Sub DisplayMsg1(ByVal strReceive As String)
            If Me.Label1.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg1)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label1.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg2Callback(ByVal InputString As String)
        Private Sub DisplayMsg2(ByVal strReceive As String)
            If Me.Label2.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg2)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label2.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg3Callback(ByVal InputString As String)
        Private Sub DisplayMsg3(ByVal strReceive As String)
            If Me.Label3.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg3)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label3.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg4Callback(ByVal InputString As String)
        Private Sub DisplayMsg4(ByVal strReceive As String)
            If Me.Label4.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg4)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label4.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg5Callback(ByVal InputString As String)
        Private Sub DisplayMsg5(ByVal strReceive As String)
            If Me.Label5.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg5)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label5.Text = strReceive
            End If
        End Sub

        Delegate Sub SetMsg6Callback(ByVal InputString As String)
        Private Sub DisplayMsg6(ByVal strReceive As String)
            If Me.Label6.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg6)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label6.Text = strReceive
            End If
        End Sub

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            SerialPort1.Open()

        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest))


        End Sub

      
        Private Sub PLCtest(ByVal state As Object)
            Do
                Application.DoEvents()
                Dim SXDLOT1 As String
                ' CheckForIllegalCrossThreadCalls = False
                SerialPort1.DiscardOutBuffer()
                SerialPort1.DiscardInBuffer()
                SXDLOT1 = "@00" + "RC" + "0300" + "0030"
                SXDLOT1 = SXDLOT1 + fcs(SXDLOT1) + "*" + Chr(13)
                SerialPort1.Write(SXDLOT1)
                myResetEvent.WaitOne(5000, False) '如果五秒內沒有引發SerialPort1_DataReceived就繼續

            Loop
        End Sub
        Private Function fcs(ByVal str As String) As String

        End Function
        Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            RemoveHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
            '  CheckForIllegalCrossThreadCalls = False
            Dim RXD As String = ""
            Dim i As Integer = 0
            Try
                While Microsoft.VisualBasic.Right(RXD, 1) <> Chr(13)
                    System.Threading.Thread.Sleep(200)
                    RXD &= SerialPort1.ReadExisting
                    i += 1
                    If i > 5 Then '進入回圈超過五次就跳離,不管有沒有讀完
                        Exit While
                    End If
                End While
                If i <= 5 Then
                    TXT(RXD)
                Else
                    DisplayMsg5("讀取超過次數")
                End If
            Catch ex As Exception
                DisplayMsg6(ex.Message)
            Finally
                Application.DoEvents()
                AddHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
                myResetEvent.Set()
            End Try

        End Sub
        Public Sub TXT(ByVal P As String)  '判斷字元長度顯示
            Dim strLen As Integer = Len(P)
            Select Case Len(P)
                Case 15
                    DisplayMsg1(P)
                Case 31
                    DisplayMsg3(P)
                Case 131
                    DisplayMsg2(P)
                Case 135
                    DisplayMsg4(P)
                Case Else
                    DisplayMsg5("讀取不完全")
            End Select
         
        End Sub


    End Class

    • 已標示為解答 eblue 2009年4月3日 上午 04:38
    2009年4月2日 下午 03:56
    版主
  • 真是不好意思!!還麻煩大大您寫了段測試程式..以小弟測試您的程式在PLC上是可以RUN而回傳值也是正常的..
    偶而的讀取不完全..是因為"@00" + "RC" + "0300" + "0030"表示抓取30個暫存區的值每個4個位元
    所以他整個字串會有131字元,所以小弟是認為太長造成後面的3~5碼被截掉,所以當我收碼控制在不超過128位元,就不會發生讀取不完全!
    但是..我會用timer元件是因為監控的區塊很多
    有的一次讀取5個 / 一次讀取 10 個 ...等
    感謝大大..教了我一個取代timer元件做輪詢的方法
    所以這段我做了些改變:
       Dim SXDLOT1 As String
            Do
                Application.DoEvents()
                For i As Integer = 0 To 4
                    Select Case i
                        Case 1
                            SerialPort1.DiscardOutBuffer()
                            SerialPort1.DiscardInBuffer()
                            SXDLOT1 = "@00" + "RC" + "0300" + "0028"        
                            SXDLOT1 = SXDLOT1 + fcss(SXDLOT1) + "*" + Chr(13)
                            SerialPort1.Write(SXDLOT1)
                            myResetEvent.WaitOne(5000, False)
                        Case 2
                            SerialPort1.DiscardOutBuffer()
                            SerialPort1.DiscardInBuffer()
                            SXDLOT1 = "@00" + "RC" + "0300" + "0005"
                            SXDLOT1 = SXDLOT1 + fcss(SXDLOT1) + "*" + Chr(13)
                            SerialPort1.Write(SXDLOT1)
                            myResetEvent.WaitOne(5000, False)
                        Case 3
                            SerialPort1.DiscardOutBuffer()
                            SerialPort1.DiscardInBuffer()
                            SXDLOT1 = "@00" + "RC" + "0300" + "0001"
                            SXDLOT1 = SXDLOT1 + fcss(SXDLOT1) + "*" + Chr(13)
                            SerialPort1.Write(SXDLOT1)
                            myResetEvent.WaitOne(5000, False)
                        Case 4
                            SerialPort1.DiscardOutBuffer()
                            SerialPort1.DiscardInBuffer()
                            SXDLOT1 = "@00" + "RC" + "0300" + "0029"
                            SXDLOT1 = SXDLOT1 + fcss(SXDLOT1) + "*" + Chr(13)
                            SerialPort1.Write(SXDLOT1)
                            myResetEvent.WaitOne(5000, False)
                    End Select
                Next
            Loop
    以監控不同數量的暫存區測試..目前程式cycle為0.6sec


    另外請教大大您這段程式的意思

    Delegate Sub SetMsg5Callback(ByVal InputString As String)  
        Private Sub DisplayMsg5(ByVal strReceive As String)
            If Me.Label5.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg5)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label5.Text = strReceive
            End If
        End Sub

    可否請您解說一下?
    新手上路
    2009年4月3日 上午 01:55
  • 因為你這問題太有趣了,我就忍不住手癢..哈..

    Delegate Sub SetMsg5Callback(ByVal InputString As String)  
        Private Sub DisplayMsg5(ByVal strReceive As String)
            If Me.Label5.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg5)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label5.Text = strReceive
            End If
        End Sub
    這一段是委派,因為處理主畫面的控制項(在這個例子是Label控制項)是由其它執行緒引發的,
    正常的狀況下,不能直接做跨執行緒存取控制項,所以必需使用Invoke的方法來呼叫
    原來你的寫法是用了CheckForIllegalCrossThreadCalls = False去避掉不同執行緒呼叫會引發InvalidOperationException的問題,我看過有一些範例是這樣寫,不過因為自己習慣用委派,而且用委派感覺上應該是比較正確一些.
    另一件事,迴圈這樣寫看來清爽點

             For i As Integer = 0 To 4
                If i<>0 Then
                    SerialPort1.DiscardOutBuffer()
                    SerialPort1.DiscardInBuffer()
                    Select Case i
                        Case 1
                            SXDLOT1 = "@00" + "RC" + "0300" + "0028"         
                         Case 2
                             SXDLOT1 = "@00" + "RC" + "0300" + "0005"
                         Case 3
                            SXDLOT1 = "@00" + "RC" + "0300" + "0001"
                        Case 4
                            SXDLOT1 = "@00" + "RC" + "0300" + "0029"
                    End Select
                           SXDLOT1 = SXDLOT1 + fcss(SXDLOT1) + "*" + Chr(13)
                           SerialPort1.Write(SXDLOT1)
                 End If
                       myResetEvent.WaitOne(5000, False)
                Next

    2009年4月3日 上午 03:28
    版主
  • 問了這個問題..的確讓我在這方面獲得很多幫助!!以這個程式來說..還少了一個通訊斷線檢知在以前使用VB6時MSCOMM元建會回傳一個空字串"",我藉此判斷通訊異常
    而在VB2008中該從哪個事件去看呢??SerialPort1.ReadTimeout 嗎??這個事件該如何抓取??


    新手上路
    2009年4月3日 上午 03:45
  • 有一種取巧的方法是使用變數來判斷是否有發生SerialDataReceived Event來決定
    因為SerialPort不像TCP/IP可以在Connect或Send動作時就知道是不是斷了,這一點有點像Udp,有點飛彈射後不理的味道
    因此來改一下SerialDataReceived Event
    先設定一個全域布林變數     Dim bIsChanged as Boolean = False

    System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            RemoveHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
            '  CheckForIllegalCrossThreadCalls = False
            Dim RXD As String = ""
            Dim i As Integer = 0
            Try
                While Microsoft.VisualBasic.Right(RXD, 1) <> Chr(13)
                    System.Threading.Thread.Sleep(200)
                    RXD &= SerialPort1.ReadExisting
                    i += 1
                    If i > 5 Then '進入回圈超過五次就跳離,不管有沒有讀完
                        Exit While
                    End If
                End While
                If i <= 5 Then
                    TXT(RXD)
                Else
                    DisplayMsg5("讀取超過次數")
                End If
            Catch ex As Exception
                DisplayMsg6(ex.Message)
            Finally
                Application.DoEvents()
                AddHandler SerialPort1.DataReceived, AddressOf SerialPort1_DataReceived
                bIsChanged=True
                myResetEvent.Set()
            End Try

        End Sub

    然後在  Private Sub PLCtest(ByVal state As Object)程序中判斷
       For i As Integer = 0 To 4
                If i<>0 Then
                    SerialPort1.DiscardOutBuffer()
                    SerialPort1.DiscardInBuffer()
                    Select Case i
                        Case 1
                            SXDLOT1 = "@00" + "RC" + "0300" + "0028"         
                         Case 2
                             SXDLOT1 = "@00" + "RC" + "0300" + "0005"
                         Case 3
                            SXDLOT1 = "@00" + "RC" + "0300" + "0001"
                        Case 4
                            SXDLOT1 = "@00" + "RC" + "0300" + "0029"
                    End Select
                           SXDLOT1 = SXDLOT1 + fcss(SXDLOT1) + "*" + Chr(13)
                           SerialPort1.Write(SXDLOT1)
                            myResetEvent.WaitOne(5000, False) <--這個要移到這邊來
                            If bIsChanged = False Then
                                     MessageBox.Show("PLC無回應")
                            Else
                                      bIsChanged = True
                            End If
                Else
                           System.Threading.Thread.Sleep(2000) <--當i=0,給它睡個兩秒鐘,
                End If
           Next

     進階的話,可以設一些數字的變數,比方PLC無回應的次數達到五以後才發出斷訊訊號






    2009年4月3日 上午 04:09
    版主
  • 大大新增的程式下...在程式一開始時若通訊失敗的確會出現訊息
    但程式執行後通訊異常則不會被檢知
    所以在
    For i As Integer = 0 To 4
              略...
     Next
    bIsChanged = False
    這樣就可以隨時偵測通訊狀態了!!
    寫程式的痛苦跟樂趣果真是一線間啊...再次感謝大大一直陪著我完成他

    新手上路
    2009年4月3日 上午 04:32
  • 我剛剛在思考關於太長收不完的問題,有可能太長,收的次數要變多
    While Microsoft.VisualBasic.Right(RXD, 1) <> Chr(13)
                    System.Threading.Thread.Sleep(200)
                    RXD &= SerialPort1.ReadExisting
                    i += 1
                    If i > 5 Then '進入回圈超過五次就跳離,不管有沒有讀完
                        Exit While
                    End If
                End While

    試著把粗體那一段的數字拉大,讓它多讀幾次看看,比如改成9,或10

    2009年4月3日 上午 07:57
    版主
  • 經測試結果,變更i為10,讀取超過30個暫存區,值仍舊會被切掉,並且會影響其他的
    目前測試抓取28個暫存區是最穩的.再增加便會出現收碼不全!!


    新手上路
    2009年4月6日 上午 02:23
  • eblue:
             關於收碼不全的事還真奇怪,我有收過大於200 Bytes的碼,未曾出現問題.
             觀察了一下你的PLC回應碼
             @00RR0000     2547 * (Chr(13))
             其中應該只有2547才是資料吧? 所以30個暫存區 =30*4+11=131 Bytes  
             如果要避免前一資料不全導致的問題,可以在SerialPort.DataReceived Event讀取第一個字元判斷是否為"@"
             若不是就跳出程序,捨棄這一次的讀取
             對了,當你拉大迴圈值的時候,可讀的暫存區數量有跟著變大嗎?
             提供一個方法測試看看:
             把RS232串接到另一部電腦,在那個電腦上寫一個簡單的SerialPort.DataReceived Event,不需要控制只要收到值就丟到TextBox上(用TextBox1.Text &=送來的字串,一路連下去),可以觀察在PLC 回送命令時是否真的有回傳完整字串,如果可以加上時間的測量,就更容易了解問題發生在哪裡.
    2009年4月6日 上午 07:19
    版主
  • 您判斷收碼的數量是這樣沒錯..以目前實測的結果就是會少碼..
    如果抓取的
    Select Case i
    ---略                              
    End Select
    i值在3個內都還OK..當i值>4而其中有case 抓取的區域>30個 就會開始發生收碼異常了..是傳輸的鮑率太慢嗎??
    更改迴圈數值最高收到過172個byte..

    在請問大大
    當我程式結束時,如何中斷者各執行緒??
    **
    小弟目前的做法
    宣告全域變數Workstart=true

    DO
    If Workstart = False Then
                    Exit Do
    End If
    For i As Integer = 0 To 5
                    If Workstart = False Then
                    Exit Do
    End If
    ------略
    loop
    Private Sub 結束程式ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles 結束程式ToolStripMenuItem.Click
         
            'myResetEvent.Reset() 
            'myResetEvent.Close()
            Workstart = False
            Me.Close()
        End Sub
    在這兩個地方放置跳出迴圈的程式,請問有其他做法嗎??


    新手上路
    2009年4月6日 上午 09:48
  • eblue:
              跳出執行緒,以下為一個簡單的範例,你可試著玩玩看,畫面上只有一個Label和兩個button<控制開始與結束>
    Imports System.Threading
    Public Class Form1
        Dim IsStop As Boolean = False
        Delegate Sub SetMsg1Callback(ByVal InputString As String)
        Private Sub DisplayMsg1(ByVal strReceive As String)
            If Me.Label1.InvokeRequired Then
                Dim d As New SetMsg1Callback(AddressOf DisplayMsg1)
                Me.Invoke(d, New Object() {strReceive})
            Else
                Me.Label1.Text = strReceive
            End If
        End Sub

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        End Sub

        Private Sub BTN_Start_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTN_Start.Click
            IsStop = False
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf PLCtest))

        End Sub

        Private Sub PLCtest(ByVal state As Object)
            Do
                System.Threading.Thread.Sleep(500)
                Application.DoEvents()
                DisplayMsg1(Now.ToString & "Loop")
                If IsStop = True Then
                    Exit Do
                End If
            Loop
            System.Threading.Thread.Sleep(500)
            Application.DoEvents()
            DisplayMsg1(Now.ToString & "Loop stop!!")
        End Sub

        Private Sub BTN_Stop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTN_Stop.Click
            IsStop = True
        End Sub
    End Class
         其實用ThreadPool.QueueUserWorkItem方法,當Form Disposed時,執行緒也會跟著停止。

    2009年4月7日 上午 06:57
    版主
  • 的確..當我在這裡再加上dispose事件後關閉的動作就順暢了
    Private Sub 結束程式ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles 結束程式ToolStripMenuItem.Click
            Workstart = False
            Me.Dispose()
            Me.Close()
     End Sub

    新手上路
    2009年4月7日 上午 07:09
  • eblue:
            我寫了一個檢測Serial Port傳遞資料的程式,也許可以用這個程式來觀察一下收碼異常的問題.
            http://www.dotblogs.com.tw/billchung/archive/2009/04/12/7981.aspx
    2009年4月11日 下午 07:46
    版主