본문 바로가기
PLC 프로그래밍/MELSEC PLC

시리얼 통신 하기[2]

by lemy 2019. 3. 6.
반응형

 



시리얼 통신 하기[2]


출처: melsec PLC 동호회(http://cafe.daum.net/melsec)의 회색늑대 (grizlupo)님의 초보 통신 이야기 연재글 입니다. 

회색늑대 (grizlupo)님의 초보 통신 이야기중 시리얼 통신에 관련하여 전반적으로 이해 할 수 있는 좋은 글입니다. 총 7회 분량의 글 입니다.


통신 프로그램을 만들자!


1. 바이트와 워드

무엇을 보내면 무엇이 돌아 오는지를 프로토콜 설명서를 통해 알게 되었다면 장치로 보낼 그 문자들을 만들어 내고, 장치가 되돌려 주는 문자들에서 내가 원하는 것을 뽑아내는 작업! 그것이 통신 프로그램입니다. 따라서 통신을 하기 위해서는 보내야 할 그 문자들을 머리 속이 아닌 PLC의 디바이스 상에 표현할 수 있어야 하고, 회신이 PLC의 디바이스에 들어 있다고 할 때 그 회신에서 필요한 문자들을 골라 낼 수 있어야 합니다.


PLC의 워드 디바이스에 기록된 문자들이 어떻게 반대편 장치로 전송될 수 있는지 혹은 반대편 장치에서 회신된 문자들이 어떻게 내 PLC 워드 디바이스에 저장되어 있을 수 있지는 지금 생각하지 마십시요. 그냥 PLC의 워드 디바이스에 내가 보내야 할 문자들을 저장하기만 하면 전송이 되고, 가만히 기다리고 있으면 반대편의 회신이 내 PLC의 워드 디바이스에 짠! 하고 기록된다고 일단은 생각하십시요. 우리가 알고자 하는 통신 프로그램의 핵심은 어떻게 전송이 이루어지는가가 아니라 어떻게 전송할 그 문자들을 만들어 낼 것인가? 어떻게 회신된 내용에서 내가 필요로 하는 문자들을 뽑아 낼 것인가 하는 것이니까요!


우리는 지금 PLC를 전제로 통신을 얘기하고 있습니다. 통신은 바이트들을 주고 받는 일이라고 했습니다. 문자들을 주고 받는 일이라고도 했습니다. 흔데 이 PLC라는 녀석이 처리할 수 있는 기본 단위는 바이트가 아니라 워드입니다. 비트 디바이스와 워드 디바이스는 있지만 바이트 디바이스라는 것은 없습니다. 바이트를 디바이스 차원에서 간단하게 처리할 수 없다는 것은 프로그래머에게 아주 성가신 일입니다. 하지만 이러한 사실이 성가신 정도를 넘어 처리 불능인 상태로 되어 버리는 초보가 때때로 있습니다. 그래서 이런 설명을 포함시켰습니다.


워드라는 것은 두개의 바이트를 모아 놓은 아주 단순한 것입니다. 하지만 겉보기에는 아무런 문제도 없을 것 같은 이 단순한 사실 속에 지랄 같은 선택의 문제가 하나 있습니다. 만약 워드 속에 있는 두 개의 바이트를 분리해서 나열해 놓아야 한다면 둘 중 어느 바이트를 먼저 놓아야 하는가? 하는 것입니다.


통신은 바이트들을 전송하는 것이고, 우리의 PLC는 이들을 둘씩 쌍쌍으로 묶어 놓았습니다. 이제 PLC의 디바이스에 있는 내용을 반대편 장치로 전송을 해야 합니다. 물론 한번에 한 바이트씩 전송을 해야 합니다. 둘 중 어느 것을 먼저 보내야 할 까요? 아니면  둘 중 어느 것이 먼저 보내질까요?


하위 바이트가 먼저 보내집니다. 결론은 하위 바이트가 먼저 보내진다는 것입니다. 두 개의 바이트를 상위 바이트와 하위 바이트로 구분하겠습니다. H1234라는 워드가 있는 경우 H12가 상위 바이트이고, H34가 하위 바이트입니다. 전송을 하는 경우 H34가 먼저 보내지고, H12가 나중에 보내지는 셈입니다. 겉보기에는 뭔가 뒤집어진다는 느낌이 있습니다.


2. LON 커맨드 작성

전송 형식은 커맨드 + <CR> 그러므로 우리에게 필요한 것은 "LON" + <CR>인 셈입니다. 우리는 워드 디바이스에 이 문자들을 기록해야 합니다. 워드 디바이스에 문자를 어떻게 기록하지? 제발 이런 의문이 없기를 바랍니다(만약 이런 의문이 생긴다면 제가 앞서 바이트와 문자라는 제목으로 쓴 글을 먼저 읽어 주십시요).


디바이스에 문자를 기록하기 위해서는 각 문자의 아스키코드를 먼저 알아야 합니다. 아스키 문자표에서 각각을 찾아 보면 L은 76, O는 79, N은 78 그리고 <CR>은 13입니다. 각각을 16진수로 표현하면 H4C, H4F, H4E, H0D 입니다. 워드는 두 바이트이므로 두개씩을 짝짓고, 앞선 바이트가 하위 바이트가 되도록 하면 H4F4C, H0D4E 라는 두 개의 워드가 만들어 집니다. 이것을 MOV 명령으로 디바이스 메모리에 옮기면 됩니다. 


MOV H4F4C D1000

MOV H0D4E D1001


이제 D1000과 D1001을 전송하도록 통신 모듈에 지시를 내리면 우리는 바코드 리더에게 바코드를 읽으라는 명령을 통신을 통해 전달할 수 있게 되는 것입니다. 하지만 통신 모듈이 지정한 워드 디바이스를 전송하도록 하는 방법은 나중으로 미루겠습니다.


3. 문자열 처리 명령

멜섹의 Q 시리즈에는 문자열 처리 명령이라는 것이 있습니다. 문자열 이라는 것은 말 그대로 문자들을 줄 세워 놓은 것입니다. 이것은 제가 앞서 바이트들이나 문자들이라고 표현한 것과 같은 표현입니다.


문자열 처리 명령 중에 $MOV라는 것이 있습니다($는 베이직이라는 언어에서 문자열을 지칭하는 용도로 사용되는데 멜섹의 문자열 처리 명령들은 이 베이직이라는 언어와 비슷합니다). 즉, $MOV는 문자열($)을 MOV 하는 명령이라는 뜻입니다. 앞서 아스키코드를 구하고 어쩌구 저쩌구 생 난리를 쳤던 문제는


$MOV "LON" D1000


이렇게 간단합니다. 하지만 단지 이렇게만 하면 안 됩니다. <CR>이 빠졌잖아요! 하지만 <CR>이라는 것은 제어문자로 눈에 보이지 않는 문자이니까 큰 따옴표 안에 표현하는 것이 좀 애매 합니다. 굳이 하려고 한다면 할 수는 있겠지만....

그래서 $+라는 문자열 처리 명령을 하나 더 사용하겠습니다. $+는 문자열($)을 더(+)해 줍니다.


MOV K13 D200

$MOV "LON" D1000

$+ D200 D1000


MOV을 사용할 때보다 한 줄이 더 많고 뭔가 손해보는 것 같지만, "LON"이라고 프로그램에 직접적으로 사용할 수 있다는 것만으로도 충분히 의미 있는 것입니다. 처리 시간이 더 길다거나 더 많은 메모리를 필요로 한다거나 하는 것들은 잊어 버리십시요. 늘어나는 처리 시간은 까짓 것 세발에 피고, 늘어 나는 메모리는 한강에 오줌누기 입니다.


문자열의 끝에는 반드시 <NUL>이 있어야 합니다.

주의 해서 보셨다면 $MOV와 $+가 모두 D1000을 사용하고 있다는 것을 발견하셨을 겁니다. 생각하기에 따라 기존에 기록되어 있던 "LON"을 덮어써 버릴 것 같은 걱정이 될 수도 있습니다. 하지만 정확하게 "LON" 바로 다음에 <CR>이 더해 집니다. 이렇게 하기 위해서는 D1000부터 기록된 "LON"의 문자 수가 셋이라는 것을 알고 있어야 하는데 $+는 이것을 어떻게 알까요?


동일하게 LEN이라는 명령이 있습니다. 이것은 직접적으로 문자열을 길이를 구하는 명령인데, 어떻게 길이를 아는 것일까? 결론부터 말하면 문자열의 끝이라는 표시가 있습니다. 아스키 문자표에서 가장 먼저 만나게 되는 코드가 0인 <NUL>이 그것입니다. 즉, LEN이라는 명령은 D1000에서부터 문자를 하나씩 헤아려 나가다가 <NUL>을 만나면 그 때까지 헤아린 것이 그 문자열의 길이가 되는 것입니다.


이것은 반대로 모든 문자열의 끝에는 반드시 문자열의 끝이라는 <NUL> 문자, 숫자로는 0이 있어야 한다는 것입니다. 이것은 문자열 관련 명령을 사용하시려면 반드시 인식하고 있어야 하는 아주 중요한 사실입니다. 만약 $MOV "1234" D1000 이라는 명령을 한다고 해 보십시요. 이 명령에 의해 영향을 받는 디바이스가 몇 개일 것 같습니까? 문자열이 길이가 4이고, 워드 디바이스는 2개의 문자을 담을 수 있으니까 D1000과 D1001이라고 생각하시면 안 됩니다. D1002도 영향을 받습니다. <NUL>이 필요하니까요!


앞서 MOV K13 D200은 $MOV를 사용하기 좀 껄끄러운 제어문자를 위해 그냥 MOV 명령을 사용한 경우입니다. 이 경우에도 마지막이 <NUL>이 될 수 있도록 신경을 쓰셔야 합니다. 만약 <CR> <LF> 두 개의 제어 문자를 이런 식으로 처리하고 싶다면


MOV H0A0D D200

MOV H0 D201


이렇게 두 개의 MOV 명령을 사용하셔야 합니다. 그래야 <CR><LF><NUL>이 되는 셈이니까요. DMOV H0A0D D200 을 사용하시면 하나의 명령으로도 가능 합니다.


4. 회신 분석하기

구체적인 문자열의 송/수신은 나중에 다루겠습니다. 여기서는 수신이 이루어져서 회신 내용이 워드 디바이스 영역에 기록되어 있다고 그냥 가정하고, 그렇게 도착한 회신 내용에서 필요한 것을 뽑아 내는 것만을 이야기 하겠습니다.


우리는 바코드를 읽기 위해 리더에게 "LON" <CR>이라는 명령을 보냈습니다. 이제 바코드 리더는 바코드를 실제로 읽고 그 내용을 회신할테고, 통신모듈을 그것을 수신해서 우리가 지정하는 워드 디바이스에 기록해 줍니다. 지정된 디바이스를 D2000이라고 하겠습니다. 그리고 회신된 바코드는 "12345" <CR> 입니다. 끝에 <NUL>은 없습니다. 통신 모듈은 자신이 수신한 문자열의 갯수를 알기 때문에 굳이 <NUL>이라는 표시가 없어도 상관이 없거든요. 문자열의 길이를 따로 기록해 두는 방법. 이것도 문자열을 다루는 또 하나의 방법입니다. 때때로 널종료 문자열은 <NUL>도 하나의 문자임에도 불구하고, 문자열의 끝이라는 표시로 작동하기 때문에 문자열에 절대로 <NUL>을 포함시킬 수 없는 불편함이 있습니다.


문자열의 끝에 <NUL> 표시를 하는 소위 말하는 <NUL> 종료 문자열은 대표적으로 C라는 언어에서 사용합니다. 그리고 문자열의 길이를 따로 저장하는 형태의 문자열은 대표적으로 파스칼이라는 언어에서 사용합니다. 그래서 길이를 따로 저장해 두는 형태의 문자열을 파스칼 문자열이라고도 합니다. 파스칼 문자열은 문자열의 길이를 문자열의 첫번째 바이트에 기록해 둡니다. 그리고 실제의 문자열은 그 다음부터 기록합니다. 따라서 파스칼 문자열은 그 길이가 최대 255를 넘을 수 없습니다. 하지만 근래에 생겨난 자바나 C# 같은 객체지향 언어들은 문자열 객체라는 형태입니다. 굳이 따지면 파스칼 처럼 문자열의 길이를 따로 저장해 두는 형태이지만 파스칼 문자열 형태를 훨씬 뛰어 넘는 것이니까 구태여 연관성을 둘 필요는 없을 것 같습니다.


다시 원래의 내용으로 돌아가서 "12345" <CR>에서 우리에게 필요한 것은 "12345" 뿐입니다. 지금부터 얘기하고 싶은 것이 바로 우리에게 필요한 "12345"만을 뽑아 내는 방법들에 대한 것입니다.


5. 비트 연산을 사용하는 방법

"12"는 D2000에 "34"는 D2001에 들어 있을 것입니다. 그리고 "5"와 <CR>이 D2002에 들어 있을 것입니다. 여기서 D2002의 상위 바이트에 있는 <CR>은 우리에게 필요없는 것이니까 비트 연산을 사용해서 0으로 지워 버리도록 하겠습니다.


WAND H00FF D2002


이렇게 하면 <CR>이 <NUL>로 바뀌기 때문에 D2000을 문자열처럼 다룰 수 있게 되는 셈입니다.

만약 회신이 <STX> "1234" <ETX> 라면 앞에서처럼 한번의 비트 연산으로 우리가 원하는 "1234"를 뽑아 낼 수가 없습니다. 이런 경우에는 비트 연산을 여러번 해야 합니다.


WAND HFF00 D2000

SFR D2000 K8

WAND D2001 H00FF D300

SFL D300 K8

WOR D300 D2000

WAND HFF00 D2001

SFR D2001 K8

WAND D2002 H00FF D300

SFL D300 K8

WOR D300 D2001

MOV K0 D2002


6. 비트 자리수 지정을 사용하는 방법

이 방법은 우리에게 필요한 비트 연산이라는 것이 바이트 단위로 이루어지는 것이라는 데서 착안한 것입니다. <STX> "1234" <ETX>의 예를 비트 자리수 지정 방법으로 하면 다음과 같이 됩니다.


DMOV D2000 K8Y400

MOV K4Y408 D2000

DMOV D2001 K8Y400

MOV K4Y408 D2001

MOV K0 D2002


비트 연산을 사용하는 것 보다는 간결해 보이지만 그래도 왠지 애매 합니다.


7. MIDR 명령

MIDR 라는 문자열 처리 명령을 사용하면 간단하다. MID 명령을 사용한 경우에는 그것이 무엇을 하고 있는지 쉽고, 명확하게 파악 할 수 있습니다.


MOV K2 D300

MOV K4 D301

MIDR D2000 D3000 D300


해석하면 D2000에 있는 문자열의 2번째 부터 4개의 문자를 뽑아내서 D3000에 저장하라! 위 명령들을 수행하면 D3000에는 정확하게 "1234"가 기록된다. 정말 멋지지 않습니까!


하지만 주의! 이 명령을 사용하기 위해서는 D2000이 <NUL>종료 문자열이어야 합니다. 하지만 앞서도 말씀드린 것처럼 통신 모듈은 문자열의 끝에 <NUL>을 추가시켜 주지 않습니다. 즉, 우리가 <NUL>을 추가 시켜 주어야 한다는 것입니다. 말은 쉬운데 정작 하려고 하면 생각해야 할 것들이 많습니다. 그래서 저 같은 경우에는 통신 모듈이 수신 내용을 기록하도록 지시하기 전에 기록할 디바이스 영역을 모두 0으로 깨끗하게 지워 놓습니다. 이런 상태라면 통신 모듈이 자신이 수신한 내용만을 기록하더라도 그 끝은 언제나 <NUL>일 수 밖에 없으니까요.


결론, 쓸데 없는 얘기들을 잔뜩 나열한 것이 아닌가 하는 걱정이 되기도 합니다만. 솔직히 제가 생각하는 통신 프로그램은 이렇게 저렇게 통신 모듈의 버퍼를 조작하고, 전용 명령을 사용하고, 하는 것 보다는 "LON" <CR>과 같은 송신할 내용을 얼마나 잘 만드느냐, "12345"<CR>과 같은 회신에서 필요한 것들만을 얼마나 잘 뽑아 내느냐 하는 것이라고 생각합니다. 즉, 문자열을 얼마나 잘 다루느냐의 문제라고 생각합니다. 그래서 다른 모든 것에 앞서서 바이트니 문자니 워드니 하는 것들과 프로토콜에 대한 것을 구구절절이 얘기하고 강조했던 것입니다.


만들어진 명령 문자열을 어떻게 보내고, 회신을 어떻게 받을 수 있는가 하는 것들은 프로그래머의 능력과는 무관한 것이라고 봐야 합니다. 그런 것들은 이미 통신 모듈을 설계한 사람에 의해 다 정해져 있는 것입니다. 경험 많은 프로그래머라도 이런 정해진 절차를 무시하고 제 나름의 방법을 사용할 수는 없습니다. 따라서 송/수신에 관련한 부분은 초보가 했던 프로가 했던 똑 같을 수 밖에 없습니다. 따라서 좋은 통신 프로그램을 만들기 위해서는 송/수신을 어떻게 하느냐가 아니라 필요한 명령 문자열을 잘 만들고 그 회신을 잘 분석해 낼 수 있는 능력을 쌓아야 한다고 생각합니다.

반응형

'PLC 프로그래밍 > MELSEC PLC ' 카테고리의 다른 글

시리얼 통신 하기[4]  (0) 2019.03.07
시리얼 통신 하기[3]  (0) 2019.03.07
시리얼 통신 하기[1]  (0) 2019.03.06
전압 강하 계산 엑셀 TOOL  (2) 2019.02.28
Ethernet 통신 하기[3]  (0) 2019.02.20

댓글