통신 프로토콜 모범 사례 및 패턴


19

두 개의 arduino 사이에 사용할 직렬 프로토콜을 설계 할 때마다 휠을 재발 명하는 것처럼 느껴집니다. 사람들이 따르는 모범 사례 나 패턴이 있는지 궁금합니다. 이 질문은 실제 코드에 대한 것이 아니라 메시지 형식에 대한 것입니다.

예를 들어, arduino에게 플래시를 시작하도록 지시하려면 첫 번째 LED를 3 번 ​​보냅니다.

^L1,F3\n
  • '^': 새 명령을 시작합니다
  • 'L': 명령을 정의합니다 (L :이 명령을 LED로 대상 지정)
  • '1': 첫 번째 LED를 타겟팅
  • ',': 명령 행 구분 기호,이 메시지의 새로운 값
  • 'F': 플래시 하위 명령
  • '3': 3 번 (LED를 3 번 ​​깜박임)
  • '\ n': 명령을 종료하십시오

생각? 일반적으로 새로운 직렬 프로토콜을 작성하는 방법은 무엇입니까? arduino 1에서 arduino 2로 쿼리를 보내고 응답을 받으려면 어떻게해야합니까?

답변:


13

원하는 기능과 필요한 오류 검사 양에 따라 직렬 프로토콜을 작성하는 방법에는 여러 가지가 있습니다.

포인트-투-포인트 프로토콜에서 흔히 볼 수있는 몇 가지 사항은 다음과 같습니다.

메시지 끝

가장 간단한 ASCII 프로토콜은 메시지 문자 시퀀스의 끝을 갖습니다. 종종 \r또는 \nEnter 키를 누르면 인쇄됩니다. 이진 프로토콜은 0x03다른 공통 바이트를 사용합니다.

메시지 시작

메시지가 끝났을 때의 문제점은 메시지를 보낼 때 이미 수신 한 다른 바이트를 모른다는 것입니다. 그런 다음이 바이트는 메시지의 접두어가되어 잘못 해석됩니다. 예를 들어 Arduino가 절전 모드에서 해제 된 경우 직렬 버퍼에 약간의 가비지가있을 수 있습니다. 이 문제를 해결하려면 메시지 순서가 시작됩니다. 귀하의 예에서 ^, 이진 프로토콜에서0x02

오류 확인

메시지가 손상 될 수있는 경우 오류 검사가 필요합니다. 이것은 체크섬 또는 CRC 오류 또는 다른 것일 수 있습니다.

탈출 캐릭터

체크섬이 '메시지 시작'또는 '메시지 끝'바이트와 같은 제어 문자에 추가되거나 메시지에 제어 문자와 같은 값이 포함되어있을 수 있습니다. 해결책은 이스케이프 문자를 도입하는 것입니다. 이스케이프 문자는 실제 제어 문자가 없도록 수정 된 제어 문자 앞에 배치 됩니다. 예를 들어 시작 문자가 0x02 인 경우 이스케이프 문자 0x10을 사용 하여 메시지에서 0x02 값을 바이트 쌍 0x10 0x12 (바이트 XOR 제어 문자)로 보낼 수 있습니다.

패킷 번호

메시지가 손상된 경우 nack 또는 retry 메시지로 재전송을 요청할 수 있지만 여러 메시지가 전송 된 경우 최신 메시지 만 재전송 할 수 있습니다. 대신 패킷에는 특정 수의 메시지 후에 롤오버되는 숫자가 제공 될 수 있습니다. 예를 들어,이 번호가 16 인 경우, 전송 장치는 마지막 16 개의 메시지 전송을 저장할 수 있으며, 손상된 경우 수신 장치는 패킷 번호를 사용하여 재전송을 요청할 수 있습니다.

길이

이진 프로토콜에서는 수신 장치에 메시지에 몇 개의 문자가 있는지 알려주는 길이 바이트가 표시됩니다. 이렇게하면 올바른 바이트 수가 수신되지 않은 경우 오류가 발생한 것처럼 다른 수준의 오류 검사가 추가됩니다.

아두 이노 전용

Arduino 용 프로토콜을 제안 할 때 가장 먼저 고려해야 할 사항은 통신 채널의 안정성입니다. 대부분의 무선 매체, XBee, WiFi 등을 통해 전송하는 경우 이미 오류 검사 및 재시도 기능이 내장되어 있으므로 프로토콜에 넣을 필요가 없습니다. RS422를 통해 몇 킬로미터를 보내려면 필요합니다. 내가 포함하는 것은 메시지의 시작과 메시지의 끝 문자입니다. 내 일반적인 구현은 다음과 같습니다.

>messageType,data1,data2,…,dataN\n

쉼표로 데이터 부분을 구분하면 쉽게 구문 분석 할 수 있으며 메시지는 ASCII를 사용하여 전송됩니다. ASCII 프로토콜은 직렬 모니터에 메시지를 입력 할 수 있기 때문에 좋습니다.

이진 프로토콜을 원한다면 메시지 크기를 줄이려면 데이터 바이트가 제어 바이트와 같을 수있는 경우 이스케이프를 구현해야합니다. 이진 제어 문자는 전체 범위의 오류 검사 및 재 시도가 필요한 시스템에 더 좋습니다. 원하는 경우 페이로드는 여전히 ASCII 일 수 있습니다.


메시지 코드의 실제 시작보다 먼저 가비지에 메시지 제어 코드의 시작이 포함될 수 있습니까? 이 문제를 어떻게 처리 하시겠습니까?
CMCDragonkai

@CMCDragonkai 예, 특히 단일 바이트 제어 코드의 경우 가능성이 있습니다. 그러나 메시지 구문 분석 도중에 시작 제어 코드가 발생하면 메시지가 삭제되고 구문 분석이 다시 시작됩니다.
geometrikal

9

직렬 프로토콜에 대한 공식적인 전문 지식은 없지만이 프로토콜을 꽤 몇 번 사용 했으며이 체계에 다소 정산했습니다.

(패킷 헤더) (ID 바이트) (데이터) (fletcher16 체크섬) (패킷 바닥 글)

나는 보통 헤더를 2 바이트와 바닥 글을 1 바이트로 만든다. 내 파서는 새 패킷 헤더가 표시되면 모든 것을 덤프하고 바닥 글이 표시되면 메시지를 구문 분석하려고 시도합니다. 체크섬이 실패하면 메시지를 버리지 않고 바닥 글 문자가 발견되고 체크섬이 성공할 때까지 계속 추가합니다. 그렇게하면 충돌이 메시지를 방해하지 않기 때문에 바닥 글은 1 바이트 만 필요합니다.

ID는 임의적이며 때로는 데이터 섹션의 길이가 맨 아래 니블 (4 비트)입니다. 두 번째 길이의 비트를 사용할 수 있지만 길이를 올바르게 구문 분석하기 위해 알 필요가 없으므로 일반적으로 신경 쓰지 않습니다. 따라서 주어진 ID에 올바른 길이가 오는 것이 메시지가 올바른지 확인하는 것입니다.

fletcher16 체크섬은 CRC와 거의 동일한 품질의 2 바이트 체크섬이지만 ​​구현하기가 훨씬 쉽습니다. 여기에 몇 가지 세부 사항이 있습니다 . 코드는 다음과 같이 간단 할 수 있습니다.

for(int i=0; i < bufSize; i++ ){
   sum1 = (sum1 + buffer[i]) % 255;
   sum2 = (sum2 + sum1) % 255;
}
uint16_t checksum = (((uint16_t)sum1)<<8) | sum2;

또한 중요한 메시지에 대해 전화 및 응답 시스템을 사용했습니다. PC는 500ms마다 메시지를 보내 전체 원본 메시지의 체크섬이 데이터 (원래 체크섬 포함)와 함께 OK 메시지를받을 때까지 메시지를 보냅니다.

물론이 체계는 예제와 같이 터미널에 입력하기에 적합하지 않습니다. 귀하의 프로토콜은 ASCII로 제한되는 것으로 보이며 직접 메시지를 읽고 보낼 수있는 빠른 프로젝트가 더 쉽다고 확신합니다. 대규모 프로젝트의 경우 바이너리 프로토콜의 밀도와 체크섬의 보안을 유지하는 것이 좋습니다.


"[귀하의] 파서가 새로운 패킷 헤더를 볼 때 모든 것을 버릴 것이므로"우연히 데이터 내부에서 헤더가 발견 될 때 이것이 문제를 일으키지 않는지 궁금합니까?
humanandANDpeace

@humanityANDpeace 삭제하는 이유는 패킷이 잘릴 때 올바르게 구문 분석되지 않기 때문에 언제 쓰레기를 결정하고 진행할 것인가? 가장 쉽고, 내 경험상 충분히 좋은 해결책은 다음 헤더가 나 오자마자 불량 패킷을 삭제하는 것입니다. 문제없이 16 비트 헤더를 사용하고 있지만 확실성이 더 중요하다면 더 길게 만들 수 있습니다 대역폭.
BrettAM

따라서 헤더라고하는 것은 다소 Magic 16 비트 조합입니다. 010101001 10101010, 맞습니까? 나는 그것이 1/256 * 256의 적중으로 변경되는 것에 동의하지만 데이터 에서이 16 비트를 사용하지 못하게하면 헤더로 잘못 해석되어 메시지를 버립니다.
humanandANDpeace

@humanityANDpeace 나는 1 년 후인 것을 알고 있지만 탈출 순서를 소개해야합니다. 전송하기 전에 서버는 페이로드에서 특수 바이트를 확인한 다음 다른 특수 바이트로 이스케이프합니다. 클라이언트 쪽에서는 원래 페이로드를 다시 합쳐야합니다. 이것은 고정 길이 패킷을 보낼 수 없으며 구현이 복잡하다는 것을 의미합니다. 이 모든 것을 선택할 수있는 많은 직렬 프로토콜 표준이 있습니다. 여기에 주제에 대해 잘 읽어보십시오 .
RubberDuck

1

표준에 익숙하다면 ASN.1 / BER TLV 인코딩을 살펴보십시오. ASN.1은 통신을 위해 특별히 만들어진 데이터 구조를 설명하는 데 사용되는 언어입니다. BER은 ASN.1을 사용하여 구조화 된 데이터를 인코딩하는 TLV 방법입니다. 문제는 ASN.1 인코딩이 까다로울 수 있다는 것입니다. 자체 프로젝트 전체 깃털 ASN.1 컴파일러입니다 만들기 (그에서 특히 까다로운 일이 생각 개월 ).


TLV 구조 만 유지하는 것이 좋습니다. TLV는 기본적으로 태그, 길이 및 값 필드의 세 가지 요소로 구성됩니다. 태그는 데이터 유형 (텍스트 문자열, 옥텟 문자열, 정수 등)과 의 길이 를 정의합니다 .

BER에서 T는 또한 값이 TLV 구조 자체 (구성된 노드)인지 아니면 직접 값 (기본 노드)인지를 나타냅니다. 그렇게하면 XML과 비슷하지만 XML 오버 헤드없이 이진 트리를 만들 수 있습니다.

예:

TT LL VV
02 01 FF

02값의 길이가 1 (length 01) 및 값 -1 (value FF) 인 정수 (tag )입니다 . ASN.1 / BER에서 정수는 부호가 큰 엔디안 숫자이지만 물론 자체 형식을 사용할 수 있습니다.

TT LL (TT LL VV, TT LL VV VV)
30 07  02 01 FF  02 02 00 FF

는 길이가 7 인 두 개의 정수를 포함하는 길이 7의 시퀀스 (목록)입니다 . 하나는 값 -1이고 다른 하나는 255입니다. 두 개의 정수 인코딩은 함께 시퀀스 값을 구성합니다.

이것을 온라인 디코더에도 넣을 수 있습니다.


BER에서 무한 길이를 사용하여 데이터를 스트리밍 할 수도 있습니다. 이 경우 트리를 올바르게 구문 분석해야합니다. 나는 그것을 고급 주제라고 생각할 것입니다. 너비 우선 및 깊이 우선 구문 분석에 대해 알아야합니다.


TLV 방식을 사용하면 기본적으로 모든 종류의 데이터 구조를 생각하고 인코딩 할 수 있습니다. ASN.1은 고유 식별자 (OID), 선택 (C- 연합과 유사), 다른 ASN.1 구조 등을 제공하지만 프로젝트에 무리를 줄 수 있습니다. 아마도 오늘날 가장 잘 알려진 ASN.1 정의 구조는 브라우저에서 사용하는 인증서 일 것입니다.


0

그렇지 않은 경우 기본 사항을 다룹니다. 인간과 기계 모두 명령을 작성하고 읽을 수 있습니다. 특히 채널에 긴 케이블이나 라디오 링크가 포함 된 경우 잘못된 명령 또는 전송 중에 손상된 명령을 감지하기 위해 체크섬을 추가 할 수 있습니다.

산업적인 힘이 필요한 경우 (기기가 다른 사람을 다치게하거나 죽게해서는 안됩니다. 높은 데이터 속도, 오류 복구, 누락 된 패킷 감지 등이 필요합니다) 표준 프로토콜 및 설계 방법을 살펴보십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.