기술 가이드

Modbus RTU CRC-16 검증: 계산값과 전송 바이트 순서 구분

공식 Modbus 직렬 라인 명세의 CRC-16 절차와 하위 바이트 우선 전송을 C 예제로 검증합니다.

목차

Modbus RTU 프레임을 직접 만드는 MCU 펌웨어에서 CRC가 맞지 않으면 장치는 요청을 조용히 버릴 수 있습니다. 특히 계산된 16비트 값의 표시 순서와 선로에 보내는 두 바이트 순서를 혼동하기 쉽습니다. 이 가이드는 공식 Modbus 직렬 라인 명세의 CRC 절차를 C로 구현하고, 수신 프레임의 한 바이트를 바꾸어 오류 검출을 확인합니다.

이 예제는 호스트 PC에서 바이트 배열을 검사합니다. UART 타이밍, RS-485 방향 제어, 실제 장치 응답을 시험한 결과는 아닙니다. Modbus Organization의 직렬 라인 명세 V1.02, 2.5.1.2·부록 B

1. CRC 범위와 바이트 순서

Modbus RTU의 CRC-16은 주소 바이트부터 마지막 데이터 바이트까지 계산합니다. 시작·정지·패리티 비트와 프레임 간 침묵 시간은 계산 대상이 아닙니다. CRC 레지스터를 0xFFFF로 초기화하고 입력 바이트를 XOR한 뒤, 각 바이트마다 8회 오른쪽으로 이동합니다. 이동 전 최하위 비트가 1이면 0xA001을 XOR합니다. 결과의 하위 바이트를 먼저, 상위 바이트를 나중에 프레임에 붙입니다. 공식 CRC 알고리즘

예제 요청 항목 바이트
슬레이브 주소·기능 코드(보유 레지스터 읽기) 01 03
시작 주소·읽을 개수(10개) 00 00 00 0A
계산한 16비트 CRC 표시 0xCDC5
실제 전송 CRC 바이트 C5 CD

이 표의 시작 주소 0x0000과 화면에 표시되는 40001 같은 레지스터 표기는 장치 문서에서 따로 대조하세요. 앞선 Modbus 주소·부호·워드 순서 가이드와 달리 여기서는 RTU 프레임의 CRC 검증만 다룹니다. 이 예제의 01 03 00 00 00 0A C5 CD는 표준 알고리즘의 계산값을 호스트 프로그램으로 확인한 검사용 프레임입니다.

2. 복사해서 실행하는 C 예제

아래 코드를 modbus_crc.c로 저장하고 gcc -std=c11 -Wall -Wextra -Werror modbus_crc.c -o modbus_crc로 빌드하세요. Linux에서는 ./modbus_crc, Windows PowerShell에서는 .\\modbus_crc.exe를 실행합니다. assert가 시험을 수행하므로 -DNDEBUG는 붙이지 않습니다.

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

/* MODBUS serial line V1.02, Appendix B: init FFFF, reflected A001. */
static uint16_t modbus_crc(const uint8_t *data, size_t length)
{
uint16_t crc = 0xffff;
for (size_t i = 0; i < length; ++i) {
crc ^= data[i];
for (unsigned bit = 0; bit < 8; ++bit) {
crc = (crc & 1u) ? (uint16_t)((crc >> 1) ^ 0xa001u)
: (uint16_t)(crc >> 1);
}
}
return crc;
}

static bool verify_rtu(const uint8_t *frame, size_t length)
{
if (frame == NULL || length < 4) return false;
uint16_t expected = modbus_crc(frame, length - 2);
return frame[length - 2] == (uint8_t)(expected & 0xffu) &&
frame[length - 1] == (uint8_t)(expected >> 8);
}

int main(void)
{
/* Slave 1, read 10 holding registers starting at address 0. */
uint8_t request[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x0a, 0xc5, 0xcd};
uint16_t crc = modbus_crc(request, sizeof request - 2);
assert(crc == 0xcdc5u);
assert(verify_rtu(request, sizeof request));
request[3] ^= 0x01u; /* A changed address byte must fail. */
assert(!verify_rtu(request, sizeof request));
assert(!verify_rtu(request, 3));
assert(!verify_rtu(NULL, sizeof request));
printf("PASS: CRC CDC5, wire C5 CD, changed byte rejected\n");
return 0;
}

예상 출력:

PASS: CRC CDC5, wire C5 CD, changed byte rejected

verify_rtu는 최소 길이를 확인한 뒤, 마지막 두 바이트를 제외한 내용으로 CRC를 다시 계산합니다. 함수는 CRC 일치 여부만 반환합니다. 슬레이브 주소, 기능 코드, 길이, 타임아웃, 예외 응답의 적절성은 별도로 검사해야 합니다. CRC는 우발적인 전송 오류 검출용이며 송신자 인증이나 암호학적 무결성 보장이 아닙니다.

3. 장치에서 값이 다르면 확인할 순서

  1. CRC 계산에 주소부터 데이터 끝까지 넣고, 이미 받은 CRC 두 바이트는 제외했는지 확인합니다.
  2. 로그의 0xCDC5를 선로 바이트 CD C5로 그대로 보내지 않았는지 확인합니다. 이 예제는 C5 CD입니다.
  3. RS-485 송신 완료 전에 방향 핀을 수신으로 바꾸어 끝 바이트가 잘리지 않았는지, UART 패리티·보오율과 RTU 프레임 간 간격이 양쪽에서 일치하는지 확인합니다. CRC 코드가 맞아도 물리 계층 설정이 다르면 통신하지 못합니다.
  4. 가짜 배열 테스트가 통과한 뒤에는 로직 애널라이저로 실제 바이트 순서를 캡처하고, 기기별 주소·기능 지원 여부를 매뉴얼로 대조합니다.

공식 참고: Modbus 직렬 라인 명세 및 구현 안내, CRC 생성 절차 PDF.

관련 글

댓글

첫 댓글을 남겨 보세요.

관리자 확인 후 게시됩니다.

처리 중입니다...

잠시만 기다려주세요.