none
PLC通訊的問題 RRS feed

  • 問題

  • 之前小弟有發問過關於PLC的讀取問題

    當時有前輩教我使用以下的指令

    System.Threading.Thread.Sleep(500)
    加入這行我的通訊問題就解決了,有等待到PLC的回傳值

    可是當初小弟只讀取一個PLC的接點位址而已

    現在小弟我想要讀取很多不同的位址

    例如Y0~Y7

    M0~M16

    D0與D100

    種類很多,而且接點還會跳掉(比方說我只想抓Y0、Y12、Y24之類的),

    每個讀取都用以上指令的話會造成的畫面會有點像是當機的情形

    請問各位前輩我應該怎麼解決才是??

    我希望可以timer去掃描而且不會使我的其他事件當掉那我應該要怎麼辦??


    2011年7月11日 下午 01:13

解答

  • 用多執行緒.

    請參閱以下文章

    [Visual Basic 中的多執行緒]

    [多執行緒應用程式]

    [論壇相關討論 PLC]

    [論壇相關討論 Thread]

    PS: Sleep的時間不一定會是500 (也就是0.5秒), 你可以試著去調整看看, 也許可以更少一點


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2011年7月11日 下午 02:10
    版主
  • 你的寫法,根本沒用到BackGroundWorker

    因為你的 PLC.Read呼叫是在 ProgressChanged事件委派函式, 這已經又回到UI執行緒了.

    你應該是在 DoWork中呼叫PLC.Read, 而將 OrderCommand 這個字串傳出來給ProgressChanged.

    所以先理解一下這個方法:

    [BackgroundWorker.ReportProgress 方法 (Int32, Object)]

    所以大致上是這樣 (這個Code我沒有在VS 測過, 你要自己測)

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
      Dim i As Integer=0
      Do Until vRun = False
      i=1
       OrderCommand = PLC.Read("Y0", 2, 16)
      BackgroundWorker1.ReportProgress(i,  OrderCommand)
    i=2
      OrderCommand = PLC.Read("D0", 2, 16)
     BackgroundWorker1.ReportProgress(i,  OrderCommand)
    i=3
      OrderCommand = PLC.Read("D10", 2, 16)
      BackgroundWorker1.ReportProgress(i,  OrderCommand)
    i=4
      OrderCommand = PLC.Read("M0", 2, 16)
     BackgroundWorker1.ReportProgress(i,  OrderCommand)
      
      
      Loop
     End Sub

    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged  '狀態讀取
    Select e.ProgressPercentage
    Case 1 
      TextBox3.Text = e.UserState.ToString()
    Case 2
      TextBox4.Text = e.UserState.ToString()
    Case 3
      TextBox5.Text =e.UserState.ToString()
    Case 4
      TextBox6.Text = e.UserState.ToString()
    End Select
     End Sub

    所以說, 其實你還是要花多點時間瞭解整個BackGroundWorker


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2011年7月13日 上午 10:04
    版主

所有回覆

  • 用多執行緒.

    請參閱以下文章

    [Visual Basic 中的多執行緒]

    [多執行緒應用程式]

    [論壇相關討論 PLC]

    [論壇相關討論 Thread]

    PS: Sleep的時間不一定會是500 (也就是0.5秒), 你可以試著去調整看看, 也許可以更少一點


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2011年7月11日 下午 02:10
    版主
  • Application.DoEvents()

    加這一行試看看

    2011年7月13日 上午 07:59
  • 我想請教一下BILL大,我上網尋了一些多執行緒的資料,明白了BackgroundWorker大概的用法,當BackgroundWorker1_DoWork啟動後就會開始動作

    然後我把timer裡面的動作丟到子執行緒去處理,程式碼如下

     Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
        BackgroundWorker1.WorkerReportsProgress = True
    
          BackgroundWorker1.RunWorkerAsync()
         
      End Sub
    
      Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        OrderCommand = PLC.Read("Y0", 2, 16)
        TextBox3.Text = OrderCommand
        OrderCommand = PLC.Read("D0", 2, 1)
        TextBox4.Text = OrderCommand
        OrderCommand = PLC.Read("D10", 4, 2)
        TextBox5.Text = OrderCommand
        OrderCommand = PLC.Read("M0", 2, 16)
        TextBox6.Text = OrderCommand
    
      End Sub
    
    
      Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        BackgroundWorker1.RunWorkerAsync()
      End Sub

    可是他會在TextBox3.text那邊顯示錯誤說"跨執行緒作業無效: 存取控制項 'TextBox3' 時所使用的執行緒與建立控制項的執行緒不同。"

    請問前被我應該怎麼辦??

    我的PLC.READ裡面都會加入System.Threading.Thread.Sleep(??)關於??的數值我試著調整後,最少要20,不過資料一多的時候還是會異常,而且因為每一次READ的都會延遲一下,

    結果我整格式窗都有點LAG



     

    2011年7月13日 上午 08:02
  • (1)  用BackgroundWorker來循環即可

    (2) 跨執行緒的控制項呼叫是要使用委派的方式, 以你的現況, 若是跨執行緒委派太複雜, 可以使用BackGroundWorker的 ProgressChanged事件來處理

    (3) 先把BackGourndWorker練習熟悉再回頭來改這個程式會比較順手, 你可以參考下列文章

    [多執行緒初探--使用BackgroundWorker(1)]

    [多執行緒初探--使用BackgroundWorker(2)]

    MSDN文件庫

    [BackgroundWorker 類別]

    [BackgroundWorker 元件概觀]

    欲速則不達, 寫程式還是很需要基本概念釐清的過程, 所以建議你先把注意力放在BackGroundWorker, 試著去實做它, 瞭解它, 你應用起來才會得心應手.


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。


    2011年7月13日 上午 08:17
    版主
  • 回復BILL大,我上網找到的就是您貼的這兩篇文章,裡面講得很詳細很好懂,我現在沒有用Timer,是把原本在timer裡面的程式移到BackgroundWorker裡面去做
    經過你的說明我寫了一個很笨的方法,程式如下
     Dim vRun As Boolean
    
    Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click '開啟掃瞄
      vRun = True
      If vRun Then
       BackgroundWorker1.WorkerReportsProgress = True
    
       BackgroundWorker1.RunWorkerAsync()
      End If
     End Sub
    
     Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
      Dim i As Integer
      Do Until vRun = False
       i += 1
       BackgroundWorker1.ReportProgress(i)
      Loop
     End Sub
    
    
     Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged  '狀態讀取
      OrderCommand = PLC.Read("Y0", 2, 16)
      TextBox3.Text = OrderCommand
      OrderCommand = PLC.Read("D0", 2, 16)
      TextBox4.Text = OrderCommand
      OrderCommand = PLC.Read("D10", 2, 16)
      TextBox5.Text = OrderCommand
      OrderCommand = PLC.Read("M0", 2, 16)
      TextBox6.Text = OrderCommand
     End Sub
    
     Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
      If vRun Then
       BackgroundWorker1.RunWorkerAsync()
      End If
     End Sub
    
     Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click '停止掃瞄
      vRun = False
     End Sub
    

    可是我這樣做反而掃描便更慢= =畫面LAG得更嚴重,其他按鈕按下去根本沒反應(包括那個停止掃瞄的按鈕)
    是因為我每一次READ裡面都要System.Threading.Thread.Sleep(20)的關係嗎??

    2011年7月13日 上午 08:58
  • 你的寫法,根本沒用到BackGroundWorker

    因為你的 PLC.Read呼叫是在 ProgressChanged事件委派函式, 這已經又回到UI執行緒了.

    你應該是在 DoWork中呼叫PLC.Read, 而將 OrderCommand 這個字串傳出來給ProgressChanged.

    所以先理解一下這個方法:

    [BackgroundWorker.ReportProgress 方法 (Int32, Object)]

    所以大致上是這樣 (這個Code我沒有在VS 測過, 你要自己測)

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
      Dim i As Integer=0
      Do Until vRun = False
      i=1
       OrderCommand = PLC.Read("Y0", 2, 16)
      BackgroundWorker1.ReportProgress(i,  OrderCommand)
    i=2
      OrderCommand = PLC.Read("D0", 2, 16)
     BackgroundWorker1.ReportProgress(i,  OrderCommand)
    i=3
      OrderCommand = PLC.Read("D10", 2, 16)
      BackgroundWorker1.ReportProgress(i,  OrderCommand)
    i=4
      OrderCommand = PLC.Read("M0", 2, 16)
     BackgroundWorker1.ReportProgress(i,  OrderCommand)
      
      
      Loop
     End Sub

    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged  '狀態讀取
    Select e.ProgressPercentage
    Case 1 
      TextBox3.Text = e.UserState.ToString()
    Case 2
      TextBox4.Text = e.UserState.ToString()
    Case 3
      TextBox5.Text =e.UserState.ToString()
    Case 4
      TextBox6.Text = e.UserState.ToString()
    End Select
     End Sub

    所以說, 其實你還是要花多點時間瞭解整個BackGroundWorker


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2011年7月13日 上午 10:04
    版主
  • 回復BILL前輩,昨天小弟又再重新了解一次多執行緒,並且把BILL前輩上面的利字放入程式中,畫面果然變正常了,但是我這邊又出現一個問題

    就是我必須打開我的SerialPort然後再關閉這樣反覆動作來做讀取,可是當我想卸入的時候也是一樣開Port再關閉

    那我目前是先用手動的方式,先手動執行子執行緒,當我想寫入的時候再關閉子執行緒,然後按寫入的按鈕,在開啟子執行緒

    form程式如下

    Imports System.IO.Ports
    Imports System.Text
    Public Class Form1
     Dim PLC As New FX2N_ComPort   '宣告新的物件名稱
     Private CommPort As String   ' PC Comm埠號碼
     Dim baudrate As Integer    '鮑率設定
     Dim parity As String    '同位元檢查
     Dim databits As Integer    '資料長度設定
     Dim stopbits As Integer    '停止位元
     Dim OrderCommand As String 'OrderCommand定義命令的名稱(不重要)
     Dim vRun As Boolean
    
     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      For Each sp As String In SerialPort.GetPortNames
       ComboBox1.Items.Add(sp)
      Next
      ComboBox1.Sorted = True '排序
      ComboBox1.SelectedIndex = 0 '第一個是預設選項
      PLC.SerialPort1.PortName = ComboBox1.SelectedItem.ToString()
       End Sub
    
    '寫入
     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        OrderCommand = PLC.Force("X0", 1)
       End Sub
    '寫入
     Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
      OrderCommand = PLC.Force("X1", 1)
     End Sub
     
     '子執行緒掃瞄
     Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
      vRun = True
      If vRun Then
       BackgroundWorker1.WorkerReportsProgress = True
       BackgroundWorker1.RunWorkerAsync()
      End If
     End Sub
    
     Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
      Dim i As Integer = 0
      Do Until vRun = False
       i = 1
       OrderCommand = PLC.Read("Y0", 2, 16)
       BackgroundWorker1.ReportProgress(i, OrderCommand)
       i = 2
       OrderCommand = PLC.Read("D0", 2, 16)
       BackgroundWorker1.ReportProgress(i, OrderCommand)
       i = 3
       OrderCommand = PLC.Read("D10", 2, 16)
       BackgroundWorker1.ReportProgress(i, OrderCommand)
       i = 4
       OrderCommand = PLC.Read("M0", 2, 16)
       BackgroundWorker1.ReportProgress(i, OrderCommand)
      Loop
     End Sub
    
     Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
      Select Case e.ProgressPercentage
       Case 1
        TextBox3.Text = e.UserState.ToString()
       Case 2
        TextBox4.Text = e.UserState.ToString()
       Case 3
        TextBox5.Text = e.UserState.ToString()
       Case 4
        TextBox6.Text = e.UserState.ToString()
      End Select
    
     End Sub
    
     Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
      If vRun Then
       BackgroundWorker1.RunWorkerAsync()
      Else
       BackgroundWorker1.CancelAsync()
      End If
     End Sub
    
     '子執行緒停止
     Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
      vRun = False
     End Sub
    
    

    在PLC.read與PLC.force裡面都會有一段程式是

     

          ......(前面太多略)

                Me.SerialPort1.Open()
                Me.SerialPort1.Write(Force)
                Me.SerialPort1.Close()

         .........

    我有嘗試過把寫入的地方插入先暫停背景值型蓄如下,

     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    vRun = False
     OrderCommand = PLC.Force("X0", 1)
    If Not OrderCommand = Nothing Then
       vRun = True
      End If
     End Sub
    

    結果就會錯在Me.SerialPort1.Open()這行,說ComPort已開啟,在勞請前輩在指導我一下

    然後我在請教一下前輩你教我的程是裡面有寫 TextBox3.Text = e.UserState.ToString()
    我想問一下前面的其中裡面的小寫e是什麼意思阿??我上網找到的好像都不太對,都是說是數學符號,可是我覺得這應該不是數學符號吧(抱歉問了很笨的問題)

    是指ByVal e As System.ComponentModel.ProgressChangedEventArgs嗎??如果不適的話可不可以舉個簡單的例子讓我了解一下,謝謝前輩


    2011年7月14日 上午 03:56
  • ProgressChangedEventArgs 成員

    http://msdn.microsoft.com/zh-tw/library/system.componentmodel.progresschangedeventargs_members%28VS.80%29.aspx

    SerialPort的Open和Close,應該是在程式的開始和結束來做,不懂頻繁做開關,有什麼好處...

    2011年7月14日 上午 05:42
  • 依照前輩的說法我已經法SerialPort改成開始掃描就開啟,不掃描就關閉

    可是我現在的問題就又變成說假設現在已經在讀取了,可是因為我要寫入,所以就又會跳出異常,所以我又把寫入的程式改成

     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            vRun = False
            BackgroundWorker1.CancelAsync()
            OrderCommand = PLC.Force("X0", 1)

     vRun = true

            BackgroundWorker1.RunWorkerAsync()
        End Sub

     

    結果就變成要按兩次按鈕才會寫入,我想了一下應該是因為案第一次是子續停止而已,按第二次才真正寫入進去

    而且我也想不出來要怎麼去知道當我寫入完成後{PLC.Force("X0", 1)結束後},再讓子執行緒繼續RUN

    請問前輩我要怎麼按一次就寫入然後還抓得到信號說我已經寫入完成了,子緒可以繼續RUN???

    還有小弟上面其實主要是想了解那個小e代表什麼意思??

    謝謝前輩





    2011年7月14日 上午 06:18
  • e <--你要看它為什麼型別

    例如

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    此時 e的型別為 System.EventArgs 於是你可以 搜尋 EventArgs 類別 + MSDN

    http://www.google.com.tw/#hl=zh-TW&sa=X&ei=9ooeTsf7J6edmQXM2KS0Aw&ved=0CBcQBSgA&q=EventArgs+%2BmSDN&spell=1&bav=on.2,or.r_gc.r_pw.&fp=1d6e2c15ade864a4&biw=1280&bih=609

    就會找到

    [EventArgs 類別] <--這邊就可以找到這類別的相關

    同理可證, 在

    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles

    這邊的 e則是System.ComponentModel.ProgressChangedEventArgs

    這樣瞭解了吧.

    由以上, 表示你對程式基礎不夠熟捻, 你應該先把 [Visual Basic 程式設計手冊] 讀通

     

     

     


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。
    2011年7月14日 上午 06:26
    版主
  • 抱歉BILL前輩,一直勞煩你,小弟會盡可能去了解您給的相關資料,不過小弟非資訊類科,讀起MSDN有點障礙,一個說明點進去有點不完的說明,越看越不懂= =

    所以才想直接透過網路的搜尋與自己的想法加以修改來寫一些小程式,不太明白的地方便PO上網請教各位前輩,還請前輩多多幫忙了。

    關於我上面關於子執行緒與寫入讀取的疑問不知道前是否可在指導小弟一下,謝謝。

    2011年7月14日 上午 08:57
  • 人都有剛開始的時候, 我只是提醒你要走對的方法, 因為頭痛醫頭腳痛醫腳, 你會很容易在相同的問題中打轉, 關於怎麼讀MSDN文件, 我有一些文可以參考

    MSDN文件庫閱讀入門 (1)

    MSDN文件庫閱讀入門 (2)

    MSDN文件庫閱讀入門 (3)

    MSDN文件庫閱讀入門 (4)

    關於其它問題

    (1)  多設兩個Button, 一個是開啟COM Port, 一個是關閉COM Port, 這樣在開啟COM Port後保持住開啟狀態, 就可以不需要開開關關

    (2)  要避免在開啟COM Port的狀況下又去開啟它造成錯誤, 你可以先用 [SerialPort.IsOpen 屬性] 來檢查該COM Port是否已在開啟狀態. 通常我會以類似以下的方式檢查

               If Not RS232 Is Nothing Then
                    If RS232.IsOpen = False Then
                        RS232.Open()
                    End If
                Else
                    MessageBox.Show("沒有建立serialport實體")
                End If

    (3)  要達到你要的功能, 也就是在寫入時要中斷讀取動作, 也有很多種方式

    (3-1) 其中一種方式是將命令放在一個自訂的佇列中, 這就是先放先贏, 然後照著佇列的順序去執行命令, 不論讀取寫入

    關於這個觀念可以參考這一篇 [VB2008 UDP通訊方式傳送指令與接收的應用疑問]

    (3-2) 另一個方法是利用兩個執行緒, 一個負責讀取, 一個負責寫入, 當寫入行為產生的時候則用信號中斷讀取

    這是應用 WaitHandle來做, 相關參考

    [AutoResetEvent,ManualResetEvent]

    [請問怎樣在同一個sub中停止特定的Thread]

    [有關執行緒暫停、啟動的問題]

    以上觀念都不是太容易, 你要花時間消化一下

    [補充說明]

    回答你的問題我並不會嫌煩, 我害怕的是明明我想教你從正途學九陰真經, 最後你學到的卻是九陰白骨爪.


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2011年7月14日 上午 09:43
    版主