본문 바로가기
카테고리 없음

[C#] Modbus-RTU

by YJHTPII 2023. 7. 20.
반응형

 

C# 모드버스 RTU 시리얼통신 구현하기

 

 

C# 모드버스 RTU 시리얼통신 구현하기

안녕하세요~ 시리얼 통신도 TCP/IP 모드버스 프로토콜과 차이가 많이나지 않습니다! Send Receive 기본틀도 둘다 비슷하구요 단지 요청프로토콜에 앞 헤더를 제외하고 뒤에 CRC가 추가되는 점이 조금

dodo1054.tistory.com

 

 

C# 모드버스 RTU 시리얼통신 구현하기2 (주석 추가, 소스 공개)

 

C# 모드버스 RTU 시리얼통신 구현하기2 (주석 추가, 소스 공개)

안녕하세요 모드버스 시리얼 통신에 대해 소스 요청이 많아 테스트로 작성한 코드입니다. 블로그에 기존에 작성된 소스를 기반으로 작성했으며 클래스를 상속하여 오버라이딩한 부분도 같습니

dodo1054.tistory.com

 

C# 모드버스 RTU 시리얼통신 구현 폼 (주석 추가, 소스 공개)

 

C# 모드버스 RTU 시리얼통신 구현 폼 (주석 추가, 소스 공개)

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TEST_RTU { public partial class

dodo1054.tistory.com

 

안녕하세요~ 

시리얼 통신도 TCP/IP 모드버스 프로토콜과 차이가 많이나지 않습니다!

Send Receive 기본틀도 둘다 비슷하구요

단지 요청프로토콜에 앞 헤더를 제외하고 뒤에 CRC가 추가되는 점이 조금 다르죠!

 

 시리얼 통신용 기본 요청 프로토콜 입니다.

 

 public static byte[] W_buff =
         {
                0x01,        //ID
                0x04,        //FunctionCode
                0x00, 0x01,  //시작주소 
                0x00, 0x40,  //데이터 길이(64)  
                0x73, 0xFB   //CRC 값 계산필요!
         };

 

CRC 값 구하는 부분은 밑에 링크로 올려두었습니다.

시리얼포트 기본소스입니다.

 

iniPort 메서드는 는 ini파일을 이용하여 가져온 시리얼 포트 연결속성을 설정하는 부분입니다.

통신포트, 속도, 패리티비트, 데이타비트, 스탑비트..

 

나머지 메서드는 메서드 이름과 같은 동작을 하구요!

 

SerialDataReceivedEventHandler 이벤트는 데이터를 읽기위해 필수적인 이벤트입니다.

후에 추가적으로 더 올리도록 하겠습니다 Receive 부분 포함해서요!

궁금한점은 댓글로..

class TSerialPort
    {
 
        public SerialPort serialport = new SerialPort();
 
        //===========================================================//
        /// <summary>
        /// 시리얼 통신 설정 값 설정 메서드
        /// </summary>
        /// <param name="com"></param>
        /// <param name="baudrate"></param>
        /// <param name="parity"></param>
        /// <param name="data"></param>
        /// <param name="stop"></param>
        public void fn_iniPort(string com, int baudrate, string parity, int data, int stop)
        {
            serialport.PortName = com;
            serialport.BaudRate = baudrate;
            if (parity == "N") serialport.Parity = Parity.None;
            else if (parity == "O") serialport.Parity = Parity.Odd;
            else if (parity == "E") serialport.Parity = Parity.Even;
 
            serialport.DataBits = data;
            if (stop == 1) serialport.StopBits = StopBits.One;
            else if (stop == 2) serialport.StopBits = StopBits.Two;
        }
 
        //=================================================================//
        /// <summary>
        /// 시리얼 포트 오픈
        /// </summary>
        /// <param name="fn_OnDataReceived"></param>
        /// <returns></returns>
        //=================================================================//
        public int fn_Open(SerialDataReceivedEventHandler fn_OnDataReceived)
        {
            int iret = 1;
            serialport.DataReceived += fn_OnDataReceived;
            try
            {
                serialport.Open();
            }
            catch(Exception e)
            {
                iret = -1;
            }
            return iret;
        }
 
        //=====================================================================//
        //시리얼 포트 연결 종료
        //=====================================================================//
        public void fn_Close(SerialDataReceivedEventHandler fn_OnDataReceived)
        {
            if (serialport.IsOpen)
            {
                serialport.Close();
                serialport.DataReceived -= fn_OnDataReceived ;
            }
        }
 
        //=====================================================================//
        //데이터 요청
        //=====================================================================//
        public void fn_Send(byte[] buffer, int offset, int count)
        {
            //보낼데이터(버퍼값), 번지수, 데이터 개수)
            serialport.Write(buffer, offset, count);
        } 
    }

 

 

 

안녕하세요

모드버스 시리얼 통신에 대해 소스 요청이 많아 테스트로 작성한 코드입니다.

 

블로그에 기존에 작성된 소스를 기반으로 작성했으며

클래스를 상속하여 오버라이딩한 부분도 같습니다.

 

주석을 조금 자세히 추가하였으며 궁금한 점은 댓글로 여쭤봐주시면 감사하겠습니다.

 

CRC 계산하는 클래스와 리시브한 데이터를 컨버전하는 클래스는 따로 사용하지 않았습니다.

 

Sendbuff 는 통신 장비에 따라 얼마든지 바뀔 수 있습니다.

함수번호와 아이디는 대부분 0x01 ~ 0x02 , 0x03 ~ 0x04 일 확률이 높습니다.

 

MainConnect.cs (통신 연결을 도와주는 메인 클래스)

 

전체 파일 요청은  댓글로 부탁드려요

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TEST_RTU
{
    //cSerialPort 함수 상속(오버라이딩을 하기위함)
    class MainConnect : cSerialPort
    {
        //CRC 계산 함수(현재 쓰지 않음)
        //cModbusRTU RtuCRC = new cModbusRTU();

        //컨버젼 클래스(데이터 컨버전)  
        //cConversion conv = new cConversion();
        cLog log = new cLog();
        cWaitBuff WaitBuff = new cWaitBuff();
        // 시리얼 통신시 쓰레기 값 자르기
        public int COM_READtail;
        private int ilen;

        //요청버퍼 
        public static byte[] Sendbuff =
        {
             0x01,        //ID
             0x04,        //FunctionCode
             0x0B, 0xB8,  //시작주소 
             0x00, 0x40,  //데이터 길이(64)  ///(63) //0x00 0x3F 0x32 0x1B
             0x73, 0xFB   //CRC
         };


        private byte[] ReadBuff = new byte[1024];
        public static byte[] CopyReadBuff = new byte[1024];
    //================================================================================================
    //================================================================================================//

        //==============================
        //시리얼 포트 오픈 함수 오버라이딩 
        //iret 값이 1이면 오픈 / -1이면 에러
        //==============================//
        public int SerialPort_Open()
        {
            int iret = 0;
            iret = fn_Open(DataReceived);
            return iret;
        }

        //=====================================
        // 시리얼 포트 닫기 함수 오버라이딩 
        // 포트가 오픈되어있는 경우 CLOSE
        //=====================================//
        public void SerialPort_Close()
        {
            if (serialport.IsOpen == true)
            {
                fn_Close(DataReceived);
                serialport.DataReceived -= DataReceived;
            }
        }
        //=====================================
        // 데이터를 받기위한 함수(리시브)
        // 시리얼포트 오픈시 바로 데이터 수신
        //=====================================//
        private void DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            try
            {
                ilen = serialport.BytesToRead;

                // 받은 데이터 길이(바이트)가 200 이상일 경우 COM_READtail 변수 초기화
                if (ilen > 200 || COM_READtail > 200) COM_READtail = 0;

                /* 통신을 통해 읽은 데이터(바이트 수) 만큼 
                 * ReadBuff에 복사 
                 * (COM_READtail 0 일시 ReadBuff 0 번째 부터 복사)
                 * (COM_READtail 1 일시 ReadBuff 1 번째 부터 복사) */
                serialport.BaseStream.Read(ReadBuff, COM_READtail, ilen);
                COM_READtail += ilen;

                //로그 함수를 이용한 데이터 확인용 로그만들기 //
                log.fn_LogWrite("수신한 바이트 수 : " + COM_READtail);

                // 읽은 데이터 길이가 133개, 함수번호가 0x04, 장치번호가 0x00 일 경우에
                // 알맞은 데이터가 수신된 것으로 확인
                // (보내기(요청) 버퍼에 따라 수신되는 길이와 함수번호 장치번호는 다를 수 있음
                
                if (COM_READtail == 133 && ReadBuff[1] == 0x04)
                {
                    log.fn_LogWrite("알맞은 데이터를 수신함");
                    //ReadBuff의 값을 CopyReadBuff 0번째 부터 128 데이터의 길이를 복사
                    //앞 시작주소, 함수번호, 장비번호를 제외한 실질 데이터를 구분하기 위함
                    Array.Copy(ReadBuff, 3, CopyReadBuff, 0, 128);
                    //COM_READtail = 0;
                }
                else COM_READtail = 0;
            }catch (Exception ex)
            {
                log.fn_LogWrite("DataRecived 함수 에러 : " + ex.Message.ToString());
            }
        }


        //===========================================
        //쓰레드 타이머를 이용하여 데이터 요청(Send)
        //===========================================
        public void CallbackData(object o)
        {
            try
            {
                //웨이트 버퍼의 큐에 카운트가 0일 시 데이터 요청
                //웨이트 버퍼의 큐에 카운트가 있을 시는(다른 데이터 요청이 있을경우)
                //Dequeue 함수를 통해 다른 데이터 요청 버퍼 처리
                if (WaitBuff.queue_WAITBUFF.Count == 0) fn_Send(Sendbuff, 0, 8);
                else if(WaitBuff.queue_WAITBUFF.Count > 0)
                {
                    byte[] SenBuff = new byte[8];
                    SenBuff = WaitBuff.queue_WAITBUFF.Dequeue();
                    fn_Send(SenBuff, 0, 8);
                }
            }
            catch (Exception e)
            {
                log.fn_LogWrite("CallBackData 함수 에러 : " + e.Message.ToString());
            }
        }

    }
}
 

 

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TEST_RTU
{
    public partial class Form1 : Form
    {
        //통신 연결 클래스
        MainConnect MainCon = new MainConnect();
        cThreadingTimer ThreadTimer = new cThreadingTimer();

        int OpenCheck = 0;
        public Form1()
        {
            InitializeComponent();
            RtuConnection();
            timer1.Enabled = true;
            timer2.Enabled = true;
        }


        //=========================================================
        //시리얼포트 연결을 위한 설정 
        //=========================================================
        private void SeriportSetUp()
        { 
            //설정 값은 (각 통신 장비마다 다름)//
             MainCon.fn_iniPort("COM1", 9600, "N", 8, 1);
        }

        //========================================================
        //프로그램 동작시 장치정보(셋업)에 따라 시리얼포트 연결 
        //========================================================
        private void RtuConnection()
        {
            try
            {
                SeriportSetUp();
                MainCon.SerialPort_Open();
                if (MainCon.serialport.IsOpen == true)
                {
                    OpenCheck = 1;
                    ThreadTimer.fn_start(MainCon.CallbackData, 2000, 1000);
                }
                else
                {
                    SeriportSetUp();
                    MainCon.SerialPort_Open();
                }
            }catch (Exception e)
            {
                e.Message.ToString();
            }
        }
        /* ****************** 송수신 데이터 확인 메서드 **********************
         * ****************** Hex 변환 값 뿌려주기 ********************
         * *******************************************************************/
        private void fn_TestData()
        {
            if (OpenCheck == 1)
            {
                for (int i = 0; i < 8; i++) textBox1.Text += fn_SendToHex(i).ToString() + " ";
                for (int i = 0; i < 126; i++) textBox2.Text += fn_ReadToHex(i).ToString() + " "; 
            }
        }

        /* ****************** 송신 버퍼 값을 Hex 값으로 변환하여 확인 *********************/
        /**********************************************************************************/
        private string fn_SendToHex(int startx)
        {
            string str;
            str = string.Format("{0:X2}", MainConnect.Sendbuff[startx]);
            return str;
        }

        /* *************** 수신(리드) 버퍼 값을 Hex 값으로 변환하여 확인 ****************/
        /********************************************************************************/
        private string fn_ReadToHex(int startx)
        {
            string str;
            str = string.Format("{0:X2}", MainConnect
                .CopyReadBuff[startx]);
            return str;
        }

        // 1초 타이머 함수 
        // fn_TestData IN Timer 
        private void timer1_Tick(object sender, EventArgs e)
        {
            fn_TestData();
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox1.Text = "";
            textBox2.Text = "";
        }
    }
}

 

반응형

댓글