1. ESP32 회로구성
이전에 ESP32 Modbus 관련하여 정리한 것이 그림 때문에 너무 긴거 같아, 다시 두개로 나누어 정리한다.
더불어,RS485 와 Modbus를 분리하여, 잘못 안 부분이 있다면, 수정하도록 하겠다.
RS232 와 RS485 회로구성 (ESP32 회로도 참조)
1.1 RS232 와 RS485 회로구성
ESP32는 RS232만 지원가능하며, RS232는 Full Duplex이며, RS485는 Half Duplex 이다.
Modbus를 구성하려면 두가지 방법이 있는데, ESP32에서만 제공하는 회로도만 보고, 이해했다가,
현재 사용하고 USB제품을 통해 Echo/Non Echo 모드를 알게되었다.
USB to RS422/RS485 (Echo/Non Echo 제공)
- Echo Mode: 이전 ESP32 Sample 회로도 with CD(Collision Detection)기능
- Non-Echo Mode: 이전 ESP32 Sample 회로도 without CD(Collision Detection) 기능
Echo/Non-Echo Mode 관련내용
문제는 CD(Collision Detection)기능이 있는 Echo Mode를 ESP32에서 완전히 제공하고 있지 않으므로, 주의해햐한다.
동작은 되지만, 멈추는 현상이 발생하며, ESP-IDF를 업그레이드 하면 추후에 제공될 것이겠지만, 현재 내 버전은 그렇지 않다.
1.2 RS485 Bus 와 다른 Bus 구성 비교
RS485 Tranceiver VCC마다 다 다를수 있으며, VCC의 Range 정해져 있지, 동일한 Volatage로 사용여부는 정해져 있지 않다.
이는 아래의 CSMA/CD에서 CD부분에서 자세히 설명하겠다.
- Ethernet 와 RS485 Tranceiver 비교
Etherent과 비교하면, 구성면에서 있어서 비슷한 구성, VCC가 다 동일하며, Volatage 기반으로 Collision Detection이 가능하다
- USB 와 RS485 Tranceiver 비교
USB과 비교하면, USB Master에서 VCC/VDD 나오며 전력도 제어하는 구조로 상위와 완전다르다.
USB는 Power만 가지고도, 끝도 없이 이야기 할수 있으므로, USB 세부사항들은 아래참고
1.3 RS485 의 CD(Collision Detection) 기능
CSMA/CD or CSMA/CA는 대부분 Bus의 Layer 2에서 제공을 해주는 기능이며, 공용 Bus를 같이 사용하기 위해서 사용되는 기술이다.
간단하게 말해, 공용 Bus에서 TX를 하기전에, CS(Carrier Sense) 즉, Bus 이용중인지 확인하고, TX를 하고,
Collision을 어떻게 감지(Detection)하는지 혹은 피하는 기능(Avoid)이며, 이로 인하여, 여러 Device들이 MA(Multiple Access)하여 같이 통신하는 것이다.
Layer 2의 CSMA/CD(유선) 와 CSMA/CA(무선) 차이
CSMA/CD(유선): 대표적인 것이 Ehternet
CSMA/CA(무선): 대표적인 것이 WIFI
Ethernet과 비교하면, 예전의 Dummy Hub를 사용할때의 구성과 현재 RS485 구성 얼추 비슷하게 보인다.
RS485는 CSMA/CD를 Layer2 즉, RS485 Tranceiver에서 처리못하고, 이를 RS232를 걸쳐 SW로 처리 해야한다.
- Ethernet의 CD(Collision Detection)
Ethernet의 경우, 예전에 Dummy Hub에 Station을 연결하면 할 수록 속도는 떨어진다.
이유는 Station들간에 충돌나면, Volatage 변경되어 CD(Collision Detection)발생하고, 이 때문에,
JAM 신호를 보내어 모든 Device를 멈추게 한다.
그리고, 이런일이 반복되어지면, Backoff Time이 더 늘어난다.
결론적으로 Station을 추가하면 추가할수록 Bus 속도는 기하급수적으로 늦어진다.
- RS485의 CD(Collision Detection) 다른이유
RS485와 Ethernet 의 비슷하지만, Voltage기반으로는 Collision Detection을 하기가 힘들다 .(이유는 간단하다.)
RS485 Tranceiver Differential Range가 넓어서, Bus에서 각 Device 의 RS485 Tranceiver 마다 구동되는 VCC가 다를수 있다.
예를들면, 같은 RS485 Bus 내에서도, 어떤 Device는 Voltage 3.3v 사용하고, 어떤 Device Voltage 5v를 사용가능하다.
이를 특정 Voltage Thrshold Level 정해서 Collision Detection으로 하기가 힘든 구조이다.
(물론 HW 보완해서 하면 얼마든지 가능하다)
- RS485 와 Volatage Range(-12v/+12V)
RS485의 Differential Voltage Range가 넓은 이유는 거리때문일 것이라고 생각한다.
간단히 생각해보면, 거리가 길어질 수록 선로는 길어지며, 선로 저항값이 점점 늘어난다.
2. ESP32 Modbus 구성
Modbus Main 협회사이트인 것 같은데, 주요 볼 것은 검색해서 나오는 링크일 것 같으며, Member로는 등록을 안했다.
ESP32의 Modbus 지원내용과 API 관련내용을 자세히 설명
2.1 Modbus의 기본구성
Modbus를 검색만 해도 너무 많은 자료가 나오는데, 검색된 블로그 들어가서 글을 읽으면,
아래내용과 틀린부분이 있어, 아래의 2개의 링크를 추천
Modbus의 Protocol 설명과 실제 예제 및 분석과 비교 까지 해주어서 괜찮음
Modbus 기본적인 설명이 잘 정리되어있으나, 상위와 용어가 조금씩 다름 주의
- Modbus의 Object(Data) Types
Modbus는 아래의 4가지 Type으로 Data접근가능.
2byte 전송형태와 1bit 전송형태 구분
이는 Read Only 와 Read Write 로 나뉨
Object type | Access | Size | Address Space |
---|
Coil | Read-write | 1 bit | 0x0000 – 0xFFFF |
Discrete input | Read-only | 1 bit | 0x0000 – 0xFFFF |
Input register | Read-only | 16 bits | 0x0000 – 0xFFFF |
Holding register | Read-write | 16 bits | 0x0000 – 0xFFFF
|
Modbus를 하기위해서는 상위 각 Object Type Address Map을 정의해야하며, 이기반으로 통신
Modbus RTU/ASCII 이건 둘 다 Function Code 기본으로 상위 Read/Write 넣음
Input register/ Holding Register Map 구현예제
Modbus Frame은 아래 ADU(Application Data Unit)로 구성되며, 다시 PDU(Protocol Data Unit)구성형태이다.
- ADU = Address + PDU + Error check
- PDU = Function code + Data
2.2 Modbus Frame 종류
Modbus Frame 종류와 각 Physical Interface 관계는 아래와 같으며, 이를 기반으로 연결되어 통신을 한다.
주로 가장 많이 사용되어지는 것이 Modbus RTU 와 Modbus TCP 인 것으로 보이며,
이 두 개를 연결 Mapping하여 Serial <-> Ethernet에서도 Control 할 수 있는 구조로 확장해서 사용하기도 한다.
상위 내용을 보면, Modbus ASCII 보다는 RTU가 더 효율적으로 보이며, Modbus TCP 호환으로 보아도
Modbus RTU를 사용해야 할 것 같다.
** 상위 링크에서 Modbus RTU 와 ASCII를 비교가능
Modbus RTU와 구조는 유사하지만, Binary용 전송이아니라, 세부사항들이 조금씩 다르며, DATA 기본단위도 2byte단위
- Start는 0x3A End는 0x0D,0x0A 로 감지목적사용
- Address는 Station Address로 2byte 사용하여 총 247까지 사용가능
- Function는 2byte Object Type 방식을 선택되어지는 것으로 각 Function에 따라 동작
- 상위 Object Type 방식에 Address Space는 DATA에 포함되며 세부사항은 Format 참조
주의: Modbus RTU 와 동일한 Data를 전송할 경우, 항상 2배의 Data 량을 소비
Unit Address (PLC Address): 2byte
- Address: 0 Broadcast 목적
- Address: 1 ~247, 각 Station의 Address로 이며 Unit Address 이라고도 함 (1byte)
- Function: 아래 Wiki 참조
- Data: Function에서 결정되며, 2byte 단위로 Encode 되어진다고 한다.
LRC: 2byte Checksum 이며, CRC가 아님
Name | Length (bytes) | Function |
---|
Start | 1 | Starts with colon : (ASCII hex value is 3A ) |
---|
Address | 2 | Station address |
---|
Function | 2 | Indicates the function codes like read coils / inputs |
---|
Data | n × 2 | Data + length will be filled depending on the message type |
---|
LRC | 2 | Checksum (Longitudinal redundancy check) |
---|
End | 2 | Carriage return – line feed (CR/LF) pair (ASCII values of 0D , 0A |
---|
- Start/End는 0으로 RTU Packet의 감지목적으로 사용
- Address는 Modbus RTU Slave의 Station Address로 1byte허용하여 총 247까지 사용가능
- Function는 상위 Object Type 방식을 선택되어지는 것으로 각 Function에 따라 동작
- 상위 Object Type 방식에 Address Space는 DATA에 포함되며 세부사항은 Format 참조
Unit Address (PLC Address): 1byte
- Address: 0 Broadcast 목적
- Address: 1 ~247, 각 Station의 Address로 이며 Unit Address 이라고도 함 (1byte)
- Function: 아래 Wiki 참조
- Data: Function에서 결정
CRC: 2bytes
총 256 Bytes
Name | Length (bits) | Function |
---|
Start | 28[citation needed] | At least 3½ character times of silence (mark condition) |
---|
Address | 8 | Station address |
---|
Function | 8 | Indicates the function code; e.g., read coils/holding registers |
---|
Data | n × 8 | Data + length will be filled depending on the message type |
---|
CRC | 16 | Cyclic redundancy check |
---|
End | 28[citation needed] | At least 3½ character times of silence between frames |
---|
** 주의 : 상위 Length는 Bits 단위
상위 Frame과 호환성을 가지기 위해서 Function code와 Unit Identifier로 구성되며, 이는 Modbus RTU와 쉽게 호환
- Transaction ID는 상위 Start/End 목적처럼 Sync를 위한 감지목적
- Protocol ID는 Modbus TCP는 0
- Unit ID는 기존의 Stations Adddress
- Function는 상위 Object Type 방식을 선택되어지는 것으로 각 Function에 따라 동작
- 상위 Object Type 방식에 Address Space는 DATA에 포함되며 세부사항은 Format 참조
- Modbus RTU over TCP의 경우, 아래의 CRC가 추가.
Unit Identifier (PLC Address): 1byte
- Address: 0 Broadcast 목적
- Address: 1 ~247, 각 Station의 Address로 이며 Unit Address 이라고도 함 (1byte)
- Function: 아래 Wiki 참조
- Data: Function에서 결정
CRC: Modbus RTU over TCP에서 사용
Name | Length (bytes) | Function |
---|
Transaction identifier | 2 | For synchronization between messages of server and client |
---|
Protocol identifier | 2 | 0 for Modbus/TCP |
---|
Length field | 2 | Number of remaining bytes in this frame |
---|
Unit identifier | 1 | Server address (255 if not used) |
---|
Function code | 1 | Function codes as in other variants |
---|
Data bytes | n | Data as response or commands |
---|
* Modbus RTU over TCP의 경우 상위 끝에 CRC추가되어 Modbus RTU와 호환가능
- 상위 모든 정보는 Wiki기반으로 가져온것이며 세부내용은 아래 참조
세부내용은 특히 Function 및 동작순서는 아래 Wiki에 너무 자세히 잘 나옴
2.3 Modbus Request /Response 분석
아래는 동일한 Station Address / Function / Data 이용하여 각 Frame 마다 전송비교 분석이며, 이를 쉽게 이해 할 수 있다.
- Read Input Register 예제 분석
1. Request: Address 200 의 Size 2개의 Data 요청
2. Response : Size:4 byte 와 Data 2개 (0x2710, 0xC350)
-------------------------------------------------------------------------------------------------------------------------------------
Request | Response
-------------------------------------------------------------------------------------------------------------------------------------
Modbus ASCII 3A | 3A // MODBUS ASCII START
30 31 | 30 31 (ASCII:01) // Station Address 0 1
30 34 | 30 34 (ASCII:04) // Function 0 4
30 30 43 38 30 30 30 32 (ASCII:00C80002) | 30 34 32 37 31 30 43 33 35 30 (ASCII:04 27 10 C3 50 ) // Data x 2byte
33 31 | 41 44 // LRC (Checksum)
0D 0A | 0D 0A // END (CR/LF)
--------------------------------------------------------------------------------------------------------------------------------------
Modbus RTU 01 | 01 // MODBUS RTU: Station Address(Unit) 1
04 | 04 // Function 4 Read Input Register
00 C8 00 02 Address: 200 Size:2x2 | 04 27 10 C3 50 (Byte:4, Data:2 words) // Data x 1byte (Size, Data 2 Words)
F0 35 | A0 39 // CRC
--------------------------------------------------------------------------------------------------------------------------------------
Modbus TCP 00 14 | 00 14 // MODBUS TCP: Transaction ID 2byte
00 00 | 00 00 // Protocol ID 2byte
00 06 | 00 07 // Length 2byte
01 | 01 // Station Address(Unit) 1
04 | 04 // Function 4
00 C8 00 02 Address: 200 Size:2x2 | 04 27 10 C3 50 // Data x 1 byte
---------------------------------------------------------------------------------------------------------------------------------------
상위예제는 Station(Unit) 1 주소로 Function 4의 Request/Resonse 구조로 보내는 Data는 동일하다.
간단히 보면, ASCII로 보낼 경우, Packet Size가 두 배가 되어진다.
Protocol을 보면, RTU 와 TCP는 쉽게 호환되어지는 구조이다.
- Modbus-RTU Write 와 Exception 확인
상위와 동일하게 아래를 참조하여 분석하면 된다.
Slave에서 에러가 발생하면, Response에서 Function Code 90h 과 함께 Exception Code가 별도로 존재
3. Modbus TEST 방법
다양한 Tool이 존재하지만 유료도 존재하여 나의 경우 Python 기반으로 테스트를 진행하였다.
3.1 Python 기반의 Pymodbus TEST 방법 및 분석
Python Pymodbus Manual
현재 나도 이기반으로 TEST Program들을 작성했으며, 다양한 예제가 있어 편하다.
- PC에서 ModbusTCP/RTU/ASCII Slave 테스트
PC가 Modbus Master가 되어 IOT (Modbus Slave) 테스트 할 경우 소스이며 각 Function에 관련된 함수에 따라 각각의 테스트 가능
- 상위 read/write coill 및 read only discreate input 테스트 가능 (1bit)
- 상위 read/write holding register 및 read only input register 테스트 가능 (2byte)
보통의 경우, sync용 예제를 봐야하며, async의 경우는 빠르게 보내는 경우에 사용되어진다고 보면될꺼 같다.
내 생각으로는 sync 와 async의 차이는 아래라고 하는데 추후 다시 확인해야하겠지만, blocking 과 non-blocking 의 차이라고 생각되어짐
기본적으로 Unix System programing or network programing 알면 대충 이해가 되실듯함
- sync : request 와 responce를 각 확인 인 되어 동작 (blocking mode) 일 것 같음
- async: request하고 response는 그냥 blocking 없이 동작 (non blocking mode) 일 것 같음
# 간단하게 이런식으로 연결후 원하는 정보를 읽어서 확인하면 된다
# pip install pymodbus
from pymodbus.client.sync import ModbusSerialClient
def run_sync_client():
client = ModbusSerialClient(
method='rtu',
port=Port,
baudrate=Baud,
timeout=3,
parity='N',
stopbits=1,
bytesize=8
)
if client.connect():
print('connected to Modbus Slave UNIT=',UNIT)
else:
print('failed to connect to the Modbus Slave UNIT=',UNIT)
- PC의 PayloadBuilder 와 PayloadDecoder 사용 및 확인이유
Holding Register와 Input Register는 기본적으로 2byte 통신이므로 Byte Order과 Word Order가 따라 결과 값이 달라질 수 있다.
- Byte Order: BigEndian/ Little Endian : CPU에 종속됨 (Word의 저장되는 순서)
- Word Order: Big Endian / Little Endian: 소스에서 확인해봐야함
PayloadBuild의 경우 Slave도 Write를 할 경우가 발생하는데, 이때 Holding Register를 이용하여 Write할 때 필요
PayloadDecoder의 경우 이를 다시 Decode를 할 경우 사용
PayloadBuilder 예제 (주로 write시 사용)
2byte단위 통신이므로 다양한 포맷으로 변경 및 추가하여 이를 Packet 생성
PayloadDecoder 예제 (주로 read시 사용)
2byte 단위 통신이므로 다른 포맷으로 변환시 사용
ESP32의 내부 Ordering 확인사항
사실 Word Order는 u16을 u32로 확장할 경우, 문제가 발생하는 것으로 보인다. (기본 32bit CPU의 word는 4byte)
예를들면 struct로 u16로 두개로 잡은 후에 이를 u16 pointer에서 u32로 변경할 경우 u16의 두개가 위치, 즉 address를 확인해야함
modbus 내부 api에서 변경할 가능성도 있으므로 이는 상위 wiki 세부확인
ESP32의 경우 내부적으로 분석해보면, ESP32의 경우 Bigendian 혹 정확히 말하며 Binary로 보내는게 맞다.
이는 아래와 같이 Data 영역에 Byte Align으로 Data를 저장하여 이를 Memory 접근한다.
Byte Align 방법
아래의 preprocessor로 1 byte align을 하여 byte ordering에 상관없이 사용하고 있다.
#pragma pack(push, 1)
typedef struct
{
...
}...
#pragma pack(pop)
PyModbus에서 입장에서 Byte Oder는 BigEndian 과 Word Order를 Littel Endian으로 해야 Float32가 제대로 보인다
개인생각으로는 PyModbus가 받은 Data를 함수내에 Stack영역에 저장하여 발생되어지는 문제로 생각되어진다.
u16->f32을 확장할 경우 PC(Program Counter) 의 Address 가 위로 증가 할지, 아래로 증가할지는 각 영역(Stack/BSS/Init)에 따라 달라진다
현재 이 부분은 현재 PyModbus가 Data 저장을 Stack영역에 저장하여 상위와 같은 현상이 발생되어지는 것으로 생각되어진다.
TEST Program 확인사항
상위를 먼저확인 후 Builder를 이용하여 u32으로 register를 write 한 후 decoder로 이를 재확인필요
ESP32의 Core Cadence의 Tensilica (Xtensa LX6)
Xtensa 의 ABI (너무 간단하게만 나오며, 함수와 ELF 관련내용)
혹시 몰라 Xtensa의 ABI도 링크함,
ARM이나, Power PC에 비해 너무 간단하게 나오며, 아마 다른문서가 있을 거라고 생각됨
일단, 간단하게만 알고나 있도록하자.