Troubleshooting

[통신 데이터] 내비게이션/센서 연동 시 빅엔디안 vs 리틀엔디안 바이트 스와핑(C언어) (가장 추천)

임베디드 친구 2026. 6. 22. 21:06
반응형

내용 요약

현상: 외부 수신 데이터 값이 예상 범위를 벗어나 표현됨(ex: 0x12345678 -> 0x78563412).

원인: 프로토콜 규격 빅엔디안(Big-Endian), 리틀엔디안(Little-Endian) 사이의 바이트 정렬 불일치.

해결: 수신 버퍼 바이트의 배열 순서를 역순으로 재배치 Byte Swapping 적용.

엔디안 불일치(Endianness Mismatch) 데이터 오염 및 값 왜곡 증상

외부 센서 모듈이나 GPS/내비게이션 장비와 RS-232, RS-422, SPI 등의 하드웨어 인터페이스를 통해 패킷 통신을 수행할 때, 전송된 바이너리 원시 데이터(Raw Data)가 정상적인 스케일링 범위(Scaling Range)를 벗어나는 현상이 발생합니다.
예를 들어, 센서가 송신한 정상적인 부동소수점 또는 정수형 고정 제어 데이터가 메모리 매핑 이후 터무니없이 큰 정수나 정의되지 않은 NaN(Not a Number) 예외를 발생시킵니다. 디버깅 모드에서 메모리 버퍼를 실측하면 데이터 바이트 시퀀스의 상위 비트와 하위 비트가 뒤집혀 매핑되어 있음을 발견할 수 있습니다.
글로벌 통신 규격 표준(네트워크 바이트 오더)과 대다수 상용 센서의 제어부 아키텍처는 데이터의 상위 바이트를 메모리 낮은 주소에 먼저 배치하는 Big-Endian 방식을 고수하는 반면, 이를 받아 연산하는 일반적인 MCU 툴체인은 Little-Endian 방식을 디폴트로 채택하기 때문에 발생하는 Endianness Mismatch 트러블입니다.

빅엔디안(Big-Endian)과 리틀엔디안(Little-Endian)의 하드웨어 메모리 배치 메커니즘

MCU 레지스터와 물리 메모리 시스템에서 16비트(Half-Word) 또는 32비트(Word) 데이터를 참조하는 메커니즘은 프로세서 아키텍처 코어 하드웨어 설계 명세에 종속됩니다.

엔디안 유형(Endianness) 하위 바이트 (LSB) 배치 주소 상위 바이트(MSB) 배치 주소 적용 분야 및 아키텍처
빅엔디안 (Big-Endian) 상대적 고주소 (High Address) 상대적 저주소 (Low Address) 네트워크 프로토콜(TCP/IP), 군용 GPS, 항공 내비게이션, 특정 외장 DSP 코어
리틀엔디안 (Little-Endian) 상대적 저주소 (Low Address) 상대적 고주소 (High Address) ARM Cortex-M 시리즈, x86/x64, 대다수 범용 임베디드 MCU

32비트 데이터 0x12345678을 물리 메모리 주소 0x20000000 번지부터 정렬할 때 하드웨어 레벨의 데이터 배열 구조 차이는 다음과 같습니다.

  • Big-Endian: 0x20000000 -> 0x12 | 0x20000001 -> 0x34 | 0x20000002 -> 0x56 | 0x20000003 -> 0x78
  • Little-Endian: 0x20000000 -> 0x78 | 0x20000001 -> 0x56 | 0x20000002 -> 0x34 | 0x20000003 -> 0x12

따라서, 별도의 소프트웨어적 재배치 프로세스가 없다면 리틀엔디안 기반 MCU는 수신된 빅엔디안 원시 바이트 스트림을 그대로 파싱하여 전혀 다른 정수 심볼 코드로 인식하게 됩니다.

데이터 왜곡을 유발하는 잘못된 엔디안 참조 C 코드 (Bad Case)

아래 소스코드는 통신 버퍼 수신 직후 바이트 순서의 차이를 고려하지 않고, 수신된 원시 배열 포인터를 캐스팅 매핑하여 데이터 변형을 유발하는 잘못된 구조입니다.

#include <stdio.h>
#include <stdint.h>

/* Bad Case: Direct type casting without byte swapping */
typedef struct {
    uint32_t latitude;  /* Big-Endian data from Navigation system */
    uint16_t speed;     /* Big-Endian data from Navigation system */
} __attribute__((packed)) NavData_t;

void Process_Navigation_Packet(uint8_t *raw_buffer) {
    /* Critical Error: Directly casting Little-Endian pointer to Big-Endian data stream */
    NavData_t *nav_packet = (NavData_t *)raw_buffer;

    /* Output values will be corrupted due to Endianness Mismatch */
    printf("Latitude: 0x%08X\n", nav_packet->latitude);
    printf("Speed   : 0x%04X\n", nav_packet->speed);
}

int main(void) {
    /* Simulating incoming network byte order (Big-Endian stream) */
    /* Intended logical values: Latitude = 0x12345678, Speed = 0xABCD */
    uint8_t rx_buffer[6] = {0x12, 0x34, 0x56, 0x78, 0xAB, 0xCD};

    Process_Navigation_Packet(rx_buffer);
    return 0;
}

엔디안 변환을 반영한 방어적 바이트 스와핑 C 코드 (Good Case)

문제를 안정적으로 해결하기 위해 매크로 및 인라인 함수를 활용하여 컴파일러 최적화 효율성을 올리고, 비트 연산을 수행하여 원시 메모리 스트림 구조의 바이트 순서를 완벽히 보정하는 복구 프로그래밍 기법입니다.

#include <stdio.h>
#include <stdint.h>

/* Defensive Programming: Inline conversion macros for safe byte swapping */
#define SWAP_UINT16(val)  ((uint16_t)(((val) >> 8) | ((val) << 8)))

#define SWAP_UINT32(val)  ((uint32_t)(\
    (((val) & 0x000000FF) << 24) | \
    (((val) & 0x0000FF00) << 8)  | \
    (((val) & 0x00FF0000) >> 8)  | \
    (((val) & 0xFF000000) >> 24)))

typedef struct {
    uint32_t latitude;  /* Corrected native fields */
    uint16_t speed;     
} __attribute__((packed)) DefensiveNavData_t;

void Parse_Navigation_Packet_Safe(uint8_t *raw_buffer) {
    DefensiveNavData_t parsed_data;

    /* Temporary hardware mapping via bit-copy buffer pointer */
    uint32_t raw_lat;
    uint16_t raw_spd;

    /* Extract big-endian byte blocks explicitly using memory alignment */
    raw_lat = *(uint32_t *)(&raw_buffer[0]);
    raw_spd = *(uint16_t *)(&raw_buffer[4]);

    /* Execute hardware defensive byte-swapping logic */
    parsed_data.latitude = SWAP_UINT32(raw_lat);
    parsed_data.speed    = SWAP_UINT16(raw_spd);

    /* Verified and isolated Little-Endian registers */
    printf("Fixed Latitude: 0x%08X\n", parsed_data.latitude);
    printf("Fixed Speed   : 0x%04X\n", parsed_data.speed);
}

int main(void) {
    /* Input raw binary packet array sequence (Big-Endian specification) */
    uint8_t rx_buffer[6] = {0x12, 0x34, 0x56, 0x78, 0xAB, 0xCD};

    Parse_Navigation_Packet_Safe(rx_buffer);
    return 0;
}

핵심 수정 포인트 설명

  • 비트 시프트 및 마스킹 격리화 (Bitwise Isolation): SWAP_UINT32 매크로는 & 연산자로 특정 위치의 8비트 데이터 세그먼트만 추출해 낸 후, 비트 이동 연산(<<, >>)을 조합하여 물리 바이트 위치를 정확한 정렬 구조로 재배치합니다.
  • 원자적 결합 파싱 (Atomic Extraction): 구조체 메모리를 다이렉트로 캐스팅하지 않고, 통신 패킷 오프셋 버퍼 포인터 주소에서 단위 데이터를 우선 로드한 다음 변환 연산을 수행하여 아키텍처 정렬 예외(Alignment Fault) 위험을 사전에 차단했습니다.

엔디안 트러블슈팅 및 디버깅 가이드 (Debugging Tips)

  • 디버거 실시간 메모리 뷰어 (Memory Window View) 크로스 체크: 데이터 통신 오류가 감지되는 즉시 하드웨어 디버거(J-Link, ST-LINK 등)의 Memory View 기능이나 Expression 모니터 탭을 연 후, 해당 데이터가 올라가 있는 물리 램(SRAM) 영역의 헥사 바이트 원시 배열 상태를 관측하십시오. 변수로 직접 선언하여 읽어온 값과 램 어드레스의 실제 원시 바이트 데이터 배치가 정반대로 나열되어 있다면 100% 엔디안 불일치 버그로 의심할 수 있습니다.
  • 임베디드 하드웨어 전용 툴체인 내장 빌트인 함수 이용: C언어 표준 비트 시프트 연산 외에도 프로세서 코어 자체에서 지원하는 고속 전용 기계어 명령어가 있습니다. ARM Cortex-M 기반 시스템의 경우, CMSIS 헤더 파일이 지원하는 하드웨어 가속 매크로 함수인 __REV(), __REV16() 코드를 소스 단에 적용하면 소프트웨어 연산 부하 없이 1사이클 내에 하드웨어 레레지스터 단에서 바이트 스와핑 처리를 완료할 수 있으므로, 실시간성을 크게 높일 수 있습니다.
반응형