// IoT Protocol — Controller Area Network
CAN Bus
자동차와 산업 기계의 신경계 — 1986년 Bosch가 만들고 전 세계 자동차 ECU가 신뢰하는 멀티마스터 직렬 버스
CAN Bus란?
자동차를 바꾼 통신 기술의 탄생
CAN(Controller Area Network)은 1986년 독일 Bosch가 자동차 내 ECU(Electronic Control Unit) 간 통신을 위해 개발한 직렬 버스 프로토콜입니다. 1991년 Mercedes-Benz W140에 최초 양산 적용되었고, 1993년 ISO 11898로 국제 표준화되었습니다.
오늘날 자동차 1대에는 70개 이상의 ECU가 탑재되어 있으며 이들 모두 CAN으로 통신합니다. 자동차를 넘어 산업 기계, 의료 기기, 로봇, 항공 분야에서도 핵심 통신 버스로 자리잡았습니다.
| 표준 | 식별자 길이 | ID 범위 | 특징 |
|---|---|---|---|
| CAN 2.0A | 11-bit | 0 ~ 2,047 | 초기 표준, 2047개 고유 메시지 |
| CAN 2.0B 현재 표준 | 29-bit | 0 ~ 536,870,911 | 확장 프레임. SAE J1939 사용 |
| CAN FD 고속 | 11 또는 29-bit | 위와 동일 | 최대 64 bytes 데이터, 최대 8Mbps |
메시지 구조
CAN 프레임과 아비트레이션
(ID 11/29b)12/32 bits
아비트레이션(비파괴 비트 중재): 여러 노드가 동시에 전송을 시작하면, ID가 작은(우선순위가 높은) 메시지가 자동으로 버스를 점유합니다. 패킷 충돌로 데이터가 손상되는 이더넷 CSMA/CD 방식과 달리, 높은 우선순위 메시지는 손상 없이 전송됩니다.
오류 검출
5가지 오류 검출 메커니즘
| 오류 유형 | 검출 방법 | 원인 |
|---|---|---|
| CRC 오류 | 수신 CRC와 계산 CRC 비교 | 전송 중 비트 반전 |
| Bit 오류 | 전송 비트 ≠ 버스 비트 | 드라이버 오류, 짧은 회로 |
| Stuff 오류 | 6연속 동일 비트 감지 | 비트 스터핑 규칙 위반 |
| Form 오류 | 고정 필드 값이 잘못됨 | EOF, ACK Delimiter 오류 |
| ACK 오류 | ACK 슬롯에서 ACK 없음 | 수신 노드 없음 또는 오류 |
오류 카운터와 노드 상태: 각 노드는 TEC(Transmit Error Counter)와 REC(Receive Error Counter)를 유지합니다. 카운터가 누적되면 노드 상태가 전환되어 결함 노드가 버스 전체를 오염시키지 않도록 격리됩니다.
TEC/REC < 128
TEC/REC 128~255
TEC ≥ 256
128 × 11 recessive
물리 계층
차동 신호 · RS-485 배선 · 종단 저항
신호 방식
차동 신호
CAN-H · CAN-L 두 선
종단 저항
120Ω × 2개
버스 양 끝에 필수
최대 속도
1 Mbps (CAN)
CAN FD: 최대 8 Mbps
최대 거리
500m @ 125kbps
1Mbps에서 최대 40m
최대 노드
이론 2032개
실제 ~110개 (부하)
케이블
Twisted Pair
ISO 11898-2
CAN FD
CAN FD — 더 빠르고 더 많은 데이터
CAN FD(Flexible Data-Rate)는 2012년 Bosch가 도입한 CAN 확장 규격입니다. 아비트레이션 단계는 기존 CAN과 동일한 속도로 진행하고, 데이터 전송 단계에서만 최대 8Mbps로 속도를 올립니다. 또한 데이터 크기가 기존 8바이트에서 최대 64바이트로 확장되었습니다.
| 항목 | CAN 2.0 | CAN FD |
|---|---|---|
| 최대 데이터 | 8 bytes | 64 bytes |
| 아비트레이션 속도 | 최대 1 Mbps | 최대 1 Mbps (동일) |
| 데이터 속도 | 최대 1 Mbps | 최대 8 Mbps |
| CRC | 15-bit | 17-bit 또는 21-bit (강화) |
| ISO 표준 | ISO 11898-1 | ISO 11898-1:2015 |
| 하위 호환성 | CAN FD 노드와 기존 CAN 노드 혼용 불가 (별도 네트워크 필요) | |
상위 프로토콜
CANopen · SAE J1939 · OBD-II
CAN은 데이터 링크 계층까지만 정의합니다. 실제 응용에서는 CAN 위에 상위 프로토콜을 사용해 기기 유형별 표준화된 데이터 형식을 정합니다.
| 상위 프로토콜 | 기반 | 용도 | 특징 |
|---|---|---|---|
| CANopen | CAN 2.0 | 산업 자동화, 기계, 로봇 | Object Dictionary로 기기 파라미터 표준화 |
| SAE J1939 | CAN 2.0B | 트럭·버스 등 대형 차량 | PGN(Parameter Group Number)으로 데이터 정의 |
| OBD-II / ISO-TP | CAN 2.0 | 승용차 진단 | OBD-II 포트로 차량 상태 읽기, 스캐너 연결 |
| AUTOSAR | CAN / CAN FD | 자동차 SW 아키텍처 | 신규 차량 전기 아키텍처 표준, CAN FD 기반 |
| DeviceNet | CAN 2.0 | 공장 자동화 | Rockwell Automation 주도, 산업 기기 연결 |
실전 예제
Python python-can · Arduino ESP32 TWAI
Python — python-can으로 OBD-II 데이터 읽기
import can, time # USB-CAN 어댑터 (Vector, PEAK, Kvaser, SavvyCAN 등) bus = can.interface.Bus(channel="can0", bustype="socketcan") # Windows: bustype="vector" / "pcan" / "kvaser" def obd_request(bus, pid): """OBD-II 요청 전송 — ISO 15765-2 단일 프레임""" msg = can.Message( arbitration_id=0x7DF, # OBD-II 브로드캐스트 요청 ID data=[0x02, 0x01, pid, 0x00, 0x00, 0x00, 0x00, 0x00], is_extended_id=False ) bus.send(msg) resp = bus.recv(timeout=0.5) return resp # PID 0x0C: 엔진 RPM = ((A×256) + B) / 4 r = obd_request(bus, 0x0C) if r: rpm = ((r.data[3] * 256) + r.data[4]) / 4 print(f"엔진 RPM: {rpm:.0f}") # PID 0x0D: 차속 (km/h) = A r = obd_request(bus, 0x0D) if r: speed = r.data[3] print(f"차속: {speed} km/h") # PID 0x05: 냉각수 온도 (°C) = A - 40 r = obd_request(bus, 0x05) if r: temp = r.data[3] - 40 print(f"냉각수 온도: {temp}°C") # ── CAN 버스 모든 메시지 스니핑 ── print("\n=== 버스 스니핑 시작 ===") for msg in bus: print(f"ID: 0x{msg.arbitration_id:03X} DLC:{msg.dlc} Data: {msg.data.hex(' ')}") bus.shutdown()
Arduino (ESP32) — TWAI 컨트롤러 송수신
#include "driver/twai.h" // TWAI: ESP32 내장 CAN 컨트롤러 (GPIO21=TX, GPIO22=RX) // 별도 CAN 트랜시버 필요 — TJA1050, SN65HVD230 등 void setup() { Serial.begin(115200); twai_general_config_t g_cfg = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL); twai_timing_config_t t_cfg = TWAI_TIMING_CONFIG_500KBITS(); // 500kbps twai_filter_config_t f_cfg = TWAI_FILTER_CONFIG_ACCEPT_ALL(); if (twai_driver_install(&g_cfg, &t_cfg, &f_cfg) != ESP_OK) { Serial.println("TWAI 드라이버 설치 실패"); return; } twai_start(); Serial.println("CAN 버스 준비"); } void loop() { // ── 메시지 전송 ── twai_message_t tx; tx.identifier = 0x100; // 11-bit ID tx.extd = 0; // 0=표준, 1=확장 29-bit tx.data_length_code = 4; tx.data[0] = 0x01; tx.data[1] = 0x02; tx.data[2] = 0x03; tx.data[3] = 0x04; esp_err_t r = twai_transmit(&tx, pdMS_TO_TICKS(100)); if (r == ESP_OK) Serial.printf("TX: ID=0x%03X Data=%02X %02X %02X %02X\n", tx.identifier, tx.data[0], tx.data[1], tx.data[2], tx.data[3]); // ── 메시지 수신 ── twai_message_t rx; if (twai_receive(&rx, pdMS_TO_TICKS(100)) == ESP_OK) { Serial.printf("RX: ID=0x%03X DLC=%d Data=", rx.identifier, rx.data_length_code); for (int i = 0; i < rx.data_length_code; i++) Serial.printf("%02X ", rx.data[i]); Serial.println(); } delay(500); }
CANopen — Python canopen 라이브러리
import canopen # CANopen 네트워크 초기화 network = canopen.Network() network.connect(channel="can0", bustype="socketcan") # 노드 추가 (Node ID=5, EDS 파일로 Object Dictionary 로드) node = canopen.RemoteNode(5, "motor_drive.eds") network.add_node(node) # NMT — 노드 상태 제어 node.nmt.send_command(0x01) # Operational 상태로 전환 # SDO — Object Dictionary 읽기/쓰기 device_name = node.sdo[0x1008].raw # Manufacturer Device Name print(f"기기명: {device_name}") node.sdo[0x6040].raw = 0x0006 # Controlword: Shutdown node.sdo[0x6040].raw = 0x000F # Controlword: Enable Operation # PDO — 주기적 실시간 데이터 수신 def on_rpdo1(msg): pos = int.from_bytes(msg.data[0:4], 'little') vel = int.from_bytes(msg.data[4:8], 'little') print(f"위치: {pos}, 속도: {vel}") node.rpdo.read() # PDO 설정 읽기 node.rpdo[1].add_callback(on_rpdo1) node.rpdo[1].start(0.01) # 10ms 주기 수신 network.disconnect()
장단점
CAN Bus 선택 가이드
| 상황 | 권장 |
|---|---|
| 자동차 ECU 간 통신 | CAN / CAN FD |
| 산업 기계·로봇 관절 제어 | CAN / CANopen |
| PLC·계측기·인버터 통신 | Modbus RTU 검토 |
| 차량 진단 (OBD-II) | CAN 2.0 + ISO-TP |
| 고속 대용량 영상·데이터 전송 | 이더넷 기반 고려 |
댓글