C#은 Siemens PLC1500의 ModbusTcp 서버와 통신합니다. 4-ModbusTcp 클라이언트 구축

 1. 고객 선정

클라이언트는 프로그램일 수도 있고 장치일 수도 있습니다. 여기서는 C#WINFORM 프로그램을 사용하여 클라이언트와 PLC의 Modbustcp 서버 간의 통신을 구현합니다. 개발 환경은 VS2019이고, .NET Framework 버전은 4.7.2입니다.

2. winform 프로그램 만들기

 클래스 라이브러리 만들기

 

다양한 유형의 C#에 대한 변환 라이브러리를 작성하세요. 라이브러리는 제가 제공하므로 걱정하지 마세요. 기사 마지막 부분에 제공되어 있습니다.

프로젝트는 이 클래스 라이브러리를 가져옵니다. 

 

3. Nmodbus4 프로토콜 도입

프로젝트를 찾아 레퍼런스를 찾아 "Manage nuget program"을 마우스 오른쪽 버튼으로 클릭하면 아래와 같은 대화상자에서 조작이 가능합니다.

 4. 인터페이스 레이아웃은 다음과 같습니다:

레이아웃은 드롭다운 상자 콤보 상자, 텍스트 상자 텍스트 상자, 버튼 버튼, 라벨 라벨을 사용합니다.

 이 IP 주소와 포트 번호는

 

5. 양식은 두 개의 변수를 정의하고 해당 명령 공간을 소개합니다.

        ModbusIpMaster 마스터 = null;//modbus 객체
        TcpClient tcpClient = null;//tcp 클라이언트 객체

6. 연결 버튼 코드

 private void btnOpen_Click(object sender, EventArgs e)
        {
            string ip = txtIPAddress.Text.Trim();
            bool t = IsIP(ip);
            if (t)
            {
                try
                {
                    int port = int.Parse(txtPort.Text.Trim());
                    tcpClient = new TcpClient();
                    tcpClient.Connect(ip, port);//连接到主机
                    master = ModbusIpMaster.CreateIp(tcpClient);//Ip 主站
                    master.Transport.ReadTimeout = 1000;//读超时
                    master.Transport.WriteTimeout = 1000;//写超时
                    master.Transport.Retries = 3;//尝试重复连接次数
                    master.Transport.WaitToRetryMilliseconds = 200;//尝试重复连接间隔
                    lblMessage.Text = "连接成功!";
                    btnOpen.Enabled = false;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("连接失败," + ex.Message);
                }
            }
            else
            {
                MessageBox.Show("无效的ip地址!");
            }
        }

 7. 읽기 코드 - ushort 유형

이 예에서는 홀딩 레지스터를 읽는 기능 코드, 즉 ReadHoldingRegisters(슬레이브 스테이션 주소, 시작 주소, 레지스터 수)만 사용됩니다.

  /// <summary>
        /// 读取
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void myread_Click(object sender, EventArgs e)
        {
            //由于NModbus4读取到寄存器的数据都是ushort类型
            //功能码
            string readType = cboReadTypes.Text.Trim();
            //从站地址
            byte slaveAddr = byte.Parse(txtRSlaveId.Text.Trim());
            //开始地址
            ushort startAddr = ushort.Parse(txtRStartAddress.Text.Trim());
            //读取数量
            ushort readCount = ushort.Parse(txtRCount.Text.Trim());
            switch (readType)
            {
                case "读线圈":
                    bool[] blVals = master.ReadCoils(slaveAddr, startAddr, readCount);
                    txtReadDatas1.Text = string.Join(",", blVals.Select(b => b ? "1" : "0"));
                    break;
                case "读输入线圈":
                    bool[] blInputVals = master.ReadInputs(slaveAddr, startAddr, readCount);
                    txtReadDatas1.Text = string.Join(",", blInputVals.Select(b => b ? "1" : "0"));
                    break;
                case "读保持寄存器":
                    //情况1:ushort到ushort类型:即读取无符号的整数,如23,89,处理方法是:原封不动
                    //ushort[] uDatas = master.ReadHoldingRegisters(slaveAddr, startAddr, readCount);
                    //txtReadDatas.Text = string.Join(",", uDatas);

                    //功能码
                    string dataType = cmddatatype.Text.Trim();
                    switch (dataType)
                    {
                        case "ushort":
                            //利用token循环读取
                            ushortctsRead = new CancellationTokenSource();
                            Task.Run(new Action(() =>
                            {
                                ReadUshortFromPLC(slaveAddr, startAddr, readCount);
                            }), ushortctsRead.Token);
                            break;
                        case "short":
                            //利用token循环读取
                            shortctsRead = new CancellationTokenSource();
                            Task.Run(new Action(() =>
                            {
                                ReadShortFromPLC(slaveAddr, startAddr, readCount);
                            }), shortctsRead.Token);
                            break;
                        case "float":
                            //利用token循环读取
                            floatctsRead = new CancellationTokenSource();
                            Task.Run(new Action(() =>
                            {
                                ReadFloatFromPLC(slaveAddr, startAddr, readCount);
                            }), floatctsRead.Token);
                            break;
                    }  
                    break;
                case "读输入寄存器":
                    ushort[] uDatas1 = master.ReadInputRegisters(slaveAddr, startAddr, readCount);
                    txtReadDatas1.Text = string.Join(",", uDatas1);
                    break;
            }
        }

여기서 참고하세요,

NModbus4가 레지스터로 읽은 데이터는 ushort 유형입니다.

NModbus4가 레지스터로 읽은 데이터는 ushort 유형입니다.

ReadUshortFromPLC 메서드, ReadShortFromPLC 메서드 및 ReadFloatFromPLC 메서드는 이 기사 끝에 있는 링크의 코드에서 사용됩니다.

프로그램을 실행하고, 성공적으로 연결하고, 데이터를 읽습니다.

 여기서 슬레이브 스테이션 주소는 일반적으로 1이며 변경하지 않는 한 시작 주소는 레지스터의 시작 주소를 의미하는 0이고 숫자는 읽기 3 레지스터의 수를 의미하는 3입니다. 처음 3개 변수, m1-speed, m1-duaror, m1-level

 여기서 수량이 4가 될 수 없는 이유는 네 번째 변수가 2개의 레지스터, 즉 4바이트를 차지하는 실수이고 ushort 유형이 아니며 여기 주소는 %DB3.DBW4로 쓸 수 없기 때문입니다. S7 프로토콜 읽기 변수는 MODBUS 읽기 레지스터입니다. 둘은 서로 다릅니다. 혼동하지 마십시오.

8. 코드 읽기 - 부동 소수점 유형

 많은 분들이 시작주소와 수량에 대해 헷갈려 하시는데요, 시작주소는 모드버스의 주소이고, 모드버스 주소번호는 0부터 시작하므로 8개의 변수의 주소는 0, 1, 2, 3, 4, 5, 6 입니다. , 7, 수량 읽을 레지스터 수를 의미하며 워드는 1개, 실수는 2개를 차지합니다. 여기서 이해하기 어렵고 더 혼란스럽습니다. 하나는 PLC 주소이고 다른 하나는 MODBUS 주소입니다.

우리가 읽으려는 온도는 세 번째 레지스터로, 실제 유형이고 2개의 레지스터를 차지합니다.

『본투의 온도2』를 읽고 싶다면 어떻게 읽어요?

 

 생각해 보세요. 시작 주소가 왜 8인가요?

9. 작성된 코드 - ushort 유형

 /// <summary>
        /// 写入
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnWrite_Click(object sender, EventArgs e)
        {
            //功能码
            string writeType = cboWriteTypes.Text.Trim();
            //从站地址
            byte slaveAddr = byte.Parse(txtWSlaveId.Text.Trim());
            //开始地址
            ushort startAddr = ushort.Parse(txtWStartAddress.Text.Trim());
            //数量
            //实际数量
            string objWriteVals = "";
            string dataType = cmddatatype2.Text.Trim();
            switch (dataType)
            {
                case "ushort":
                    objWriteVals = txtWriteDatas1.Text.Trim();
                    break;
                case "short":
                    objWriteVals = txtWriteDatas2.Text.Trim();
                    break;
                case "float":
                    objWriteVals = txtWriteDatas3.Text.Trim();
                    break;
            }
            ushort writeCount = ushort.Parse(txtWCount.Text.Trim()); 
            ushort objWCount = (ushort)objWriteVals.Split(',').Length;
            //实际数量与要求数量不一致,不允许操作
            if (writeCount != objWCount)
            {
                MessageBox.Show("写入值的数量不正确!");
                return;
            }
            string vals = objWriteVals;
            switch (writeType)
            {
                case "写单线圈":
                    bool blVal = vals == "1" ? true : false;
                    try
                    {
                        master.WriteSingleCoil(slaveAddr, startAddr, blVal);
                        MessageBox.Show("【单线圈】写入成功!");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    break;
                case "写单保持寄存器":
                    ushort uVal01 = ushort.Parse(vals);
                    try
                    {
                        master.WriteSingleRegister(slaveAddr, startAddr, uVal01);
                        MessageBox.Show("【单保持寄存器】写入成功!");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    break;
                case "写多线圈":
                    bool[] blVals = vals.Split(',').Select(s => s == "1" ? true : false).ToArray();//bool数组
                    try
                    {
                        master.WriteMultipleCoils(slaveAddr, startAddr, blVals);
                        MessageBox.Show("【多线圈】写入成功!");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    break;
                case "写多保持寄存器":
                    try
                    {
                        //功能码
                        //string dataType = cmddatatype2.Text.Trim();
                        switch (dataType)
                        {
                            case "ushort":
                                情况1:写入无符号的整数,即写入ushort数据,如写入33,44
                                ushort[] uVals01 = vals.Split(',').Select(s => ushort.Parse(s)).ToArray();
                                master.WriteMultipleRegisters(startAddr, uVals01);
                                break;
                            case "short":
                                //情况2:写入有符号的整数,即写入short数据,如写入-133,-65,98等,处理方法是:short[]=>byte[]=>ushort[],情况2包括了情况1 
                                short[] uVals02 = vals.Split(',').Select(s => short.Parse(s)).ToArray();
                                byte[] y2 = ByteArrayLib.GetByteArrayFromShortArray(uVals02);
                                ushort[] ushorts2 = UShortLib.GetUShortArrayFromByteArray(y2);
                                master.WriteMultipleRegisters(startAddr, ushorts2);
                                MessageBox.Show("【short类型数据】写入成功!");
                                break;
                            case "float":
                                //情况3:写入有符号的小数,即写入float数据,如写入-6.3,-2.65,56.893,51,-465等,处理方法是:float[]=>byte[]=>ushort[],情况3包括了情况2和情况1 
                                float[] uVals03 = vals.Split(',').Select(s => float.Parse(s)).ToArray();
                                byte[] y3 = ByteArrayLib.GetByteArrayFromFloatArray(uVals03);
                                ushort[] ushorts3 = UShortLib.GetUShortArrayFromByteArray(y3);
                                master.WriteMultipleRegisters(startAddr, ushorts3);
                                MessageBox.Show("【float类型数据】写入成功!");
                                break;
                        }



                        情况2:写入有符号的整数,即写入short数据,如写入-133,-65,98等,处理方法是:short[]=>byte[]=>ushort[],情况2包括了情况1 
                        //short[] uVals02 = vals.Split(',').Select(s => short.Parse(s)).ToArray();
                        //byte[] y = ByteArrayLib.GetByteArrayFromShortArray(uVals02);
                        //ushort[] ushorts = UShortLib.GetUShortArrayFromByteArray(y);
                        //master.WriteMultipleRegisters(slaveAddr, startAddr, ushorts);

                        情况3:写入有符号的小数,即写入float数据,如写入-6.3,-2.65,56.893,51,-465等,处理方法是:float[]=>byte[]=>ushort[],情况3包括了情况2和情况1 
                        //float[] uVals02 = vals.Split(',').Select(s => float.Parse(s)).ToArray();
                        //byte[] y = ByteArrayLib.GetByteArrayFromFloatArray(uVals02);
                        //ushort[] ushorts = UShortLib.GetUShortArrayFromByteArray(y);
                        //master.WriteMultipleRegisters(slaveAddr, startAddr, ushorts);

                        MessageBox.Show("【多保持寄存器】写入成功!");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    break;
            }
        }

 쓰기가 성공하고, 방금 쓴 값도 동시에 읽어오는데, 이는 Portal의 모니터링 테이블에서 확인할 수 있습니다.

10. 작성된 코드 - float 유형

 

부정적인 글을 쓰다

 "모터 2 온도" 레지스터에 데이터를 씁니다.

블로그의 데이터를 살펴보세요

 

11. 요약

클라이언트는 TCP 클라이언트 객체를 생성하고 Modbus는 TCP 객체를 사용하여 Modbus 통신을 생성한 다음 다양한 데이터 유형을 통해 PLC 데이터를 성공적으로 읽고 씁니다.

코드 링크:

링크: https://pan.baidu.com/s/1aCqv3eSX-7SXAdGtrGNpTw 
추출 코드: kyqo  

추천

출처blog.csdn.net/hqwest/article/details/132450229