본문 바로가기
프로그래밍/C Sharp

[ C# ] 미쯔비시 QnA 시리즈 PLC와 PC 이더넷 통신하기

by jeong-f 2021. 11. 23.
반응형

통신 설정

통신 설정을 위해서 [ PLC 파라미터 ] 설정 메뉴를 눌러 파라미터 창을 띄웁니다.

  • IP 설정 : [ 디바이스 설정 ]에서 IP 및 서블릿 기본 라우터 IP를 입력합니다.
  • 통신 모드:   바이너리로 체크하고 RUN 중 쓰기를 허가합니다.
  • 통신방식 및 포트 : [ 오픈 설정 ]에서 통신 방식 TCP , 오픈 방식 MC 프로토콜 , 포트번호를 지정합니다.

프로토콜 형식

프로토콜 형식은 시리얼 방식 C 타입과 이더넷 방식 E 타입으로 나뉩니다.

4C

  • 최댓값으로 외부 장치에서 액세스 가능 접근 범위.
  • MELSEC-QnA 시리즈 시리얼 전용 프로토콜 통신 모듈(QnA 확장 프레임)

3C

  • 메시지 형식은 4C 프레임에 비해 단순합니다.
  • MELSEC-QnA 시리즈 프로그래밍 가능한 컨트롤러용 데이터 통신 소프트웨어를 사용할 수 있습니다.
  • MELSEC-QnA 시리즈 시리얼 통신 모듈 전용 프로토콜(QnA 프레임)

2C

  • 메시지 형식은 4C 프레임에 비해 단순합니다.
  • MELSEC-QnA 시리즈 프로그래밍 가능한 컨트롤러용 데이터 통신 소프트웨어를 사용할 수 있습니다.
  • MELSEC-QnA 시리즈 시리얼 통신 모듈 전용 프로토콜(QnA 단순 프레임)

1C

  • MELSEC-A 시리즈 컴퓨터 링크 모듈을 사용하여 CPU 모듈에 액세스 할 때와 동일한 메시지 구조를 가집니다.
  • MELSEC-A 시리즈 프로그래밍 가능한 컨트롤러를 위한 데이터 통신 소프트웨어를 사용할 수 있습니다.
  • MELSEC-A 시리즈 컴퓨터 링크 모듈 전용 프로토콜

4E

  • 시리얼 번호(메시지 식별을 위한 임의 번호)가 3E 프레임에 추가되는 메시지 형식입니다.
  • 시리얼 번호를 추가하여 여러 요청 메시지가 전송되었을 때 송신 소스를 식별할 수 있습니다.
  • SLMP용 메시지 형식

3E

  • 프레임은 MELSEC-QnA 시리즈 이더넷 인터페이스 모듈을 사용하여 CPU 모듈에 액세스 할 때와 동일한 메시지 구조를 가집니다.
  • MELSEC-QnA 시리즈 프로그래머블 컨트롤러용 데이터 통신 소프트웨어를 사용할 수 있습니다.
  • SLMP용 메시지 형식, MELSEC-QnA 시리즈 이더넷 인터페이스 모듈용 메시지 형식

1E

  • MELSEC-A 시리즈 이더넷 인터페이스 모듈을 사용하여 CPU 모듈에 액세스 할 때와 동일한 메시지 구조를 가집니다.
  • MELSEC-A 시리즈 프로그램 가능 컨트롤러용 데이터 통신 소프트웨어를 사용할 수 있습니다.
  • MELSEC-A 시리즈 이더넷 인터페이스 모듈용 메시지 형식

우리가 사용할 프로토콜은 위의 형식 중 QnA 시리지의 이더넷 인터페이스 이므로 3E 방식을 사용하려고 합니다.

명령어

아래의 기본 데이터 프레임으로 명령어를 전송하여 데이터를 읽거나 쓰는 역할을 합니다.

코드에서 보기

반응형
[Serializable]
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    struct _3E_BIN_REQUEST_COMMAND
    {
        public ushort wSubHeader;           // 서브 머리글
        public byte byNetworkNo;            // 네트워크 번호
        public byte byPlcNo;                // PLC 번호
        public ushort wModuleIONo;          // 요구 상대 모듈 I/O 번호	
        public byte byModuleStateNo;        // 요구 상대 모듈 국번호
        public ushort wDataLength;          // 요구 데이터 길이
        public ushort wCpuTimer;            // CPU 감시 타이머
        public ushort wCommand;             // Command(Read)
        public ushort wSubCommand;          // Sub Command

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] byDeviceAddr;         // 선두 Device Addr
        public byte byDeviceCode;           // Device 코드
        public ushort wCount;               // 읽기/쓰기 점수
    };

PLC와 통신을 하려면 기본적으로 읽기와 쓰기 동작을 필요로 하며 나머지 기능들은 실제로 사용할 경우가 없습니다.
기본적으로 Command와 Subcommand 조합으로 데이터 명령을 PLC에 보냅니다.

읽기 명령

매뉴얼에 나온 명령을 그대로 풀면 T100번 워드 3개를 읽어오라는 명령입니다.

  • Command : 0401
  • Subcommand : 0001= 비트 읽기
  • 디바이스 번호 : 0x64 = 100번
  • 디바이스 코드 : 0xC2 = 'T'번지
  • 디바이스 개수 : 3개

코드에서 보기

추가적인 내용이 있으나 길이가 길어 일부만 표시하였습니다.

3E_BIN_REQUEST_COMMAND _Request_Header = new _3E_BIN_REQUEST_COMMAND();
            _Request_Header.byDeviceAddr = new byte[3];

            int dwTemp = 0x64; // 주소

	        _Request_Header.wSubHeader			= 0x0050;// 바이너리 50 Subheader			
	        _Request_Header.byNetworkNo			= 0x00;				
	        _Request_Header.byPlcNo				= 0xFF;				
	        _Request_Header.wModuleIONo			= 0x03FF;			
	        _Request_Header.byModuleStateNo		= 0x00;				
	        _Request_Header.wDataLength			= 0x000C;			
	        _Request_Header.wCpuTimer			= 0x0010;			
	        _Request_Header.wCommand			= 0x0401;				// read command '0401';		
	        _Request_Header.wSubCommand			= 0x0001;			
	        _Request_Header.byDeviceAddr[0]		= (byte)(dwTemp		& 0x000000FF);
            _Request_Header.byDeviceAddr[1]     = (byte)(dwTemp >> 8 & 0x000000FF);
            _Request_Header.byDeviceAddr[2]     = (byte)(dwTemp >> 16 & 0x000000FF);
            _Request_Header.byDeviceCode        = 0xC2; // T
            _Request_Header.wCount              = (ushort)3; // 읽을 수량

            byte[] buffer = new byte[Marshal.SizeOf(_Request_Header)];

            unsafe
            {
                fixed (byte* fixed_buffer = buffer)
                {

                    Marshal.StructureToPtr(_Request_Header, (IntPtr)fixed_buffer, false);
                }
            }

쓰기 명령

매뉴얼에 나온 명령을 그대로 풀면 D100번부터 순서대로 0x1995 , 0x1202, 0x1103을 쓰라는 명령입니다.

  • Command : 0401
  • Subcommand : 0001= 비트 읽기
  • 디바이스 번호 : 0x64 = 100번
  • 디바이스 코드 : 0xA8 = 'D'번지
  • 쓰기 데이터 : 0x1995 , 0x1202, 0x1103

코드에서 보기

추가적인 내용이 있으나 길이가 길어 일부만 표시하였습니다.

_3E_BIN_REQUEST_COMMAND _Request_Header = new _3E_BIN_REQUEST_COMMAND();
            _Request_Header.byDeviceAddr = new byte[3];

            int dwTemp = 0 + _nOffset;
            int nWordCnt = 3;

            _Request_Header.wSubHeader      = 0x0050;// MC3E	
            _Request_Header.byNetworkNo     = 0x00;
            _Request_Header.byPlcNo         = 0xFF;
            _Request_Header.wModuleIONo     = 0x03FF;
            _Request_Header.byModuleStateNo = 0x00;
            _Request_Header.wDataLength     = (ushort)(0x000C + (nWordCnt * 2));					
            _Request_Header.wCpuTimer       = 0x0010;
            _Request_Header.wCommand        = 0x1401;						// write command '1401';		
            _Request_Header.wSubCommand     = 0x0000;						// Bit 라이트 할 경우에는 0x0001, 만약 하지 않으면 전체 16bit write
            _Request_Header.byDeviceAddr[0] = (byte)(dwTemp & 0x000000FF);
            _Request_Header.byDeviceAddr[1] = (byte)(dwTemp >> 8 & 0x000000FF);
            _Request_Header.byDeviceAddr[2] = (byte)(dwTemp >> 16 & 0x000000FF);
            _Request_Header.byDeviceCode = 0xA8; // D메모리
            _Request_Header.wCount = (ushort)3;						// 3개

            byte[] buffer = new byte[Marshal.SizeOf(_Request_Header)];

            unsafe
            {
                fixed (byte* fixed_buffer = buffer)
                {
                    Marshal.StructureToPtr(_Request_Header, (IntPtr)fixed_buffer, false);
                }
            }
            
            Array.Clear(cmd_buf, 0, cmd_buf.Length);
            Array.Copy(buffer, cmd_buf, Marshal.SizeOf(_Request_Header));
            int nDataLen = buffer.Length;

            int idx = nDataLen;

            for (int i = 0; i < nWordCnt; i++)
            {
                cmd_buf[idx++] = (byte)(_pData[i] & 0xFF);
                cmd_buf[idx++] = (byte)(_pData[i] >> 8);
            }

            if (nWordCnt > 1)
            {
            }

            cmd_len = idx;// 20 + (nWordCnt * 2);

프로그램의 내용이 길어 완벽한 내용으로는 공유가 힘든 점 양해 바라며 위와 같은 형식으로 데이터를 송수신이 가능하다는 점을 참고하시기 바랍니다.

데이터를 읽어서 처리하는 부분은 코드가 길어 여기에서는 제외하였습니다.
매뉴얼을 참고하시어 프로그램을 제작하시기 바랍니다.

참고자료

191010 미쯔비시 통신_sh080008x.pdf
6.47MB

반응형

댓글