close

update : 2018.07.26

今天來分享FX3U程式的部分好了,

 

三菱PLC的通訊,主要是Modbus,

PC下一道指令到PLC,PLC回覆一個回碼給PC。

 

一台PC可能有多個COM Port,

每個COM Port可能連接多個設備,

那...我們先建立一個串列埠的執行序,

養成好習慣,能拆的就拆吧,

所有程式都在主Form裡是不好的,

建立一個 class ThreadSerial

Form1中去new ThreadSerial 物件

(下圖用實心箭頭表示使用or擁有)

PLC有狀態要監控,又要送控制訊號,

因此我用排程的方式,因此這個物件主要的元件不外乎就是執行序、串列埠、指令陣列

( 基本的執行序建立可以看這篇: Thread in C# )

 

因為有兩個串列埠,因此用兩個執行序,

執行序1 與執行序2,主要基本的功能只做通訊,

每傳一道指令過去,就必須等待回碼。

 

 

再來就是一些細部的功能,就看設計的人怎麼處理,

EX:

  • 送碼後回碼Timeout
  • 回碼錯誤
  • 狀態多久問一次
  • 沒有連線
  • 離線

 

例如:

        //執行緒 - 通道1 
        private void Execute_1()
        {            
            while (true)
            {
                Thread.Sleep(1);
                if (bClose) break;

                
                //通訊未開啟
                if (!serialPort1.IsOpen)
                {
                    continue;
                }

                //離線
                if (bDisconnectSerial1)
                {
                    serialPort1.Close();
                    bDisconnectSerial1 = false;
                    continue;
                }

                //增加監視指令
                Query();

                //沒有指令要傳送
                if (CmdList_1.Count == 0) continue;

                //清除佇列中殘存的資料
                serialPort1.DiscardOutBuffer();
                serialPort1.DiscardInBuffer();

                //取得第一筆指令
                Command Cmd = CmdList_1[0];
                
                //傳送指令
                Write_1(Cmd);

                //等待回碼
                Listen_1();

                //收到回碼
                if (Cmd.ErrorCode == 0)
                {
                    //執行收到回碼要做的事情
                    Synchronize(OnReceive_1);
                }

                //有錯誤發生時
                if (Cmd.ErrorCode != 0)
                {
                    //顯示錯誤訊息
                    Synchronize(OnError_1);

                    //重送計數+1
                    RetryCount_1++;
                    //重送計數到達前重送
                    if (RetryCount_1 <= RetryMax_1) continue;
                }

                //重送歸零
                RetryCount_1 = 0;
                //移除第一筆指令
                CmdList_1.RemoveAt(0);
                
            }
            //執行緒關閉完成
            bCloseFinish = true;
        }

 

Modbus 指令分為ASCII 與 RTU ,

依照手冊將指令寫出,懂的話,並不會太難,

        public void Write_1(Command Cmd)
        {
            Cmd.ErrorCode = 0;

            //ASCII
            if (Mode == CmdMode.ASCII)
            {
                Cmd.Str = ":" + Cmd.Str + CheckSum.LRC(Cmd.Str) + "\r\n";
                Byte[] buf = Encoding.ASCII.GetBytes(Cmd.Str);
                try
                {
                    serialPort1.Write(buf, 0, buf.Length);
                }
                catch (Exception)
                {
                    Cmd.ErrorCode = -1;
                    return;
                }
            }
            //RTU
            else
            {
                Cmd.Str += CheckSum.CRC_16(Cmd.Str);
                //ASCII轉RTU,兩個字為一個Byte的資料,長度會減半
                int len = Cmd.Str.Length / 2;
                byte[] buf = new byte[len];
                for (int i = 0; i < len; i++)
                {
                    buf[i] = (byte)(Byte)Int16.Parse(Cmd.Str.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
                }
                try
                {
                    serialPort1.Write(buf, 0, len);
                }
                catch (Exception)
                {
                    Cmd.ErrorCode = -1;
                    return;
                }
            }
        }

 

等待回碼的部分

        public void Listen_1()
        {
            ReceiveMsg = "";

            int iStart = Environment.TickCount;
            int data = -1;
            String msg = "";

            do
            {
                //超時
                int iTime = Environment.TickCount - iStart;
                if (iTime > Timeout)
                {
                    ErrorCode = -1;
                    return;
                }
                //讀取一個字元,此函式會一直等到有訊號
                try
                {
                    data = serialPort1.ReadByte();
                }
                catch (TimeoutException)
                {
                    ErrorCode = -1;
                    return;
                }
                if (data < 0) continue;
                iStart = Environment.TickCount;

                if (Mode==CmdMode.ASCII)
                {
                    msg += (char)(data);

                    //計算是否為符合LRC
                    if (msg.Length > 2)
                    {
                        String StrData = msg.Substring(0, msg.Length - 2);
                        String StrLRC = msg.Substring(msg.Length - 2, 2);
                        if (CheckSum.LRC(StrData) == StrLRC) break;
                    }

                }
                else
                {                    
                    msg += data.ToString("X2");

                    //計算是否為符合CRC
                    if (msg.Length > 4)
                    {
                        String StrData = msg.Substring(0, msg.Length - 4);
                        String StrCRC = msg.Substring(msg.Length - 4, 4);
                        if (CheckSum.CRC_16(StrData) == StrCRC) break;
                    }
                }
            } while (true);

            if (ErrorCode == 0) ReceiveMsg = msg;
        }

下載範例

我的問卷調查

相關文章:

arrow
arrow
    文章標籤
    程式語言 RS232
    全站熱搜

    史克威爾凱特 發表在 痞客邦 留言(0) 人氣()