상세 컨텐츠

본문 제목

C# MELSECNET-G로 Mitsubishi PLC와 연동하기

프로그래밍/C#

by TickTack 2022. 10. 12. 15:57

본문

이번에는 C#에서 Mitsubishi PLC와 데이터를 주고 받기 위하여 연동할 때

MELSECNET을 이용하는 방법에 대하여 알아보겠습니다.

해당 글에서는 다음과 같은 기종을 사용하였습니다.

 

- CPU : Q06UDV

- Data Link Unit (CC IE Control) : QJ71GP21-SX

 

사용하기 위해서는 먼저 MELSECNET을 설치해야 합니다.

그래야 같이 추가되는 dll 파일을 이용하여 접근할 수 있기 때문입니다.

설치파일은 아래에서 받으실 수 있습니다.

 

 

MelsecNet-G 설치파일.zip

 

drive.google.com

 

매뉴얼은 MELSECNET을 설치하면 같이 설치가 될 것으로 보이나 만약 없을 경우 아래에서 받으실 수 있습니다.

 

 

MELSEC Data Link Library Manual.pdf

 

drive.google.com

 

만약 제가 올린 파일이 불안해서 받기 힘드신 분들은 공식사이트를 링크걸어 놓겠습니다.

그러나 제가 공식페이지에서 받아본 적이 없기에 정확한 위치는 말씀드리기 힘든 점 양해바랍니다.

공식페이지에서 자료를 다운받으려면 로그인을 해야하는 것으로 알고 있습니다.

저는 따로 회원가입을 하지 않아 대부분의 링크에 접근이 불가능하여

MELSECNET 프로그램은 위치를 알려드리기 힘든 점 양해바랍니다.

 

공식페이지의 매뉴얼은 아래에서 로그인 후 찾아서 받으실 수 있습니다.

 

 

[고객지원-일본 다운로드 링크] | 한국미쓰비시전기오토메이션(주)

DA 변환 모듈 Q62DA,Q64DA,Q68DAV,Q68DAI Digital-Analog Converter Module-User's Manual.pdf

kr.mitsubishielectric.com

 

설치파일의 압축을 해제 후 Setup.exe 파일을 실행합니다.

 

MELSECNET 설치 파일 폴더

 

실행하면 다음과 같은 창이 나오는데 확인을 누릅니다.

 

 

해당 창이 뜬다면 똑같이 확인을 누릅니다.

 

 

Setup 화면이 뜨면서 다음과 같은 창이 나타나면 예를 누릅니다.

 

 

Next를 누릅니다.

 

 

Next를 누르면 설치가 자동으로 진행됩니다.

 

설치 경로 설정

 

다음과 같이 선택한 후 Finish를 눌러 PC를 재시작합니다.

 

 

설치가 완료되면 시작 메뉴에 다음과 같은 폴더 및 프로그램이 나타납니다.

 

MELSEC 폴더

 

위의 프로그램 중 Device Monitor Utility가 MELSECNET 으로 접속해서 값을 변경했을 때 PLC의 값을 확인할 수 있는 프로그램이고, CC IE Control Utility는 연결되어 있는 CC IE Board를 검색하는 프로그램입니다.

아래는 각 프로그램의 화면입니다.

 

- CC IE Control utility

CC IE Control 모듈이 꼽혀있는 PLC가 연결되면 각 Board 항목에 값들이 자동으로 나타납니다.

해당 정보는 Own Station 정보가 아닌 Other Station의 설정된 값들로 보입니다.

 

 

- Device Monitor Utility

여러 개를 띄울 수 있고, 네트워크 설정 후 데이터를 모니터링 하거나 Bit 단위로 값을 변경할 수 있습니다.

먼저 초기화면입니다. 하단에는 연결된 CC IE Control 모듈의 정보가 나옵니다.

 

 

상단 메뉴바의 Setting에 있는 Device Setting에서 다음과 같이 설정하면

 

 

아래처럼 B0100 위치부터 총 64개의 Bit 값을 실시간으로 확인할 수 있습니다.

해당 값이 있는 곳을 더블클릭하면 On/Off도 가능합니다.

 

 

그리고 GX Works2에서 설정되어있는 PLC 정보입니다.

데이터는 해당 프로그램에서 보지 못하였으나 Ethernet 설정은 관계가 있을 것 같아 올립니다.

설정 창 메인부터 시작하여 Network Range Assignment ... 순으로 되어있습니다.

Network Operation Settings와 Interrupt Settings는 칸이 비어있어 올리지 않았습니다.

 

설정 메인화면

 

Network Range Assignment

 

Refresh Parameters

 

그리고 MELSECNET을 설치하면 다음 위치에 C#에서 참조할 dll 파일이 존재합니다.

 

MdFunc32.dll 위치

 

이제 C#에서 MELSECNET으로 PLC에 접속해보겠습니다.

먼저 소스창에서 다음과 같이 추가합니다.

접속, 해제, 송신, 수신에 대한 것만 있으며, 다른 여러 가지 함수도 존재합니다.

이 글에서 등장하는 함수의 반환값은 모두 0이면 정상, 0이 아니면 각각의 정해진 에러 코드에 따른 문제를 나타냅니다.

단, 일부 Ex 항목은 MELSECNET/H 보드를 사용 중 이라면 지원이 되지 않을 수 있으므로 참고바랍니다.

저는 현재 기종에서 mdSend, mdSendEx 함수 둘 다 동작을 확인하였습니다.

 

// 일부 항목
[DllImport("MDFUNC32.DLL")]
private static extern short mdOpen(short Chan, short Mode, ref int Path);
[DllImport("MDFUNC32.DLL")]
private static extern short mdClose(int Path);
[DllImport("MDFUNC32.DLL")]
private static extern int mdSendEx(int Path, int Netno, int Stno, int Devtyp, int devno, ref int size, ref short buf);
[DllImport("MDFUNC32.DLL")]
private static extern int mdReceiveEx(int Path, int Netno, int Stno, int Devtyp, int devno, ref int size, ref short buf);

// MELSECNET/H Board 인 경우 반드시 Ex 대신 사용
[DllImport("MDFUNC32.DLL")]
private static extern short mdSend(int Path, short Stno, short Devtyp, short devno, ref short size, ref short buf);
[DllImport("MDFUNC32.DLL")]
private static extern short mdReceive(int Path, short Stno, short Devtyp, short devno, ref short size, ref short buf);

 

그리고 Network Number와 Station Number를 설정합니다.

Other Setting의 Number 값은 예시이며, 실제 PLC에서 설정된 값을 따릅니다.

 

// Own Setting
private short stNo = 255;
private short netNo = 0;

// Other Setting
private short stNo = 1;
private short netNo = 1;

 

이제 연결을 시도해보겠습니다. 먼저 mdOpen 함수를 사용해서 PLC와 연결합니다.

mdOpen에는 3개의 인자가 있는데 의미는 다음과 같습니다.

뒤에 나오는 함수들도 매뉴얼에 각각의 인자에 대한 설명이 담겨있으므로 참고하시면 도움이 됩니다.

 

- chan

코드에 사용하는 chan 값은 Channel number 항목을 참조하시면 됩니다.

 

- mode

매뉴얼에는 Dummy 값이라고 나와있으며, 사용시에는 아래처럼 사용하시면 됩니다.

 

short result = -1;
switch (mnetMode)
{
    case "MELSECNET(usual)": result = 0; break;
    case "MELSECNET-II": result = 1; break;
    case "MELSECNET(mixed)": result = 2; break;
}

 

- path

매뉴얼에는 Return the opened line path. 이라고 설명되어있습니다.

사용시에는 초기값이 0인 변수를 하나 생성하여 집어넣으면 됩니다. (아래 소스에서는 변수명이 channelPath 임)

 

아래는 mdOpen 함수 사용 예시입니다.

반환값이 0이라면 연결 성공이고, 그 외의 값은 Error Code입니다.

Error Code에 대한 설명은 매뉴얼의 133페이지부터 나와있으므로 참조하시면 됩니다.

실패시에는 mdClose 함수를 사용함으로써 연결을 해제하고 있습니다.

 

private void TryConnect()
{
    int checkConnect = plc.Connect("MNETG-1", "MELSECNET(usual)");

    if (checkConnect == 0)
    {
        MessageBox.Show("연결되었습니다", "Connect Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
        _plcAct.Start();
    }
    else if (checkConnect != 0)
    {
        MessageBox.Show("연결에 실패하였습니다.", "Connect Fail", MessageBoxButtons.OK, MessageBoxIcon.Error);
        plc.Disconnect();
    }
}

public short Connect(string channel, string mnetMode)
{
    short mCH = GetChannelNum(channel);
    short mMD = GetMnetModeNum(mnetMode);

    return mdOpen(mCH, mMD, ref channelPath);
}

public short Disconnect()
{
    return mdClose(channelPath);
}

 

이제 mdSend 함수를 사용해서 데이터를 보내보겠습니다.

mdSend와 mdSendEx 함수의 인자가 살짝 다른데 각각의 사용 예시를 보겠습니다.

먼저 mdSend 입니다. 인자의 의미는 다음과 같습니다.

 

- path : Path of channel

            초기 접속 시 사용하였던 path 값을 사용하면 됩니다.

- stno : Station Number

            Own Station은 255이며, Other Station을 사용 할 경우 매뉴얼에 공식이 나와있습니다.

            매뉴얼의 48페이지부터 자세한 내용을 확인하실 수 있습니다.

- devtyp : Device type

            아래는 매뉴얼에 있는 내용을 가져온 것입니다. 52페이지부터 해당 내용들을 확인 가능합니다.

Device Type 1 Page

 

Device Type 2 Page

 

예시로 B 영역은 23, W 영역은 24입니다.

 

- devno : Start device number

               십진수 형태로 주어야하며, PLC에서는 16진수로 인식합니다.

               예를 들어 B4096으로 주게 되면 PLC의 B1000 위치로 접근한다는 의미입니다.

- size : Written byte size

            PLC에 쓸 byte 길이입니다.

- data : Written data

             PLC에 쓸 데이터입니다.

 

다음은 사용 예시입니다. 접속한 path, Station Number 값으로 B 영역의 1000번지에 5의 값을 쓰겠다는 의미입니다.

5를 쓰게되면 5 = 0000 0101 이니 B1000, B1002 번지에 1이 쓰여질 것입니다.

 

short size = 4;
short data = 5;
short result = mdSend(channelPath, stNo, 23, 4096, ref size, ref data);

 

이제 mdSendEx를 보겠습니다. 먼저 사용 예시 함수입니다.

앞서 설명드린 mdSend 함수도 이와 비슷하게 사용 가능할 것으로 보입니다.

 

public enum MelsecNetDevice
{
    M = 4,
    D = 13,
    B = 23,
    W = 24,
    ZR = 220
}

private int SendText(short networkNo, short stationNo, MelsecNetDevice device, int start, string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return 0;
    }

    byte[] bytes = Encoding.Default.GetBytes(text + "\0");
    short[] data = new short[bytes.Length / 2];
    int sendSize = bytes.Length;

    for (int i = 0; i < data.Length; i++)
    {
        data[i] = (short)(bytes[i * 2 + 1] * 0x100 + bytes[i * 2]);
    }

    return mdSendEx(channelPath, networkNo, stationNo, (int)device, start, ref sendSize, ref data[0]);
}

private int SendShort(short networkNo, short stationNo, MelsecNetDevice device, int start, short value)
{
    int sendSize = 2;
    return mdSendEx(channelPath, networkNo, stationNo, (int)device, start, ref sendSize, ref value);
}

 

channelPath는 접속 시 사용한 값을 사용하고,

networkNo는 0,

stationNo는 Own Station 으로 사용해서 255,

device는 W 영역을 사용할 것이기 때문에 24가 됩니다.

start는 시작 위치이고, 4096으로 주면 16진수인 1000으로 인식하고 동작합니다.

sendSize는 기록할 데이터의 길이이고 Byte 단위이므로 1word에 기록하려면 2를 주어야 합니다.

SendText에는 문자를 여러 개 보내기에 배열에 문자를 1word씩 담아놓고 0번째 위치부터 사용하도록 되어있고,

SendShort에는 숫자를 기록하는 것이라 short 형태의 숫자를 담습니다.

 

그리고 SendText에서 data 배열에 값을 넣을 때 0x100을 곱해주는 이유는 1word는 1111 1111 1111 1111 이기 때문에

1111 1111 부분으로 적용하기 위해서 입니다.

십진수로 풀어서보면 1111 1111은 255입니다. 그러나 1111 1111 0000 0000은 65280이 됩니다.

그래서 0x100 (256)을 곱해주어야 비로소 각각의 Byte에 대응할 수 있게 되는 것입니다.

물론 0x100을 곱해주지 않고 << 8 과 같이 Shift 연산자로 Bit를 이동시키는 방법도 있습니다.

Bit를 왼쪽으로 8칸 이동시켰으니 1111 1111 0000 0000이 나오기 때문입니다.

 

예시로 1word에 AB 라는 문자를 넣게되면 대문자 A는 Hex로 41, B는 Hex로 42 이므로, 해당 Word를 읽었을 때

Hex는 4241, 십진수는 16961이 나옵니다. Hex를 거꾸로 붙여놓았다고 생각하면 이해가 쉽습니다.

왜 거꾸로 붙여서 저장되는지에 대한 부분은 빅 엔디언(Big-endian), 리틀 엔디언(Little-endian)과 연관이 있습니다만 해당 내용까지 적게 되면 글이 너무 길어져서 추후 별도로 작성하겠습니다.

 

이제 mdReceive 함수의 사용 예시를 보겠습니다.

 

short readSize = 10;
mdReceive(channelPath, stNo, 23, 4096, ref readSize, ref readBuffer[0]);

 

함수에 사용된 인자의 의미를 보면

앞에서 계속 사용한 channelPath, Station Number 변수를 사용하고,

B1000 (Hex) 영역에서 읽을 예정이므로 23, 4096 (Dec)으로 설정합니다.

readSize는 10으로 설정하여 10개의 Bit를 읽을 것이고,

읽은 데이터를 readBuffer 배열의 0번째 인덱스부터 차례대로 채워넣는다는 의미입니다.

 

이제 mdReceiveEx 함수의 사용 예시를 보겠습니다.

앞서 설명드린 mdReceive 함수도 이와 비슷하게 사용이 가능할 것으로 보입니다.

 

private bool[] ReceiveBit(short networkNo, short stationNo, MelsecNetDevice dev, int start, int size)
{
    if (size <= 0)
    {
        return new bool[0];
    }

    int realSize = size * 8;
    bool[] result = new bool[realSize];
    short[] data = new short[size / 2];

    int res = mdReceiveEx(channelPath, networkNo, stationNo, (int)dev, start, ref size, ref data[0]);
    if (res != 0)
    {
        return null;
    }

    for (int i = 0; i < realSize; i++)
    {
        int index1 = i / 16;
        int index2 = i % 16;

        switch (index2)
        {
            case 0: if ((data[index1] & 0x1) == 0x1) result[i] = true; break;
            case 1: if ((data[index1] & 0x2) == 0x2) result[i] = true; break;
            case 2: if ((data[index1] & 0x4) == 0x4) result[i] = true; break;
            case 3: if ((data[index1] & 0x8) == 0x8) result[i] = true; break;
            case 4: if ((data[index1] & 0x10) == 0x10) result[i] = true; break;
            case 5: if ((data[index1] & 0x20) == 0x20) result[i] = true; break;
            case 6: if ((data[index1] & 0x40) == 0x40) result[i] = true; break;
            case 7: if ((data[index1] & 0x80) == 0x80) result[i] = true; break;
            case 8: if ((data[index1] & 0x100) == 0x100) result[i] = true; break;
            case 9: if ((data[index1] & 0x200) == 0x200) result[i] = true; break;
            case 10: if ((data[index1] & 0x400) == 0x400) result[i] = true; break;
            case 11: if ((data[index1] & 0x800) == 0x800) result[i] = true; break;
            case 12: if ((data[index1] & 0x1000) == 0x1000) result[i] = true; break;
            case 13: if ((data[index1] & 0x2000) == 0x2000) result[i] = true; break;
            case 14: if ((data[index1] & 0x4000) == 0x4000) result[i] = true; break;
            case 15: if ((data[index1] & 0x8000) == 0x8000) result[i] = true; break;
        }
    }

    return result;
}

public short[] ReceiveWord(short networkNo, short stationNo, MelsecNetDevice dev, int start, int size)
{
    if (size <= 0)
    {
        return new short[0];
    }

    short[] result = new short[size];
    int recvSize = result.Length * 2;
    if (mdReceiveEx(channelPath, networkNo, stationNo, (int)dev, start, ref recvSize, ref result[0]) != 0)
    {
        return null;
    }

    return result;
}

 

Bit를 읽을 때와 Word를 읽는 경우를 나누어 놨는데 Bit 부분부터 보겠습니다.

 

먼저 1byte = 8bit 이기 때문에 size * 8을 해서 realSize 변수에 저장합니다.

 

그 다음 bool 형태의 배열을 만들어서 realSize 만큼 크기를 지정합니다.

여기서 왜 bool 타입을 사용했는지 궁금할수도 있는데 Bit는 0과 1로만 되어있기 때문에

false, true로만 되어있는 bool 타입과 같은 의미를 지녔기 때문입니다.

 

다음은 data라는 이름의 short (Word) 배열을 생성하는데 1word는 2byte 이므로 처음 받은 size에서

나누기 2를하여 크기를 지정합니다.

 

mdReceiveEx 함수를 사용하여 PLC에서 데이터를 읽어옵니다.

channelPath 변수는 위에서 사용하였던 변수이고,

networkNo는 0, stationNo는 Own Station이기 때문에 255입니다.

dev는 B 영역이므로 23, start는 B1000 부터 읽을 예정이므로 1000 (Hex)의 십진수인 4096 (Dec)으로 줍니다.

size는 10byte를 읽을 경우 10으로 설정하고, data 배열에 처음부터 담을 것이므로 data[0] 으로 지정합니다.

결과값은 변수에 저장하여 성공 여부를 확인하고 실패했을 경우 null을 반환하도록 합니다.

 

그 밑의 반복문은 1word를 기준으로 각 Bit의 상태를 추출합니다. i가 16이 되어야 한 Word의 사이클이 돌아간 것입니다.

 

이제 Word 부분을 보겠습니다.

 

먼저 size 만큼 Word 배열을 만듭니다.

mdReceiveEx는 size 1 당 1byte를 읽기 때문에 실제로 읽는 크기는 1size = 2byte로 보고 맞춰줍니다.

안에 인자는 Bit를 읽는 것과 동일한 의미를 가집니다. 단지 읽고나서의 처리 과정이 다른 것입니다.

 

이상으로 MELSECNET으로 C#에서 PLC에 접속하는 방법에 대하여 알아보았습니다.

내용이 많아 글이 조금 지저분 할 수도 있으나, MELSECNET 형태로 C#에서 PLC와 연동하는데에 도움이 되었으면 합니다.

관련글 더보기

댓글 영역