모드버스(Modbus) 슬레이브 제작
발췌: http://blog.daum.net/nnjjyy105/456
모드버스 마스터 제작(“일반 모드버스 시물레이터,” 서킷 셀라 200/201, 2007)에 대한 글을 2회 연재한 후, 약간 연관이 있는 세 번째 글로 모드버스 슬레이브에 대해 다루어야겠다고 생각했었다. 하지만 그 실행에 시간이 상당히 소요되었고 또 다른 모드버스 프로젝트를 준비하는 단계였으므로 이러한 나의 논리에는 약간 모순이 있었다.
사이프레스 세미콘덕터 CY8C27243 PSoC 마이크로콘트롤러에서 구현되는 모드버스 슬레이브를 제작하였지만, 거의 대부분 C 언어로 개발되었다 (그림 1 참조). 따라서, 다른 마이크로콘트롤러로의 포팅에는 문제가 없다. PSoC는 순환 중복 검사(CRC)를 하드웨어적으로 생성하고, 범용적인 소프트웨어 접근법에 덧붙여, CRC 생성 속도를 현저히 높일 수 있는 독특한 접근법에 대해 설명할 것이다. 세 번째 접근법에서는 설정을 동적으로 바꾸는 PSoC의 유연성을 보여주도록 하겠다.
모드버스 개요
모드버스는 I/O 공간을 단일 비트 디지털(코일) 레지스터 또는 16 비트 레지스터로 분류한다. 홀딩 레지스터와 인풋 레지스터(16비트)의 해석은 슬레이브의 설계자에게 맡겨진다. 이러한 레지스터는 ADC 값 또는 다른 기능에 사용될 수 있으며, 슬레이브의 설계 (16 비트 레지스터 양식에 맞도록 데이터 변형이 필요), 릴레이, 근접 센서에 의해 결정이 된다. 마스터 콘트롤러는 직렬 네트워크에 지정되는 슬레이브에 포함된 레지스터를 통해 읽기와 쓰기를 수행한다.
슬레이브의 레지스터 맵도 슬레이브 설계자에게 맡겨진다. 이에 대해서는 추후 자세히 설명하도록 하겠다. 일반적으로 슬레이브 레지스터는 네트워크 적분기용 주소 공간을 설명하는 표로 나타난다.
모드버스 직렬 프로토콜 표준에서 통신은 아스키(ASCII) 포맷 또는 8 비트 바이트(RTU 포맷)로 이루어진다. 아스키 포맷은 선택적이며, 모드버스 유닛은 RTU 포맷을 반드시 지원해야 한다. 따라서, 여기서는 RTU 포맷에 대해서만 설명토록 하겠다.
직렬 포트 하드웨어 인터페이스는 데이터 전송률, 스톱 비트의 개수, 패리티와 관련된 몇 개의 매개 변수들을 가진다. 메시지의 무결성은 전송 프레임 종단에 붙는 2 바이트 CRC에 의해 제공된다. 모드버스 패킷의 최대 길이는 256 바이트이다. 동기화를 위해 모드버스 사이에 3.5 전송 바이트가 필요하다. 프레임 내 2개 캐릭터 사이의 간격은 1.5 캐릭터 길이를 넘으면 안 된다. 데이터 전송률이 19,200을 넘어설 때, 시간 간격은 고정된 값이 된다.
모드버스 처리는 지정된 슬레이브로 패킷을 보내고, 응답을 대기하고 수신하는 마스터로 구성된다. 슬레이브가 명령을 받았을 때, 제공된 주소가 일치되고 CRC가 올바를 경우 응답한다. 명령어에 문제가 있을 경우 (두 조건이 만족할 경우), 문제를 알려주는 예외 메시지로 응답하게 된다. 몇몇 경우, 슬레이브가 명령어 수행 완료에 시간이 걸릴 때가 있는데, 이 경우의 예외 코드는 “애크날리지(Acknowlege)”이다. 일반적인 작동에서 응답 부족 또는 예외 응답으로 전송이 실패할 경우, 문제를 보고하거나 재전송을 시도하는 것은 마스터의 몫이다.
모드버스 메시지 포맷은 서킷 셀라 200회 기사에 자세히 설명되어 있지만, 모드버스 시스템에서 종종 사용되는 쿼크(Quirk) 지정에 대해 언급하도록 하겠다. 모드버스 I/O의 서로 다른 포맷은 서로 다른 주소 공간에 지정된다. 단일 비트 I/O는 0~10,000을, 단일 비트 입력은 10,001~30,000을, 16비트 레지스터 입력은 30,001~40,000을, 16 비트 I/O는 나머지 40,001~65,534를 차지한다. 쿼크는 상쇄된 주소로, 모드버스 상에 전송되는 실제 주소는 실제 주소에서 관련 주소 공간의 기수를 차감하여 생성된다. 예를 들어, 30,194의 값을 가지는 주소는 모드버스 주소 193(30,194-30,001)으로 전송되므로 주소가 겹치지 않도록 주의를 기울여야 한다.
하드웨어와 소프트웨어
이 프로젝트와 관련된 하드웨어는 직렬 버스 인터페이스뿐이다. PSoC와 연결되는 RS-485 인터페이스 IC칩으로 구성된다 (그림 1 참조).
모드버스 메모리 맵을 생성하는 것은 창조적인 연습이다. 만일 연속된 모드버스 위치가 많을 경우, 메시지가 버퍼에 쌓이게 되므로 메시지는 RAM의 용량을 많이 차지하게 된다. PSoC 자원(램, I/O, 플래시 메모리, 주변기기 등)을 모드버스 주소에 지정할 때, 이러한 요소를 고려한다면 연속된 블록으로써 주소를 생성하는데 도움이 될 것이다. 이렇게 하지 않을 경우, 모드버스 블록 접근을 다루는 것이 메모리를 소비함을 관련 프로젝트에서 볼 수 있을 것이다.
이 프로젝트에서 내가 작성한 코드는 최적화와는 거리가 멀다. 이는 가장 일반적인 경우로 모드버스 장소에 묶인 대부분의 변수를 허용하도록 되어있지만, 일단 이해를 한 후, 해당 접근법을 변경할 수 있을 것이다.
이 프로젝트를 위해 모드버스 맵 상의 가상 슬레이브를 생성하였다 (표 1 참조). 연관된 명령과 함께 “물리 주소” 컬럼으로 전송되는 실제 주소뿐 아니라 기능 코드 또한 사용될 수 있다.
사용자 모듈 설정
PSoC 제어기는 필요에 정확히 맞게 설정될 수 있는 논리회로와 아날로그 블록으로 구성된다. 이러한 설정은 사이프레스의 무료 소프트웨어인 PSoC 디자이너를 통해 처리된다 (C 컴파일러는 덤이다). 원하는 사용자 모듈(UM) 형태를 선택하여 디지털 또는 아날로그 블록에 위치시키고, 관련된 변수와 어플리케이션에 적절한 상호연결을 설정하면 된다. 사진 1은 초기 설정을 보여준다. 하지만, 처음부터 SPIM과 CRC16 모듈은 사용되지 않는다. EEPROM 모듈이 상주하지만 이번 프로젝트에선 사용되지 않는다. 타이머는 “틱”을 발생하기 위해 필요하다. UART는 직렬 통신을 위해, 카운터는 데이터 전송률 발생에 사용된다.
표 1-이 프로젝트를 위해 생성된 가상의 모드버스 맵으로, 모드버스 주소 컬럼의 서로 다른 레지스터 공간의 주소 오프셋은 물리적 주소 컬럼의 주소로 번역된다. 레지스터 공간 사이에 의도적인 틈이 있는데, 영역 접속 명령어들은 서로 다른 영역 형태로 포함되지 않는다.
사진 1-사용자 모듈 배치도이다. SPIM은 CRC 생성기에 직렬 입력을 제공하기 위해 사용되지만, 하드웨어 CRC 생성에만 필요하다. UART의 데이터 전송률은 Counter8에서 유도된다. 전체 시스템의 타이밍은 타이머8의 인터럽트에 의해 발생되는 “틱”에서 유도된다.
모드버스의 주요 관심사 중 하나는 통신의 안정성이며, 이는 CRC 사용을 통해 해결된다. 이번 모드버스 슬레이브 생성의 두 가지 예제에서는 세 가지 접근법을 채택하였다. 첫째는 소프트웨어만을 사용하여 CRC를 생성한다. 여기엔 2개의 해결책이 있는데, 룩업(Look-up) 테이블 또는 루핑 코드(CRC는 소프트웨어를 통해 한 비트씩 생성된다)이다. 이 두 가지는 모드버스 문서에 설명되어 있다. 전자는 빠르고 후자는 훨씬 적은 메모리를 필요로 한다.
PSoC의 사용자 모듈 중 하나는 CRC16 생성기로, 직렬 형태로 데이터를 받고 SPIM(M은 마스터를 의미)이라 불리는 직렬-병렬 모듈을 필요로 한다. CRC 생성을 매우 빠른 속도로 처리하지만 사진 1(DBB00, DBB01, DBB02)에서 볼 수 있듯이 세 개의 디지털 블록을 사용하므로 PSoC 자원으로는 사치스럽다. 좀 더 상세히 설명하겠지만 여기선 설정에 대해 설명하고 있으므로 우선 세 번째 접근법에 대해 설명토록 하겠다.
PSoC의 사용자 모듈은 레지스터 구조로 읽혀지므로, 동적으로 변경이 가능하다. CRC와 데이터 통신이 동시에 발생되지 않으므로 두 기능 사이에서 세 개의 디지털 블록을 공유하는 것이 가능하다. 사진 2에서 이 세 가지 설정에 대해 확인할 수 있다.
소프트웨어 세부 내역
서로 다른 작업이 동시에 실행되도록 하기 위해 각 작업이 할당된 숫자를 가지는 단순한 협동 멀티태스크 방법을 사용하였다. 카운터와 C언어의 switch 선언문을 사용하여 각 작업은 순서대로 실행된다.
각 작업은 여러 개의 상태를 가진다. 실행되어야 하는 상태는 표준 C switch 기법을 사용하여 선택된다. 서킷 셀라 FTP 사이트의 main.c 모듈에서 이를 확인할 수 있을 것이다.
이 프로젝트에서는 신호의 RMS 값을 측정해야 하므로, 데이터 획득과 관련 없는 인터럽트 루틴에 소비되는 시간을 최소화하고 싶었다. 이를 위해 직렬 캐릭터 수신에 단지 하나의 타이머 “틱”과 하나의 인터럽트만을 허용하였다 (serial.c의 Serial_Rx 인터럽트 절차를 참고). 도달한 캐릭터는 버퍼로 전달되고 인터럽트 루틴에서 별다른 분석은 이루어지지 않는다. 도착한 모드버스 메시지의 검사, 해석, 응답 전송은 직렬 절차에 이어지는 업무에 맡겨진다.
틱은 매 0.5ms 마다 초기화된다. 이는 꽤 짧은 간격이지만, 몇몇 모드버스 타이밍에 필요하다. UART는 9,600bps, 1개의 스톱 비트, 8개의 데이터 비트, 패리티 없음으로 실행되기 위해 설정된다. 만일 패리티가 없다면 모드버스 사양은 2개의 스톱 비트를 호출하지만 2번째 비트를 점검하는 하드웨어 UART는 실제로 없으며 여기서도 구현되지 않았다.
사진 2-세 가지 설정으로, mod2로 명명된 설정은 기본 배치이며 CRC와 직렬 설정이 불려지고 내려가는 것에 영향 받지 않고 계속 작동한다. 동일한 자원을 사용하는 것에 유의하자. 각 설정은 화면 상단의 탭을 클릭함으로써 선택된다.
CRC 생성
약속한대로 모든 모드버스 메시지의 종단에 덧붙여지는 CRC에 대해 설명하도록 하겠다. CRC는 16비트 쉬프트 레지스터를 통해 생성된다. CRC의 값은 시드(Seed) 값에 의해 미리 조절된다. 레지스터 초기 단계로부터의 데이터는 곧장 다음 단계로 전달되거나 배타적 OR 게이트를 통해 (도달한 데이터와 함께) 전달된다. 배타적 OR 패턴은 다항식의 “1”이 게이트 존재여부를 의미하는 CRC 다항식에 의해 결정된다.
모드버스 구성을 관리하는 문서들은 소프트웨상에서의 CRC 생성에 대해 정확하게 설명한다. 첫 번째 접근법은 룩업 테이블을 사용하는 것으로 빠르지만 룩업 테이블 자체만으로 500 바이트 이상을 필요로 한다. 두 번째 방법은 느리지만 훨씬 적은 코드 공간을 사용한다. 두 방법 중 선호하는 것을 사용하면 된다 (Modbus.c 내 소프트웨어 모듈의 룩업 테이블용 CRC_TypeA와 짧은 접근법용 CRC_TypeB).
다.
표 2-CRC 접근법을 함축하였다. 실제 주기는 실제 데이터에 따라 약간 다를 수 있다.
CRC 계산을 빠르게 처리하기 위해 CRC16 사용자 모듈을 사용하기로 결정하였다. 슬램덩크가 될 것으로 예상하였지만 실제로는 그렇지 못했다. CRC16 사용자 모듈에서는 데이터가 레지스터에 동기를 맞추어 이동해야 한다. UART 통신은 스톱과 스타트 비트를 포함하기 때문에 UART 데이터 스트림을 CRC 모듈에 직접 공급하기는 불가능하다. 차선책은 UART를 통해 통신되는 동일한 데이터를 사용하여 SPIM 모듈에서 CRC16 하드웨어에 공급하는 것이다. SPIM 쉬프트의 데이터 전송률은 최대 12MHz로 설정되므로 빠른 이동이 가능하다. 소트프웨어는 SPIM으로 수신되거나 전송된 각 바이트를 읽어와야 하며, 그 후 전체 메시지가 저장되면 결과 CRC를 다시 읽어와야 한다. 물론, 이러한 절차는 UART로부터 데이터를 읽어오거나 UART에 데이터를 저장하는 것에 덧붙여지는 것이다.
PSoC의 CRC 생성기는 모드버스 CRC와 직접 호환이 되지는 않지만, 약간의 조작을 통해 간단히 구현된다. 이 솔루션 작동을 위해 사이프레스의 가네쉬 라쟈(Ganesh Raaja)에게 신세를 졌다. 결국 CRC를 생성하는 방법은 0xC002 다항식과 시드 0xFFFF (모드버스에서 일반적임)를 사용하는 것이었다. 처음에 데이터는 SPIM의 LSB로부터 이동되었다. 그 후, 마지막 비틀림(의도된 말장난)에서 CRC의 결과 비트들은 비트 15는 비트 0과, 비트 14는 비트 1과 상호 교환되는 형식이 되도록 반사되어야 했다. 이러한 반사를 수행하는 코드가 프로그램 내에 있지만, C 언어는 캐리 비트가 데이터 쉬프트에서 영향을 받는다는 사실을 무시하므로 실행 주기는 2배가 된다. 이 절차를 빠르게 하기 위해 소스 레지스터를 캐리 비트를 통해 이동시킨 후, 캐리 비트를 데스티네이션 레지스트로 이동시키는 최적화된 어셈블리 절차를 덧붙였다. 이 CRC 계산법은 프로젝트 Mod1에서 CRC_TypeC로 불린다.
앞서 언급된 것처럼, CRC 생성기와 UART를 덧붙여 PSoC 자원을 경제적으로 활용하는 것이 가능하다. PSoC는 대부분의 시간 동안 UART로 설정되며, CRC가 계산될 필요가 있을 때, UART 설정이 내려가고 CRC 설정이 올라온다. CRC 계산이 종료되면 CRC 모듈은 내려가고 UART 설정이 다시 올라온다. 이러한 로딩과 언로딩은 시간이 걸리는 단점이 있다. 초기 Mod2 설정 내의 모듈 설정과 작동은 변경되지 않는다. 이 CRC 계산은 프로젝트 Mod2에서 CRC_TypeC로 불린다.
목록 1-상태 4에서 벌어지는 작동은 cSerBuff[1]에 저장된 수신 명령 코드에 의존한다. 단지 몇 개의 유효한 코드가 있으며, 그 외에는 ILLEGAL FUNCTION 예외 코드가 반환된다. 이 목록은 극도로 압축된 것이다. 이 중 하나가 목록 2에 더욱 자세히 나와있다.
프로세서의 사용에 따라 속도와 코드 용량은 문제가 된다. 표 2는 서로 다른 가능성들간의 비교를 제공한다.
상태 설명
serial.c 모듈 내 serial() 절차에서 발생하는 대부분의 모드버스 동작은 프로젝트의 직렬 통신을 다룬다. 절차에서의 상태들은 표 3에 요약되어 있다.
아마도 목록 1에 훨씬 간단화되어 있는 상태 4는 가장 따르기 어려운 것일 것이다. CRC와 명령어 문제가 전 단계에서 해결되었다면, 슬레이브는 수신된 주소 영역이 자신의 주소 영역 내부인지 분석을 해야 한다. 주소가 생성되는 방법은 명령에 의존하므로, 각 명령에 대해 케이스(switch 선언문 내)를 만들었다 (목록 1 참조). 비트가 바이트에 포장되므로 모드버스에 걸쳐 수신된 코일, 이진 입력(비트), 레지스터(16비트 워드)들은 서로 다르게 처리되어야 한다. 물론 서로 다른 명령을 사용하여 단일 위치를 지정하는 것도 가능하다. 따라서 서로 다른 명령들에 필요한 처리 사이에는 공통성이 많지 않으며, 거의 모든 명령은 개별적인 코드를 필요로 한다. 만일 어떤 비트도 접속되거나 레지스터되지 않는 슬레이브 메모리 맵을 작성한다면 필요한 코드의 양을 줄이거나 최소한 최적화할 수 있다. 가장 일반적인 경우에 대해 소개하려고 노력했지만 여기엔 많은 코드가 있으며, 공간 제약과 나의 집중력 기간이 짧아 단지 한 경우에 대해서만 설명하는 것으로 제한하였다.
표 3-직렬 업무의 상태이다. 매번 serial() 절차가 실행되며, 이들 중 하나만 실행된다. 모든 상태 머신과 동일하게 상태 변화 또는 상태 유지 결정은 해당 상태에 정의된 특정 입력을 판단하여 이루어진다.
표 2는 다수의 코일 출력으로 설정된 코드를 보여준다. I/O의 16비트 시작 주소 (모듈 모드버스 주소에 반대되는)는 cSerBuff[2]와 cSerBuff[3]에서 빼지고 변수 iI에 저장된다. 이 주소는 헤더 파일에 정의된 BOTTOM_00000과 TOP_00000 상수에 정의된 코일 주소 영역 내부(00000-10000)인지를 확인하기 위해 검사된다. 다음 단계는 출력 영역의 끝부분이 모듈에 정해진 영역에 포함되는지를 확인하는 것이다. 이 작업은 수신된 데이터, cSerBuff[4], cSerBuff[5]에서 파생된 변수 iJ의 주소 숫자를 생성함으로써 이루어진다. 상단 주소는 변수 iK에 저장되어 모듈 제한치와 비교된다. 만일 주소가 허용된 영역을 벗어난다면, 예외 응답이 다음 상태 전송에 준비된다.
RAM을 금방 차지하는 직렬 버퍼의 실제 길이와는 별도로, 레지스터 맵에 간격이 있다면, 각 주소는 검증될 필요가 있으므로 좀 더 복잡해진다. 시작과 끝 부분 아니라 영역 중간에 존재하지 않는 메모리로의 접근을 확인하기 위한 코드가 필요하다. 따라서 맵에 대해 충분한 고려가 있어야 한다. 나의 코드는 특정 영역 내에서 어떤 간격도 고려하지 않았다.
어떤 이들은 수신된 데이터의 검증을 포함하여 소프트웨어가 수행될 수 있는지를 점검한다. 12비트 DAC는 4,095 이상의 숫자를 수용할 수 없다. 수신된 오류는 사전에 정의된 오류 형태로 분류가 되어야 하지만, 이는 가끔 제한적일 수 있다. 이러한 단편적인 코드는 디지털 출력만을 다루기 때문에, 값은 1과 0뿐이며, 검증이 이루어지지 않는다.
다음 단계는 모드버스 명령 내의 초기 주소로 변경되는 슬레이브 실제 출력을 정렬하고 출력 값을 변경하는 것이다. 초기 주소는 iI에 저장되고 출력들의 숫자는 (앞서) iJ에 저장된다. 이는 약간 복잡한데 실제 출력 값은 8비트 바이트로 압축되기 때문이며, 여러 개의 출력으로 포함되므로 한 바이트에서 여러 개 (8개까지)의 비트를 추출하고 다음 바이트로 밀어내는 것은 도전거리이다. 처음에는 변수 iK에 마스크가 설정되고 cSerBuff[8] 위치의 첫 데이터 바이트에 해당하는 포인터는 변수 cPoint로 정해진다. for (cI=0;cI<(unsigned char)iJ;cI++)절에 의거하는 목록 2의 잘려진 코드는 이 작업이 어떻게 이루어지는지 보여준다. 슬레이브 모듈에서 수정될 실제 비트는 switch 선언문의 각 케이스에 의해 다루어진다. switch 선언문을 통한 각 반복은 매 8번마다 순환되는 마스크 iK를 야기한다. 마스크는 재설정되고 입력되는 메시지 어레이 cPoint의 포인터는 증분된다.
몇몇 작동은 실행에 시간이 소요될 수 있다. 예를 들어, 모드버스에 걸쳐 EEPROM 설정을 업데이트 시키고 싶을 것이다. 일단 수신된 차례가 검증되면, 슬레이브는 “애크날리지” 메시지를 발행하여 문제가 없음을 알리고, 업데이트가 종료될 때까지 “오프라인”이 된다. 이 작업에 걸리는 시간을 알 수 있는 방법이 없지만, 업데이트 기간 동안 응답 폴을 슬레이브로부터 배제하지는 않는다. 즉각적인 응답의 목록 2에는 FinaliseTransmit() 절차가 준비된다.
모드버스 마스터 시물레이션
슬레이브 시험은 데이터를 송/수신할 수 있는 도구를 필요로 한다. 기존 셋업이 없을 경우, 모드버스 협회의 웹사이트에서 몇몇 도구를 찾을 수 있다. 또한 나의 제품은 PC상에서 구현된다 (서킷 셀라 200과 201). 모드버스 슬레이브 프로젝트는 표 1의 구현인 엑셀 워크시트(Slave.xls)를 포함한다.
목록 2-여러 개의 코일 출력에 저장하는 명령의 처리 과정을 보여주는 수정본이다. 출력은 비트 형태에서 바이트로 압축되어, 한계치를 점검하고 압축을 푸는 일은 좀 더 복잡한 명령을 코드화시킬 수 있도록 한다.
오래되었음에도 불구하고, 여전히 모드버스는 데이터 상호 통신에 있어 쓸만한 선택이다. 간단하고, 잘 정리되어 있으며, 구현이 쉽고, 충분히 많은 제조사들이 지원한다. 심지어 이더넷 상에서 모드버스를 효과적으로 사용할 수 있는 모드버스/TCP도 나와있다. 통신 매체가 다르지만 어플리케이션 단에서 모드버스는 동일하게 보인다. 비록 이는 해당 주제의 글에 대한 암시를 주지만, 한동안 모드버스에게 휴식을 줄 생각이다.
오브레이 케이건 (akagan@emphatec.com)은 이스라엘 과기원인 테크니온(Technion)에서 B.S.E.E를 취득하였고 위트워터스랜드(Witwatersrand) 대학에서 MBA를 취득한 엔지니어이다. 토론토에 위치한 산업제어 인터페이스와 스위치 모드 전원 공급기를 설계하는 엠파텍(Empatech)에 근무하고 있다. 서킷 셀라에 기고하는 것 외에도 몇몇 잡지에 글을 기고하고 있다. Excel by Example: A Microsoft Excel Cookbook for Electronics Engineers (Newnes, 2004)란 책을 출판하였다.
모드버스(Modbus) 슬레이브 제작
수개월전 제작된 모드버스 마스터에 이어 저자인 오브레이는 사이프레스(Cypress) PSoC 마이크로콘트롤러에서 구현되는 모드버스 슬레이브 제작에 착수하였다. 사용자 설정 변경을 가능케 하거나 CRC 생성 속도를 높이기 위한 슬레이브 제작에 필요한 3개의 방법을 설명할 것이다.
모드버스 마스터 제작(“일반 모드버스 시물레이터,” 서킷 셀라 200/201, 2007)에 대한 글을 2회 연재한 후, 약간 연관이 있는 세 번째 글로 모드버스 슬레이브에 대해 다루어야겠다고 생각했었다. 하지만 그 실행에 시간이 상당히 소요되었고 또 다른 모드버스 프로젝트를 준비하는 단계였으므로 이러한 나의 논리에는 약간 모순이 있었다.
사이프레스 세미콘덕터 CY8C27243 PSoC 마이크로콘트롤러에서 구현되는 모드버스 슬레이브를 제작하였지만, 거의 대부분 C 언어로 개발되었다 (그림 1 참조). 따라서, 다른 마이크로콘트롤러로의 포팅에는 문제가 없다. PSoC는 순환 중복 검사(CRC)를 하드웨어적으로 생성하고, 범용적인 소프트웨어 접근법에 덧붙여, CRC 생성 속도를 현저히 높일 수 있는 독특한 접근법에 대해 설명할 것이다. 세 번째 접근법에서는 설정을 동적으로 바꾸는 PSoC의 유연성을 보여주도록 하겠다.
모드버스 개요
모드버스는 I/O 공간을 단일 비트 디지털(코일) 레지스터 또는 16 비트 레지스터로 분류한다. 홀딩 레지스터와 인풋 레지스터(16비트)의 해석은 슬레이브의 설계자에게 맡겨진다. 이러한 레지스터는 ADC 값 또는 다른 기능에 사용될 수 있으며, 슬레이브의 설계 (16 비트 레지스터 양식에 맞도록 데이터 변형이 필요), 릴레이, 근접 센서에 의해 결정이 된다. 마스터 콘트롤러는 직렬 네트워크에 지정되는 슬레이브에 포함된 레지스터를 통해 읽기와 쓰기를 수행한다.
슬레이브의 레지스터 맵도 슬레이브 설계자에게 맡겨진다. 이에 대해서는 추후 자세히 설명하도록 하겠다. 일반적으로 슬레이브 레지스터는 네트워크 적분기용 주소 공간을 설명하는 표로 나타난다.
모드버스 직렬 프로토콜 표준에서 통신은 아스키(ASCII) 포맷 또는 8 비트 바이트(RTU 포맷)로 이루어진다. 아스키 포맷은 선택적이며, 모드버스 유닛은 RTU 포맷을 반드시 지원해야 한다. 따라서, 여기서는 RTU 포맷에 대해서만 설명토록 하겠다.
직렬 포트 하드웨어 인터페이스는 데이터 전송률, 스톱 비트의 개수, 패리티와 관련된 몇 개의 매개 변수들을 가진다. 메시지의 무결성은 전송 프레임 종단에 붙는 2 바이트 CRC에 의해 제공된다. 모드버스 패킷의 최대 길이는 256 바이트이다. 동기화를 위해 모드버스 사이에 3.5 전송 바이트가 필요하다. 프레임 내 2개 캐릭터 사이의 간격은 1.5 캐릭터 길이를 넘으면 안 된다. 데이터 전송률이 19,200을 넘어설 때, 시간 간격은 고정된 값이 된다.
모드버스 처리는 지정된 슬레이브로 패킷을 보내고, 응답을 대기하고 수신하는 마스터로 구성된다. 슬레이브가 명령을 받았을 때, 제공된 주소가 일치되고 CRC가 올바를 경우 응답한다. 명령어에 문제가 있을 경우 (두 조건이 만족할 경우), 문제를 알려주는 예외 메시지로 응답하게 된다. 몇몇 경우, 슬레이브가 명령어 수행 완료에 시간이 걸릴 때가 있는데, 이 경우의 예외 코드는 “애크날리지(Acknowlege)”이다. 일반적인 작동에서 응답 부족 또는 예외 응답으로 전송이 실패할 경우, 문제를 보고하거나 재전송을 시도하는 것은 마스터의 몫이다.
모드버스 메시지 포맷은 서킷 셀라 200회 기사에 자세히 설명되어 있지만, 모드버스 시스템에서 종종 사용되는 쿼크(Quirk) 지정에 대해 언급하도록 하겠다. 모드버스 I/O의 서로 다른 포맷은 서로 다른 주소 공간에 지정된다. 단일 비트 I/O는 0~10,000을, 단일 비트 입력은 10,001~30,000을, 16비트 레지스터 입력은 30,001~40,000을, 16 비트 I/O는 나머지 40,001~65,534를 차지한다. 쿼크는 상쇄된 주소로, 모드버스 상에 전송되는 실제 주소는 실제 주소에서 관련 주소 공간의 기수를 차감하여 생성된다. 예를 들어, 30,194의 값을 가지는 주소는 모드버스 주소 193(30,194-30,001)으로 전송되므로 주소가 겹치지 않도록 주의를 기울여야 한다.
하드웨어와 소프트웨어
이 프로젝트와 관련된 하드웨어는 직렬 버스 인터페이스뿐이다. PSoC와 연결되는 RS-485 인터페이스 IC칩으로 구성된다 (그림 1 참조).
모드버스 메모리 맵을 생성하는 것은 창조적인 연습이다. 만일 연속된 모드버스 위치가 많을 경우, 메시지가 버퍼에 쌓이게 되므로 메시지는 RAM의 용량을 많이 차지하게 된다. PSoC 자원(램, I/O, 플래시 메모리, 주변기기 등)을 모드버스 주소에 지정할 때, 이러한 요소를 고려한다면 연속된 블록으로써 주소를 생성하는데 도움이 될 것이다. 이렇게 하지 않을 경우, 모드버스 블록 접근을 다루는 것이 메모리를 소비함을 관련 프로젝트에서 볼 수 있을 것이다.
이 프로젝트에서 내가 작성한 코드는 최적화와는 거리가 멀다. 이는 가장 일반적인 경우로 모드버스 장소에 묶인 대부분의 변수를 허용하도록 되어있지만, 일단 이해를 한 후, 해당 접근법을 변경할 수 있을 것이다.
이 프로젝트를 위해 모드버스 맵 상의 가상 슬레이브를 생성하였다 (표 1 참조). 연관된 명령과 함께 “물리 주소” 컬럼으로 전송되는 실제 주소뿐 아니라 기능 코드 또한 사용될 수 있다.
사용자 모듈 설정
PSoC 제어기는 필요에 정확히 맞게 설정될 수 있는 논리회로와 아날로그 블록으로 구성된다. 이러한 설정은 사이프레스의 무료 소프트웨어인 PSoC 디자이너를 통해 처리된다 (C 컴파일러는 덤이다). 원하는 사용자 모듈(UM) 형태를 선택하여 디지털 또는 아날로그 블록에 위치시키고, 관련된 변수와 어플리케이션에 적절한 상호연결을 설정하면 된다. 사진 1은 초기 설정을 보여준다. 하지만, 처음부터 SPIM과 CRC16 모듈은 사용되지 않는다. EEPROM 모듈이 상주하지만 이번 프로젝트에선 사용되지 않는다. 타이머는 “틱”을 발생하기 위해 필요하다. UART는 직렬 통신을 위해, 카운터는 데이터 전송률 발생에 사용된다.
표 1-이 프로젝트를 위해 생성된 가상의 모드버스 맵으로, 모드버스 주소 컬럼의 서로 다른 레지스터 공간의 주소 오프셋은 물리적 주소 컬럼의 주소로 번역된다. 레지스터 공간 사이에 의도적인 틈이 있는데, 영역 접속 명령어들은 서로 다른 영역 형태로 포함되지 않는다.
사진 1-사용자 모듈 배치도이다. SPIM은 CRC 생성기에 직렬 입력을 제공하기 위해 사용되지만, 하드웨어 CRC 생성에만 필요하다. UART의 데이터 전송률은 Counter8에서 유도된다. 전체 시스템의 타이밍은 타이머8의 인터럽트에 의해 발생되는 “틱”에서 유도된다.
모드버스의 주요 관심사 중 하나는 통신의 안정성이며, 이는 CRC 사용을 통해 해결된다. 이번 모드버스 슬레이브 생성의 두 가지 예제에서는 세 가지 접근법을 채택하였다. 첫째는 소프트웨어만을 사용하여 CRC를 생성한다. 여기엔 2개의 해결책이 있는데, 룩업(Look-up) 테이블 또는 루핑 코드(CRC는 소프트웨어를 통해 한 비트씩 생성된다)이다. 이 두 가지는 모드버스 문서에 설명되어 있다. 전자는 빠르고 후자는 훨씬 적은 메모리를 필요로 한다.
PSoC의 사용자 모듈 중 하나는 CRC16 생성기로, 직렬 형태로 데이터를 받고 SPIM(M은 마스터를 의미)이라 불리는 직렬-병렬 모듈을 필요로 한다. CRC 생성을 매우 빠른 속도로 처리하지만 사진 1(DBB00, DBB01, DBB02)에서 볼 수 있듯이 세 개의 디지털 블록을 사용하므로 PSoC 자원으로는 사치스럽다. 좀 더 상세히 설명하겠지만 여기선 설정에 대해 설명하고 있으므로 우선 세 번째 접근법에 대해 설명토록 하겠다.
PSoC의 사용자 모듈은 레지스터 구조로 읽혀지므로, 동적으로 변경이 가능하다. CRC와 데이터 통신이 동시에 발생되지 않으므로 두 기능 사이에서 세 개의 디지털 블록을 공유하는 것이 가능하다. 사진 2에서 이 세 가지 설정에 대해 확인할 수 있다.
소프트웨어 세부 내역
서로 다른 작업이 동시에 실행되도록 하기 위해 각 작업이 할당된 숫자를 가지는 단순한 협동 멀티태스크 방법을 사용하였다. 카운터와 C언어의 switch 선언문을 사용하여 각 작업은 순서대로 실행된다.
각 작업은 여러 개의 상태를 가진다. 실행되어야 하는 상태는 표준 C switch 기법을 사용하여 선택된다. 서킷 셀라 FTP 사이트의 main.c 모듈에서 이를 확인할 수 있을 것이다.
이 프로젝트에서는 신호의 RMS 값을 측정해야 하므로, 데이터 획득과 관련 없는 인터럽트 루틴에 소비되는 시간을 최소화하고 싶었다. 이를 위해 직렬 캐릭터 수신에 단지 하나의 타이머 “틱”과 하나의 인터럽트만을 허용하였다 (serial.c의 Serial_Rx 인터럽트 절차를 참고). 도달한 캐릭터는 버퍼로 전달되고 인터럽트 루틴에서 별다른 분석은 이루어지지 않는다. 도착한 모드버스 메시지의 검사, 해석, 응답 전송은 직렬 절차에 이어지는 업무에 맡겨진다.
틱은 매 0.5ms 마다 초기화된다. 이는 꽤 짧은 간격이지만, 몇몇 모드버스 타이밍에 필요하다. UART는 9,600bps, 1개의 스톱 비트, 8개의 데이터 비트, 패리티 없음으로 실행되기 위해 설정된다. 만일 패리티가 없다면 모드버스 사양은 2개의 스톱 비트를 호출하지만 2번째 비트를 점검하는 하드웨어 UART는 실제로 없으며 여기서도 구현되지 않았다.
사진 2-세 가지 설정으로, mod2로 명명된 설정은 기본 배치이며 CRC와 직렬 설정이 불려지고 내려가는 것에 영향 받지 않고 계속 작동한다. 동일한 자원을 사용하는 것에 유의하자. 각 설정은 화면 상단의 탭을 클릭함으로써 선택된다.
CRC 생성
약속한대로 모든 모드버스 메시지의 종단에 덧붙여지는 CRC에 대해 설명하도록 하겠다. CRC는 16비트 쉬프트 레지스터를 통해 생성된다. CRC의 값은 시드(Seed) 값에 의해 미리 조절된다. 레지스터 초기 단계로부터의 데이터는 곧장 다음 단계로 전달되거나 배타적 OR 게이트를 통해 (도달한 데이터와 함께) 전달된다. 배타적 OR 패턴은 다항식의 “1”이 게이트 존재여부를 의미하는 CRC 다항식에 의해 결정된다.
모드버스 구성을 관리하는 문서들은 소프트웨상에서의 CRC 생성에 대해 정확하게 설명한다. 첫 번째 접근법은 룩업 테이블을 사용하는 것으로 빠르지만 룩업 테이블 자체만으로 500 바이트 이상을 필요로 한다. 두 번째 방법은 느리지만 훨씬 적은 코드 공간을 사용한다. 두 방법 중 선호하는 것을 사용하면 된다 (Modbus.c 내 소프트웨어 모듈의 룩업 테이블용 CRC_TypeA와 짧은 접근법용 CRC_TypeB).
다.
표 2-CRC 접근법을 함축하였다. 실제 주기는 실제 데이터에 따라 약간 다를 수 있다.
CRC 계산을 빠르게 처리하기 위해 CRC16 사용자 모듈을 사용하기로 결정하였다. 슬램덩크가 될 것으로 예상하였지만 실제로는 그렇지 못했다. CRC16 사용자 모듈에서는 데이터가 레지스터에 동기를 맞추어 이동해야 한다. UART 통신은 스톱과 스타트 비트를 포함하기 때문에 UART 데이터 스트림을 CRC 모듈에 직접 공급하기는 불가능하다. 차선책은 UART를 통해 통신되는 동일한 데이터를 사용하여 SPIM 모듈에서 CRC16 하드웨어에 공급하는 것이다. SPIM 쉬프트의 데이터 전송률은 최대 12MHz로 설정되므로 빠른 이동이 가능하다. 소트프웨어는 SPIM으로 수신되거나 전송된 각 바이트를 읽어와야 하며, 그 후 전체 메시지가 저장되면 결과 CRC를 다시 읽어와야 한다. 물론, 이러한 절차는 UART로부터 데이터를 읽어오거나 UART에 데이터를 저장하는 것에 덧붙여지는 것이다.
PSoC의 CRC 생성기는 모드버스 CRC와 직접 호환이 되지는 않지만, 약간의 조작을 통해 간단히 구현된다. 이 솔루션 작동을 위해 사이프레스의 가네쉬 라쟈(Ganesh Raaja)에게 신세를 졌다. 결국 CRC를 생성하는 방법은 0xC002 다항식과 시드 0xFFFF (모드버스에서 일반적임)를 사용하는 것이었다. 처음에 데이터는 SPIM의 LSB로부터 이동되었다. 그 후, 마지막 비틀림(의도된 말장난)에서 CRC의 결과 비트들은 비트 15는 비트 0과, 비트 14는 비트 1과 상호 교환되는 형식이 되도록 반사되어야 했다. 이러한 반사를 수행하는 코드가 프로그램 내에 있지만, C 언어는 캐리 비트가 데이터 쉬프트에서 영향을 받는다는 사실을 무시하므로 실행 주기는 2배가 된다. 이 절차를 빠르게 하기 위해 소스 레지스터를 캐리 비트를 통해 이동시킨 후, 캐리 비트를 데스티네이션 레지스트로 이동시키는 최적화된 어셈블리 절차를 덧붙였다. 이 CRC 계산법은 프로젝트 Mod1에서 CRC_TypeC로 불린다.
앞서 언급된 것처럼, CRC 생성기와 UART를 덧붙여 PSoC 자원을 경제적으로 활용하는 것이 가능하다. PSoC는 대부분의 시간 동안 UART로 설정되며, CRC가 계산될 필요가 있을 때, UART 설정이 내려가고 CRC 설정이 올라온다. CRC 계산이 종료되면 CRC 모듈은 내려가고 UART 설정이 다시 올라온다. 이러한 로딩과 언로딩은 시간이 걸리는 단점이 있다. 초기 Mod2 설정 내의 모듈 설정과 작동은 변경되지 않는다. 이 CRC 계산은 프로젝트 Mod2에서 CRC_TypeC로 불린다.
목록 1-상태 4에서 벌어지는 작동은 cSerBuff[1]에 저장된 수신 명령 코드에 의존한다. 단지 몇 개의 유효한 코드가 있으며, 그 외에는 ILLEGAL FUNCTION 예외 코드가 반환된다. 이 목록은 극도로 압축된 것이다. 이 중 하나가 목록 2에 더욱 자세히 나와있다.
프로세서의 사용에 따라 속도와 코드 용량은 문제가 된다. 표 2는 서로 다른 가능성들간의 비교를 제공한다.
상태 설명
serial.c 모듈 내 serial() 절차에서 발생하는 대부분의 모드버스 동작은 프로젝트의 직렬 통신을 다룬다. 절차에서의 상태들은 표 3에 요약되어 있다.
아마도 목록 1에 훨씬 간단화되어 있는 상태 4는 가장 따르기 어려운 것일 것이다. CRC와 명령어 문제가 전 단계에서 해결되었다면, 슬레이브는 수신된 주소 영역이 자신의 주소 영역 내부인지 분석을 해야 한다. 주소가 생성되는 방법은 명령에 의존하므로, 각 명령에 대해 케이스(switch 선언문 내)를 만들었다 (목록 1 참조). 비트가 바이트에 포장되므로 모드버스에 걸쳐 수신된 코일, 이진 입력(비트), 레지스터(16비트 워드)들은 서로 다르게 처리되어야 한다. 물론 서로 다른 명령을 사용하여 단일 위치를 지정하는 것도 가능하다. 따라서 서로 다른 명령들에 필요한 처리 사이에는 공통성이 많지 않으며, 거의 모든 명령은 개별적인 코드를 필요로 한다. 만일 어떤 비트도 접속되거나 레지스터되지 않는 슬레이브 메모리 맵을 작성한다면 필요한 코드의 양을 줄이거나 최소한 최적화할 수 있다. 가장 일반적인 경우에 대해 소개하려고 노력했지만 여기엔 많은 코드가 있으며, 공간 제약과 나의 집중력 기간이 짧아 단지 한 경우에 대해서만 설명하는 것으로 제한하였다.
표 3-직렬 업무의 상태이다. 매번 serial() 절차가 실행되며, 이들 중 하나만 실행된다. 모든 상태 머신과 동일하게 상태 변화 또는 상태 유지 결정은 해당 상태에 정의된 특정 입력을 판단하여 이루어진다.
표 2는 다수의 코일 출력으로 설정된 코드를 보여준다. I/O의 16비트 시작 주소 (모듈 모드버스 주소에 반대되는)는 cSerBuff[2]와 cSerBuff[3]에서 빼지고 변수 iI에 저장된다. 이 주소는 헤더 파일에 정의된 BOTTOM_00000과 TOP_00000 상수에 정의된 코일 주소 영역 내부(00000-10000)인지를 확인하기 위해 검사된다. 다음 단계는 출력 영역의 끝부분이 모듈에 정해진 영역에 포함되는지를 확인하는 것이다. 이 작업은 수신된 데이터, cSerBuff[4], cSerBuff[5]에서 파생된 변수 iJ의 주소 숫자를 생성함으로써 이루어진다. 상단 주소는 변수 iK에 저장되어 모듈 제한치와 비교된다. 만일 주소가 허용된 영역을 벗어난다면, 예외 응답이 다음 상태 전송에 준비된다.
RAM을 금방 차지하는 직렬 버퍼의 실제 길이와는 별도로, 레지스터 맵에 간격이 있다면, 각 주소는 검증될 필요가 있으므로 좀 더 복잡해진다. 시작과 끝 부분 아니라 영역 중간에 존재하지 않는 메모리로의 접근을 확인하기 위한 코드가 필요하다. 따라서 맵에 대해 충분한 고려가 있어야 한다. 나의 코드는 특정 영역 내에서 어떤 간격도 고려하지 않았다.
어떤 이들은 수신된 데이터의 검증을 포함하여 소프트웨어가 수행될 수 있는지를 점검한다. 12비트 DAC는 4,095 이상의 숫자를 수용할 수 없다. 수신된 오류는 사전에 정의된 오류 형태로 분류가 되어야 하지만, 이는 가끔 제한적일 수 있다. 이러한 단편적인 코드는 디지털 출력만을 다루기 때문에, 값은 1과 0뿐이며, 검증이 이루어지지 않는다.
다음 단계는 모드버스 명령 내의 초기 주소로 변경되는 슬레이브 실제 출력을 정렬하고 출력 값을 변경하는 것이다. 초기 주소는 iI에 저장되고 출력들의 숫자는 (앞서) iJ에 저장된다. 이는 약간 복잡한데 실제 출력 값은 8비트 바이트로 압축되기 때문이며, 여러 개의 출력으로 포함되므로 한 바이트에서 여러 개 (8개까지)의 비트를 추출하고 다음 바이트로 밀어내는 것은 도전거리이다. 처음에는 변수 iK에 마스크가 설정되고 cSerBuff[8] 위치의 첫 데이터 바이트에 해당하는 포인터는 변수 cPoint로 정해진다. for (cI=0;cI<(unsigned char)iJ;cI++)절에 의거하는 목록 2의 잘려진 코드는 이 작업이 어떻게 이루어지는지 보여준다. 슬레이브 모듈에서 수정될 실제 비트는 switch 선언문의 각 케이스에 의해 다루어진다. switch 선언문을 통한 각 반복은 매 8번마다 순환되는 마스크 iK를 야기한다. 마스크는 재설정되고 입력되는 메시지 어레이 cPoint의 포인터는 증분된다.
몇몇 작동은 실행에 시간이 소요될 수 있다. 예를 들어, 모드버스에 걸쳐 EEPROM 설정을 업데이트 시키고 싶을 것이다. 일단 수신된 차례가 검증되면, 슬레이브는 “애크날리지” 메시지를 발행하여 문제가 없음을 알리고, 업데이트가 종료될 때까지 “오프라인”이 된다. 이 작업에 걸리는 시간을 알 수 있는 방법이 없지만, 업데이트 기간 동안 응답 폴을 슬레이브로부터 배제하지는 않는다. 즉각적인 응답의 목록 2에는 FinaliseTransmit() 절차가 준비된다.
모드버스 마스터 시물레이션
슬레이브 시험은 데이터를 송/수신할 수 있는 도구를 필요로 한다. 기존 셋업이 없을 경우, 모드버스 협회의 웹사이트에서 몇몇 도구를 찾을 수 있다. 또한 나의 제품은 PC상에서 구현된다 (서킷 셀라 200과 201). 모드버스 슬레이브 프로젝트는 표 1의 구현인 엑셀 워크시트(Slave.xls)를 포함한다.
목록 2-여러 개의 코일 출력에 저장하는 명령의 처리 과정을 보여주는 수정본이다. 출력은 비트 형태에서 바이트로 압축되어, 한계치를 점검하고 압축을 푸는 일은 좀 더 복잡한 명령을 코드화시킬 수 있도록 한다.
오래되었음에도 불구하고, 여전히 모드버스는 데이터 상호 통신에 있어 쓸만한 선택이다. 간단하고, 잘 정리되어 있으며, 구현이 쉽고, 충분히 많은 제조사들이 지원한다. 심지어 이더넷 상에서 모드버스를 효과적으로 사용할 수 있는 모드버스/TCP도 나와있다. 통신 매체가 다르지만 어플리케이션 단에서 모드버스는 동일하게 보인다. 비록 이는 해당 주제의 글에 대한 암시를 주지만, 한동안 모드버스에게 휴식을 줄 생각이다.
오브레이 케이건 (akagan@emphatec.com)은 이스라엘 과기원인 테크니온(Technion)에서 B.S.E.E를 취득하였고 위트워터스랜드(Witwatersrand) 대학에서 MBA를 취득한 엔지니어이다. 토론토에 위치한 산업제어 인터페이스와 스위치 모드 전원 공급기를 설계하는 엠파텍(Empatech)에 근무하고 있다. 서킷 셀라에 기고하는 것 외에도 몇몇 잡지에 글을 기고하고 있다. Excel by Example: A Microsoft Excel Cookbook for Electronics Engineers (Newnes, 2004)란 책을 출판하였다.
기사제공 : Circuit cellar 아이씨뱅크에서는 미국 써킷셀라 잡지사와 라이센스 계약을 맺고 아이씨뱅크 회원들에게 무료로 기사를 제공하고 있습니다. 매월 5개의 엄선된 기사가 온라인으로 배포되고 있으니, 회원님들에게 많은 도움이 되길 바랍니다. 써킷셀라(Circuit Cellar)는 1998년부터 북미지역에서 발행되고 있는 임베디드 하드웨어/소프트웨어 고급개발자 수준의 취미생활자를 위한 유료 잡지입니다. 써킷셀라의 유료독자는 북미 84%, 이외 지역 16% 이고, 웹사이트 이용자는 북미 43%, 유럽 30%, 아시아 17% 입니다. 써킷셀라의 기사들은 대부분 현장에서 일하고 있는 개발 엔지니어들에 의해 기고되며, 이 잡지에 기사를 기고하는 것이 개발엔지니어들의 목표가 될 만큼, 그 실력을 검증 받을 수 있는 하나의 수단으로도 여겨지고 있습니다. 매월 발행되는 써킷셀라는 전세계 27,000 여명에게 정기적으로 구독되고 있습니다. 아이씨뱅크에서는 역량있는 엔지니어분들의 자작 임베디드 하드웨어/소프트웨어를 소개할 수 있는 기회를 제공하고 있습니다. 기고내용의 수준에 따라 아이씨뱅크의 엔지니어 기고에 게재해 드리며, 써킷셀라 잡지에 영문으로 번역되어 기고되는 영광도 누리실 수 있습니다. 기고문의 : marketing@icbank.com |
댓글